Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Integration Tests

on:
push:
pull_request:
workflow_dispatch:
Comment thread
Neo23x0 marked this conversation as resolved.

jobs:
live-demo-tests:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt .

- name: Run live integration smoke tests
run: |
python -m pytest -m integration -v \
tests/test_basic.py::test_status \
tests/test_basic.py::test_demo_rules_json \
tests/test_cli_and_sigma.py::test_sigma_zip_updates_retrieved_rule_count_live_demo
31 changes: 31 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Python Tests

on:
push:
pull_request:
workflow_dispatch:

jobs:
unit-tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.11"]

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt .

- name: Run offline pytest suite
run: python -m pytest -m "not integration" -v tests
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
venv
valhallaAPI.egg*
dist
valhalla-rules.yar
build
*.yar
*.yara
*.zip
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help prepare-dev test lint release release-test
.PHONY: help prepare-dev test test-integration lint release release-test

VENV_NAME?=venv
VENV_ACTIVATE=. $(VENV_NAME)/bin/activate
Expand All @@ -8,7 +8,9 @@ help:
@echo "make prepare-dev"
@echo " prepare development environment, use only once"
@echo "make test"
@echo " run tests"
@echo " run offline tests"
@echo "make test-integration"
@echo " run live integration tests"
@echo "make lint"
@echo " run pylint and mypy"

Expand All @@ -28,6 +30,9 @@ $(VENV_NAME)/bin/activate: setup.py
test: venv
${PYTHON} -m pytest -v

test-integration: venv
${PYTHON} -m pytest -m integration -v

release:
rm -rf ./dist/*
${PYTHON} -m pip install --upgrade setuptools wheel
Expand All @@ -50,4 +55,3 @@ lint: venv




3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=42,<60"]
build-backend = "setuptools.build_meta"
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
Comment thread
Neo23x0 marked this conversation as resolved.
addopts = -m "not integration"
markers =
integration: tests that call the live Valhalla service
29 changes: 29 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[metadata]
name = valhallaapi
version = attr: valhallaAPI.version.__version__
author = Nextron
author_email = florian.roth@nextron-systems.com
description = Valhalla API Client
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/NextronSystems/valhallaAPI
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent

[options]
packages = find:
install_requires =
packaging
requests
configparser
python_requires = >=3.6

[options.packages.find]
include =
valhallaAPI*

[options.entry_points]
console_scripts =
valhalla-cli = valhallaAPI.valhalla_cli:main
31 changes: 1 addition & 30 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,3 @@
import setuptools

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
name="valhallaapi",
version="0.6.2",
author="Nextron",
author_email="florian.roth@nextron-systems.com",
description="Valhalla API Client",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/NextronSystems/valhallaAPI",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
],
install_requires=[
'packaging',
'requests',
'configparser',
],
python_requires='~=3.5',
entry_points={
'console_scripts': [
'valhalla-cli = valhallaAPI.valhalla_cli:main',
],
},
)
setuptools.setup()
4 changes: 3 additions & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from valhallaAPI.valhalla import ValhallaAPI

pytestmark = pytest.mark.integration

Comment thread
Neo23x0 marked this conversation as resolved.
DEMO_KEY = "1111111111111111111111111111111111111111111111111111111111111111"
INVALID_KEY = "invalid"
RULES_TEXT = "VALHALLA YARA RULE SET"
Expand Down Expand Up @@ -183,4 +185,4 @@ def test_get_rule_info_invalid():
response = v.get_rules_json()
assert response['status'] == 'error'
response2 = v.get_rules_json(score=75)
assert response2['status'] == 'error'
assert response2['status'] == 'error'
128 changes: 128 additions & 0 deletions tests/test_cli_and_sigma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import io
import json
import sys
import zipfile

import pytest

import valhallaAPI.valhalla as valhalla_module
import valhallaAPI.valhalla_cli as valhalla_cli
from valhallaAPI.valhalla import ValhallaAPI, ApiError
from valhallaAPI.version import __version__

DEMO_KEY = ValhallaAPI.DEMO_KEY


class MockResponse(object):
def __init__(self, payload):
self.text = json.dumps(payload)


def test_cli_banner_uses_package_version(monkeypatch, capsys):
monkeypatch.setattr(sys, "argv", ["valhalla-cli", "--check"])
monkeypatch.setattr(valhalla_cli.os.path, "exists", lambda path: False)
monkeypatch.setattr(
valhalla_cli.ValhallaAPI,
"get_subscription",
lambda self: {"active": True},
)

with pytest.raises(SystemExit) as exc:
valhalla_cli.main()

captured = capsys.readouterr()

assert exc.value.code == 0
assert "Ver. %s" % __version__ in captured.out
assert "No config file found; using the API key passed on the command line (-k)" in captured.err
assert "Use '-k APIKEY' with your private API key to retrieve the rule sets you are subscribed to" in captured.err


def test_sigma_zip_updates_retrieved_rule_count(monkeypatch):
def fake_post(url, data=None, proxies=None, headers=None):
assert url.endswith("/getsigma")
return MockResponse(
{
"rules": [
{
"signature_type": "sigma",
"type": "Process Creation",
"filename": "first.yml",
"content": "title: first",
},
{
"signature_type": "sigma",
"type": "Network Connection",
"filename": "second.yml",
"content": "title: second",
},
]
}
)

monkeypatch.setattr(valhalla_module.requests, "post", fake_post)
v = ValhallaAPI(api_key=DEMO_KEY)
archive = v.get_sigma_rules_zip()

assert v.last_retrieved_rules_count == 2

with zipfile.ZipFile(io.BytesIO(archive), "r") as zip_file:
names = sorted(zip_file.namelist())

assert names == [
"sigma/NetworkConnection/second.yml",
"sigma/ProcessCreation/first.yml",
]


def test_sigma_json_error_response_does_not_keyerror(monkeypatch):
def fake_post(url, data=None, proxies=None, headers=None):
assert url.endswith("/getsigma")
return MockResponse(
{
"status": "error",
"message": "demo failure",
}
)

monkeypatch.setattr(valhalla_module.requests, "post", fake_post)
v = ValhallaAPI(api_key=DEMO_KEY)

response = v.get_sigma_rules_json(search="suspicious", private_only=True)

assert response["status"] == "error"
assert v.last_retrieved_rules_count == 0


def test_sigma_zip_raises_api_error_on_error_response(monkeypatch):
def fake_post(url, data=None, proxies=None, headers=None):
assert url.endswith("/getsigma")
return MockResponse(
{
"status": "error",
"message": "demo failure",
}
)

monkeypatch.setattr(valhalla_module.requests, "post", fake_post)
v = ValhallaAPI(api_key=DEMO_KEY)

with pytest.raises(ApiError) as exc:
v.get_sigma_rules_zip(search="suspicious", private_only=True)

assert exc.value.message == "demo failure"


@pytest.mark.integration
def test_sigma_zip_updates_retrieved_rule_count_live_demo():
v = ValhallaAPI(api_key=DEMO_KEY)

archive = v.get_sigma_rules_zip()

assert len(archive) > 0
assert v.last_retrieved_rules_count > 0

with zipfile.ZipFile(io.BytesIO(archive), "r") as zip_file:
names = zip_file.namelist()

assert len(names) == v.last_retrieved_rules_count
4 changes: 3 additions & 1 deletion valhallaAPI/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
name = "valhallaAPI"
name = "valhallaAPI"

from .version import __version__
2 changes: 1 addition & 1 deletion valhallaAPI/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def get_product_requirements(product):
"""
# Product requirements
if product not in PRODUCT_REQUIREMENTS:
raise UnknownProductError("product name '%s' is not in the predefined list: %s" %
raise UnknownProductError("Product name '%s' is not in the predefined list: %s" %
(product, ", ".join(PRODUCT_REQUIREMENTS)))
# Get the values from the dict
sup_version = PRODUCT_REQUIREMENTS[product]['maximum_version']
Expand Down
3 changes: 1 addition & 2 deletions valhallaAPI/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
YARA_SET_HEADER = """/*
VALHALLA YARA RULE SET
Retrieved: {{ date }}
Generated for User: {{ user }}
Generated for user: {{ user }}
Number of Rules: {{ len_rules }}

{{ legal_note }}
Expand Down Expand Up @@ -37,4 +37,3 @@ def generate_header(rules_response):
for module in required_modules:
header_elements.append('import "{0}"'.format(module))
return "\n".join(header_elements)

Loading
Loading