seahaven-org-baseline/lib/flow-logs.ts
Adam Moussa 3ba90ddc40
Add monitoring + logging layer (audit Day 2: H-1/H-14/M-13) (#6)
- H-1: 15 CIS Section 4 metric filters (4.1-4.15) on the CloudTrail log group,
  each alarming to a new SSE SNS topic seahaven-cis-alarms (email to adam).
  ALARM-only actions per Sea Haven preference. 4.16 = Security Hub (Day 1).
- H-14: VPC flow logs (ALL traffic) on all 5 VPCs → hardened S3 bucket. Delivery
  bucket policy cross-reviewed; kept the AWS-required s3:x-amz-acl condition +
  logs:*:* source-ARN (cross-reviewer wrongly flagged these; verified against
  AWS flow-logs-s3-permissions docs), dropped the unneeded s3:ListBucket.
- M-13: SES configuration set seahaven-email-events capturing bounce/complaint/
  reject to CloudWatch for reputation visibility.

L-4 (log retention) and L-5 (alarm action) applied via CLI, documented in README.
2026-06-02 15:16:24 -04:00

108 lines
3.7 KiB
TypeScript

import * as cdk from "aws-cdk-lib";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as iam from "aws-cdk-lib/aws-iam";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
/**
* VPC flow logs for every VPC, delivered to a hardened S3 bucket (audit H-14,
* CIS 3.9/5.6). S3 destination (not CloudWatch Logs) for cost — query
* forensically via Athena. ALL traffic (accept + reject).
*
* S3 delivery needs no IAM role; instead the bucket policy grants the
* `delivery.logs.amazonaws.com` service principal write access, scoped to this
* account. That bucket policy is the Day 2 cross-review item.
*/
// All 5 VPCs in 328440206208 / us-east-1 (4 custom + default), audit Agent 7.
const VPC_IDS = [
"vpc-061d66990b6a4d1fb",
"vpc-0542a9e934b417d23",
"vpc-062d200c68bd4ca0e",
"vpc-0d3d4b67bd0cf8a68",
"vpc-02c10a89d66f6f9b8",
];
export class FlowLogs extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
const stack = cdk.Stack.of(this);
const bucket = new s3.Bucket(this, "FlowLogsBucket", {
bucketName: `seahaven-vpc-flow-logs-${stack.account}`,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
versioned: false,
lifecycleRules: [
{
id: "transition-and-expire",
transitions: [
{
storageClass: s3.StorageClass.GLACIER,
transitionAfter: cdk.Duration.days(90),
},
],
expiration: cdk.Duration.days(365),
abortIncompleteMultipartUploadAfter: cdk.Duration.days(7),
},
],
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
// Log-delivery service permissions (scoped to this account) — the standard
// VPC-flow-logs-to-S3 bucket policy.
bucket.addToResourcePolicy(
new iam.PolicyStatement({
sid: "AWSLogDeliveryWrite",
effect: iam.Effect.ALLOW,
principals: [new iam.ServicePrincipal("delivery.logs.amazonaws.com")],
actions: ["s3:PutObject"],
resources: [bucket.arnForObjects(`AWSLogs/${stack.account}/*`)],
conditions: {
StringEquals: {
"s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceAccount": stack.account,
},
ArnLike: {
"aws:SourceArn": `arn:${stack.partition}:logs:${stack.region}:${stack.account}:*`,
},
},
})
);
bucket.addToResourcePolicy(
new iam.PolicyStatement({
sid: "AWSLogDeliveryAclCheck",
effect: iam.Effect.ALLOW,
principals: [new iam.ServicePrincipal("delivery.logs.amazonaws.com")],
// AWS's documented flow-logs-to-S3 policy uses GetBucketAcl only
// (verified against flow-logs-s3-permissions.html); ListBucket is not
// needed and would be over-permissioned.
actions: ["s3:GetBucketAcl"],
resources: [bucket.bucketArn],
conditions: {
StringEquals: { "aws:SourceAccount": stack.account },
ArnLike: {
"aws:SourceArn": `arn:${stack.partition}:logs:${stack.region}:${stack.account}:*`,
},
},
})
);
VPC_IDS.forEach((vpcId, i) => {
const flowLog = new ec2.CfnFlowLog(this, `FlowLog${i}`, {
resourceId: vpcId,
resourceType: "VPC",
trafficType: "ALL",
logDestinationType: "s3",
logDestination: bucket.bucketArn,
maxAggregationInterval: 600,
tags: [{ key: "Name", value: `flow-log-${vpcId}` }],
});
flowLog.node.addDependency(bucket.policy!);
});
new cdk.CfnOutput(this, "FlowLogsBucketName", { value: bucket.bucketName });
}
}