mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 05:43:17 +00:00
Add Bedrock invocation logging destinations (#12)
Audit finding H-20: no audit trail of model I/O for seahaven-alex, which returns payments, invoices, WO/PO, and HR/SA8000 data. S3 bucket (Glacier at 90d, expire 365d) + CloudWatch log group (90d) + delivery role assumable only by bedrock.amazonaws.com scoped by SourceAccount/SourceArn. The account-level logging configuration has no CloudFormation resource type, so it is applied via CLI post-deploy (documented in the construct header) - same pattern as the Config recorder (INFRA-17). Cross-reviewed: no BLOCKs. Verified live: converse invocation logged to /aws/bedrock/model-invocations.
This commit is contained in:
parent
14593440cf
commit
2289dcb0c9
2 changed files with 115 additions and 0 deletions
|
|
@ -7,6 +7,7 @@ import * as cloudtrail from "aws-cdk-lib/aws-cloudtrail";
|
||||||
import { Construct } from "constructs";
|
import { Construct } from "constructs";
|
||||||
import { DetectiveControls } from "./detective-controls";
|
import { DetectiveControls } from "./detective-controls";
|
||||||
import { GovernanceToggles } from "./governance-toggles";
|
import { GovernanceToggles } from "./governance-toggles";
|
||||||
|
import { BedrockLogging } from "./bedrock-logging";
|
||||||
import { CisMonitoring } from "./cis-monitoring";
|
import { CisMonitoring } from "./cis-monitoring";
|
||||||
import { FlowLogs } from "./flow-logs";
|
import { FlowLogs } from "./flow-logs";
|
||||||
import { SesMonitoring } from "./ses-monitoring";
|
import { SesMonitoring } from "./ses-monitoring";
|
||||||
|
|
@ -158,6 +159,12 @@ export class AccountBaselineStack extends cdk.Stack {
|
||||||
// Shared CloudFront WAF WebACL (M-17); ARN published to SSM for app stacks.
|
// Shared CloudFront WAF WebACL (M-17); ARN published to SSM for app stacks.
|
||||||
new AppWebAcl(this, "AppWebAcl");
|
new AppWebAcl(this, "AppWebAcl");
|
||||||
|
|
||||||
|
// ── Day 5 AI governance ──
|
||||||
|
// Bedrock model invocation logging destinations + delivery role (H-20).
|
||||||
|
// The account-level logging configuration itself has no CFN resource type;
|
||||||
|
// applied via CLI post-deploy (see lib/bedrock-logging.ts header).
|
||||||
|
new BedrockLogging(this, "BedrockLogging");
|
||||||
|
|
||||||
cdk.Tags.of(this).add("Project", "account-baseline");
|
cdk.Tags.of(this).add("Project", "account-baseline");
|
||||||
cdk.Tags.of(this).add("Owner", "adam@seahavenind.com");
|
cdk.Tags.of(this).add("Owner", "adam@seahavenind.com");
|
||||||
cdk.Tags.of(this).add("Environment", "prod");
|
cdk.Tags.of(this).add("Environment", "prod");
|
||||||
|
|
|
||||||
108
lib/bedrock-logging.ts
Normal file
108
lib/bedrock-logging.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 logs from "aws-cdk-lib/aws-logs";
|
||||||
|
import { Construct } from "constructs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destinations + delivery role for Bedrock model invocation logging (audit
|
||||||
|
* H-20). `seahaven-alex` is employee-facing and returns payments, invoices,
|
||||||
|
* WO/PO, and HR/SA8000 data — model I/O needs an audit trail.
|
||||||
|
*
|
||||||
|
* CloudFormation has no resource type for the logging configuration itself
|
||||||
|
* (account-level `PutModelInvocationLoggingConfiguration`), so — like the
|
||||||
|
* Config recorder (INFRA-17) — the toggle is applied via CLI after deploy:
|
||||||
|
*
|
||||||
|
* aws bedrock put-model-invocation-logging-configuration --logging-config '{
|
||||||
|
* "cloudWatchConfig": {
|
||||||
|
* "logGroupName": "<BedrockInvocationLogGroup>",
|
||||||
|
* "roleArn": "<BedrockLoggingRole ARN>",
|
||||||
|
* "largeDataDeliveryS3Config": {"bucketName": "<bucket>", "keyPrefix": "large-payloads"}
|
||||||
|
* },
|
||||||
|
* "s3Config": {"bucketName": "<bucket>", "keyPrefix": "invocation-logs"},
|
||||||
|
* "textDataDeliveryEnabled": true,
|
||||||
|
* "imageDataDeliveryEnabled": true,
|
||||||
|
* "embeddingDataDeliveryEnabled": false
|
||||||
|
* }'
|
||||||
|
*/
|
||||||
|
export class BedrockLogging extends Construct {
|
||||||
|
public readonly bucket: s3.Bucket;
|
||||||
|
public readonly logGroup: logs.LogGroup;
|
||||||
|
public readonly deliveryRole: iam.Role;
|
||||||
|
|
||||||
|
constructor(scope: Construct, id: string) {
|
||||||
|
super(scope, id);
|
||||||
|
|
||||||
|
const stack = cdk.Stack.of(this);
|
||||||
|
|
||||||
|
this.bucket = new s3.Bucket(this, "InvocationLogsBucket", {
|
||||||
|
bucketName: `seahaven-bedrock-invocation-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,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bedrock writes invocation logs to S3 directly via bucket policy — no role.
|
||||||
|
this.bucket.addToResourcePolicy(
|
||||||
|
new iam.PolicyStatement({
|
||||||
|
sid: "AmazonBedrockLogsWrite",
|
||||||
|
effect: iam.Effect.ALLOW,
|
||||||
|
principals: [new iam.ServicePrincipal("bedrock.amazonaws.com")],
|
||||||
|
actions: ["s3:PutObject"],
|
||||||
|
resources: [this.bucket.arnForObjects("*")],
|
||||||
|
conditions: {
|
||||||
|
StringEquals: { "aws:SourceAccount": stack.account },
|
||||||
|
ArnLike: {
|
||||||
|
"aws:SourceArn": `arn:aws:bedrock:${stack.region}:${stack.account}:*`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logGroup = new logs.LogGroup(this, "InvocationLogGroup", {
|
||||||
|
logGroupName: "/aws/bedrock/model-invocations",
|
||||||
|
retention: logs.RetentionDays.THREE_MONTHS,
|
||||||
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||||||
|
});
|
||||||
|
|
||||||
|
// CloudWatch delivery requires a role Bedrock can assume, scoped to this
|
||||||
|
// account/source and to the one log group.
|
||||||
|
this.deliveryRole = new iam.Role(this, "DeliveryRole", {
|
||||||
|
roleName: "seahaven-bedrock-invocation-logging",
|
||||||
|
assumedBy: new iam.ServicePrincipal("bedrock.amazonaws.com", {
|
||||||
|
conditions: {
|
||||||
|
StringEquals: { "aws:SourceAccount": stack.account },
|
||||||
|
ArnLike: {
|
||||||
|
"aws:SourceArn": `arn:aws:bedrock:${stack.region}:${stack.account}:*`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.deliveryRole.addToPolicy(
|
||||||
|
new iam.PolicyStatement({
|
||||||
|
actions: ["logs:CreateLogStream", "logs:PutLogEvents"],
|
||||||
|
resources: [this.logGroup.logGroupArn, `${this.logGroup.logGroupArn}:log-stream:*`],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
new cdk.CfnOutput(this, "BedrockLogBucketName", { value: this.bucket.bucketName });
|
||||||
|
new cdk.CfnOutput(this, "BedrockLogGroupName", { value: this.logGroup.logGroupName });
|
||||||
|
new cdk.CfnOutput(this, "BedrockLoggingRoleArn", { value: this.deliveryRole.roleArn });
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue