fix(api): refuse the published JWT signing key

This commit is contained in:
pedrooot committed 2026-10-07 16:44:52 +02:00
1 parent 28aed610d2
commit 1898ace69b
5 files changed
+74 -36

No files matched your search

@@ -0,0 +1 @@
API refuses to start with a JWT signing key published in this repository, and the Helm minimal-installation example no longer ships working secrets
+29
View File
@@ -38,6 +38,15 @@ PUBLISHED_ENCRYPTION_KEY_DIGESTS = frozenset(
}
)
# SHA-256 of the base64 body of JWT signing keys that were committed to this
# repository with a working value. Anyone holding one can forge tokens.
PUBLISHED_SIGNING_KEY_DIGESTS = frozenset(
{
# contrib/k8s/helm/prowler-app/examples/minimal-installation/secrets.yaml
"375d5755ab4a7d38c58b38c67c1a6d1cab5afc91844445d8489c15ce7779d949",
}
)
ENCRYPTION_KEY_DOCS = (
"https://docs.prowler.com/getting-started/installation/prowler-app"
"#secrets-encryption-key"
@@ -50,6 +59,16 @@ def _digest(value):
return hashlib.sha256(value.encode()).hexdigest()
def _pem_digest(value):
"""Digest of a PEM's base64 body, so indentation and line wrapping do not matter."""
body = "".join(
line.strip()
for line in value.splitlines()
if line.strip() and "-----" not in line
)
return _digest(body)
class ApiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "api"
@@ -207,6 +226,16 @@ class ApiConfig(AppConfig):
signing_key = env.str(SIGNING_KEY_ENV, default="").strip()
verifying_key = env.str(VERIFYING_KEY_ENV, default="").strip()
if signing_key and _pem_digest(signing_key) in PUBLISHED_SIGNING_KEY_DIGESTS:
raise ImproperlyConfigured(
f"'{SIGNING_KEY_ENV}' is set to a key pair that was published in the "
"Prowler repository and is therefore public. Anyone holding it can "
"forge API tokens. Generate a new pair with 'openssl genrsa -out "
"private.pem 2048' and 'openssl rsa -in private.pem -pubout', set both "
"variables and restart. Existing sessions end and users sign in again; "
"no stored data needs re-encrypting."
)
if not signing_key or not verifying_key:
logger.info(
f"Generating JWT RSA key pair. In production, set '{SIGNING_KEY_ENV}' and '{VERIFYING_KEY_ENV}' "
+34
View File
@@ -251,6 +251,40 @@ def test_ensure_secrets_refuses_published_encryption_key(monkeypatch, secrets_di
assert not (secrets_dir / ENCRYPTION_KEY_FILE).exists()
def test_ensure_crypto_keys_refuses_published_signing_key(monkeypatch, tmp_path):
published, verifying = _stub_keys()
monkeypatch.setattr(
api_apps_module, "KEYS_DIRECTORY", Path(tmp_path), raising=False
)
monkeypatch.setattr(api_apps_module, "_keys_initialized", False, raising=False)
monkeypatch.setenv(SIGNING_KEY_ENV, published)
monkeypatch.setenv(VERIFYING_KEY_ENV, verifying)
monkeypatch.setattr(settings, "TESTING", False, raising=False)
monkeypatch.setattr(
api_apps_module,
"PUBLISHED_SIGNING_KEY_DIGESTS",
frozenset({api_apps_module._pem_digest(published)}),
raising=False,
)
with pytest.raises(ImproperlyConfigured) as exc_info:
ApiConfig("api", api_apps_module)._ensure_crypto_keys()
assert SIGNING_KEY_ENV in str(exc_info.value)
assert "PRIVATE" not in str(exc_info.value)
assert not (Path(tmp_path) / PRIVATE_KEY_FILE).exists()
def test_pem_digest_ignores_indentation_and_wrapping():
# derived from the existing stub so no PEM literal is added to this file
flat = _stub_keys()[0]
indented = "".join(f" {line}\n" for line in flat.splitlines())
wrapped = flat.replace("PRIVATE\n", "PRIV\nATE\n", 1)
assert api_apps_module._pem_digest(indented) == api_apps_module._pem_digest(flat)
assert api_apps_module._pem_digest(wrapped) == api_apps_module._pem_digest(flat)
def test_ensure_secrets_generates_encryption_key_when_empty(secrets_dir):
_make_app()._ensure_secrets()