mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-09-30 04:13:12 +00:00
* feat(api): serve meals on ECS Fargate instead of Lambda Keep the Flask app always-on with in-process jobs so CloudFront no longer fronts a cold-start API Gateway. * fix(jobs): run delayed close and reminder deliveries Wall-clock skip windows dropped the only weekly SQS attempt when Scheduler already fired in Eastern time. Dev schedules stay disabled. * fix(api): return JSON objects and stop logging job payloads Flask now jsonify-s handler dicts so API responses are not HTML, and the worker logs only event and status. * fix(ci): restore the reusable workflow so the required check is named ci / ci Inlining the job reported `ci` instead of the org ruleset's `ci / ci`. * fix(secrets): drop unused os import so ruff check passes * style: apply ruff format so ci-python-app lint passes * fix(infra): give meals its own VPC because prod has none * chore(security): re-key ALB SG checkov suppression after vpc.tf
209 lines
4.3 KiB
HCL
209 lines
4.3 KiB
HCL
# Form-hosting and reports buckets.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Form bucket — private origin for the CloudFront distribution
|
|
# ---------------------------------------------------------------------------
|
|
|
|
resource "aws_s3_bucket" "form" {
|
|
bucket = local.form_bucket_name
|
|
|
|
tags = {
|
|
Purpose = "meal-order-form-hosting"
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_public_access_block" "form" {
|
|
bucket = aws_s3_bucket.form.id
|
|
|
|
block_public_acls = true
|
|
block_public_policy = true
|
|
ignore_public_acls = true
|
|
restrict_public_buckets = true
|
|
}
|
|
|
|
resource "aws_s3_bucket_ownership_controls" "form" {
|
|
bucket = aws_s3_bucket.form.id
|
|
|
|
rule {
|
|
object_ownership = "BucketOwnerEnforced"
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_server_side_encryption_configuration" "form" {
|
|
bucket = aws_s3_bucket.form.id
|
|
|
|
rule {
|
|
apply_server_side_encryption_by_default {
|
|
sse_algorithm = "AES256"
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_lifecycle_configuration" "form" {
|
|
bucket = aws_s3_bucket.form.id
|
|
|
|
rule {
|
|
id = "delete-old-archives"
|
|
status = "Enabled"
|
|
|
|
filter {
|
|
prefix = "archive/"
|
|
}
|
|
|
|
expiration {
|
|
days = 90
|
|
}
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "form" {
|
|
statement {
|
|
sid = "DenyInsecureTransport"
|
|
effect = "Deny"
|
|
|
|
principals {
|
|
type = "*"
|
|
identifiers = ["*"]
|
|
}
|
|
|
|
actions = ["s3:*"]
|
|
|
|
resources = [
|
|
aws_s3_bucket.form.arn,
|
|
"${aws_s3_bucket.form.arn}/*",
|
|
]
|
|
|
|
condition {
|
|
test = "Bool"
|
|
variable = "aws:SecureTransport"
|
|
values = ["false"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "AllowCloudFrontOAC"
|
|
effect = "Allow"
|
|
|
|
principals {
|
|
type = "Service"
|
|
identifiers = ["cloudfront.amazonaws.com"]
|
|
}
|
|
|
|
actions = ["s3:GetObject"]
|
|
resources = ["${aws_s3_bucket.form.arn}/*"]
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "AWS:SourceArn"
|
|
values = [aws_cloudfront_distribution.form.arn]
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_policy" "form" {
|
|
bucket = aws_s3_bucket.form.id
|
|
policy = data.aws_iam_policy_document.form.json
|
|
|
|
depends_on = [aws_s3_bucket_public_access_block.form]
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reports bucket — payroll CSVs and weekly summary PDFs
|
|
# ---------------------------------------------------------------------------
|
|
|
|
resource "aws_s3_bucket" "reports" {
|
|
bucket = local.reports_bucket_name
|
|
|
|
tags = {
|
|
Purpose = "meal-order-reports"
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_public_access_block" "reports" {
|
|
bucket = aws_s3_bucket.reports.id
|
|
|
|
block_public_acls = true
|
|
block_public_policy = true
|
|
ignore_public_acls = true
|
|
restrict_public_buckets = true
|
|
}
|
|
|
|
resource "aws_s3_bucket_ownership_controls" "reports" {
|
|
bucket = aws_s3_bucket.reports.id
|
|
|
|
rule {
|
|
object_ownership = "BucketOwnerEnforced"
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_server_side_encryption_configuration" "reports" {
|
|
bucket = aws_s3_bucket.reports.id
|
|
|
|
rule {
|
|
apply_server_side_encryption_by_default {
|
|
sse_algorithm = "AES256"
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_lifecycle_configuration" "reports" {
|
|
bucket = aws_s3_bucket.reports.id
|
|
|
|
# Whole-bucket rule, matching the SAM template's unprefixed rule.
|
|
rule {
|
|
id = "archive-old-reports"
|
|
status = "Enabled"
|
|
|
|
filter {
|
|
prefix = ""
|
|
}
|
|
|
|
transition {
|
|
days = 90
|
|
storage_class = "GLACIER_IR"
|
|
}
|
|
}
|
|
}
|
|
|
|
# The SAM template attached no policy to this bucket. The TLS-only deny is a
|
|
# deliberate addition; it grants nothing and blocks plaintext access to payroll
|
|
# data.
|
|
data "aws_iam_policy_document" "reports" {
|
|
statement {
|
|
sid = "DenyInsecureTransport"
|
|
effect = "Deny"
|
|
|
|
principals {
|
|
type = "*"
|
|
identifiers = ["*"]
|
|
}
|
|
|
|
actions = ["s3:*"]
|
|
|
|
resources = [
|
|
aws_s3_bucket.reports.arn,
|
|
"${aws_s3_bucket.reports.arn}/*",
|
|
]
|
|
|
|
condition {
|
|
test = "Bool"
|
|
variable = "aws:SecureTransport"
|
|
values = ["false"]
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_policy" "reports" {
|
|
bucket = aws_s3_bucket.reports.id
|
|
policy = data.aws_iam_policy_document.reports.json
|
|
|
|
depends_on = [aws_s3_bucket_public_access_block.reports]
|
|
}
|