From 3369b3bc592a5eec237df5e90c6df26aacccb4d8 Mon Sep 17 00:00:00 2001
From: Donny Peeters <46660228+Donnype@users.noreply.github.com>
Date: Thu, 5 Sep 2024 13:26:28 +0200
Subject: [PATCH 01/82] Feature: improve settings and environment logic and
phase out redundant environment keys (#3384)
Signed-off-by: Donny Peeters
Co-authored-by: Jan Klopper
Co-authored-by: stephanie0x00 <9821756+stephanie0x00@users.noreply.github.com>
Co-authored-by: ammar92
---
boefjes/.ci/docker-compose.yml | 14 ++++--
.../.ci/wiremock/mappings/organisations.json | 15 -------
boefjes/boefjes/api.py | 3 +-
boefjes/boefjes/dependencies/plugins.py | 23 ++++------
boefjes/boefjes/job_handler.py | 45 ++++++++++++-------
boefjes/boefjes/katalogus/plugins.py | 2 -
...fc302b852_remove_environment_keys_field.py | 37 +++++++++++++++
...de6eb7824b_introduce_boefjeconfig_model.py | 6 +--
boefjes/boefjes/models.py | 1 -
.../plugins/kat_binaryedge/boefje.json | 3 --
.../boefjes/plugins/kat_censys/boefje.json | 4 --
.../plugins/kat_cve_finding_types/boefje.json | 3 --
boefjes/boefjes/plugins/kat_dns/boefje.json | 4 --
.../plugins/kat_external_db/boefje.json | 7 ---
.../boefjes/plugins/kat_leakix/boefje.json | 3 --
.../boefjes/plugins/kat_log4shell/boefje.json | 3 --
.../boefjes/plugins/kat_masscan/boefje.json | 4 --
.../plugins/kat_maxmind_geoip/boefje.json | 6 +--
.../plugins/kat_nmap_ip_range/boefje.json | 6 ---
.../plugins/kat_nmap_ports/boefje.json | 3 --
.../boefjes/plugins/kat_nmap_tcp/boefje.json | 3 --
.../boefjes/plugins/kat_nmap_udp/boefje.json | 3 --
.../boefjes/plugins/kat_shodan/boefje.json | 3 --
.../kat_testssl_sh_ciphers/boefje.json | 5 +--
.../plugins/kat_webpage_analysis/boefje.json | 3 --
.../boefjes/plugins/kat_wpscan/boefje.json | 3 --
boefjes/boefjes/sql/db_models.py | 2 -
boefjes/boefjes/sql/plugin_storage.py | 4 --
boefjes/boefjes/storage/interfaces.py | 7 +--
boefjes/pyproject.toml | 1 +
boefjes/tests/conftest.py | 38 ++++++++++++++--
boefjes/tests/integration/test_bench.py | 4 +-
.../tests/integration/test_get_environment.py | 22 +++++++++
.../test_migration_add_schema_field.py | 6 +--
.../integration/test_sql_repositories.py | 2 -
.../boefjes_test_dir/kat_test/boefje.json | 1 -
.../kat_test/kat_test_2/boefje.json | 1 -
.../kat_test/kat_test_4/boefje.json | 1 -
.../tests/katalogus/test_plugin_service.py | 30 +++----------
.../tests/modules/dummy_boefje/boefje.json | 3 +-
.../dummy_boefje_environment/boefje.json | 3 +-
.../boefje.json | 3 +-
.../boefje.json | 3 +-
.../dummy_boefje_missing_run/boefje.json | 3 +-
.../boefje.json | 3 +-
.../dummy_oci_boefje_no_main/boefje.json | 1 -
boefjes/tests/test_tasks.py | 6 +--
.../source/developer_documentation/boefjes.md | 4 +-
.../development_tutorial/creating_a_boefje.md | 25 +++++++++--
docs/source/introduction/makeyourown.rst | 43 ++++++++++++------
mula/scheduler/models/plugin.py | 1 -
rocky/katalogus/client.py | 1 -
rocky/tests/conftest.py | 1 -
rocky/tests/stubs/katalogus_boefjes.json | 9 ----
rocky/tests/stubs/katalogus_normalizers.json | 5 ---
rocky/tests/test_indemnification.py | 1 -
56 files changed, 223 insertions(+), 223 deletions(-)
delete mode 100644 boefjes/.ci/wiremock/mappings/organisations.json
create mode 100644 boefjes/boefjes/migrations/versions/870fc302b852_remove_environment_keys_field.py
create mode 100644 boefjes/tests/integration/test_get_environment.py
diff --git a/boefjes/.ci/docker-compose.yml b/boefjes/.ci/docker-compose.yml
index 407d1678336..f3dc4ecc244 100644
--- a/boefjes/.ci/docker-compose.yml
+++ b/boefjes/.ci/docker-compose.yml
@@ -8,6 +8,7 @@ services:
command: sh -c 'python -m pytest -v tests/integration'
depends_on:
- ci_katalogus-db
+ - ci_katalogus
env_file:
- .ci/.env.test
volumes:
@@ -103,8 +104,15 @@ services:
hard: 262144
ci_katalogus:
- image: "docker.io/wiremock/wiremock:2.34.0"
- volumes:
- - .ci/wiremock:/home/wiremock
+ build:
+ context: ..
+ dockerfile: boefjes/Dockerfile
+ args:
+ - ENVIRONMENT=dev
+ command: uvicorn boefjes.katalogus.root:app --host 0.0.0.0 --port 8080
+ depends_on:
+ - ci_katalogus-db
env_file:
- .ci/.env.test
+ volumes:
+ - .:/app/boefjes
diff --git a/boefjes/.ci/wiremock/mappings/organisations.json b/boefjes/.ci/wiremock/mappings/organisations.json
deleted file mode 100644
index 13124e6222f..00000000000
--- a/boefjes/.ci/wiremock/mappings/organisations.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "request": {
- "method": "GET",
- "url": "/v1/organisations"
- },
- "response": {
- "status": 200,
- "jsonBody": {
- "_dev": {
- "id": "_dev",
- "name": "Development Organisation"
- }
- }
- }
-}
diff --git a/boefjes/boefjes/api.py b/boefjes/boefjes/api.py
index 72ac9ccc96c..22542898dfd 100644
--- a/boefjes/boefjes/api.py
+++ b/boefjes/boefjes/api.py
@@ -151,8 +151,7 @@ def get_task(task_id, scheduler_client):
def create_boefje_meta(task, local_repository):
boefje = task.data.boefje
boefje_resource = local_repository.by_id(boefje.id)
- env_keys = boefje_resource.environment_keys
- environment = get_environment_settings(task.data, env_keys) if env_keys else {}
+ environment = get_environment_settings(task.data, boefje_resource.schema)
organization = task.data.organization
input_ooi = task.data.input_ooi
diff --git a/boefjes/boefjes/dependencies/plugins.py b/boefjes/boefjes/dependencies/plugins.py
index ccb3187c0e2..080ff6d7da7 100644
--- a/boefjes/boefjes/dependencies/plugins.py
+++ b/boefjes/boefjes/dependencies/plugins.py
@@ -98,7 +98,7 @@ def clone_settings_to_organisation(self, from_organisation: str, to_organisation
self.set_enabled_by_id(plugin_id, to_organisation, enabled=True)
def upsert_settings(self, settings: dict, organisation_id: str, plugin_id: str):
- self._assert_settings_match_schema(settings, organisation_id, plugin_id)
+ self._assert_settings_match_schema(settings, plugin_id)
self._put_boefje(plugin_id)
return self.config_storage.upsert(organisation_id, plugin_id, settings=settings)
@@ -122,14 +122,14 @@ def _put_boefje(self, boefje_id: str) -> None:
try:
self.plugin_storage.boefje_by_id(boefje_id)
- except PluginNotFound:
+ except PluginNotFound as e:
try:
plugin = self.local_repo.by_id(boefje_id)
except KeyError:
- raise
+ raise e
if plugin.type != "boefje":
- raise
+ raise e
self.plugin_storage.create_boefje(plugin)
def _put_normalizer(self, normalizer_id: str) -> None:
@@ -150,12 +150,7 @@ def _put_normalizer(self, normalizer_id: str) -> None:
def delete_settings(self, organisation_id: str, plugin_id: str):
self.config_storage.delete(organisation_id, plugin_id)
- try:
- self._assert_settings_match_schema({}, organisation_id, plugin_id)
- except SettingsNotConformingToSchema:
- logger.warning("Making sure %s is disabled for %s because settings are deleted", plugin_id, organisation_id)
-
- self.set_enabled_by_id(plugin_id, organisation_id, False)
+ # We don't check the schema anymore because we can provide entries through the global environment as well
def schema(self, plugin_id: str) -> dict | None:
try:
@@ -184,9 +179,7 @@ def description(self, plugin_id: str, organisation_id: str) -> str:
return ""
def set_enabled_by_id(self, plugin_id: str, organisation_id: str, enabled: bool):
- if enabled:
- all_settings = self.get_all_settings(organisation_id, plugin_id)
- self._assert_settings_match_schema(all_settings, organisation_id, plugin_id)
+ # We don't check the schema anymore because we can provide entries through the global environment as well
try:
self._put_boefje(plugin_id)
@@ -195,14 +188,14 @@ def set_enabled_by_id(self, plugin_id: str, organisation_id: str, enabled: bool)
self.config_storage.upsert(organisation_id, plugin_id, enabled=enabled)
- def _assert_settings_match_schema(self, all_settings: dict, organisation_id: str, plugin_id: str):
+ def _assert_settings_match_schema(self, all_settings: dict, plugin_id: str):
schema = self.schema(plugin_id)
if schema: # No schema means that there is nothing to assert
try:
validate(instance=all_settings, schema=schema)
except ValidationError as e:
- raise SettingsNotConformingToSchema(organisation_id, plugin_id, e.message) from e
+ raise SettingsNotConformingToSchema(plugin_id, e.message) from e
def _set_plugin_enabled(self, plugin: PluginType, organisation_id: str) -> PluginType:
with contextlib.suppress(KeyError, NotFound):
diff --git a/boefjes/boefjes/job_handler.py b/boefjes/boefjes/job_handler.py
index a7e0d4feca1..8b0d979d256 100644
--- a/boefjes/boefjes/job_handler.py
+++ b/boefjes/boefjes/job_handler.py
@@ -7,6 +7,8 @@
import httpx
import structlog
from httpx import HTTPError
+from jsonschema.exceptions import ValidationError
+from jsonschema.validators import validate
from boefjes.clients.bytes_client import BytesAPIClient
from boefjes.config import settings
@@ -15,6 +17,7 @@
from boefjes.local_repository import LocalPluginRepository
from boefjes.plugins.models import _default_mime_types
from boefjes.runtime_interfaces import BoefjeJobRunner, Handler, NormalizerJobRunner
+from boefjes.storage.interfaces import SettingsNotConformingToSchema
from octopoes.api.models import Affirmation, Declaration, Observation
from octopoes.connector.octopoes import OctopoesAPIConnector
from octopoes.models import Reference, ScanLevel
@@ -35,7 +38,7 @@ def get_octopoes_api_connector(org_code: str) -> OctopoesAPIConnector:
return OctopoesAPIConnector(str(settings.octopoes_api), org_code)
-def get_environment_settings(boefje_meta: BoefjeMeta, environment_keys: list[str]) -> dict[str, str]:
+def get_environment_settings(boefje_meta: BoefjeMeta, schema: dict | None = None) -> dict[str, str]:
try:
katalogus_api = str(settings.katalogus_api).rstrip("/")
response = httpx.get(
@@ -43,22 +46,34 @@ def get_environment_settings(boefje_meta: BoefjeMeta, environment_keys: list[str
timeout=30,
)
response.raise_for_status()
- environment = response.json()
-
- # Add prefixed BOEFJE_* global environment variables
- for key, value in os.environ.items():
- if key.startswith("BOEFJE_"):
- katalogus_key = key.split("BOEFJE_", 1)[1]
- # Only pass the environment variable if it is not explicitly set through the katalogus,
- # if and only if they are defined in boefje.json
- if katalogus_key in environment_keys and katalogus_key not in environment:
- environment[katalogus_key] = value
-
- return {k: str(v) for k, v in environment.items() if k in environment_keys}
except HTTPError:
logger.exception("Error getting environment settings")
raise
+ allowed_keys = schema.get("properties", []) if schema else []
+ new_env = {
+ key.split("BOEFJE_", 1)[1]: value
+ for key, value in os.environ.items()
+ if key.startswith("BOEFJE_") and key in allowed_keys
+ }
+
+ settings_from_katalogus = response.json()
+
+ for key, value in settings_from_katalogus.items():
+ if key in allowed_keys:
+ new_env[key] = value
+
+ # The schema, besides dictating that a boefje cannot run if it is not matched, also provides an extra safeguard:
+ # it is possible to inject code if arguments are passed that "escape" the call to a tool. Hence, we should enforce
+ # the schema somewhere and make the schema as strict as possible.
+ if schema is not None:
+ try:
+ validate(instance=new_env, schema=schema)
+ except ValidationError as e:
+ raise SettingsNotConformingToSchema(boefje_meta.boefje.id, e.message) from e
+
+ return new_env
+
class BoefjeHandler(Handler):
def __init__(
@@ -97,10 +112,8 @@ def handle(self, boefje_meta: BoefjeMeta) -> None:
boefje_meta.arguments["input"] = ooi.serialize()
- env_keys = boefje_resource.environment_keys
-
boefje_meta.runnable_hash = boefje_resource.runnable_hash
- boefje_meta.environment = get_environment_settings(boefje_meta, env_keys) if env_keys else {}
+ boefje_meta.environment = get_environment_settings(boefje_meta, boefje_resource.schema)
mime_types = _default_mime_types(boefje_meta.boefje)
diff --git a/boefjes/boefjes/katalogus/plugins.py b/boefjes/boefjes/katalogus/plugins.py
index 134243ad963..6ecc4b031db 100644
--- a/boefjes/boefjes/katalogus/plugins.py
+++ b/boefjes/boefjes/katalogus/plugins.py
@@ -123,7 +123,6 @@ class BoefjeIn(BaseModel):
version: str | None = None
created: datetime.datetime | None = None
description: str | None = None
- environment_keys: list[str] = Field(default_factory=list)
scan_level: int = 1
consumes: set[str] = Field(default_factory=set)
produces: set[str] = Field(default_factory=set)
@@ -167,7 +166,6 @@ class NormalizerIn(BaseModel):
version: str | None = None
created: datetime.datetime | None = None
description: str | None = None
- environment_keys: list[str] = Field(default_factory=list)
consumes: list[str] = Field(default_factory=list) # mime types (and/ or boefjes)
produces: list[str] = Field(default_factory=list) # oois
diff --git a/boefjes/boefjes/migrations/versions/870fc302b852_remove_environment_keys_field.py b/boefjes/boefjes/migrations/versions/870fc302b852_remove_environment_keys_field.py
new file mode 100644
index 00000000000..7bdfbd9e024
--- /dev/null
+++ b/boefjes/boefjes/migrations/versions/870fc302b852_remove_environment_keys_field.py
@@ -0,0 +1,37 @@
+"""Remove environment keys field
+
+Revision ID: 870fc302b852
+Revises: 5be152459a7b
+Create Date: 2024-08-20 06:08:20.943924
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.dialects import postgresql
+
+# revision identifiers, used by Alembic.
+revision = "870fc302b852"
+down_revision = "5be152459a7b"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("boefje", "environment_keys")
+ op.drop_column("normalizer", "environment_keys")
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column(
+ "normalizer",
+ sa.Column("environment_keys", postgresql.ARRAY(sa.VARCHAR(length=128)), autoincrement=False, nullable=False),
+ )
+ op.add_column(
+ "boefje",
+ sa.Column("environment_keys", postgresql.ARRAY(sa.VARCHAR(length=128)), autoincrement=False, nullable=False),
+ )
+ # ### end Alembic commands ###
diff --git a/boefjes/boefjes/migrations/versions/f9de6eb7824b_introduce_boefjeconfig_model.py b/boefjes/boefjes/migrations/versions/f9de6eb7824b_introduce_boefjeconfig_model.py
index 40a44d504d3..d46f360b703 100644
--- a/boefjes/boefjes/migrations/versions/f9de6eb7824b_introduce_boefjeconfig_model.py
+++ b/boefjes/boefjes/migrations/versions/f9de6eb7824b_introduce_boefjeconfig_model.py
@@ -95,7 +95,7 @@ def upgrade() -> None:
str(boefje.scan_level),
list(boefje.consumes),
list(boefje.produces),
- boefje.environment_keys,
+ ["TEST_KEY"],
boefje.oci_image,
boefje.oci_arguments,
boefje.version,
@@ -137,7 +137,7 @@ def upgrade() -> None:
str(boefje.scan_level),
list(boefje.consumes),
list(boefje.produces),
- boefje.environment_keys,
+ ["TEST_KEY"],
boefje.oci_image,
boefje.oci_arguments,
boefje.version,
@@ -177,7 +177,7 @@ def upgrade() -> None:
normalizer.description,
normalizer.consumes,
normalizer.produces,
- normalizer.environment_keys,
+ ["TEST_KEY"],
normalizer.version,
)
for normalizer in normalizers_to_insert
diff --git a/boefjes/boefjes/models.py b/boefjes/boefjes/models.py
index 4881b26008a..028d1a1c9a6 100644
--- a/boefjes/boefjes/models.py
+++ b/boefjes/boefjes/models.py
@@ -17,7 +17,6 @@ class Plugin(BaseModel):
version: str | None = None
created: datetime.datetime | None = None
description: str | None = None
- environment_keys: list[str] = Field(default_factory=list)
enabled: bool = False
static: bool = True # We need to differentiate between local and remote plugins to know which ones can be deleted
diff --git a/boefjes/boefjes/plugins/kat_binaryedge/boefje.json b/boefjes/boefjes/plugins/kat_binaryedge/boefje.json
index 9dc2a85d8fb..e7d90e4ee98 100644
--- a/boefjes/boefjes/plugins/kat_binaryedge/boefje.json
+++ b/boefjes/boefjes/plugins/kat_binaryedge/boefje.json
@@ -6,8 +6,5 @@
"IPAddressV4",
"IPAddressV6"
],
- "environment_keys": [
- "BINARYEDGE_API"
- ],
"scan_level": 2
}
diff --git a/boefjes/boefjes/plugins/kat_censys/boefje.json b/boefjes/boefjes/plugins/kat_censys/boefje.json
index 6aadac16fba..ef6c3ab9a67 100644
--- a/boefjes/boefjes/plugins/kat_censys/boefje.json
+++ b/boefjes/boefjes/plugins/kat_censys/boefje.json
@@ -6,9 +6,5 @@
"IPAddressV4",
"IPAddressV6"
],
- "environment_keys": [
- "CENSYS_API_ID",
- "CENSYS_API_SECRET"
- ],
"scan_level": 1
}
diff --git a/boefjes/boefjes/plugins/kat_cve_finding_types/boefje.json b/boefjes/boefjes/plugins/kat_cve_finding_types/boefje.json
index 280ea27e565..f1315d93c33 100644
--- a/boefjes/boefjes/plugins/kat_cve_finding_types/boefje.json
+++ b/boefjes/boefjes/plugins/kat_cve_finding_types/boefje.json
@@ -5,9 +5,6 @@
"consumes": [
"CVEFindingType"
],
- "environment_keys": [
- "CVEAPI_URL"
- ],
"scan_level": 0,
"enabled": true
}
diff --git a/boefjes/boefjes/plugins/kat_dns/boefje.json b/boefjes/boefjes/plugins/kat_dns/boefje.json
index 53391f0155d..5773364b9b6 100644
--- a/boefjes/boefjes/plugins/kat_dns/boefje.json
+++ b/boefjes/boefjes/plugins/kat_dns/boefje.json
@@ -5,9 +5,5 @@
"consumes": [
"Hostname"
],
- "environment_keys": [
- "RECORD_TYPES",
- "REMOTE_NS"
- ],
"scan_level": 1
}
diff --git a/boefjes/boefjes/plugins/kat_external_db/boefje.json b/boefjes/boefjes/plugins/kat_external_db/boefje.json
index 4de34b597ac..cbf7ee0c927 100644
--- a/boefjes/boefjes/plugins/kat_external_db/boefje.json
+++ b/boefjes/boefjes/plugins/kat_external_db/boefje.json
@@ -5,12 +5,5 @@
"consumes": [
"Network"
],
- "environment_keys": [
- "DB_URL",
- "DB_ACCESS_TOKEN",
- "DB_ORGANIZATION_IDENTIFIER",
- "DB_ENDPOINT_FORMAT",
- "REQUESTS_CA_BUNDLE"
- ],
"scan_level": 0
}
diff --git a/boefjes/boefjes/plugins/kat_leakix/boefje.json b/boefjes/boefjes/plugins/kat_leakix/boefje.json
index 6bbc19c76ee..173f1b3169d 100644
--- a/boefjes/boefjes/plugins/kat_leakix/boefje.json
+++ b/boefjes/boefjes/plugins/kat_leakix/boefje.json
@@ -7,8 +7,5 @@
"IPAddressV6",
"Hostname"
],
- "environment_keys": [
- "LEAKIX_API"
- ],
"scan_level": 1
}
diff --git a/boefjes/boefjes/plugins/kat_log4shell/boefje.json b/boefjes/boefjes/plugins/kat_log4shell/boefje.json
index 1841a2c6d9a..40a9b6e9991 100644
--- a/boefjes/boefjes/plugins/kat_log4shell/boefje.json
+++ b/boefjes/boefjes/plugins/kat_log4shell/boefje.json
@@ -5,8 +5,5 @@
"consumes": [
"Hostname"
],
- "environment_keys": [
- "REPLY_FQDN"
- ],
"scan_level": 4
}
diff --git a/boefjes/boefjes/plugins/kat_masscan/boefje.json b/boefjes/boefjes/plugins/kat_masscan/boefje.json
index b0681d7ba2a..fbdc03a6093 100644
--- a/boefjes/boefjes/plugins/kat_masscan/boefje.json
+++ b/boefjes/boefjes/plugins/kat_masscan/boefje.json
@@ -5,9 +5,5 @@
"consumes": [
"IPV4NetBlock"
],
- "environment_keys": [
- "PORTS",
- "MAX_RATE"
- ],
"scan_level": 2
}
diff --git a/boefjes/boefjes/plugins/kat_maxmind_geoip/boefje.json b/boefjes/boefjes/plugins/kat_maxmind_geoip/boefje.json
index bd0386c9c20..eaeae4d2095 100644
--- a/boefjes/boefjes/plugins/kat_maxmind_geoip/boefje.json
+++ b/boefjes/boefjes/plugins/kat_maxmind_geoip/boefje.json
@@ -6,9 +6,5 @@
"IPAddressV4",
"IPAddressV6"
],
- "scan_level": 1,
- "environment_keys": [
- "MAXMIND_USER_ID",
- "MAXMIND_LICENCE_KEY"
- ]
+ "scan_level": 1
}
diff --git a/boefjes/boefjes/plugins/kat_nmap_ip_range/boefje.json b/boefjes/boefjes/plugins/kat_nmap_ip_range/boefje.json
index 4b4dc1a2d06..fe3baf38eb7 100644
--- a/boefjes/boefjes/plugins/kat_nmap_ip_range/boefje.json
+++ b/boefjes/boefjes/plugins/kat_nmap_ip_range/boefje.json
@@ -6,11 +6,5 @@
"IPV6NetBlock",
"IPV4NetBlock"
],
- "environment_keys": [
- "TOP_PORTS_TCP",
- "TOP_PORTS_UDP",
- "MIN_VLSM_IPV4",
- "MIN_VLSM_IPV6"
- ],
"scan_level": 2
}
diff --git a/boefjes/boefjes/plugins/kat_nmap_ports/boefje.json b/boefjes/boefjes/plugins/kat_nmap_ports/boefje.json
index 506b6ffd478..87fdb592a06 100644
--- a/boefjes/boefjes/plugins/kat_nmap_ports/boefje.json
+++ b/boefjes/boefjes/plugins/kat_nmap_ports/boefje.json
@@ -6,8 +6,5 @@
"IPAddressV4",
"IPAddressV6"
],
- "environment_keys": [
- "PORTS"
- ],
"scan_level": 2
}
diff --git a/boefjes/boefjes/plugins/kat_nmap_tcp/boefje.json b/boefjes/boefjes/plugins/kat_nmap_tcp/boefje.json
index f3d46684aeb..2ab5f018393 100644
--- a/boefjes/boefjes/plugins/kat_nmap_tcp/boefje.json
+++ b/boefjes/boefjes/plugins/kat_nmap_tcp/boefje.json
@@ -6,9 +6,6 @@
"IPAddressV4",
"IPAddressV6"
],
- "environment_keys": [
- "TOP_PORTS"
- ],
"scan_level": 2,
"oci_image": "ghcr.io/minvws/openkat/nmap:latest",
"oci_arguments": [
diff --git a/boefjes/boefjes/plugins/kat_nmap_udp/boefje.json b/boefjes/boefjes/plugins/kat_nmap_udp/boefje.json
index f9839e53e37..671ff93fe2a 100644
--- a/boefjes/boefjes/plugins/kat_nmap_udp/boefje.json
+++ b/boefjes/boefjes/plugins/kat_nmap_udp/boefje.json
@@ -6,9 +6,6 @@
"IPAddressV4",
"IPAddressV6"
],
- "environment_keys": [
- "TOP_PORTS_UDP"
- ],
"scan_level": 2,
"oci_image": "ghcr.io/minvws/openkat/nmap:latest",
"oci_arguments": [
diff --git a/boefjes/boefjes/plugins/kat_shodan/boefje.json b/boefjes/boefjes/plugins/kat_shodan/boefje.json
index ad1e1c3787e..c1309e7e1ac 100644
--- a/boefjes/boefjes/plugins/kat_shodan/boefje.json
+++ b/boefjes/boefjes/plugins/kat_shodan/boefje.json
@@ -6,8 +6,5 @@
"IPAddressV4",
"IPAddressV6"
],
- "environment_keys": [
- "SHODAN_API"
- ],
"scan_level": 1
}
diff --git a/boefjes/boefjes/plugins/kat_testssl_sh_ciphers/boefje.json b/boefjes/boefjes/plugins/kat_testssl_sh_ciphers/boefje.json
index 91a7d6f9994..92626c95337 100644
--- a/boefjes/boefjes/plugins/kat_testssl_sh_ciphers/boefje.json
+++ b/boefjes/boefjes/plugins/kat_testssl_sh_ciphers/boefje.json
@@ -5,8 +5,5 @@
"consumes": [
"IPService"
],
- "scan_level": 2,
- "environment_keys": [
- "TIMEOUT"
- ]
+ "scan_level": 2
}
diff --git a/boefjes/boefjes/plugins/kat_webpage_analysis/boefje.json b/boefjes/boefjes/plugins/kat_webpage_analysis/boefje.json
index c6c12b6e73c..e284ad6f61b 100644
--- a/boefjes/boefjes/plugins/kat_webpage_analysis/boefje.json
+++ b/boefjes/boefjes/plugins/kat_webpage_analysis/boefje.json
@@ -141,8 +141,5 @@
"video/x-msvideo",
"video/x-sgi-movie"
],
- "environment_keys": [
- "USERAGENT"
- ],
"scan_level": 2
}
diff --git a/boefjes/boefjes/plugins/kat_wpscan/boefje.json b/boefjes/boefjes/plugins/kat_wpscan/boefje.json
index 83658bb2d56..9c1d0b343da 100644
--- a/boefjes/boefjes/plugins/kat_wpscan/boefje.json
+++ b/boefjes/boefjes/plugins/kat_wpscan/boefje.json
@@ -5,8 +5,5 @@
"consumes": [
"SoftwareInstance"
],
- "environment_keys": [
- "WP_SCAN_API"
- ],
"scan_level": 2
}
diff --git a/boefjes/boefjes/sql/db_models.py b/boefjes/boefjes/sql/db_models.py
index e8e9740f8f4..f9b32a9a9ea 100644
--- a/boefjes/boefjes/sql/db_models.py
+++ b/boefjes/boefjes/sql/db_models.py
@@ -75,7 +75,6 @@ class BoefjeInDB(SQL_BASE):
# Job specifications
consumes = Column(types.ARRAY(types.String(length=128)), default=lambda: [], nullable=False)
produces = Column(types.ARRAY(types.String(length=128)), default=lambda: [], nullable=False)
- environment_keys = Column(types.ARRAY(types.String(length=128)), default=lambda: [], nullable=False)
schema = Column(types.JSON(), nullable=True)
# Image specifications
@@ -99,5 +98,4 @@ class NormalizerInDB(SQL_BASE):
# Job specifications
consumes = Column(types.ARRAY(types.String(length=128)), default=lambda: [], nullable=False)
produces = Column(types.ARRAY(types.String(length=128)), default=lambda: [], nullable=False)
- environment_keys = Column(types.ARRAY(types.String(length=128)), default=lambda: [], nullable=False)
version = Column(types.String(length=16), nullable=True)
diff --git a/boefjes/boefjes/sql/plugin_storage.py b/boefjes/boefjes/sql/plugin_storage.py
index c9bcffbaa2f..263d035ab76 100644
--- a/boefjes/boefjes/sql/plugin_storage.py
+++ b/boefjes/boefjes/sql/plugin_storage.py
@@ -111,7 +111,6 @@ def to_boefje_in_db(boefje: Boefje) -> BoefjeInDB:
consumes=boefje.consumes,
produces=boefje.produces,
schema=boefje.schema,
- environment_keys=boefje.environment_keys,
oci_image=boefje.oci_image,
oci_arguments=boefje.oci_arguments,
version=boefje.version,
@@ -127,7 +126,6 @@ def to_normalizer_in_db(normalizer: Normalizer) -> NormalizerInDB:
description=normalizer.description,
consumes=normalizer.consumes,
produces=normalizer.produces,
- environment_keys=normalizer.environment_keys,
version=normalizer.version,
static=normalizer.static,
)
@@ -144,7 +142,6 @@ def to_boefje(boefje_in_db: BoefjeInDB) -> Boefje:
consumes=boefje_in_db.consumes,
produces=boefje_in_db.produces,
schema=boefje_in_db.schema,
- environment_keys=boefje_in_db.environment_keys,
oci_image=boefje_in_db.oci_image,
oci_arguments=boefje_in_db.oci_arguments,
version=boefje_in_db.version,
@@ -161,7 +158,6 @@ def to_normalizer(normalizer_in_db: NormalizerInDB) -> Normalizer:
description=normalizer_in_db.description,
consumes=normalizer_in_db.consumes,
produces=normalizer_in_db.produces,
- environment_keys=normalizer_in_db.environment_keys,
version=normalizer_in_db.version,
static=normalizer_in_db.static,
)
diff --git a/boefjes/boefjes/storage/interfaces.py b/boefjes/boefjes/storage/interfaces.py
index ff19ca7c9f8..3f3d58e4a63 100644
--- a/boefjes/boefjes/storage/interfaces.py
+++ b/boefjes/boefjes/storage/interfaces.py
@@ -11,11 +11,8 @@ def __init__(self, message: str):
class SettingsNotConformingToSchema(StorageError):
- def __init__(self, organisation_id: str, plugin_id: str, validation_error: str):
- super().__init__(
- f"Settings for organisation {organisation_id} and plugin {plugin_id} are not conform the plugin schema: "
- f"{validation_error}"
- )
+ def __init__(self, plugin_id: str, validation_error: str):
+ super().__init__(f"Settings for plugin {plugin_id} are not conform the plugin schema: {validation_error}")
class NotFound(StorageError):
diff --git a/boefjes/pyproject.toml b/boefjes/pyproject.toml
index 31c11e2cf98..b1ca215be31 100644
--- a/boefjes/pyproject.toml
+++ b/boefjes/pyproject.toml
@@ -87,6 +87,7 @@ line-length = 120
transform-concats = true
[tool.pytest.ini_options]
+markers = ["slow: marks tests as slow"]
addopts = "-m 'not slow'"
env = [
"D:KATALOGUS_DB_URI=postgresql://postgres:postgres@ci_katalogus-db:5432/ci_katalogus",
diff --git a/boefjes/tests/conftest.py b/boefjes/tests/conftest.py
index d94dc074344..079185699ed 100644
--- a/boefjes/tests/conftest.py
+++ b/boefjes/tests/conftest.py
@@ -16,12 +16,16 @@
from boefjes.clients.bytes_client import BytesAPIClient
from boefjes.clients.scheduler_client import Queue, SchedulerClientInterface, Task, TaskStatus
from boefjes.config import Settings, settings
+from boefjes.dependencies.plugins import PluginService
from boefjes.job_handler import bytes_api_client
from boefjes.job_models import BoefjeMeta, NormalizerMeta
+from boefjes.local_repository import get_local_repository
from boefjes.models import Organisation
from boefjes.runtime_interfaces import Handler, WorkerManager
+from boefjes.sql.config_storage import SQLConfigStorage, create_encrypter
from boefjes.sql.db import SQL_BASE, get_engine
from boefjes.sql.organisation_storage import SQLOrganisationStorage
+from boefjes.sql.plugin_storage import SQLPluginStorage
from octopoes.api.models import Declaration, Observation
from octopoes.connector.octopoes import OctopoesAPIConnector
from octopoes.models import OOI
@@ -142,21 +146,47 @@ def api(tmp_path):
@pytest.fixture
-def organisation_repository():
+def session():
engine = get_engine()
session = sessionmaker(bind=engine)()
- yield SQLOrganisationStorage(session, settings)
+ yield session
session.execute(";".join([f"TRUNCATE TABLE {t} CASCADE" for t in SQL_BASE.metadata.tables]))
+ session.commit()
session.close()
@pytest.fixture
-def organisation(organisation_repository) -> Organisation:
+def organisation_storage(session):
+ return SQLOrganisationStorage(session, settings)
+
+
+@pytest.fixture
+def config_storage(session):
+ return SQLConfigStorage(session, create_encrypter())
+
+
+@pytest.fixture
+def plugin_storage(session):
+ return SQLPluginStorage(session, settings)
+
+
+@pytest.fixture
+def local_repo():
+ return get_local_repository()
+
+
+@pytest.fixture
+def plugin_service(plugin_storage, config_storage, local_repo):
+ return PluginService(plugin_storage, config_storage, local_repo)
+
+
+@pytest.fixture
+def organisation(organisation_storage) -> Organisation:
organisation = Organisation(id="test", name="Test org")
- with organisation_repository as repo:
+ with organisation_storage as repo:
repo.create(organisation)
return organisation
diff --git a/boefjes/tests/integration/test_bench.py b/boefjes/tests/integration/test_bench.py
index a77d5732092..14123016904 100644
--- a/boefjes/tests/integration/test_bench.py
+++ b/boefjes/tests/integration/test_bench.py
@@ -19,7 +19,7 @@
def test_migration(
octopoes_api_connector: OctopoesAPIConnector,
bytes_client: BytesAPIClient,
- organisation_repository: SQLOrganisationStorage,
+ organisation_storage: SQLOrganisationStorage,
valid_time,
):
octopoes_api_connector.session._timeout.connect = 60
@@ -87,7 +87,7 @@ def test_migration(
bytes_client.save_normalizer_meta(normalizer_meta)
total_oois = octopoes_api_connector.list_objects(set(), valid_time).count
- total_processed, total_failed = upgrade(organisation_repository, valid_time)
+ total_processed, total_failed = upgrade(organisation_storage, valid_time)
assert total_processed == len(hostname_range)
assert total_failed == 0
diff --git a/boefjes/tests/integration/test_get_environment.py b/boefjes/tests/integration/test_get_environment.py
new file mode 100644
index 00000000000..5aa209c0493
--- /dev/null
+++ b/boefjes/tests/integration/test_get_environment.py
@@ -0,0 +1,22 @@
+import pytest
+
+from boefjes.dependencies.plugins import PluginService
+from boefjes.job_handler import get_environment_settings
+from boefjes.models import Organisation
+from tests.loading import get_boefje_meta
+
+
+@pytest.mark.skipif("os.environ.get('CI') != '1'")
+def test_environment_builds_up_correctly(plugin_service: PluginService, organisation: Organisation):
+ plugin_id = "dns-records"
+ schema = plugin_service.schema(plugin_id)
+ environment = get_environment_settings(get_boefje_meta(boefje_id=plugin_id), schema)
+
+ assert environment == {}
+
+ with plugin_service:
+ plugin_service.upsert_settings({"RECORD_TYPES": "CNAME,AAAA", "WRONG": "3"}, organisation.id, plugin_id)
+
+ environment = get_environment_settings(get_boefje_meta(boefje_id=plugin_id), schema)
+
+ assert environment == {"RECORD_TYPES": "CNAME,AAAA"}
diff --git a/boefjes/tests/integration/test_migration_add_schema_field.py b/boefjes/tests/integration/test_migration_add_schema_field.py
index bbd66fbafa7..158e99cce34 100644
--- a/boefjes/tests/integration/test_migration_add_schema_field.py
+++ b/boefjes/tests/integration/test_migration_add_schema_field.py
@@ -30,7 +30,7 @@ def setUp(self) -> None:
str(boefje.scan_level),
list(sorted(boefje.consumes)),
list(sorted(boefje.produces)),
- boefje.environment_keys,
+ ["RECORD_TYPES", "REMOTE_NS"],
boefje.oci_image,
boefje.oci_arguments,
boefje.version,
@@ -71,7 +71,7 @@ def test_fail_on_wrong_plugin_ids(self):
"2",
["IPAddressV4", "IPAddressV6"],
["boefje/nmap-udp"],
- ["TOP_PORTS_UDP"],
+ ["RECORD_TYPES", "REMOTE_NS"],
"ghcr.io/minvws/openkat/nmap:latest",
["--open", "-T4", "-Pn", "-r", "-v10", "-sV", "-sU"],
None,
@@ -144,7 +144,7 @@ def test_fail_on_wrong_plugin_ids(self):
"2",
["IPAddressV4", "IPAddressV6"],
["boefje/nmap-udp"],
- ["TOP_PORTS_UDP"],
+ ["RECORD_TYPES", "REMOTE_NS"],
"ghcr.io/minvws/openkat/nmap:latest",
["--open", "-T4", "-Pn", "-r", "-v10", "-sV", "-sU"],
None,
diff --git a/boefjes/tests/integration/test_sql_repositories.py b/boefjes/tests/integration/test_sql_repositories.py
index ad4f804ad10..4dbe5d5c991 100644
--- a/boefjes/tests/integration/test_sql_repositories.py
+++ b/boefjes/tests/integration/test_sql_repositories.py
@@ -194,7 +194,6 @@ def test_rich_boefje_storage(self):
version="v1.09",
created=datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=datetime.UTC),
description="My Boefje",
- environment_keys=["api_key", "TOKEN"],
scan_level=4,
consumes=["Internet"],
produces=[
@@ -244,7 +243,6 @@ def test_rich_normalizer_storage(self):
version="v1.19",
created=datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=datetime.UTC),
description="My Normalizer",
- environment_keys=["api_key", "TOKEN"],
scan_level=4,
consumes=["Internet"],
produces=[
diff --git a/boefjes/tests/katalogus/boefjes_test_dir/kat_test/boefje.json b/boefjes/tests/katalogus/boefjes_test_dir/kat_test/boefje.json
index 518d9804e4b..321d8541364 100644
--- a/boefjes/tests/katalogus/boefjes_test_dir/kat_test/boefje.json
+++ b/boefjes/tests/katalogus/boefjes_test_dir/kat_test/boefje.json
@@ -5,6 +5,5 @@
"consumes": [
"DNSZone"
],
- "environment_keys": [],
"scan_level": 1
}
diff --git a/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_2/boefje.json b/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_2/boefje.json
index 07c01db4d21..67dc80c70bf 100644
--- a/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_2/boefje.json
+++ b/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_2/boefje.json
@@ -8,6 +8,5 @@
"produces": [
"text/html"
],
- "environment_keys": [],
"scan_level": 1
}
diff --git a/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_4/boefje.json b/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_4/boefje.json
index b00b312a437..d419ffd073f 100644
--- a/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_4/boefje.json
+++ b/boefjes/tests/katalogus/boefjes_test_dir/kat_test/kat_test_4/boefje.json
@@ -8,6 +8,5 @@
"produces": [
"text/html"
],
- "environment_keys": [],
"scan_level": 1
}
diff --git a/boefjes/tests/katalogus/test_plugin_service.py b/boefjes/tests/katalogus/test_plugin_service.py
index a86b4aa6fbf..acb892eedee 100644
--- a/boefjes/tests/katalogus/test_plugin_service.py
+++ b/boefjes/tests/katalogus/test_plugin_service.py
@@ -55,27 +55,13 @@ def test_update_by_id(self):
def test_update_by_id_bad_schema(self):
plugin_id = "kat_test"
- with self.assertRaises(SettingsNotConformingToSchema) as ctx:
- self.service.set_enabled_by_id(plugin_id, self.organisation, True)
-
- msg = (
- "Settings for organisation test and plugin kat_test are not conform the plugin schema: 'api_key' is a "
- "required property"
- )
- self.assertEqual(ctx.exception.message, msg)
-
self.service.config_storage.upsert(self.organisation, plugin_id, {"api_key": 128 * "a"})
self.service.set_enabled_by_id(plugin_id, self.organisation, True)
- value = 129 * "a"
- self.service.config_storage.upsert(self.organisation, plugin_id, {"api_key": 129 * "a"})
with self.assertRaises(SettingsNotConformingToSchema) as ctx:
- self.service.set_enabled_by_id(plugin_id, self.organisation, True)
+ self.service.upsert_settings({"api_key": 129 * "a"}, self.organisation, plugin_id)
- msg = (
- f"Settings for organisation test and plugin kat_test are not conform the plugin schema: "
- f"'{value}' is too long"
- )
+ msg = f"Settings for plugin kat_test are not conform the plugin schema: '{129 * 'a'}' is too long"
self.assertEqual(ctx.exception.message, msg)
def test_get_schema(self):
@@ -93,7 +79,7 @@ def test_get_schema(self):
schema = self.service.schema("kat_test_normalize")
self.assertIsNone(schema)
- def test_removing_mandatory_setting_disables_plugin(self):
+ def test_removing_mandatory_setting_does_not_disable_plugin_anymore(self):
plugin_id = "kat_test"
self.service.config_storage.upsert(self.organisation, plugin_id, {"api_key": 128 * "a"})
@@ -105,20 +91,17 @@ def test_removing_mandatory_setting_disables_plugin(self):
self.service.delete_settings(self.organisation, plugin_id)
plugin = self.service.by_plugin_id(plugin_id, self.organisation)
- self.assertFalse(plugin.enabled)
+ self.assertTrue(plugin.enabled)
def test_adding_integer_settings_within_given_constraints(self):
plugin_id = "kat_test_2"
- self.service.config_storage.upsert(self.organisation, plugin_id, {"api_key": "24"})
-
with self.assertRaises(SettingsNotConformingToSchema) as ctx:
- self.service.set_enabled_by_id(plugin_id, self.organisation, True)
+ self.service.upsert_settings({"api_key": "24"}, self.organisation, plugin_id)
self.assertIn("'24' is not of type 'integer'", ctx.exception.message)
- self.service.config_storage.upsert(self.organisation, plugin_id, {"api_key": 24})
-
+ self.service.upsert_settings({"api_key": 24}, self.organisation, plugin_id)
self.service.set_enabled_by_id(plugin_id, self.organisation, True)
plugin = self.service.by_plugin_id(plugin_id, self.organisation)
@@ -155,7 +138,6 @@ def test_clone_many_settings(self):
all_settings_1 = {"api_key": "123"}
self.service.upsert_settings(all_settings_1, self.organisation, plugin_id_1)
-
self.service.clone_settings_to_organisation(self.organisation, "org2")
all_settings_for_new_org = self.service.get_all_settings("org2", plugin_id_1)
diff --git a/boefjes/tests/modules/dummy_boefje/boefje.json b/boefjes/tests/modules/dummy_boefje/boefje.json
index 791539e798f..81b94057368 100644
--- a/boefjes/tests/modules/dummy_boefje/boefje.json
+++ b/boefjes/tests/modules/dummy_boefje/boefje.json
@@ -3,6 +3,5 @@
"name": "dummy",
"description": "",
"consumes": [],
- "produces": [],
- "environment_keys": []
+ "produces": []
}
diff --git a/boefjes/tests/modules/dummy_boefje_environment/boefje.json b/boefjes/tests/modules/dummy_boefje_environment/boefje.json
index 838cef1dae7..2ad7fdddefa 100644
--- a/boefjes/tests/modules/dummy_boefje_environment/boefje.json
+++ b/boefjes/tests/modules/dummy_boefje_environment/boefje.json
@@ -3,6 +3,5 @@
"name": "dummy",
"description": "",
"consumes": [],
- "produces": [],
- "environment_keys": []
+ "produces": []
}
diff --git a/boefjes/tests/modules/dummy_boefje_environment_with_pycache/boefje.json b/boefjes/tests/modules/dummy_boefje_environment_with_pycache/boefje.json
index 28f7090b2f0..009f2116c0f 100644
--- a/boefjes/tests/modules/dummy_boefje_environment_with_pycache/boefje.json
+++ b/boefjes/tests/modules/dummy_boefje_environment_with_pycache/boefje.json
@@ -3,6 +3,5 @@
"name": "dummy",
"description": "",
"consumes": [],
- "produces": [],
- "environment_keys": []
+ "produces": []
}
diff --git a/boefjes/tests/modules/dummy_boefje_invalid_signature/boefje.json b/boefjes/tests/modules/dummy_boefje_invalid_signature/boefje.json
index 47f325f09e9..3fca5d08ce7 100644
--- a/boefjes/tests/modules/dummy_boefje_invalid_signature/boefje.json
+++ b/boefjes/tests/modules/dummy_boefje_invalid_signature/boefje.json
@@ -3,6 +3,5 @@
"name": "dummy",
"description": "",
"consumes": [],
- "produces": [],
- "environment_keys": []
+ "produces": []
}
diff --git a/boefjes/tests/modules/dummy_boefje_missing_run/boefje.json b/boefjes/tests/modules/dummy_boefje_missing_run/boefje.json
index 838cef1dae7..2ad7fdddefa 100644
--- a/boefjes/tests/modules/dummy_boefje_missing_run/boefje.json
+++ b/boefjes/tests/modules/dummy_boefje_missing_run/boefje.json
@@ -3,6 +3,5 @@
"name": "dummy",
"description": "",
"consumes": [],
- "produces": [],
- "environment_keys": []
+ "produces": []
}
diff --git a/boefjes/tests/modules/dummy_boefje_runtime_exception/boefje.json b/boefjes/tests/modules/dummy_boefje_runtime_exception/boefje.json
index 197a6aa43f0..e7dcc24f271 100644
--- a/boefjes/tests/modules/dummy_boefje_runtime_exception/boefje.json
+++ b/boefjes/tests/modules/dummy_boefje_runtime_exception/boefje.json
@@ -3,6 +3,5 @@
"name": "dummy",
"description": "",
"consumes": [],
- "produces": [],
- "environment_keys": []
+ "produces": []
}
diff --git a/boefjes/tests/modules/dummy_oci_boefje_no_main/boefje.json b/boefjes/tests/modules/dummy_oci_boefje_no_main/boefje.json
index 2ef96a5e2da..6e8c1bc69b3 100644
--- a/boefjes/tests/modules/dummy_oci_boefje_no_main/boefje.json
+++ b/boefjes/tests/modules/dummy_oci_boefje_no_main/boefje.json
@@ -4,6 +4,5 @@
"description": "",
"consumes": [],
"produces": [],
- "environment_keys": [],
"oci_image": "openkat/test"
}
diff --git a/boefjes/tests/test_tasks.py b/boefjes/tests/test_tasks.py
index f0cbf5fac56..f9c319897a1 100644
--- a/boefjes/tests/test_tasks.py
+++ b/boefjes/tests/test_tasks.py
@@ -198,6 +198,6 @@ def test_correct_local_runner_hash(self) -> None:
assert Path(path / "__pycache__/pytest__init__.cpython-311.pyc").is_file()
assert Path(path / "__pycache__/pytest_main.cpython-311.pyc").is_file()
- assert boefje_resource_1.runnable_hash == "4bae5e869bd17759bf750bf357fdee1eedff5768d407248b8ddcb63d0abdee19"
- assert boefje_resource_2.runnable_hash == "e0c46fb915778b06f69cd5934b2157733cef84d67fc89c563c5bbd965ad52949"
- assert boefje_resource_3.runnable_hash == "0185c90d3d1a4dc1490ec918374f84e8a480101f98db14d434638147dd82c626"
+ assert boefje_resource_1.runnable_hash == "7450ebc13f6856df925e90cd57f2769468a39723f18ba835749982b484564ec9"
+ assert boefje_resource_2.runnable_hash == "874e154b572a0315cfe4329bd3b756bf9cad77f6a87bb9b9b9bb6296f1d4b520"
+ assert boefje_resource_3.runnable_hash == "70c0b0ad3b2e70fd79e52dcf043096a50ed69db1359df0011499e66ab1510bbe"
diff --git a/docs/source/developer_documentation/boefjes.md b/docs/source/developer_documentation/boefjes.md
index 553dfddd74e..2a8c4737138 100644
--- a/docs/source/developer_documentation/boefjes.md
+++ b/docs/source/developer_documentation/boefjes.md
@@ -51,13 +51,13 @@ although this would be more complicated.
## Environment variables
By design, Boefjes do not have access to the host system's environment variables.
-If a Boefje requires access to an environment variable (e.g. `HTTP_PROXY` or `USER_AGENT`), it should note as such in its `boefje.json` manifest.
+If a Boefje requires access to an environment variable (e.g. `HTTP_PROXY` or `USER_AGENT`), it should note as such in its `schema.json`.
The system-wide variables can be set as environment variable to the boefjes runner by prefixing it with `BOEFJE_`.
This is to prevent a Boefje from accessing variables it should not have access to, such as secrets.
To illustrate: if `BOEFJE_HTTP_PROXY=https://proxy:8080` environment variable is configured, the Boefje can access it as `HTTP_PROXY`.
This feature can also be used to set default values for KAT-alogus settings. For example, configuring the `BOEFJE_TOP_PORTS` environment variable
will set the default value for the `TOP_PORTS` setting (used by the nmap Boefje).
-This default value can be overridden by setting any value for `TOP_PORTS` in the KAT-alogus.
+This default value can be overridden per organisation by setting any value for `TOP_PORTS` in the KAT-alogus.
## Technical Design
diff --git a/docs/source/developer_documentation/development_tutorial/creating_a_boefje.md b/docs/source/developer_documentation/development_tutorial/creating_a_boefje.md
index dbb2f3bdc70..65e4d750f18 100644
--- a/docs/source/developer_documentation/development_tutorial/creating_a_boefje.md
+++ b/docs/source/developer_documentation/development_tutorial/creating_a_boefje.md
@@ -28,7 +28,6 @@ This file contains information about our boefje. For example, this file contains
"name": "Hello Katty",
"description": "A simple boefje that can say hello",
"consumes": ["IPAddressV4", "IPAddressV6"],
- "environment_keys": ["MESSAGE", "NUMBER"],
"scan_level": 0,
"oci_image": "openkat/hello-katty"
}
@@ -38,7 +37,6 @@ This file contains information about our boefje. For example, this file contains
- **`name`**: A name to display in the KAT-alogus.
- **`description`**: A description in the KAT-alogus.
- **`consumes`**: A list of OOI types that trigger the boefje to run. Whenever one of these OOIs gets added, this boefje will run with that OOI. In our case, we will run our boefje whenever a new IPAddressV4 or IPAddressV6 gets added.
-- **`environment_keys`**: A list of inputs provided by the user. More information about these inputs can be found in `schema.json`. OpenKAT also provides some environment variables.
- **`scan_level`**: A scan level that decides how intrusively this boefje will scan the provided OOIs. Since we will not make any external requests our boefje will have a scan level of 0.
- **`oci_image`**: The name of the docker image that is provided inside `boefjes/Makefile`
@@ -52,8 +50,9 @@ This file contains a description of the boefje to explain to the user what this
## `schema.json`
-This JSON is used as the basis for a form for the user. When the user enables this boefje they can get the option to give extra information. For example, it can contain an API key that the script requires.
-This is an example of a `schema.json` file:
+To allow the user to pass information to a boefje runtime, add a schema.json file to the folder where your boefje is located.
+This can be used, for example, to add an API key that the script requires.
+It must conform to the https://json-schema.org/ standard, for example:
```json
{
@@ -78,6 +77,24 @@ This is an example of a `schema.json` file:
}
```
+This JSON defines which additional environment variables can be set for the boefje.
+There are two ways to do this.
+Firstly, using this schema as an example, you could set the `BOEFJE_MESSAGE` environment variable in the boefje runtime.
+Prepending the key with `BOEFJE_` provides an extra safeguard.
+Note that setting an environment variable means this configuration is applied to _all_ organisations.
+Secondly, if you want to avoid setting environment variables or configure it for just one organisation,
+it is also possible to set the API key through the KAT-alogus.
+Navigate to the boefje detail page of Shodan to find the schema as a form.
+These values take precedence over the environment variables.
+This is also a way to test whether the schema is properly understood for your boefje.
+If encryption has been set up for the KATalogus, all keys provided through this form are stored encrypted in the database.
+
+Although the Shodan boefje defines an API key, the schema could contain anything your boefje needs.
+However, OpenKAT currently officially only supports "string" and "integer" properties that are one level deep.
+Because keys may be passed through environment variables,
+schema validation does not happen right away when settings are added or boefjes enabled.
+Schema validation happens right before spawning a boefje, meaning your tasks will fail if is missing a required variable.
+
- `title`: This should always contain a string containing 'Arguments'.
- `type`: This should always contain a string containing 'object'.
- `description`: A description of the boefje explaining in short what it can do. This will both be displayed inside the KAT-alogus and on the boefje's page.
diff --git a/docs/source/introduction/makeyourown.rst b/docs/source/introduction/makeyourown.rst
index 226b2dfcb5c..04d9979b694 100644
--- a/docs/source/introduction/makeyourown.rst
+++ b/docs/source/introduction/makeyourown.rst
@@ -15,7 +15,7 @@ What types of plugins are available?
There are three types of plugins, deployed by OpenKAT to collect information, translate it into objects for the data model and then analyze it. Boefjes gather facts, Whiskers structure the information for the data model and Bits determine what you want to think about it; they are the business rules. Each action is cut into the smallest possible pieces.
-- Boefjes gather factual information, such as by calling an external scanning tool like nmap or using a database like shodan.
+- Boefjes gather factual information, such as by calling an external scanning tool like nmap or using a database like Shodan.
- Whiskers analyze the information and turn it into objects for the data model in Octopoes.
@@ -51,7 +51,7 @@ To make a finding about a CVE to a software version, you need multiple objects:
Existing boefjes
================
-The existing boefjes can be viewed via the KATalog in OpenKAT and are on `GitHUB in the boefjes repository. `_
+The existing boefjes can be viewed via the KATalog in OpenKAT and are on `GitHub in the boefjes repository. `_
Object-types, classes and objects.
----------------------------------
@@ -65,7 +65,7 @@ When we talk about objects, we usually mean instance of such a class, or a 'reco
Example: the boefje for shodan
------------------------------
-The boefje calling shodan gives a good first impression of its capabilities. The boefje includes the following files.
+The boefje calling Shodan gives a good first impression of its capabilities. The boefje includes the following files.
- __init.py__, which remains empty
- boefje.json, containing the normalizers and object-types in the data model
@@ -75,7 +75,7 @@ The boefje calling shodan gives a good first impression of its capabilities. The
- normalize.py, the normalizer (whiskers)
- normalizer.json, which accepts and supplies the normalizer
- requirements.txt, with the requirements for this boefje
-- schema.json, settings for the web interface
+- schema.json, settings for the web interface formatted as JSON Schema
boefje.json
***********
@@ -99,7 +99,6 @@ An example:
"IPPort",
"CVEFindingType"
],
- "environment_keys": ["SHODAN_API"],
"scan_level": 1
}
@@ -111,15 +110,15 @@ Using the template as a base, you can create a boefje.json for your own boefje.
NOTE: If your boefje needs object-types that do not exist, you will need to create those. This will be described later in the document.
-The boefje also uses variables from the web interface, like the Shodan the API key. There are more possibilities, you can be creative with this and let the end user bring settings from the web interface. Use *environment_keys* for this. The schema.json file defines the metadata for these fields.
+The boefje also uses variables from the web interface, like the Shodan the API key. There are more possibilities, you can be creative with this and let the end user bring settings from the web interface. Use environment variables or KATalogus settings for this. The schema.json file defines the metadata for these fields.
schema.json
***********
-To allow the user to add information through the web interface, add the schema.json file to the folder where your boefje is located. This json is used as the basis for a form for the user. In this case, it can contain an API key, but it can also be something else that your boefje responds to. This Schema must conform to the https://json-schema.org/ standard.
-
-Currently, however, OpenKAT only understands fairly shallow structures. For example, not all field types are supported, nor does OpenKAT understand references. You can test whether your Schema is neatly understood by checking the settings form in Rocky's KAT catalog for your boefje.
+To allow the user to pass information to a boefje runtime, add a schema.json file to the folder where your boefje is located.
+This can be used, for example, to add an API key that the script requires.
+It must conform to the https://json-schema.org/ standard, see for example the schema.json for Shodan:
.. code-block:: json
@@ -139,6 +138,24 @@ Currently, however, OpenKAT only understands fairly shallow structures. For exam
]
}
+This JSON defines which additional environment variables can be set for the boefje.
+There are two ways to do this.
+Firstly, using the Shodan schema as an example, you could set the ``BOEFJE_SHODAN_API`` environment variable in the boefje runtime.
+Prepending the key with ``BOEFJE_`` provides an extra safeguard.
+Note that setting an environment variable means this configuration is applied to _all_ organisations.
+Secondly, if you want to avoid setting environment variables or configure it for just one organisation,
+it is also possible to set the API key through the KAT-alogus.
+Navigate to the boefje detail page of Shodan to find the schema as a form.
+These values take precedence over the environment variables.
+This is also a way to test whether the schema is properly understood for your boefje.
+If encryption has been set up for the KATalogus, all keys provided through this form are stored encrypted in the database.
+
+Although the Shodan boefje defines an API key, the schema could contain anything your boefje needs.
+However, OpenKAT currently only officially supports "string" and "integer" properties that are one level deep.
+Because keys may be passed through environment variables,
+schema validation does not happen right away when settings are added or boefjes enabled.
+Schema validation happens right before spawning a boefje, meaning your tasks will fail if is missing a required variable.
+
main.py
*******
@@ -190,8 +207,8 @@ normalizer.json
The normalizers translate the output of a boefje into objects that fit the data model.
Each normalizer defines what input it accepts and what object-types it provides.
-In the case of the shodan normalizer,
-it involves the entire output of the shodan boefje (created based on IP address),
+In the case of the Shodan normalizer,
+it involves the entire output of the Shodan boefje (created based on IP address),
where findings and ports come out. The `normalizer.json` defines these:
.. code-block:: json
@@ -295,7 +312,7 @@ If you want to add an object-type, you need to know with which other object-type
Adding object-types to the data model requires an addition in octopus. Here, an object-type can be added if it is connected to other object-types. Visually this is well understood using the `Graph explorer `_. The actual code is `in the Octopoes repo `_.
-As with the boefje for shodan, here we again use the example from the functional documentation. A description of an object-type in the data model, in this case an IPPort, looks like this:
+As with the boefje for Shodan, here we again use the example from the functional documentation. A description of an object-type in the data model, in this case an IPPort, looks like this:
.. code-block:: python
@@ -521,4 +538,4 @@ There are a number of ways to add your new boefje to OpenKAT.
- Do a commit of your code, after review it can be included
- Add an image server in the KAT catalog config file ``*``
-``*`` If you want to add an image server, join the ongoing project to standardize and describe it. The idea is to add an image server in the KAT catalog config file that has artifacts from your boefjes and normalizers as outputted by the Github CI.
+``*`` If you want to add an image server, join the ongoing project to standardize and describe it. The idea is to add an image server in the KAT catalog config file that has artifacts from your boefjes and normalizers as outputted by the GitHub CI.
diff --git a/mula/scheduler/models/plugin.py b/mula/scheduler/models/plugin.py
index f51c4952972..a30908af8a3 100644
--- a/mula/scheduler/models/plugin.py
+++ b/mula/scheduler/models/plugin.py
@@ -12,7 +12,6 @@ class Plugin(BaseModel):
authors: list[str] | None = None
created: datetime.datetime | None = None
description: str | None = None
- environment_keys: list[str] | None = None
related: list[str] | None = None
scan_level: int | None = None
consumes: str | list[str]
diff --git a/rocky/katalogus/client.py b/rocky/katalogus/client.py
index 9c293a670ba..8bb9f7fc463 100644
--- a/rocky/katalogus/client.py
+++ b/rocky/katalogus/client.py
@@ -24,7 +24,6 @@ class Plugin(BaseModel):
authors: str | None = None
created: str | None = None
description: str | None = None
- environment_keys: list[str] | None = None
related: list[str] = Field(default_factory=list)
enabled: bool
type: str
diff --git a/rocky/tests/conftest.py b/rocky/tests/conftest.py
index f5bbb28dfac..d9825e7b14e 100644
--- a/rocky/tests/conftest.py
+++ b/rocky/tests/conftest.py
@@ -1775,7 +1775,6 @@ def boefje_dns_records():
authors=None,
created=None,
description="Fetch the DNS record(s) of a hostname",
- environment_keys=None,
related=[],
enabled=True,
type="boefje",
diff --git a/rocky/tests/stubs/katalogus_boefjes.json b/rocky/tests/stubs/katalogus_boefjes.json
index 40c79a6cb1b..d7d6ba63991 100644
--- a/rocky/tests/stubs/katalogus_boefjes.json
+++ b/rocky/tests/stubs/katalogus_boefjes.json
@@ -5,9 +5,6 @@
"version": null,
"created": null,
"description": "Use BinaryEdge to find open ports with vulnerabilities that are found on that port",
- "environment_keys": [
- "BINARYEDGE_API"
- ],
"enabled": true,
"type": "boefje",
"scan_level": 2,
@@ -33,7 +30,6 @@
"version": null,
"created": null,
"description": "Scan SSL certificates of websites",
- "environment_keys": [],
"enabled": false,
"type": "boefje",
"scan_level": 1,
@@ -51,7 +47,6 @@
"version": null,
"created": null,
"description": "Scan SSL/TLS versions of websites",
- "environment_keys": [],
"enabled": false,
"type": "boefje",
"scan_level": 2,
@@ -70,9 +65,6 @@
"version": null,
"created": null,
"description": "Scan wordpress sites",
- "environment_keys": [
- "WP_SCAN_API"
- ],
"enabled": false,
"type": "boefje",
"scan_level": 2,
@@ -91,7 +83,6 @@
"version": null,
"created": null,
"description": null,
- "environment_keys": [],
"enabled": true,
"type": "normalizer",
"consumes": [
diff --git a/rocky/tests/stubs/katalogus_normalizers.json b/rocky/tests/stubs/katalogus_normalizers.json
index 182b5bbb549..dfbca8cb726 100644
--- a/rocky/tests/stubs/katalogus_normalizers.json
+++ b/rocky/tests/stubs/katalogus_normalizers.json
@@ -5,7 +5,6 @@
"version": null,
"created": null,
"description": null,
- "environment_keys": [],
"enabled": true,
"type": "normalizer",
"consumes": [
@@ -22,7 +21,6 @@
"version": null,
"created": null,
"description": null,
- "environment_keys": [],
"enabled": true,
"type": "normalizer",
"consumes": [
@@ -42,7 +40,6 @@
"version": null,
"created": null,
"description": null,
- "environment_keys": [],
"enabled": true,
"type": "normalizer",
"consumes": [
@@ -59,7 +56,6 @@
"version": null,
"created": null,
"description": null,
- "environment_keys": [],
"enabled": true,
"type": "normalizer",
"consumes": [
@@ -83,7 +79,6 @@
"version": null,
"created": null,
"description": null,
- "environment_keys": [],
"enabled": true,
"type": "normalizer",
"consumes": [
diff --git a/rocky/tests/test_indemnification.py b/rocky/tests/test_indemnification.py
index 785679ca7aa..e6d628b323e 100644
--- a/rocky/tests/test_indemnification.py
+++ b/rocky/tests/test_indemnification.py
@@ -14,7 +14,6 @@ def test_update_clearance_level(rf, client_member, httpx_mock):
"authors": None,
"created": None,
"description": "Use BinaryEdge to find open ports with vulnerabilities that are found on that port",
- "environment_keys": ["BINARYEDGE_API"],
"related": None,
"enabled": True,
"type": "boefje",
From 1e273324f94cd10ba404e5d234d6ca790e27a8d7 Mon Sep 17 00:00:00 2001
From: HeleenSG
Date: Thu, 5 Sep 2024 13:40:26 +0200
Subject: [PATCH 02/82] feat: adds notification styling and icons (#3461)
Co-authored-by: Jan Klopper
---
rocky/assets/css/components/button-plain.scss | 22 ++++++
.../assets/css/components/notifications.scss | 78 +++++++++++++++++++
rocky/assets/css/main.scss | 1 +
.../themes/soft/manon/notification-block.scss | 6 +-
4 files changed, 105 insertions(+), 2 deletions(-)
create mode 100644 rocky/assets/css/components/button-plain.scss
diff --git a/rocky/assets/css/components/button-plain.scss b/rocky/assets/css/components/button-plain.scss
new file mode 100644
index 00000000000..50553d75978
--- /dev/null
+++ b/rocky/assets/css/components/button-plain.scss
@@ -0,0 +1,22 @@
+button,
+a.button,
+input[type="button"],
+input[type="submit"],
+input[type="reset"] {
+ &.plain {
+ background-color: transparent;
+ border: 0;
+ color: var(--colors-blue-600);
+ padding: 0;
+
+ &:hover {
+ color: var(--colors-blue-700);
+ }
+
+ .icon {
+ &:hover {
+ color: var(--colors-blue-700);
+ }
+ }
+ }
+}
diff --git a/rocky/assets/css/components/notifications.scss b/rocky/assets/css/components/notifications.scss
index a6e557fdcb1..83abe718872 100644
--- a/rocky/assets/css/components/notifications.scss
+++ b/rocky/assets/css/components/notifications.scss
@@ -1,3 +1,25 @@
+/* Block notification */
+div,
+section {
+ &.error,
+ &.warning,
+ &.explanation,
+ &.confirmation,
+ &.system {
+ flex-direction: row;
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ font-size: var(--heading-small-font-size);
+ }
+ }
+}
+
+/* Help text */
div {
&.explanation {
&.help-text {
@@ -10,3 +32,59 @@ div {
margin-top: 0;
}
}
+
+/* Inline notification */
+p,
+span {
+ &.error,
+ &.warning,
+ &.explanation,
+ &.confirmation,
+ &.system {
+ display: flex;
+ align-items: center;
+ }
+}
+
+/* Icons */
+p,
+span,
+section,
+div {
+ &.error {
+ &::before {
+ content: "\eac5";
+ color: var(--colors-red-600);
+ }
+ }
+
+ &.warning {
+ &::before {
+ content: "\ea06";
+ color: var(--colors-ochre-500);
+ }
+ }
+
+ &.explanation {
+ &::before {
+ content: "\eac5";
+ color: var(--colors-blue-600);
+ }
+ }
+
+ &.confirmation {
+ &::before {
+ content: "\ea5e";
+ color: var(--colors-green-600);
+ }
+ }
+
+ &.system {
+ background-color: var(--colors-black-05);
+
+ &::before {
+ content: "\ea06";
+ color: var(--colors-blue-600);
+ }
+ }
+}
diff --git a/rocky/assets/css/main.scss b/rocky/assets/css/main.scss
index 0e41f9ea21d..d68d91f47e0 100644
--- a/rocky/assets/css/main.scss
+++ b/rocky/assets/css/main.scss
@@ -29,6 +29,7 @@
/* Components */
@import "components/action-buttons";
@import "components/block-indented";
+@import "components/button-plain";
@import "components/cat-loader";
@import "components/cat-paw-loader";
@import "components/cytoscape";
diff --git a/rocky/assets/css/themes/soft/manon/notification-block.scss b/rocky/assets/css/themes/soft/manon/notification-block.scss
index 713d94abde7..45e75d01f6c 100644
--- a/rocky/assets/css/themes/soft/manon/notification-block.scss
+++ b/rocky/assets/css/themes/soft/manon/notification-block.scss
@@ -1,7 +1,9 @@
/* Notification block - Variables */
:root {
- --notification-block-element-padding-right: 0.5rem;
- --notification-block-element-padding-left: 0.5rem;
+ --notification-block-element-padding-top: var(--spacing-grid-300);
+ --notification-block-element-padding-right: var(--spacing-grid-200);
+ --notification-block-element-padding-left: var(--spacing-grid-200);
+ --notification-block-element-padding-bottom: var(--spacing-grid-300);
--notification-block-element-gap: 0.5rem;
}
From 980415375c372e6f32086ce55078e8e5533f456b Mon Sep 17 00:00:00 2001
From: Donny Peeters <46660228+Donnype@users.noreply.github.com>
Date: Thu, 5 Sep 2024 15:16:58 +0200
Subject: [PATCH 03/82] Hotfix for normalizer API bug (#3475)
Signed-off-by: Donny Peeters
---
mula/scheduler/server/handlers/tasks.py | 17 +++++++++++------
mula/tests/integration/test_api.py | 7 +++++++
2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/mula/scheduler/server/handlers/tasks.py b/mula/scheduler/server/handlers/tasks.py
index db3391dc2d6..6912e7023ab 100644
--- a/mula/scheduler/server/handlers/tasks.py
+++ b/mula/scheduler/server/handlers/tasks.py
@@ -139,12 +139,16 @@ def list(
]
}
elif task_type == "normalizer":
- f_plugin = storage.filters.Filter(
- column="data",
- field="normalizer__id",
- operator="eq",
- value=plugin_id,
- )
+ f_plugin = {
+ "and": [
+ storage.filters.Filter(
+ column="data",
+ field="normalizer__id",
+ operator="eq",
+ value=plugin_id,
+ )
+ ]
+ }
else:
f_plugin = {
"or": [
@@ -182,6 +186,7 @@ def list(
detail=f"invalid filter(s) [exception: {exc}]",
) from exc
except storage.errors.StorageError as exc:
+ self.logger.exception(exc)
raise fastapi.HTTPException(
status_code=fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"error occurred while accessing the database [exception: {exc}]",
diff --git a/mula/tests/integration/test_api.py b/mula/tests/integration/test_api.py
index 0511c200f52..9f6440c5801 100644
--- a/mula/tests/integration/test_api.py
+++ b/mula/tests/integration/test_api.py
@@ -706,6 +706,13 @@ def test_get_tasks_min_created_at_future(self):
self.assertEqual(200, response.status_code)
self.assertEqual(0, len(response.json()["results"]))
+ def test_get_normalizer_filtered(self):
+ # This used to be a bug where the normalizer filter was missing a nesting on the operator
+ params = {"task_type": "normalizer", "plugin_id": "kat_nmap_normalize"}
+ response = self.client.get("/tasks", params=params)
+ self.assertEqual(200, response.status_code)
+ self.assertEqual(0, len(response.json()["results"]))
+
def test_get_tasks_filtered(self):
response = self.client.post(
"/tasks",
From 79bba68f818ee04bec6ee1363ae05a55d8705238 Mon Sep 17 00:00:00 2001
From: HeleenSG
Date: Fri, 6 Sep 2024 09:14:58 +0200
Subject: [PATCH 04/82] fix: toggle styling (#3449)
Co-authored-by: Jan Klopper
---
rocky/assets/css/components/toggle.scss | 70 +++++++++++++++++++
rocky/assets/css/components/toolbar.scss | 1 +
rocky/assets/css/main.scss | 1 +
.../soft/fundamentals/border-radii.scss | 3 +-
.../css/themes/soft/manon/button-icon.scss | 2 +
.../assets/css/themes/soft/manon/button.scss | 2 +-
.../templates/partials/katalogus_toolbar.html | 6 +-
7 files changed, 81 insertions(+), 4 deletions(-)
create mode 100644 rocky/assets/css/components/toggle.scss
diff --git a/rocky/assets/css/components/toggle.scss b/rocky/assets/css/components/toggle.scss
new file mode 100644
index 00000000000..d2a1dc59717
--- /dev/null
+++ b/rocky/assets/css/components/toggle.scss
@@ -0,0 +1,70 @@
+button,
+a,
+input[type="button"],
+input[type="submit"],
+input[type="reset"] {
+ $breakpoint: 24rem !default;
+
+ &.toggle-button {
+ /* Alignment */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ align-self: flex-start;
+ gap: 0;
+ margin: 0;
+ box-sizing: border-box;
+
+ /* Styling */
+ border-radius: var(--border-radius-l);
+ padding: var(--spacing-grid-150);
+ width: 2.75rem;
+ height: 2.75rem;
+ border: 1px solid var(--colors-purrple-300);
+
+ /* Text and icon styling */
+ background-color: var(--colors-white);
+ color: var(--colors-purrple-500);
+ text-decoration: none;
+ font-size: 0;
+
+ &::before {
+ color: var(--colors-purrple-500);
+ padding: 0;
+ }
+
+ /* Behaviour */
+ cursor: pointer;
+ overflow-wrap: break-word;
+
+ /* States */
+ &[aria-current="page"] {
+ background-color: var(--colors-purrple-200);
+ border-color: var(--colors-purrple-200);
+
+ &::before {
+ color: var(--colors-purrple-700);
+ }
+ }
+
+ &:hover,
+ &:active {
+ border-color: var(--colors-purrple-500);
+ }
+
+ &:focus {
+ outline: 2px solid var(--colors-purrple-500);
+ outline-offset: 0.125rem;
+ z-index: 2;
+ position: relative;
+ }
+ }
+}
+
+a {
+ &.toggle-button {
+ &:visited {
+ color: var(--colors-purrple-500);
+ }
+ }
+}
diff --git a/rocky/assets/css/components/toolbar.scss b/rocky/assets/css/components/toolbar.scss
index ae148bb349c..f8c3e962c81 100644
--- a/rocky/assets/css/components/toolbar.scss
+++ b/rocky/assets/css/components/toolbar.scss
@@ -4,6 +4,7 @@
justify-content: flex-end;
align-items: center;
width: 100%;
+ overflow: visible; /* Making sure outline is visible on focus */
&.start {
justify-content: flex-start;
diff --git a/rocky/assets/css/main.scss b/rocky/assets/css/main.scss
index d68d91f47e0..f6171506258 100644
--- a/rocky/assets/css/main.scss
+++ b/rocky/assets/css/main.scss
@@ -65,6 +65,7 @@
@import "components/sticky";
@import "components/state-tags";
@import "components/table";
+@import "components/toggle";
@import "components/toolbar";
@import "components/tree-tables";
@import "components/user-icon";
diff --git a/rocky/assets/css/themes/soft/fundamentals/border-radii.scss b/rocky/assets/css/themes/soft/fundamentals/border-radii.scss
index 6ca1b1c20b7..0ac5c9d7060 100644
--- a/rocky/assets/css/themes/soft/fundamentals/border-radii.scss
+++ b/rocky/assets/css/themes/soft/fundamentals/border-radii.scss
@@ -3,7 +3,8 @@
:root {
--border-radius-s: 0.25rem;
--border-radius-m: 0.5rem;
- --border-radius-l: 1.5rem;
+ --border-radius-l: 1rem;
+ --border-radius-xl: 1.5rem;
--border-radius-round: 50%;
}
diff --git a/rocky/assets/css/themes/soft/manon/button-icon.scss b/rocky/assets/css/themes/soft/manon/button-icon.scss
index e814863f4a2..d8cff1a5e1e 100644
--- a/rocky/assets/css/themes/soft/manon/button-icon.scss
+++ b/rocky/assets/css/themes/soft/manon/button-icon.scss
@@ -2,7 +2,9 @@
:root {
--button-icon-min-width: 2.75rem;
+ --button-icon-width: 2.75rem;
--button-icon-min-height: 2.75rem;
+ --button-icon-height: 2.75rem;
--button-icon-icon-font-size: 0.8rem;
--button-icon-icon-width: 0.75rem;
}
diff --git a/rocky/assets/css/themes/soft/manon/button.scss b/rocky/assets/css/themes/soft/manon/button.scss
index 93c3c716d21..a8260df13ec 100644
--- a/rocky/assets/css/themes/soft/manon/button.scss
+++ b/rocky/assets/css/themes/soft/manon/button.scss
@@ -4,7 +4,7 @@
/* Button */
--button-base-background-color: var(--branding-color-2-background-color);
--button-base-text-color: var(--branding-color-2-text-color);
- --button-base-border-radius: 1rem;
+ --button-base-border-radius: var(--border-radius-l);
--button-base-font-family: var(--application-base-font-family);
--button-base-padding-top: var(--spacing-grid-150);
--button-base-padding-right: var(--spacing-grid-200);
diff --git a/rocky/katalogus/templates/partials/katalogus_toolbar.html b/rocky/katalogus/templates/partials/katalogus_toolbar.html
index cea35c9210b..96f21fe1c29 100644
--- a/rocky/katalogus/templates/partials/katalogus_toolbar.html
+++ b/rocky/katalogus/templates/partials/katalogus_toolbar.html
@@ -3,8 +3,10 @@
{% if object_list %}
{% if not selected_oois %}
- {% include "partials/return_button.html" with btn_text="Go back" %}
+ {% include "partials/return_button.html" with btn_text="Go back" selected_report_types=None %}
{% else %}
@@ -40,7 +40,7 @@
action="{{ previous }}"
class="inline layout-wide">
{% csrf_token %}
- {% include "forms/report_form_fields.html" %}
+ {% include "forms/report_form_fields.html" with selected_report_types=None %}