mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-20 10:24:15 +00:00
fix(api): refuse the published JWT signing key
This commit is contained in:
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
|
||||
@@ -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}' "
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user