Skip to content
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

feat(logs): add support for cloudwatch logs resource policy #17015

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 21 additions & 4 deletions packages/@aws-cdk/aws-logs/lib/log-group.ts
@@ -1,15 +1,16 @@
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import { IResource, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core';
import { RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { LogStream } from './log-stream';
import { CfnLogGroup } from './logs.generated';
import { MetricFilter } from './metric-filter';
import { FilterPattern, IFilterPattern } from './pattern';
import { ResourcePolicy } from './policy';
import { ILogSubscriptionDestination, SubscriptionFilter } from './subscription-filter';

export interface ILogGroup extends IResource {
export interface ILogGroup extends iam.IResourceWithPolicy {
/**
* The ARN of this log group, with ':*' appended
*
Expand Down Expand Up @@ -93,6 +94,9 @@ abstract class LogGroupBase extends Resource implements ILogGroup {
*/
public abstract readonly logGroupName: string;


private policy?: ResourcePolicy;

/**
* Create a new Log Stream for this Log Group
*
Expand Down Expand Up @@ -169,13 +173,13 @@ abstract class LogGroupBase extends Resource implements ILogGroup {
* Give the indicated permissions on this log group and all streams
*/
public grant(grantee: iam.IGrantable, ...actions: string[]) {
return iam.Grant.addToPrincipal({
return iam.Grant.addToPrincipalOrResource({
grantee,
actions,
// A LogGroup ARN out of CloudFormation already includes a ':*' at the end to include the log streams under the group.
// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#w2ab1c21c10c63c43c11
resourceArns: [this.logGroupArn],
scope: this,
resource: this,
});
}

Expand All @@ -186,6 +190,19 @@ abstract class LogGroupBase extends Resource implements ILogGroup {
public logGroupPhysicalName(): string {
return this.physicalName;
}

/**
* Adds a statement to the resource policy associated with this log group.
* A resource policy will be automatically created upon the first call to `addToResourcePolicy`.
* @param statement The policy statement to add
*/
public addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult {
if (!this.policy) {
this.policy = new ResourcePolicy(this, 'Policy');
}
this.policy.document.addStatements(statement);
return { statementAdded: true, policyDependable: this.policy };
}
}

/**
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/aws-logs/lib/policy.ts
@@ -0,0 +1,47 @@
import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam';
import { Resource, Lazy, Names } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnResourcePolicy } from './logs.generated';

/**
* Properties to define Cloudwatch log group resource policy
*/
export interface ResourcePolicyProps {
/**
* Name of the log group resource policy
* @default - Uses a unique id based on the construct path
*/
readonly policyName?: string;

/**
* Initial statements to add to the resource policy
*
* @default - No statements
*/
readonly policyStatements?: PolicyStatement[];
}

/**
* Creates Cloudwatch log group resource policies
*/
export class ResourcePolicy extends Resource {
/**
* The IAM policy document for this resource policy.
*/
public readonly document = new PolicyDocument();

constructor(scope: Construct, id: string, props?: ResourcePolicyProps) {
super(scope, id);
new CfnResourcePolicy(this, 'Resource', {
policyName: Lazy.string({
produce: () => props?.policyName ?? Names.uniqueId(this),
}),
policyDocument: Lazy.string({
produce: () => JSON.stringify(this.document),
}),
});
if (props?.policyStatements) {
this.document.addStatements(...props.policyStatements);
}
}
}
51 changes: 51 additions & 0 deletions packages/@aws-cdk/aws-logs/test/loggroup.test.ts
Expand Up @@ -335,6 +335,57 @@ describe('log group', () => {

});

test('grant to service principal', () => {
// GIVEN
const stack = new Stack();
const lg = new LogGroup(stack, 'LogGroup');
const sp = new iam.ServicePrincipal('es.amazonaws.com');

// WHEN
lg.grantWrite(sp);

// THEN
expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', {
PolicyDocument: {
'Fn::Join': [
'',
[
'{"Statement":[{"Action":["logs:CreateLogStream","logs:PutLogEvents"],"Effect":"Allow","Principal":{"Service":"es.amazonaws.com"},"Resource":"',
{
'Fn::GetAtt': [
'LogGroupF5B46931',
'Arn',
],
},
'"}],"Version":"2012-10-17"}',
],
],
},
PolicyName: 'LogGroupPolicy643B329C',
});

});


test('can add a policy to the log group', () => {
// GIVEN
const stack = new Stack();
const lg = new LogGroup(stack, 'LogGroup');

// WHEN
lg.addToResourcePolicy(new iam.PolicyStatement({
resources: ['*'],
actions: ['logs:PutLogEvents'],
principals: [new iam.ArnPrincipal('arn:aws:iam::123456789012:user/user-name')],
}));

// THEN
expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', {
PolicyDocument: '{"Statement":[{"Action":"logs:PutLogEvents","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:user/user-name"},"Resource":"*"}],"Version":"2012-10-17"}',
PolicyName: 'LogGroupPolicy643B329C',
});
});

test('correctly returns physical name of the log group', () => {
// GIVEN
const stack = new Stack();
Expand Down