Skip to content

Commit

Permalink
Merge branch 'main' into feat-s3-asset-for-tar-gz
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Jul 14, 2022
2 parents f99a64d + cd4851a commit 1ad4e08
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 45 deletions.
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-iam/lib/private/immutable-role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class ImmutableRole extends Resource implements IRole {
Dependable.implement(this, {
dependencyRoots: [role],
});
this.node.defaultChild = role.node.defaultChild;
}

public attachInlinePolicy(_policy: Policy): void {
Expand Down
14 changes: 3 additions & 11 deletions packages/@aws-cdk/aws-kms/lib/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Duration, Token, ContextProvider, Arn, ArnFormat } from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { IConstruct, Construct } from 'constructs';
import { Construct } from 'constructs';
import { Alias } from './alias';
import { KeyLookupOptions } from './key-lookup';
import { CfnKey } from './kms.generated';
Expand Down Expand Up @@ -208,28 +208,20 @@ abstract class KeyBase extends Resource implements IKey {
}
// this logic should only apply to newly created
// (= not imported) resources
if (!this.principalIsANewlyCreatedResource(grantPrincipal)) {
if (!Resource.isOwnedResource(grantPrincipal)) {
return undefined;
}
// return undefined;
const keyStack = Stack.of(this);
const granteeStack = Stack.of(grantPrincipal);
if (keyStack === granteeStack) {
return undefined;
}

return granteeStack.dependencies.includes(keyStack)
? granteeStack.account
: undefined;
}

private principalIsANewlyCreatedResource(principal: IConstruct): boolean {
// yes, this sucks
// this is just a temporary stopgap to stem the bleeding while we work on a proper fix
return principal instanceof iam.Role ||
principal instanceof iam.User ||
principal instanceof iam.Group;
}

private isGranteeFromAnotherRegion(grantee: iam.IGrantable): boolean {
if (!isConstruct(grantee)) {
return false;
Expand Down
35 changes: 35 additions & 0 deletions packages/@aws-cdk/aws-kms/test/key.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,41 @@ describe('key policies', () => {
});
});

testFutureBehavior('grant for an immutable role', flags, cdk.App, (app) => {
const principalStack = new cdk.Stack(app, 'PrincipalStack', { env: { account: '0123456789012' } });
const principal = new iam.Role(principalStack, 'Role', {
assumedBy: new iam.AnyPrincipal(),
roleName: 'MyRolePhysicalName',
});

const keyStack = new cdk.Stack(app, 'KeyStack', { env: { account: '111111111111' } });
const key = new kms.Key(keyStack, 'Key');
principalStack.addDependency(keyStack);
key.grantEncrypt(principal.withoutPolicyUpdates());

Template.fromStack(keyStack).hasResourceProperties('AWS::KMS::Key', {
KeyPolicy: {
Statement: Match.arrayWith([{
Action: 'kms:*',
Effect: 'Allow',
Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::111111111111:root']] } },
Resource: '*',
},
{
Action: [
'kms:Encrypt',
'kms:ReEncrypt*',
'kms:GenerateDataKey*',
],
Effect: 'Allow',
Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::0123456789012:root']] } },
Resource: '*',
}]),
Version: '2012-10-17',
},
});
});

testFutureBehavior('additional key admins can be specified (with imported/immutable principal)', flags, cdk.App, (app) => {
const stack = new cdk.Stack(app);
const adminRole = iam.Role.fromRoleArn(stack, 'Admin', 'arn:aws:iam::123456789012:role/TrustedAdmin');
Expand Down
42 changes: 35 additions & 7 deletions packages/@aws-cdk/cloudformation-include/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ radio buttons on the bottom pane.
This will add all resources from `my-template.json` / `my-template.yaml` into the CDK application,
preserving their original logical IDs from the template file.

Note that this including process will _not_ execute any
[CloudFormation transforms](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-macros.html) -
including the [Serverless transform](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-serverless.html).

Any resource from the included template can be retrieved by referring to it by its logical ID from the template.
If you know the class of the CDK object that corresponds to that resource,
you can cast the returned object to the correct type:
Expand Down Expand Up @@ -118,7 +114,39 @@ role.addToPolicy(new iam.PolicyStatement({
}));
```

### Converting L1 resources to L2
## Migrating templates that use Transforms

You can use this module to migrate templates that use
[CloudFormation transforms](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-macros.html) -
including the [Serverless transform](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-serverless.html).

The CDK including process does not execute Transforms,
and the `cdk diff` command by default compares against the original
(meaning, unprocessed) template.
So, if you're downloading the template to include from the CloudFormation AWS Console,
make sure to download the unprocessed template
(the "View processed template" checkbox is left **unchecked**, which is the default):

![unprocessed template in the CloudFormation AWS Console](doc-images/unprocessed-template.png)

However, certain unprocessed templates can fail when used with the `CfnInclude` class.
The most common reason for the failure is that the unprocessed template can contain cycles between resources,
which get removed after the Transform is processed,
but is not allowed when being included (as pure CloudFormation does not permit cycles).

When that happens, you should instead download the processed template from the CloudFormation AWS Console
(make sure the "View processed template" checkbox is **checked** in that case):

![processed template in the CloudFormation AWS Console](doc-images/processed-template.png)

When you include that processed template in your CDK application,
running `cdk diff` will now show a lot of differences with the deployed Stack,
because `cdk diff` uses the unprocessed template by default.
To alleviate that problem, you can pass the `--processed` switch to `cdk diff`,
which will make the diff command compare against the processed template of the deployed Stack,
which will give more precise results in this case.

## Converting L1 resources to L2

The resources the `getResource` method returns are what the CDK calls
[Layer 1 resources](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_cfn)
Expand All @@ -127,7 +155,7 @@ However, in many places in the Construct Library,
the CDK requires so-called Layer 2 resources, like `IBucket`.
There are two ways of going from an L1 to an L2 resource.

#### Using`fromCfn*()` methods
### Using`fromCfn*()` methods

This is the preferred method of converting an L1 resource to an L2.
It works by invoking a static method of the class of the L2 resource
Expand Down Expand Up @@ -191,7 +219,7 @@ and would throw an exception.

In those cases, you need the use the second method of converting an L1 to an L2.

#### Using `from*Name/Arn/Attributes()` methods
### Using `from*Name/Arn/Attributes()` methods

If the resource you need does not have a `fromCfn*()` method,
or if it does, but it throws an exception for your particular L1,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion packages/@aws-cdk/core/lib/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,17 @@ export abstract class Resource extends Construct implements IResource {
/**
* Check whether the given construct is a Resource
*/
public static isResource(construct: IConstruct): construct is CfnResource {
public static isResource(construct: IConstruct): construct is Resource {
return construct !== null && typeof(construct) === 'object' && RESOURCE_SYMBOL in construct;
}

/**
* Returns true if the construct was created by CDK, and false otherwise
*/
public static isOwnedResource(construct: IConstruct): boolean {
return construct.node.defaultChild ? CfnResource.isCfnResource(construct.node.defaultChild) : false;
}

public readonly stack: Stack;
public readonly env: ResourceEnvironment;

Expand Down
7 changes: 5 additions & 2 deletions packages/aws-cdk/lib/api/cloudformation-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,12 @@ export class CloudFormationDeployments {
this.sdkProvider = props.sdkProvider;
}

public async readCurrentTemplateWithNestedStacks(rootStackArtifact: cxapi.CloudFormationStackArtifact): Promise<Template> {
public async readCurrentTemplateWithNestedStacks(
rootStackArtifact: cxapi.CloudFormationStackArtifact,
retrieveProcessedTemplate: boolean = false,
): Promise<Template> {
const sdk = (await this.prepareSdkWithLookupOrDeployRole(rootStackArtifact)).stackSdk;
return (await loadCurrentTemplateWithNestedStacks(rootStackArtifact, sdk)).deployedTemplate;
return (await loadCurrentTemplateWithNestedStacks(rootStackArtifact, sdk, retrieveProcessedTemplate)).deployedTemplate;
}

public async readCurrentTemplate(stackArtifact: cxapi.CloudFormationStackArtifact): Promise<Template> {
Expand Down
36 changes: 21 additions & 15 deletions packages/aws-cdk/lib/api/nested-stack-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import { ISDK } from './aws-auth';
import { LazyListStackResources, ListStackResources } from './evaluate-cloudformation-template';
import { CloudFormationStack, Template } from './util/cloudformation';

export interface TemplateWithNestedStackNames {
readonly deployedTemplate: Template;
readonly nestedStackNames: { [nestedStackLogicalId: string]: NestedStackNames };
}

export interface NestedStackNames {
readonly nestedStackPhysicalName: string | undefined;
readonly nestedChildStackNames: { [logicalId: string]: NestedStackNames };
}

/**
* Reads the currently deployed template from CloudFormation and adds a
* property, `NestedTemplate`, to any nested stacks that appear in either
Expand All @@ -15,8 +25,9 @@ import { CloudFormationStack, Template } from './util/cloudformation';
*/
export async function loadCurrentTemplateWithNestedStacks(
rootStackArtifact: cxapi.CloudFormationStackArtifact, sdk: ISDK,
retrieveProcessedTemplate: boolean = false,
): Promise<TemplateWithNestedStackNames> {
const deployedTemplate = await loadCurrentTemplate(rootStackArtifact, sdk);
const deployedTemplate = await loadCurrentTemplate(rootStackArtifact, sdk, retrieveProcessedTemplate);
const nestedStackNames = await addNestedTemplatesToGeneratedAndDeployedStacks(rootStackArtifact, sdk, {
generatedTemplate: rootStackArtifact.template,
deployedTemplate: deployedTemplate,
Expand All @@ -32,13 +43,18 @@ export async function loadCurrentTemplateWithNestedStacks(
/**
* Returns the currently deployed template from CloudFormation that corresponds to `stackArtifact`.
*/
export async function loadCurrentTemplate(stackArtifact: cxapi.CloudFormationStackArtifact, sdk: ISDK): Promise<Template> {
return loadCurrentStackTemplate(stackArtifact.stackName, sdk);
export async function loadCurrentTemplate(
stackArtifact: cxapi.CloudFormationStackArtifact, sdk: ISDK,
retrieveProcessedTemplate: boolean = false,
): Promise<Template> {
return loadCurrentStackTemplate(stackArtifact.stackName, sdk, retrieveProcessedTemplate);
}

async function loadCurrentStackTemplate(stackName: string, sdk: ISDK) : Promise<Template> {
async function loadCurrentStackTemplate(
stackName: string, sdk: ISDK, retrieveProcessedTemplate: boolean = false,
) : Promise<Template> {
const cfn = sdk.cloudFormation();
const stack = await CloudFormationStack.lookup(cfn, stackName);
const stack = await CloudFormationStack.lookup(cfn, stackName, retrieveProcessedTemplate);
return stack.template();
}

Expand Down Expand Up @@ -119,16 +135,6 @@ function isCdkManagedNestedStack(stackResource: any): stackResource is NestedSta
return stackResource.Type === 'AWS::CloudFormation::Stack' && stackResource.Metadata && stackResource.Metadata['aws:asset:path'];
}

export interface TemplateWithNestedStackNames {
readonly deployedTemplate: Template;
readonly nestedStackNames: { [nestedStackLogicalId: string]: NestedStackNames };
}

export interface NestedStackNames {
readonly nestedStackPhysicalName: string | undefined;
readonly nestedChildStackNames: { [logicalId: string]: NestedStackNames };
}

interface StackTemplates {
readonly generatedTemplate: any;
readonly deployedTemplate: any;
Expand Down
16 changes: 12 additions & 4 deletions packages/aws-cdk/lib/api/util/cloudformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export type ResourcesToImport = CloudFormation.ResourcesToImport;
* repeated calls to CloudFormation).
*/
export class CloudFormationStack {
public static async lookup(cfn: CloudFormation, stackName: string): Promise<CloudFormationStack> {
public static async lookup(
cfn: CloudFormation, stackName: string, retrieveProcessedTemplate: boolean = false,
): Promise<CloudFormationStack> {
try {
const response = await cfn.describeStacks({ StackName: stackName }).promise();
return new CloudFormationStack(cfn, stackName, response.Stacks && response.Stacks[0]);
return new CloudFormationStack(cfn, stackName, response.Stacks && response.Stacks[0], retrieveProcessedTemplate);
} catch (e) {
if (e.code === 'ValidationError' && e.message === `Stack with id ${stackName} does not exist`) {
return new CloudFormationStack(cfn, stackName, undefined);
Expand All @@ -57,7 +59,10 @@ export class CloudFormationStack {

private _template: any;

protected constructor(private readonly cfn: CloudFormation, public readonly stackName: string, private readonly stack?: CloudFormation.Stack) {
protected constructor(
private readonly cfn: CloudFormation, public readonly stackName: string, private readonly stack?: CloudFormation.Stack,
private readonly retrieveProcessedTemplate: boolean = false,
) {
}

/**
Expand All @@ -72,7 +77,10 @@ export class CloudFormationStack {
}

if (this._template === undefined) {
const response = await this.cfn.getTemplate({ StackName: this.stackName, TemplateStage: 'Original' }).promise();
const response = await this.cfn.getTemplate({
StackName: this.stackName,
TemplateStage: this.retrieveProcessedTemplate ? 'Processed' : 'Original',
}).promise();
this._template = (response.TemplateBody && deserializeStructure(response.TemplateBody)) || {};
}
return this._template;
Expand Down
10 changes: 9 additions & 1 deletion packages/aws-cdk/lib/cdk-toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class CdkToolkit {
// Compare N stacks against deployed templates
for (const stack of stacks.stackArtifacts) {
stream.write(format('Stack %s\n', chalk.bold(stack.displayName)));
const currentTemplate = await this.props.cloudFormation.readCurrentTemplateWithNestedStacks(stack);
const currentTemplate = await this.props.cloudFormation.readCurrentTemplateWithNestedStacks(stack, options.compareAgainstProcessedTemplate);
diffs += options.securityOnly
? numberFromBool(printSecurityDiff(currentTemplate, stack, RequireApproval.Broadening))
: printStackDiff(currentTemplate, stack, strict, contextLines, stream);
Expand Down Expand Up @@ -769,6 +769,14 @@ export interface DiffOptions {
* @default false
*/
securityOnly?: boolean;

/**
* Whether to run the diff against the template after the CloudFormation Transforms inside it have been executed
* (as opposed to the original template, the default, which contains the unprocessed Transforms).
*
* @default false
*/
compareAgainstProcessedTemplate?: boolean;
}

interface CfnDeployOptions {
Expand Down
4 changes: 3 additions & 1 deletion packages/aws-cdk/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ async function parseCommandLineArguments() {
.option('template', { type: 'string', desc: 'The path to the CloudFormation template to compare with', requiresArg: true })
.option('strict', { type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources', default: false })
.option('security-only', { type: 'boolean', desc: 'Only diff for broadened security changes', default: false })
.option('fail', { type: 'boolean', desc: 'Fail with exit code 1 in case of diff' }))
.option('fail', { type: 'boolean', desc: 'Fail with exit code 1 in case of diff' })
.option('processed', { type: 'boolean', desc: 'Whether to compare against the template with Transforms already processed', default: false }))
.command('metadata [STACK]', 'Returns all metadata associated with this stack')
.command(['acknowledge [ID]', 'ack [ID]'], 'Acknowledge a notice so that it does not show up anymore')
.command('notices', 'Returns a list of relevant notices')
Expand Down Expand Up @@ -419,6 +420,7 @@ async function initCommandLine() {
securityOnly: args.securityOnly,
fail: args.fail != null ? args.fail : !enableDiffNoFail,
stream: args.ci ? process.stdout : undefined,
compareAgainstProcessedTemplate: args.processed,
});

case 'bootstrap':
Expand Down
Loading

0 comments on commit 1ad4e08

Please sign in to comment.