-
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 1 commit
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,63 @@ | ||
| import json | ||
| import logging | ||
| import os | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| 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", "") | ||
| if secret_arn: | ||
| try: | ||
| import boto3 | ||
| from botocore.exceptions import BotoCoreError, ClientError | ||
|
|
||
| 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"]) | ||
| return data.get("SECRET_KEY", os.environ.get("SECRET_KEY", "dev-secret-key-change-in-production")) | ||
| except (ClientError, BotoCoreError) as exc: | ||
| logger.warning("Secrets Manager fetch failed (%s); falling back to SECRET_KEY env var", exc) | ||
| except (json.JSONDecodeError, KeyError) as exc: | ||
| logger.warning("Secrets Manager secret parse error (%s); falling back to SECRET_KEY env var", exc) | ||
| return os.environ.get("SECRET_KEY", "dev-secret-key-change-in-production") | ||
|
||
|
|
||
|
|
||
| def _resolve_msal_client_secret() -> str: | ||
| """Return the MSAL client secret from Secrets Manager or env var.""" | ||
| secret_arn = os.environ.get("SECRET_ARN", "") | ||
| if secret_arn: | ||
| try: | ||
| import boto3 | ||
| from botocore.exceptions import BotoCoreError, ClientError | ||
|
|
||
| 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"]) | ||
| return data.get("MSAL_CLIENT_SECRET", os.environ.get("MSAL_CLIENT_SECRET", "")) | ||
| except (ClientError, BotoCoreError) as exc: | ||
| logger.warning("Secrets Manager fetch failed (%s); falling back to MSAL_CLIENT_SECRET env var", exc) | ||
| except (json.JSONDecodeError, KeyError) as exc: | ||
| logger.warning("Secrets Manager secret parse error (%s); falling back to MSAL_CLIENT_SECRET env var", exc) | ||
| 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 +109,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( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # ─── CloudTrail — Audit Logging ────────────────────────────────────────────── | ||
| # Multi-region trail capturing all management events and S3 data events | ||
| # for the application bucket. Logs go to a dedicated encrypted S3 bucket. | ||
|
|
||
| data "aws_caller_identity" "current" {} | ||
|
|
||
| # ─── Dedicated S3 Bucket for Trail Logs ────────────────────────────────────── | ||
|
|
||
| resource "aws_s3_bucket" "cloudtrail" { | ||
| count = var.enable_cloudtrail ? 1 : 0 | ||
| bucket = "${local.prefix}-cloudtrail-${data.aws_caller_identity.current.account_id}" | ||
| force_destroy = true | ||
|
|
||
|
Comment on lines
+9
to
+32
|
||
| tags = { | ||
| Name = "${local.prefix}-cloudtrail" | ||
| } | ||
| } | ||
|
|
||
| resource "aws_s3_bucket_server_side_encryption_configuration" "cloudtrail" { | ||
| count = var.enable_cloudtrail ? 1 : 0 | ||
| bucket = aws_s3_bucket.cloudtrail[0].id | ||
|
|
||
| rule { | ||
| apply_server_side_encryption_by_default { | ||
| sse_algorithm = "AES256" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| resource "aws_s3_bucket_public_access_block" "cloudtrail" { | ||
| count = var.enable_cloudtrail ? 1 : 0 | ||
| bucket = aws_s3_bucket.cloudtrail[0].id | ||
|
|
||
| block_public_acls = true | ||
| block_public_policy = true | ||
| ignore_public_acls = true | ||
| restrict_public_buckets = true | ||
| } | ||
|
|
||
| resource "aws_s3_bucket_policy" "cloudtrail" { | ||
| count = var.enable_cloudtrail ? 1 : 0 | ||
| bucket = aws_s3_bucket.cloudtrail[0].id | ||
|
|
||
| policy = jsonencode({ | ||
| Version = "2012-10-17" | ||
| Statement = [ | ||
| { | ||
| Sid = "AWSCloudTrailAclCheck" | ||
| Effect = "Allow" | ||
| Principal = { | ||
| Service = "cloudtrail.amazonaws.com" | ||
| } | ||
| Action = "s3:GetBucketAcl" | ||
| Resource = aws_s3_bucket.cloudtrail[0].arn | ||
| Condition = { | ||
| StringEquals = { | ||
| "aws:SourceArn" = "arn:aws:cloudtrail:${var.aws_region}:${data.aws_caller_identity.current.account_id}:trail/${local.prefix}-trail" | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| Sid = "AWSCloudTrailWrite" | ||
| Effect = "Allow" | ||
| Principal = { | ||
| Service = "cloudtrail.amazonaws.com" | ||
| } | ||
| Action = "s3:PutObject" | ||
| Resource = "${aws_s3_bucket.cloudtrail[0].arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*" | ||
| Condition = { | ||
| StringEquals = { | ||
| "s3:x-amz-acl" = "bucket-owner-full-control" | ||
| "aws:SourceArn" = "arn:aws:cloudtrail:${var.aws_region}:${data.aws_caller_identity.current.account_id}:trail/${local.prefix}-trail" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| }) | ||
|
|
||
| depends_on = [aws_s3_bucket_public_access_block.cloudtrail] | ||
| } | ||
|
|
||
| # ─── CloudTrail Trail ───────────────────────────────────────────────────────── | ||
|
|
||
| resource "aws_cloudtrail" "app" { | ||
| count = var.enable_cloudtrail ? 1 : 0 | ||
| name = "${local.prefix}-trail" | ||
|
|
||
| s3_bucket_name = aws_s3_bucket.cloudtrail[0].id | ||
| include_global_service_events = true | ||
| is_multi_region_trail = true | ||
| enable_log_file_validation = true | ||
|
|
||
| # Capture all S3 object-level events on the application bucket | ||
| event_selector { | ||
| read_write_type = "All" | ||
| include_management_events = true | ||
|
|
||
| data_resource { | ||
| type = "AWS::S3::Object" | ||
| values = ["${aws_s3_bucket.app.arn}/"] | ||
| } | ||
| } | ||
|
|
||
| tags = { | ||
| Name = "${local.prefix}-trail" | ||
| } | ||
|
|
||
| depends_on = [aws_s3_bucket_policy.cloudtrail] | ||
| } | ||
|
|
||
| # ─── Outputs ────────────────────────────────────────────────────────────────── | ||
|
|
||
| output "cloudtrail_bucket" { | ||
| description = "S3 bucket name for CloudTrail logs (empty if CloudTrail disabled)" | ||
| value = var.enable_cloudtrail ? aws_s3_bucket.cloudtrail[0].id : "" | ||
| } | ||
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.