Skip to content

Commit

Permalink
Merge branch 'master' into chore/add-latest-opensearch-version-1-2
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Apr 5, 2022
2 parents e3f7e1e + 19664ae commit 553805f
Show file tree
Hide file tree
Showing 307 changed files with 7,869 additions and 495 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1183,7 +1183,7 @@ export class Table extends TableBase {
attributeDefinitions: this.attributeDefinitions,
globalSecondaryIndexes: Lazy.any({ produce: () => this.globalSecondaryIndexes }, { omitEmptyArray: true }),
localSecondaryIndexes: Lazy.any({ produce: () => this.localSecondaryIndexes }, { omitEmptyArray: true }),
pointInTimeRecoverySpecification: props.pointInTimeRecovery ? { pointInTimeRecoveryEnabled: props.pointInTimeRecovery } : undefined,
pointInTimeRecoverySpecification: props.pointInTimeRecovery != null ? { pointInTimeRecoveryEnabled: props.pointInTimeRecovery } : undefined,
billingMode: this.billingMode === BillingMode.PAY_PER_REQUEST ? this.billingMode : undefined,
provisionedThroughput: this.billingMode === BillingMode.PAY_PER_REQUEST ? undefined : {
readCapacityUnits: props.readCapacity || 5,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
"VpcPublicSubnet1Subnet5C2D37C4": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "10.0.0.0/18",
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": "test-region-1a",
"CidrBlock": "10.0.0.0/18",
"MapPublicIpOnLaunch": true,
"Tags": [
{
Expand Down Expand Up @@ -115,11 +115,11 @@
"VpcPublicSubnet2Subnet691E08A3": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "10.0.64.0/18",
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": "test-region-1b",
"CidrBlock": "10.0.64.0/18",
"MapPublicIpOnLaunch": true,
"Tags": [
{
Expand Down Expand Up @@ -180,11 +180,11 @@
"VpcPrivateSubnet1Subnet536B997A": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "10.0.128.0/18",
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": "test-region-1a",
"CidrBlock": "10.0.128.0/18",
"MapPublicIpOnLaunch": false,
"Tags": [
{
Expand Down Expand Up @@ -242,11 +242,11 @@
"VpcPrivateSubnet2Subnet3788AAA1": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "10.0.192.0/18",
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": "test-region-1b",
"CidrBlock": "10.0.192.0/18",
"MapPublicIpOnLaunch": false,
"Tags": [
{
Expand Down Expand Up @@ -631,7 +631,11 @@
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"DependsOn": [
"ServiceLBPublicListenerECSGroup0CC8688C",
"ServiceLBPublicListener46709EAA"
]
}
},
"Outputs": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ class TestStack extends Stack {
port: 80,
});

listener.addTargets('Targets', {
const target = listener.addTargets('Targets', {
targets: [new targets.AlbTarget(svc.loadBalancer, 80)],
port: 80,
healthCheck: {
protocol: elbv2.Protocol.HTTP,
},
});
target.node.addDependency(svc.listener);

new CfnOutput(this, 'NlbEndpoint', { value: `http://${nlb.loadBalancerDnsName}` });
}
Expand Down
32 changes: 32 additions & 0 deletions packages/@aws-cdk/aws-iam/lib/policy-statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export class PolicyStatement {
private readonly condition: { [key: string]: any } = { };
private principalConditionsJson?: string;

// Hold on to those principals
private readonly _principals = new Array<IPrincipal>();

constructor(props: PolicyStatementProps = {}) {
// Validate actions
for (const action of [...props.actions || [], ...props.notActions || []]) {
Expand Down Expand Up @@ -145,6 +148,7 @@ export class PolicyStatement {
* @param principals IAM principals that will be added
*/
public addPrincipals(...principals: IPrincipal[]) {
this._principals.push(...principals);
if (Object.keys(principals).length > 0 && Object.keys(this.notPrincipal).length > 0) {
throw new Error('Cannot add \'Principals\' to policy statement if \'NotPrincipals\' have been added');
}
Expand All @@ -156,6 +160,15 @@ export class PolicyStatement {
}
}

/**
* Expose principals to allow their ARNs to be replaced by account ID strings
* in policy statements for resources policies that don't allow full account ARNs,
* such as AWS::Logs::ResourcePolicy.
*/
public get principals(): IPrincipal[] {
return [...this._principals];
}

/**
* Specify principals that is not allowed or denied access to the "NotPrincipal" section of
* a policy statement.
Expand Down Expand Up @@ -319,6 +332,25 @@ export class PolicyStatement {
this.addCondition('StringEquals', { 'sts:ExternalId': accountId });
}

/**
* Create a new `PolicyStatement` with the same exact properties
* as this one, except for the overrides
*/
public copy(overrides: PolicyStatementProps = {}) {
return new PolicyStatement({
sid: overrides.sid ?? this.sid,
effect: overrides.effect ?? this.effect,
actions: overrides.actions ?? this.action,
notActions: overrides.notActions ?? this.notAction,

principals: overrides.principals,
notPrincipals: overrides.notPrincipals,

resources: overrides.resources ?? this.resource,
notResources: overrides.notResources ?? this.notResource,
});
}

/**
* JSON-ify the policy statement
*
Expand Down
39 changes: 23 additions & 16 deletions packages/@aws-cdk/aws-iam/lib/private/merge-statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,33 @@ import { StatementSchema, normalizeStatement, IamValue } from './postprocess-pol
export function mergeStatements(statements: StatementSchema[]): StatementSchema[] {
const compStatements = statements.map(makeComparable);

let i = 0;
while (i < compStatements.length) {
let didMerge = false;

for (let j = i + 1; j < compStatements.length; j++) {
const merged = tryMerge(compStatements[i], compStatements[j]);
if (merged) {
compStatements[i] = merged;
compStatements.splice(j, 1);
didMerge = true;
break;
// Keep trying until nothing changes anymore
while (onePass()) { /* again */ }
return compStatements.map(renderComparable);

// Do one optimization pass, return 'true' if we merged anything
function onePass() {
let ret = false;
let i = 0;
while (i < compStatements.length) {
let didMerge = false;

for (let j = i + 1; j < compStatements.length; j++) {
const merged = tryMerge(compStatements[i], compStatements[j]);
if (merged) {
compStatements[i] = merged;
compStatements.splice(j, 1);
ret = didMerge = true;
break;
}
}
}

if (!didMerge) {
i++;
if (!didMerge) {
i++;
}
}
return ret;
}

return compStatements.map(renderComparable);
}

/**
Expand Down
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-iam/test/merge-statements.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,36 @@ test('fail merging typed and untyped principals', () => {
]);
});

test('keep merging even if it requires multiple passes', () => {
// [A, R1], [B, R1], [A, R2], [B, R2]
// -> [{A, B}, R1], [{A, B], R2]
// -> [{A, B}, {R1, R2}]
assertMerged([
new iam.PolicyStatement({
actions: ['service:A'],
resources: ['R1'],
}),
new iam.PolicyStatement({
actions: ['service:B'],
resources: ['R1'],
}),
new iam.PolicyStatement({
actions: ['service:A'],
resources: ['R2'],
}),
new iam.PolicyStatement({
actions: ['service:B'],
resources: ['R2'],
}),
], [
{
Effect: 'Allow',
Action: ['service:A', 'service:B'],
Resource: ['R1', 'R2'],
},
]);
});

function assertNoMerge(statements: iam.PolicyStatement[]) {
const app = new App();
const stack = new Stack(app, 'Stack');
Expand Down
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,17 @@ describe('singleton lambda', () => {

// WHEN
const invokeResult = singleton.grantInvoke(new iam.ServicePrincipal('events.amazonaws.com'));
const statement = stack.resolve(invokeResult.resourceStatement);
const statement = stack.resolve(invokeResult.resourceStatement?.toJSON());

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', {
Action: 'lambda:InvokeFunction',
Principal: 'events.amazonaws.com',
});
expect(statement.action).toEqual(['lambda:InvokeFunction']);
expect(statement.principal).toEqual({ Service: ['events.amazonaws.com'] });
expect(statement.effect).toEqual('Allow');
expect(statement.resource).toEqual([
expect(statement.Action).toEqual('lambda:InvokeFunction');
expect(statement.Principal).toEqual({ Service: 'events.amazonaws.com' });
expect(statement.Effect).toEqual('Allow');
expect(statement.Resource).toEqual([
{ 'Fn::GetAtt': ['SingletonLambda84c0de93353f42179b0b45b6c993251a840BCC38', 'Arn'] },
{ 'Fn::Join': ['', [{ 'Fn::GetAtt': ['SingletonLambda84c0de93353f42179b0b45b6c993251a840BCC38', 'Arn'] }, ':*']] },
]);
Expand Down
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ const logGroup = new logs.LogGroup(this, 'LogGroup');
logGroup.grantWrite(new iam.ServicePrincipal('es.amazonaws.com'));
```

Be aware that any ARNs or tokenized values passed to the resource policy will be converted into AWS Account IDs.
This is because CloudWatch Logs Resource Policies do not accept ARNs as principals, but they do accept
Account ID strings. Non-ARN principals, like Service principals or Any princpals, are accepted by CloudWatch.

## Encrypting Log Groups

By default, log group data is always encrypted in CloudWatch Logs. You have the
Expand Down
28 changes: 26 additions & 2 deletions packages/@aws-cdk/aws-logs/lib/log-group.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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 { ArnFormat, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core';
import { Arn, ArnFormat, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { LogStream } from './log-stream';
import { CfnLogGroup } from './logs.generated';
Expand Down Expand Up @@ -194,15 +194,39 @@ abstract class LogGroupBase extends Resource implements ILogGroup {
/**
* 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`.
*
* Any ARN Principals inside of the statement will be converted into AWS Account ID strings
* because CloudWatch Logs Resource Policies do not accept ARN principals.
*
* @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);
this.policy.document.addStatements(statement.copy({
principals: statement.principals.map(p => this.convertArnPrincpalToAccountId(p)),
}));
return { statementAdded: true, policyDependable: this.policy };
}

private convertArnPrincpalToAccountId(principal: iam.IPrincipal) {
if (principal.principalAccount) {
// we use ArnPrincipal here because the constructor inserts the argument
// into the template without mutating it, which means that there is no
// ARN created by this call.
return new iam.ArnPrincipal(principal.principalAccount);
}

if (principal instanceof iam.ArnPrincipal) {
const parsedArn = Arn.split(principal.arn, ArnFormat.SLASH_RESOURCE_NAME);
if (parsedArn.account) {
return new iam.ArnPrincipal(parsedArn.account);
}
}

return principal;
}
}

/**
Expand Down
38 changes: 35 additions & 3 deletions packages/@aws-cdk/aws-logs/test/loggroup.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Template } from '@aws-cdk/assertions';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import { CfnParameter, RemovalPolicy, Stack } from '@aws-cdk/core';
import { CfnParameter, Fn, RemovalPolicy, Stack } from '@aws-cdk/core';
import { LogGroup, RetentionDays } from '../lib';

describe('log group', () => {
Expand Down Expand Up @@ -364,7 +364,7 @@ describe('log group', () => {
});
});

test('can add a policy to the log group', () => {
test('when added to log groups, IAM users are converted into account IDs in the resource policy', () => {
// GIVEN
const stack = new Stack();
const lg = new LogGroup(stack, 'LogGroup');
Expand All @@ -378,11 +378,43 @@ describe('log group', () => {

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

test('imported values are treated as if they are ARNs and converted to account IDs via CFN pseudo parameters', () => {
// GIVEN
const stack = new Stack();
const lg = new LogGroup(stack, 'LogGroup');

// WHEN
lg.addToResourcePolicy(new iam.PolicyStatement({
resources: ['*'],
actions: ['logs:PutLogEvents'],
principals: [iam.Role.fromRoleArn(stack, 'Role', Fn.importValue('SomeRole'))],
}));

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::Logs::ResourcePolicy', {
PolicyDocument: {
'Fn::Join': [
'',
[
'{\"Statement\":[{\"Action\":\"logs:PutLogEvents\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"',
{
'Fn::Select': [
4,
{ 'Fn::Split': [':', { 'Fn::ImportValue': 'SomeRole' }] },
],
},
'\"},\"Resource\":\"*\"}],\"Version\":\"2012-10-17\"}',
],
],
},
});
});

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

0 comments on commit 553805f

Please sign in to comment.