-
Notifications
You must be signed in to change notification settings - Fork 1
feat: cloud integration — WAF v2, Secrets Manager, CloudTrail retention, DynamoDB PITR, SQS DLQ alarms #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
357f4ff
e8f1c4f
385e407
187f7f3
1e17627
694d921
a9a1e47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 1.2.3 | ||
| 1.3.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,105 @@ | ||
| import json | ||
| import logging | ||
| import os | ||
| from typing import Any | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| _APP_SECRETS_CACHE: dict[str, Any] | None = None | ||
| _APP_SECRETS_CACHE_ARN: str | None = None | ||
|
|
||
|
|
||
| def _get_app_secrets() -> dict[str, Any]: | ||
| """Fetch and cache the shared app secret bundle from Secrets Manager.""" | ||
| global _APP_SECRETS_CACHE, _APP_SECRETS_CACHE_ARN | ||
|
|
||
| secret_arn = os.environ.get("SECRET_ARN", "").strip() | ||
| if not secret_arn: | ||
| return {} | ||
|
|
||
| if ( | ||
| _APP_SECRETS_CACHE is not None | ||
| and _APP_SECRETS_CACHE_ARN == secret_arn | ||
| ): | ||
| return _APP_SECRETS_CACHE | ||
|
|
||
| import boto3 | ||
| from botocore.exceptions import BotoCoreError, ClientError | ||
|
|
||
| try: | ||
| region = os.environ.get("AWS_REGION_NAME", "eu-west-3") | ||
| client = boto3.client("secretsmanager", region_name=region) | ||
| resp = client.get_secret_value(SecretId=secret_arn) | ||
| data = json.loads(resp["SecretString"]) | ||
| if not isinstance(data, dict): | ||
| raise ValueError("secret payload must be a JSON object") | ||
| except ( | ||
| ClientError, | ||
| BotoCoreError, | ||
| json.JSONDecodeError, | ||
| KeyError, | ||
| ValueError, | ||
| ) as exc: | ||
| raise RuntimeError( | ||
| f"Failed to load application secrets from {secret_arn}: {exc}" | ||
| ) from exc | ||
|
|
||
| _APP_SECRETS_CACHE = data | ||
| _APP_SECRETS_CACHE_ARN = secret_arn | ||
| return data | ||
|
|
||
|
|
||
| def _resolve_secret_key() -> str: | ||
| """Return the Flask SECRET_KEY. | ||
|
|
||
| When running on AWS Lambda the ``SECRET_ARN`` environment variable points | ||
| to a Secrets Manager secret that holds the key as a JSON object | ||
| ``{"SECRET_KEY": "..."}`` alongside other credentials. We fetch the value | ||
| at startup so the plaintext key is never stored as a Lambda environment | ||
| variable (where it is visible in the AWS console). | ||
|
|
||
| Falls back to the ``SECRET_KEY`` environment variable (or the insecure | ||
| development default) when ``SECRET_ARN`` is absent — this preserves | ||
| backward-compatibility for local development and unit tests. | ||
| """ | ||
| secret_arn = os.environ.get("SECRET_ARN", "").strip() | ||
| if secret_arn: | ||
| secret_key = _get_app_secrets().get("SECRET_KEY") | ||
| if secret_key: | ||
| return secret_key | ||
|
|
||
| env_secret_key = os.environ.get("SECRET_KEY", "") | ||
| if env_secret_key: | ||
| logger.warning( | ||
| "SECRET_ARN is set but SECRET_KEY was not found in Secrets Manager; " | ||
| "falling back to SECRET_KEY env var" | ||
| ) | ||
| return env_secret_key | ||
|
|
||
| raise RuntimeError( | ||
| "SECRET_ARN is set but SECRET_KEY could not be resolved from Secrets " | ||
| "Manager and no SECRET_KEY env fallback is configured" | ||
| ) | ||
| return os.environ.get("SECRET_KEY", "dev-secret-key-change-in-production") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| def _resolve_msal_client_secret() -> str: | ||
| """Return the MSAL client secret from Secrets Manager or env var.""" | ||
| secret_arn = os.environ.get("SECRET_ARN", "").strip() | ||
| if secret_arn: | ||
| msal_client_secret = _get_app_secrets().get("MSAL_CLIENT_SECRET") | ||
| if msal_client_secret: | ||
| return msal_client_secret | ||
|
|
||
| logger.warning( | ||
| "SECRET_ARN is set but MSAL_CLIENT_SECRET was not found in Secrets " | ||
| "Manager; falling back to MSAL_CLIENT_SECRET env var" | ||
| ) | ||
| return os.environ.get("MSAL_CLIENT_SECRET", "") | ||
|
Comment on lines
+52
to
+98
|
||
|
|
||
|
|
||
| class Config: | ||
| SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production') | ||
| SECRET_KEY = _resolve_secret_key() | ||
|
|
||
| # Flask-WTF CSRF — allow requests through CloudFront where the Referer | ||
| # header is the CloudFront domain, not the API Gateway origin. | ||
|
|
@@ -54,7 +151,7 @@ class Config: | |
| # Set all three variables to enable the "Sign in with Microsoft" button. | ||
| # Leave them empty (default) to disable SSO entirely. | ||
| MSAL_CLIENT_ID = os.environ.get('MSAL_CLIENT_ID', '') | ||
| MSAL_CLIENT_SECRET = os.environ.get('MSAL_CLIENT_SECRET', '') | ||
| MSAL_CLIENT_SECRET = _resolve_msal_client_secret() | ||
| # e.g. "https://login.microsoftonline.com/<tenant-id>/v2.0" or | ||
| # "https://login.microsoftonline.com/common/v2.0" for multi-tenant. | ||
| MSAL_AUTHORITY = os.environ.get( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This entry says
MSAL_CLIENT_SECRETwas removed from plaintext Lambda env vars and that Lambda receives onlySECRET_ARN, butphishing-platform-infra/terraform/lambda.tfstill setsMSAL_CLIENT_SECRET = var.msal_client_secretin the environment. Either update the infrastructure to actually remove it, or adjust the changelog to reflect the current behavior.