mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 11:33:17 +00:00
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.
108 lines
4 KiB
TypeScript
108 lines
4 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 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 });
|
|
}
|
|
}
|