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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions packages/@aws-cdk/aws-logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,44 @@ By default, the log group will be created in the same region as the stack. The `
log groups in other regions. This is typically useful when controlling retention for log groups auto-created by global services that
publish their log group to a specific region, such as AWS Chatbot creating a log group in `us-east-1`.

## Resource Policy

CloudWatch Resource Policies allow other AWS services or IAM Principals to put log events into the log groups.
A resource policy is automatically created when `addToResourcePolicy` is called on the LogGroup for the first time.

`ResourcePolicy` can also be created manually.

```ts
const logGroup = new LogGroup(this, 'LogGroup');
const resourcePolicy = new ResourcePolicy(this, 'ResourcePolicy');
resourcePolicy.document.addStatements(new iam.PolicyStatement({
actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
principals: [new iam.ServicePrincipal('es.amazonaws.com')],
resources: [logGroup.logGroupArn],
}));
```

Or more conveniently, write permissions to the log group can be granted as follows which gives same result as in the above example.

```ts
const logGroup = new LogGroup(this, 'LogGroup');
logGroup.grantWrite(iam.ServicePrincipal('es.amazonaws.com'));
```

Optionally name and policy statements can also be passed on `ResourcePolicy` construction.

```ts
const policyStatement = new new iam.PolicyStatement({
resources: ["*"],
actions: ['logs:PutLogEvents'],
principals: [new iam.ArnPrincipal('arn:aws:iam::123456789012:user/user-name')],
});
const resourcePolicy = new ResourcePolicy(this, 'ResourcePolicy', {
policyName: 'myResourcePolicy',
policyStatements: [policyStatement],
});
```

## Encrypting Log Groups

By default, log group data is always encrypted in CloudWatch Logs. You have the
Expand Down Expand Up @@ -182,7 +220,6 @@ line.
all of the terms in any of the groups (specified as arrays) matches. This is
an OR match.


Examples:

```ts
Expand Down Expand Up @@ -231,7 +268,6 @@ and then descending into it, such as `$.field` or `$.list[0].field`.
given JSON patterns match. This makes an OR combination of the given
patterns.


Example:

```ts
Expand Down
25 changes: 21 additions & 4 deletions packages/@aws-cdk/aws-logs/lib/log-group.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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