mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 04:33:15 +00:00
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.
This commit is contained in:
parent
38d4a5753a
commit
3ba90ddc40
5 changed files with 412 additions and 3 deletions
48
README.md
48
README.md
|
|
@ -138,12 +138,54 @@ Billing Alerts* under Billing → Billing preferences; there is no public API/CL
|
|||
The M-10 budget already provides cost alerting independent of that metric, so
|
||||
this only affects the legacy `AWS-MonthlyBilling` CloudWatch alarm (L-8).
|
||||
|
||||
### Monitoring + logging (audit Day 2)
|
||||
|
||||
| Resource | Logical ID | Finding | Notes |
|
||||
|---|---|---|---|
|
||||
| CIS metric filters + alarms | `CisMonitoring/*` | H-1 | 15 filters (CIS 4.1–4.15) on the CloudTrail log group, each with an alarm → `seahaven-cis-alarms`. ALARM-only actions (no OK). 4.16 = Security Hub (Day 1) |
|
||||
| CIS alarm topic | `seahaven-cis-alarms` | H-1 | SNS, SSE (`alias/aws/sns`), email sub to adam@seahavenind.com |
|
||||
| VPC flow logs | `FlowLogs/FlowLog0..4` | H-14 | ALL traffic on all 5 VPCs → S3 |
|
||||
| Flow-logs bucket | `seahaven-vpc-flow-logs-328440206208` | H-14 | Private, SSE-S3, TLS-only, Glacier @90d / expire @365d; delivery bucket policy cross-reviewed |
|
||||
| SES config set | `seahaven-email-events` | M-13 | Bounce/complaint/reject → CloudWatch metrics for reputation visibility |
|
||||
|
||||
**H-1 log group:** the metric filters attach to the existing CloudTrail
|
||||
CloudWatch Logs group by name (`seahaven-account-baseline-TrailLogGroup4CBE3AF5-…`),
|
||||
imported read-only so the live audit trail is never replaced. Stable unless the
|
||||
Trail is recreated.
|
||||
|
||||
**H-14 bucket policy note:** the flow-logs delivery policy keeps
|
||||
`s3:x-amz-acl=bucket-owner-full-control` and the `arn:aws:logs:…:*` source-ARN
|
||||
wildcard — both are required by AWS's documented flow-logs-to-S3 policy
|
||||
(`flow-logs-s3-permissions.html`). A cross-review suggested dropping them; that
|
||||
was rejected as it would break delivery. `s3:ListBucket` was dropped (not needed).
|
||||
|
||||
**M-13 follow-up:** associate `seahaven-email-events` as the default config set
|
||||
on the live sending identities to capture events from existing senders:
|
||||
|
||||
```bash
|
||||
aws sesv2 put-email-identity-configuration-set-attributes \
|
||||
--email-identity int.seahaven.com --configuration-set-name seahaven-email-events
|
||||
```
|
||||
|
||||
### Log-group retention + alarm wiring (audit L-4, L-5)
|
||||
|
||||
Applied via CLI (auto-created groups spread across stacks; one alarm in another
|
||||
stack). Applied 2026-06-02.
|
||||
|
||||
```bash
|
||||
# L-4 90-day retention on the 13 never-expire log groups (CodeBuild + CDK helpers)
|
||||
for lg in <the 13 groups>; do aws logs put-retention-policy --log-group-name "$lg" --retention-in-days 90; done
|
||||
|
||||
# L-5 wire the actionless forgejo backup-verification alarm to site-alerts
|
||||
aws cloudwatch put-metric-alarm --alarm-name forgejo-backup-verification-errors \
|
||||
--alarm-actions arn:aws:sns:us-east-1:328440206208:site-alerts # (preserve existing alarm config)
|
||||
```
|
||||
|
||||
## Roadmap (same stack)
|
||||
|
||||
Detective layer multi-region expansion (GuardDuty/Config/Security Hub beyond
|
||||
us-east-1). H-1: CIS Section 4 metric filters/alarms onto the CloudTrail log
|
||||
group. Backup phase 2: expand past the phase-1 set via tag-based selection and
|
||||
graduate the offsite vault to compliance mode.
|
||||
us-east-1). Backup phase 2: expand past the phase-1 set via tag-based selection
|
||||
and graduate the offsite vault to compliance mode.
|
||||
|
||||
## Deploy
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import * as cloudtrail from "aws-cdk-lib/aws-cloudtrail";
|
|||
import { Construct } from "constructs";
|
||||
import { DetectiveControls } from "./detective-controls";
|
||||
import { GovernanceToggles } from "./governance-toggles";
|
||||
import { CisMonitoring } from "./cis-monitoring";
|
||||
import { FlowLogs } from "./flow-logs";
|
||||
import { SesMonitoring } from "./ses-monitoring";
|
||||
|
||||
/**
|
||||
* Account-level security baseline for Sea Haven (account 328440206208).
|
||||
|
|
@ -143,6 +146,15 @@ export class AccountBaselineStack extends cdk.Stack {
|
|||
alertEmail: props.budgetAlertEmail,
|
||||
});
|
||||
|
||||
// ── Day 2 monitoring + logging ──
|
||||
// CIS Section 4 metric filters/alarms (H-1), VPC flow logs (H-14),
|
||||
// SES bounce/complaint config set (M-13).
|
||||
new CisMonitoring(this, "CisMonitoring", {
|
||||
alarmEmail: props.budgetAlertEmail,
|
||||
});
|
||||
new FlowLogs(this, "FlowLogs");
|
||||
new SesMonitoring(this, "SesMonitoring");
|
||||
|
||||
cdk.Tags.of(this).add("Project", "account-baseline");
|
||||
cdk.Tags.of(this).add("Owner", "adam@seahavenind.com");
|
||||
cdk.Tags.of(this).add("Environment", "prod");
|
||||
|
|
|
|||
197
lib/cis-monitoring.ts
Normal file
197
lib/cis-monitoring.ts
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import * as cdk from "aws-cdk-lib";
|
||||
import * as logs from "aws-cdk-lib/aws-logs";
|
||||
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
|
||||
import * as cwactions from "aws-cdk-lib/aws-cloudwatch-actions";
|
||||
import * as sns from "aws-cdk-lib/aws-sns";
|
||||
import * as subscriptions from "aws-cdk-lib/aws-sns-subscriptions";
|
||||
import * as kms from "aws-cdk-lib/aws-kms";
|
||||
import { Construct } from "constructs";
|
||||
|
||||
/**
|
||||
* CIS AWS Foundations Benchmark v3.0 Section 4 — Monitoring (audit H-1).
|
||||
*
|
||||
* 15 metric filters on the account CloudTrail log group, each backed by a
|
||||
* CloudWatch alarm that notifies a dedicated SNS topic. Closes CIS 4.1–4.15.
|
||||
* (4.16 "Security Hub enabled" is not a metric filter — done Day 1, H-4.)
|
||||
*
|
||||
* Alarms fire on ALARM only (no OK/recovery actions) per Sea Haven preference.
|
||||
*/
|
||||
|
||||
// The account CloudTrail (C-1) delivers to this CloudWatch Logs group. It is
|
||||
// created by the L2 cloudtrail.Trail in account-baseline-stack.ts; we import it
|
||||
// by name rather than replace it, so the live audit trail is never disrupted.
|
||||
// Stable as long as the Trail is not recreated.
|
||||
const TRAIL_LOG_GROUP_NAME =
|
||||
"seahaven-account-baseline-TrailLogGroup4CBE3AF5-e7hMDCzj8e4d";
|
||||
|
||||
interface CisControl {
|
||||
readonly id: string;
|
||||
readonly metricName: string;
|
||||
readonly pattern: string;
|
||||
readonly description: string;
|
||||
}
|
||||
|
||||
const CIS_CONTROLS: CisControl[] = [
|
||||
{
|
||||
id: "UnauthorizedApiCalls",
|
||||
metricName: "UnauthorizedAPICalls",
|
||||
pattern:
|
||||
'{ ($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*") && ($.sourceIPAddress != "delivery.logs.amazonaws.com") && ($.eventName != "HeadBucket") }',
|
||||
description: "CIS 4.1 — unauthorized API calls",
|
||||
},
|
||||
{
|
||||
id: "ConsoleSigninNoMfa",
|
||||
metricName: "ConsoleSigninWithoutMFA",
|
||||
pattern:
|
||||
'{ ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success") }',
|
||||
description: "CIS 4.2 — console sign-in without MFA",
|
||||
},
|
||||
{
|
||||
id: "RootAccountUsage",
|
||||
metricName: "RootAccountUsage",
|
||||
pattern:
|
||||
'{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }',
|
||||
description: "CIS 4.3 — root account usage",
|
||||
},
|
||||
{
|
||||
id: "IamPolicyChanges",
|
||||
metricName: "IAMPolicyChanges",
|
||||
pattern:
|
||||
"{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}",
|
||||
description: "CIS 4.4 — IAM policy changes",
|
||||
},
|
||||
{
|
||||
id: "CloudTrailConfigChanges",
|
||||
metricName: "CloudTrailConfigChanges",
|
||||
pattern:
|
||||
"{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }",
|
||||
description: "CIS 4.5 — CloudTrail configuration changes",
|
||||
},
|
||||
{
|
||||
id: "ConsoleAuthFailures",
|
||||
metricName: "ConsoleAuthenticationFailures",
|
||||
pattern:
|
||||
'{ ($.eventName = ConsoleLogin) && ($.errorMessage = "Failed authentication") }',
|
||||
description: "CIS 4.6 — console authentication failures",
|
||||
},
|
||||
{
|
||||
id: "CmkDisableOrDelete",
|
||||
metricName: "CMKDisableOrScheduledDelete",
|
||||
pattern:
|
||||
"{ ($.eventSource = kms.amazonaws.com) && (($.eventName = DisableKey) || ($.eventName = ScheduleKeyDeletion)) }",
|
||||
description: "CIS 4.7 — disabling or scheduled deletion of CMKs",
|
||||
},
|
||||
{
|
||||
id: "S3BucketPolicyChanges",
|
||||
metricName: "S3BucketPolicyChanges",
|
||||
pattern:
|
||||
"{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }",
|
||||
description: "CIS 4.8 — S3 bucket policy changes",
|
||||
},
|
||||
{
|
||||
id: "ConfigChanges",
|
||||
metricName: "AWSConfigChanges",
|
||||
pattern:
|
||||
"{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }",
|
||||
description: "CIS 4.9 — AWS Config configuration changes",
|
||||
},
|
||||
{
|
||||
id: "SecurityGroupChanges",
|
||||
metricName: "SecurityGroupChanges",
|
||||
pattern:
|
||||
"{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }",
|
||||
description: "CIS 4.10 — security group changes",
|
||||
},
|
||||
{
|
||||
id: "NaclChanges",
|
||||
metricName: "NetworkACLChanges",
|
||||
pattern:
|
||||
"{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }",
|
||||
description: "CIS 4.11 — network ACL changes",
|
||||
},
|
||||
{
|
||||
id: "NetworkGatewayChanges",
|
||||
metricName: "NetworkGatewayChanges",
|
||||
pattern:
|
||||
"{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }",
|
||||
description: "CIS 4.12 — network gateway changes",
|
||||
},
|
||||
{
|
||||
id: "RouteTableChanges",
|
||||
metricName: "RouteTableChanges",
|
||||
pattern:
|
||||
"{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }",
|
||||
description: "CIS 4.13 — route table changes",
|
||||
},
|
||||
{
|
||||
id: "VpcChanges",
|
||||
metricName: "VPCChanges",
|
||||
pattern:
|
||||
"{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }",
|
||||
description: "CIS 4.14 — VPC changes",
|
||||
},
|
||||
{
|
||||
id: "OrganizationsChanges",
|
||||
metricName: "OrganizationsChanges",
|
||||
pattern:
|
||||
'{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = "AcceptHandshake") || ($.eventName = "AttachPolicy") || ($.eventName = "CreateAccount") || ($.eventName = "CreateOrganizationalUnit") || ($.eventName = "CreatePolicy") || ($.eventName = "DeclineHandshake") || ($.eventName = "DeleteOrganization") || ($.eventName = "DeleteOrganizationalUnit") || ($.eventName = "DeletePolicy") || ($.eventName = "DetachPolicy") || ($.eventName = "DisablePolicyType") || ($.eventName = "EnablePolicyType") || ($.eventName = "InviteAccountToOrganization") || ($.eventName = "LeaveOrganization") || ($.eventName = "MoveAccount") || ($.eventName = "RemoveAccountFromOrganization") || ($.eventName = "UpdatePolicy") || ($.eventName = "UpdateOrganizationalUnit")) }',
|
||||
description: "CIS 4.15 — AWS Organizations changes",
|
||||
},
|
||||
];
|
||||
|
||||
export interface CisMonitoringProps {
|
||||
/** Email subscribed to the CIS alarm topic. */
|
||||
readonly alarmEmail: string;
|
||||
}
|
||||
|
||||
export class CisMonitoring extends Construct {
|
||||
constructor(scope: Construct, id: string, props: CisMonitoringProps) {
|
||||
super(scope, id);
|
||||
|
||||
// Dedicated topic for security/CIS alarms, encrypted with the AWS-managed
|
||||
// SNS key (also clears audit L-14 for this topic).
|
||||
const topic = new sns.Topic(this, "CisAlarmTopic", {
|
||||
topicName: "seahaven-cis-alarms",
|
||||
displayName: "Sea Haven CIS / security alarms",
|
||||
masterKey: kms.Alias.fromAliasName(this, "SnsKey", "alias/aws/sns"),
|
||||
});
|
||||
topic.addSubscription(new subscriptions.EmailSubscription(props.alarmEmail));
|
||||
|
||||
const logGroup = logs.LogGroup.fromLogGroupName(
|
||||
this,
|
||||
"TrailLogGroup",
|
||||
TRAIL_LOG_GROUP_NAME
|
||||
);
|
||||
|
||||
for (const c of CIS_CONTROLS) {
|
||||
const mf = new logs.MetricFilter(this, `${c.id}Filter`, {
|
||||
logGroup,
|
||||
filterPattern: logs.FilterPattern.literal(c.pattern),
|
||||
metricNamespace: "CISBenchmark",
|
||||
metricName: c.metricName,
|
||||
metricValue: "1",
|
||||
defaultValue: 0,
|
||||
});
|
||||
|
||||
const alarm = mf
|
||||
.metric({
|
||||
statistic: "Sum",
|
||||
period: cdk.Duration.minutes(5),
|
||||
})
|
||||
.createAlarm(this, `${c.id}Alarm`, {
|
||||
alarmName: `cis-${c.metricName}`,
|
||||
alarmDescription: c.description,
|
||||
threshold: 1,
|
||||
comparisonOperator:
|
||||
cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
||||
evaluationPeriods: 1,
|
||||
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
|
||||
});
|
||||
|
||||
// ALARM-only notification (no OK/recovery action) per Sea Haven preference.
|
||||
alarm.addAlarmAction(new cwactions.SnsAction(topic));
|
||||
}
|
||||
|
||||
new cdk.CfnOutput(this, "CisAlarmTopicArn", { value: topic.topicArn });
|
||||
}
|
||||
}
|
||||
108
lib/flow-logs.ts
Normal file
108
lib/flow-logs.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
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 });
|
||||
}
|
||||
}
|
||||
50
lib/ses-monitoring.ts
Normal file
50
lib/ses-monitoring.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import * as cdk from "aws-cdk-lib";
|
||||
import * as ses from "aws-cdk-lib/aws-ses";
|
||||
import { Construct } from "constructs";
|
||||
|
||||
/**
|
||||
* SES configuration set capturing bounce/complaint events (audit M-13).
|
||||
*
|
||||
* Gives reputation visibility beyond the suppression list by emitting bounce,
|
||||
* complaint, and reject events to CloudWatch metrics (dimensioned by config
|
||||
* set). Associating this set as the default on the live sending identities is a
|
||||
* follow-up CLI step (documented in the README) — creating it here does not
|
||||
* change current sending behaviour.
|
||||
*/
|
||||
export class SesMonitoring extends Construct {
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id);
|
||||
|
||||
const configSetName = "seahaven-email-events";
|
||||
|
||||
const configSet = new ses.CfnConfigurationSet(this, "ConfigSet", {
|
||||
name: configSetName,
|
||||
reputationOptions: { reputationMetricsEnabled: true },
|
||||
});
|
||||
|
||||
const eventDest = new ses.CfnConfigurationSetEventDestination(
|
||||
this,
|
||||
"BounceComplaintDest",
|
||||
{
|
||||
configurationSetName: configSetName,
|
||||
eventDestination: {
|
||||
name: "bounce-complaint-cw",
|
||||
enabled: true,
|
||||
matchingEventTypes: ["bounce", "complaint", "reject"],
|
||||
cloudWatchDestination: {
|
||||
dimensionConfigurations: [
|
||||
{
|
||||
defaultDimensionValue: "none",
|
||||
dimensionName: "ses:configuration-set",
|
||||
dimensionValueSource: "messageTag",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
eventDest.node.addDependency(configSet);
|
||||
|
||||
new cdk.CfnOutput(this, "SesConfigSetName", { value: configSetName });
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue