-
Notifications
You must be signed in to change notification settings - Fork 4.3k
chore(mixins-preview): delivery destinations for vended logs #36087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ShadowCat567
wants to merge
17
commits into
main
Choose a base branch
from
vended-log-destinations
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,189
−0
Draft
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d8df3eb
delivery destinations
a6280d8
Merge branch 'main' into vended-log-destinations
9c948f1
adjust spacing
f552399
xray and naming adjustment
0a7fc27
policy singletons
0ca8f9b
add tests
4728e6e
lint fixes
eaf96ac
adjustments and fix test
db3fb43
xrays and updates
c443a79
split files
227060b
Merge branch 'main' into vended-log-destinations
c2fbdfa
adjust policy generator class
06ba54d
update bucket policy generation
234b73d
jsdocs
312d2ed
allow source
7082b25
fix test
3ce903b
Merge branch 'main' into vended-log-destinations
ShadowCat567 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
packages/@aws-cdk/mixins-preview/lib/services/aws-logs/vended-logs-helpers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import { Aws, Names, Resource, Stack } from 'aws-cdk-lib'; | ||
| import { CfnBucketPolicy } from 'aws-cdk-lib/aws-s3'; | ||
| import type { IBucketRef } from 'aws-cdk-lib/aws-s3'; | ||
| import { CfnResourcePolicy } from 'aws-cdk-lib/aws-xray'; | ||
| import type { IConstruct } from 'constructs'; | ||
|
|
||
| /** | ||
| * Attempts to find an existing bucket policy for the specified S3 bucket. | ||
| * @param bucket - The S3 bucket reference to search for an associated bucket policy | ||
| * @returns The bucket policy if found, undefined otherwise | ||
| */ | ||
| export function tryFindBucketPolicy(bucket: IBucketRef): CfnBucketPolicy | undefined { | ||
| const allConstructs = Stack.of(bucket).node.findAll(); | ||
| const bucketPolicies = allConstructs.filter(construct => construct instanceof CfnBucketPolicy) as CfnBucketPolicy[]; | ||
| const policiesForCurBucket = bucketPolicies.length > 0 ? | ||
| bucketPolicies.filter(policy => policy.bucket === bucket.bucketRef.bucketName) : undefined; | ||
| return policiesForCurBucket ? policiesForCurBucket[0] : policiesForCurBucket; | ||
| } | ||
|
|
||
| /** | ||
| * Creates and manages an X-Ray resource policy for log delivery destinations. | ||
| * This class is designed as a singleton per stack to manage permissions for multiple log sources. | ||
| */ | ||
| export class XRayDeliveryDestinationPolicy extends Resource { | ||
| /** The CloudFormation X-Ray resource policy */ | ||
| public readonly XrayResourcePolicy: CfnResourcePolicy; | ||
| /** Array of ARNs for log-generating sources that are allowed to deliver to X-Ray */ | ||
| private readonly logGeneratingSourceArns: string[] = []; | ||
|
|
||
| /** | ||
| * Creates a new X-Ray delivery destination policy. | ||
| * @param scope - The construct scope | ||
| * @param id - The construct ID | ||
| */ | ||
| constructor(scope: IConstruct, id: string) { | ||
| super(scope, id); | ||
| const stack = Stack.of(scope); | ||
| // PolicyGenerator class is a singleton, so we will only ever make one of these per stack | ||
| this.XrayResourcePolicy = new CfnResourcePolicy(stack, `CDKXRayPolicy${Names.uniqueId(this)}`, { | ||
| policyName: 'CDKXRayDeliveryDestPolicy', | ||
| policyDocument: this.buildPolicyDocument(), | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Adds a log-generating source ARN to the policy and updates the resource policy. | ||
| * @param logGeneratingSourceArn - The ARN of the log source to add to the policy | ||
| */ | ||
| public allowSource(logGeneratingSourceArn: string) { | ||
| this.logGeneratingSourceArns.push(logGeneratingSourceArn); | ||
| this.XrayResourcePolicy.policyDocument = this.buildPolicyDocument(); | ||
| } | ||
|
|
||
| /** | ||
| * Builds the IAM policy document for X-Ray delivery permissions. | ||
| * @returns The policy document as a JSON string | ||
| */ | ||
| private buildPolicyDocument() { | ||
| return JSON.stringify({ | ||
| Version: '2012-10-17', | ||
| Statement: [{ | ||
| Sid: 'CDKLogsDeliveryWrite', | ||
| Effect: 'Allow', | ||
| Principal: { | ||
| Service: 'delivery.logs.amazonaws.com', | ||
| }, | ||
| Action: 'xray:PutTraceSegments', | ||
| Resource: '*', | ||
| Condition: { | ||
| 'StringEquals': { | ||
| 'aws:SourceAccount': Stack.of(this).account, | ||
| }, | ||
| 'ForAllValues:ArnLike': { | ||
| 'logs:LogGeneratingResourceArns': this.logGeneratingSourceArns, | ||
| }, | ||
| 'ArnLike': { | ||
| 'aws:SourceArn': `arn:${Aws.PARTITION}:logs:${Stack.of(this).region}:${Stack.of(this).account}:delivery-source:*`, | ||
| }, | ||
| }, | ||
| }], | ||
| }); | ||
| } | ||
| } |
303 changes: 303 additions & 0 deletions
303
packages/@aws-cdk/mixins-preview/lib/services/aws-logs/vended-logs.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,303 @@ | ||
| import { Aws, Names, Resource, Stack, Tags } from 'aws-cdk-lib'; | ||
| import { Effect, PolicyDocument, PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; | ||
| import type { IDeliveryStreamRef } from 'aws-cdk-lib/aws-kinesisfirehose'; | ||
| import { CfnDeliveryDestination, ResourcePolicy } from 'aws-cdk-lib/aws-logs'; | ||
| import type { DeliveryDestinationReference, IDeliveryDestinationRef, ILogGroupRef } from 'aws-cdk-lib/aws-logs'; | ||
| import { CfnBucketPolicy } from 'aws-cdk-lib/aws-s3'; | ||
| import type { IBucketRef } from 'aws-cdk-lib/aws-s3'; | ||
| import type { IConstruct } from 'constructs'; | ||
| import { tryFindBucketPolicy, XRayDeliveryDestinationPolicy } from './vended-logs-helpers'; | ||
|
|
||
| /** | ||
| * Base class for all delivery destination implementations. | ||
| * Provides common functionality for log delivery destinations. | ||
| */ | ||
| abstract class DeliveryDestinationBase extends Resource implements IDeliveryDestinationRef { | ||
| /** | ||
| * Reference to the delivery destination | ||
| */ | ||
| public abstract readonly deliveryDestinationRef: DeliveryDestinationReference; | ||
| } | ||
|
|
||
| /** | ||
| * Configuration properties for S3 delivery destinations. | ||
| */ | ||
| export interface S3DestinationProps { | ||
| /** | ||
| * The version of permissions supported by the source generating logs | ||
| */ | ||
| readonly permissionsVersion: 'V1' | 'V2'; | ||
| /** | ||
| * The S3 bucket to deliver logs to | ||
| */ | ||
| readonly s3Bucket: IBucketRef; | ||
| } | ||
|
|
||
| /** | ||
| * Configuration properties for CloudWatch Logs delivery destinations. | ||
| */ | ||
| interface LogsDestinationProps { | ||
| /** | ||
| * The CloudWatch log group to deliver logs to | ||
| */ | ||
| readonly logGroup: ILogGroupRef; | ||
| } | ||
|
|
||
| /** | ||
| * Configuration properties for Kinesis Data Firehose delivery destinations. | ||
| */ | ||
| interface FirehoseDestinationProps { | ||
| /** | ||
| * The Kinesis Data Firehose delivery stream to deliver logs to | ||
| */ | ||
| readonly deliveryStream: IDeliveryStreamRef; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a delivery destination for S3 buckets with appropriate IAM permissions. | ||
| * Supports both V1 and V2 permissions for S3 bucket access. | ||
| */ | ||
| export class S3DeliveryDestination extends DeliveryDestinationBase { | ||
mrgrain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /** | ||
| * Reference to the S3 delivery destination | ||
| */ | ||
| public readonly deliveryDestinationRef: DeliveryDestinationReference; | ||
|
|
||
| /** | ||
| * Creates a new S3 delivery destination. | ||
| * @param scope - The construct scope | ||
| * @param id - The construct ID | ||
| * @param props - Configuration properties for the S3 destination | ||
| */ | ||
| constructor(scope: IConstruct, id: string, props: S3DestinationProps) { | ||
| super(scope, id); | ||
| const bucketPolicy = this.getOrCreateBucketPolicy(scope, props); | ||
|
|
||
| const destinationNamePrefix = 'cdk-s3-dest-'; | ||
| const deliveryDestination = new CfnDeliveryDestination(scope, `CDKS3Dest${Names.uniqueId(this)}`, { | ||
| destinationResourceArn: props.s3Bucket.bucketRef.bucketArn, | ||
| name: `${destinationNamePrefix}${Names.uniqueResourceName(this, { maxLength: 60 - destinationNamePrefix.length })}`, | ||
| deliveryDestinationType: 'S3', | ||
| }); | ||
| deliveryDestination.node.addDependency(bucketPolicy); | ||
| this.deliveryDestinationRef = deliveryDestination.deliveryDestinationRef; | ||
| } | ||
|
|
||
| /** | ||
| * Gets an existing bucket policy or creates a new one and adds the required permissions for log delivery. | ||
| * @param scope - The construct scope | ||
| * @param bucketProps - The S3 bucket properties | ||
| * @returns The bucket policy with log delivery permissions | ||
| */ | ||
| private getOrCreateBucketPolicy(scope: IConstruct, bucketProps: S3DestinationProps): CfnBucketPolicy { | ||
| const existingPolicy = tryFindBucketPolicy(bucketProps.s3Bucket); | ||
| const statements = []; | ||
|
|
||
| const bucketStatement = new PolicyStatement({ | ||
| effect: Effect.ALLOW, | ||
| principals: [new ServicePrincipal('delivery.logs.amazonaws.com')], | ||
| actions: ['s3:PutObject'], | ||
| resources: [`${bucketProps.s3Bucket.bucketRef.bucketArn}/AWSLogs/${Stack.of(scope).account}/*`], | ||
| conditions: { | ||
| StringEquals: { | ||
| 's3:x-amz-acl': 'bucket-owner-full-control', | ||
| 'aws:SourceAccount': Stack.of(scope).account, | ||
| }, | ||
| ArnLike: { | ||
| 'aws:SourceArn': `arn:${Aws.PARTITION}:logs:${Stack.of(scope).region}:${Stack.of(scope).account}:delivery-source:*`, | ||
| }, | ||
| }, | ||
| }); | ||
| statements.push(bucketStatement); | ||
|
|
||
| if (bucketProps.permissionsVersion == 'V1') { | ||
| const v1PermsStatement = new PolicyStatement({ | ||
| effect: Effect.ALLOW, | ||
| principals: [new ServicePrincipal('delivery.logs.amazonaws.com')], | ||
| actions: ['s3:GetBucketAcl', 's3:ListBucket'], | ||
| resources: [bucketProps.s3Bucket.bucketRef.bucketArn], | ||
| conditions: { | ||
| StringEquals: { | ||
| 'aws:SourceAccount': Stack.of(scope).account, | ||
| }, | ||
| ArnLike: { | ||
| 'aws:SourceArn': `arn:${Aws.PARTITION}:logs:${Stack.of(scope).region}:${Stack.of(scope).account}:*`, | ||
| }, | ||
| }, | ||
| }); | ||
| statements.push(v1PermsStatement); | ||
| } | ||
|
|
||
| if (existingPolicy) { | ||
| const bucketPolicy = existingPolicy; | ||
|
|
||
| // Add new statements using addOverride to avoid circular references | ||
| const existingDoc = bucketPolicy.policyDocument as any; | ||
| const existingStatements = Array.isArray(existingDoc.Statement) ? existingDoc.Statement : []; | ||
| const newStatements = statements.map(s => s.toStatementJson()); | ||
|
|
||
| bucketPolicy.addOverride('Properties.PolicyDocument.Statement', [ | ||
| ...existingStatements, | ||
| ...newStatements, | ||
| ]); | ||
| return bucketPolicy; | ||
| } else { | ||
| return new CfnBucketPolicy(scope, `CDKS3DestPolicy${Names.uniqueId(bucketProps.s3Bucket)}`, { | ||
| bucket: bucketProps.s3Bucket.bucketRef.bucketName, | ||
| policyDocument: new PolicyDocument({ | ||
| statements, | ||
| }).toJSON(), | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a delivery destination for Kinesis Data Firehose delivery streams. | ||
| * Automatically tags the delivery stream to enable log delivery. | ||
| */ | ||
| export class FirehoseDeliveryDestination extends DeliveryDestinationBase { | ||
mrgrain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /** | ||
| * Reference to the Firehose delivery destination | ||
| */ | ||
| public readonly deliveryDestinationRef: DeliveryDestinationReference; | ||
|
|
||
| /** | ||
| * Creates a new Firehose delivery destination. | ||
| * @param scope - The construct scope | ||
| * @param id - The construct ID | ||
| * @param props - Configuration properties for the Firehose destination | ||
| */ | ||
| constructor(scope: IConstruct, id: string, props: FirehoseDestinationProps) { | ||
| super(scope, id); | ||
|
|
||
| Tags.of(props.deliveryStream).add('LogDeliveryEnabled', 'true'); | ||
| const destinationNamePrefix = 'cdk-fh-dest-'; | ||
| const deliveryDestination = new CfnDeliveryDestination(scope, `CDKFHDest${Names.uniqueId(this)}`, { | ||
| destinationResourceArn: props.deliveryStream.deliveryStreamRef.deliveryStreamArn, | ||
| name: `${destinationNamePrefix}${Names.uniqueResourceName(this, { maxLength: 60 - destinationNamePrefix.length })}`, | ||
| deliveryDestinationType: 'FH', | ||
| }); | ||
| this.deliveryDestinationRef = deliveryDestination.deliveryDestinationRef; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a delivery destination for CloudWatch Logs log groups. | ||
| * Manages the required resource policy for cross-account log delivery. | ||
| */ | ||
| export class LogsDeliveryDestination extends DeliveryDestinationBase { | ||
| /** | ||
| * Reference to the CloudWatch Logs delivery destination | ||
| */ | ||
| public readonly deliveryDestinationRef: DeliveryDestinationReference; | ||
|
|
||
| /** | ||
| * Creates a new CloudWatch Logs delivery destination. | ||
| * @param scope - The construct scope | ||
| * @param id - The construct ID | ||
| * @param props - Configuration properties for the CloudWatch Logs destination | ||
| */ | ||
| constructor(scope: IConstruct, id: string, props: LogsDestinationProps) { | ||
| super(scope, id); | ||
|
|
||
| const logGroupPolicy = this.getOrCreateLogsResourcePolicy(scope, props.logGroup); | ||
|
|
||
| const destinationNamePrefix = 'cdk-cwl-dest-'; | ||
| const deliveryDestination = new CfnDeliveryDestination(scope, `CDKCWLDest${Names.uniqueId(this)}`, { | ||
| destinationResourceArn: props.logGroup.logGroupRef.logGroupArn, | ||
| name: `${destinationNamePrefix}${Names.uniqueResourceName(this, { maxLength: 60 - destinationNamePrefix.length })}`, | ||
| deliveryDestinationType: 'CWL', | ||
| }); | ||
| deliveryDestination.node.addDependency(logGroupPolicy); | ||
| this.deliveryDestinationRef = deliveryDestination.deliveryDestinationRef; | ||
| } | ||
|
|
||
| /** | ||
| * Uses singleton pattern to get an existing CloudWatch Logs resource policy or create a new one. | ||
| * Adds log delivery permissions to the policy. | ||
| * @param scope - The construct scope | ||
| * @param logGroup - The target log group | ||
| * @returns The resource policy with log delivery permissions | ||
| */ | ||
| private getOrCreateLogsResourcePolicy(scope: IConstruct, logGroup: ILogGroupRef) { | ||
| const stack = Stack.of(scope); | ||
| const policyId = 'CDKCWLLogDestDeliveryPolicy'; | ||
| const exists = stack.node.tryFindChild(policyId) as ResourcePolicy; | ||
|
|
||
| const logGroupDeliveryStatement = new PolicyStatement({ | ||
| effect: Effect.ALLOW, | ||
| principals: [new ServicePrincipal('delivery.logs.amazonaws.com')], | ||
| actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], | ||
| resources: [`${logGroup.logGroupRef.logGroupArn}:log-stream:*`], | ||
| conditions: { | ||
| StringEquals: { | ||
| 'aws:SourceAccount': Stack.of(stack).account, | ||
| }, | ||
| ArnLike: { | ||
| 'aws:SourceArn': `arn:${Aws.PARTITION}:logs:${Stack.of(stack).region}:${Stack.of(stack).account}:*`, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| if (exists) { | ||
| exists.document.addStatements(logGroupDeliveryStatement); | ||
| return exists; | ||
| } | ||
| const logGroupPolicy = new ResourcePolicy(stack, policyId, { | ||
| resourcePolicyName: 'LogDestinationDeliveryPolicy', | ||
| policyStatements: [ | ||
| logGroupDeliveryStatement, | ||
| ], | ||
| }); | ||
| return logGroupPolicy; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a delivery destination for AWS X-Ray tracing. | ||
| * Manages the X-Ray resource policy for log delivery permissions. | ||
| */ | ||
| export class XRayDeliveryDestination extends DeliveryDestinationBase { | ||
| /** Reference to the X-Ray delivery destination */ | ||
| public readonly deliveryDestinationRef: DeliveryDestinationReference; | ||
| /** | ||
| * The X-Ray resource policy manager | ||
| */ | ||
| public readonly xrayResourcePolicy: XRayDeliveryDestinationPolicy; | ||
|
|
||
| /** | ||
| * Creates a new X-Ray delivery destination. | ||
| * @param scope - The construct scope | ||
| * @param id - The construct ID | ||
| */ | ||
| constructor(scope: IConstruct, id: string) { | ||
| super(scope, id); | ||
| // only have one of these per stack | ||
| this.xrayResourcePolicy = this.getOrCreateXRayPolicyGenerator(scope); | ||
|
|
||
| const destinationNamePrefix = 'cdk-xray-dest-'; | ||
| const deliveryDestination = new CfnDeliveryDestination(scope, `CDKXRayDest${Names.uniqueId(this)}`, { | ||
| name: `${destinationNamePrefix}${Names.uniqueResourceName(this, { maxLength: 60 - destinationNamePrefix.length })}`, | ||
| deliveryDestinationType: 'XRAY', | ||
| }); | ||
| this.deliveryDestinationRef = deliveryDestination.deliveryDestinationRef; | ||
| } | ||
|
|
||
| /** | ||
| * Gets an existing X-Ray policy generator or creates a new one. | ||
| * Ensures only one X-Ray policy generator exists per stack. | ||
| * @param scope - The construct scope | ||
| * @returns The X-Ray delivery destination policy manager | ||
| */ | ||
| private getOrCreateXRayPolicyGenerator(scope: IConstruct) { | ||
| const stack = Stack.of(scope); | ||
| const poliyGeneratorId = 'CDKXRayPolicyGenerator'; | ||
| const exists = stack.node.tryFindChild(poliyGeneratorId) as XRayDeliveryDestinationPolicy; | ||
|
|
||
| if (exists) { | ||
| return exists; | ||
| } | ||
| return new XRayDeliveryDestinationPolicy(stack, poliyGeneratorId); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing jsdoc comments everwhere