-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(iam): Permissions Boundaries (#12777)
Allow configuring Permissions Boundaries for an entire subtree using Aspects, add a sample policy which can be used to reduce future misconfiguration risk for untrusted CodeBuild projects as an example. Addresses one part of aws/aws-cdk-rfcs#5. Fixes #3242. ALSO IN THIS COMMIT: Fix a bug in the `assert` library, where `haveResource()` would *never* match any resource that didn't have a `Properties` block (even if we tested for no property in particular, or the absence of properties). This fix caused two ECS tests to fail, which were asserting the wrong thing anyway (both were asserting `notTo(haveResource(...))` where they actually meant to assert `to(haveResource())`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
Showing
10 changed files
with
354 additions
and
4 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
94 changes: 94 additions & 0 deletions
94
packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts
This file contains 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,94 @@ | ||
import * as iam from '@aws-cdk/aws-iam'; | ||
import { Construct } from 'constructs'; | ||
|
||
/** | ||
* Construction properties for UntrustedCodeBoundaryPolicy | ||
*/ | ||
export interface UntrustedCodeBoundaryPolicyProps { | ||
/** | ||
* The name of the managed policy. | ||
* | ||
* @default - A name is automatically generated. | ||
*/ | ||
readonly managedPolicyName?: string; | ||
|
||
/** | ||
* Additional statements to add to the default set of statements | ||
* | ||
* @default - No additional statements | ||
*/ | ||
readonly additionalStatements?: iam.PolicyStatement[]; | ||
} | ||
|
||
/** | ||
* Permissions Boundary for a CodeBuild Project running untrusted code | ||
* | ||
* This class is a Policy, intended to be used as a Permissions Boundary | ||
* for a CodeBuild project. It allows most of the actions necessary to run | ||
* the CodeBuild project, but disallows reading from Parameter Store | ||
* and Secrets Manager. | ||
* | ||
* Use this when your CodeBuild project is running untrusted code (for | ||
* example, if you are using one to automatically build Pull Requests | ||
* that anyone can submit), and you want to prevent your future self | ||
* from accidentally exposing Secrets to this build. | ||
* | ||
* (The reason you might want to do this is because otherwise anyone | ||
* who can submit a Pull Request to your project can write a script | ||
* to email those secrets to themselves). | ||
* | ||
* @example | ||
* | ||
* iam.PermissionsBoundary.of(project).apply(new UntrustedCodeBoundaryPolicy(this, 'Boundary')); | ||
*/ | ||
export class UntrustedCodeBoundaryPolicy extends iam.ManagedPolicy { | ||
constructor(scope: Construct, id: string, props: UntrustedCodeBoundaryPolicyProps = {}) { | ||
super(scope, id, { | ||
managedPolicyName: props.managedPolicyName, | ||
description: 'Permissions Boundary Policy for CodeBuild Projects running untrusted code', | ||
statements: [ | ||
new iam.PolicyStatement({ | ||
actions: [ | ||
// For logging | ||
'logs:CreateLogGroup', | ||
'logs:CreateLogStream', | ||
'logs:PutLogEvents', | ||
|
||
// For test reports | ||
'codebuild:CreateReportGroup', | ||
'codebuild:CreateReport', | ||
'codebuild:UpdateReport', | ||
'codebuild:BatchPutTestCases', | ||
'codebuild:BatchPutCodeCoverages', | ||
|
||
// For batch builds | ||
'codebuild:StartBuild', | ||
'codebuild:StopBuild', | ||
'codebuild:RetryBuild', | ||
|
||
// For pulling ECR images | ||
'ecr:GetDownloadUrlForLayer', | ||
'ecr:BatchGetImage', | ||
'ecr:BatchCheckLayerAvailability', | ||
|
||
// For running in a VPC | ||
'ec2:CreateNetworkInterfacePermission', | ||
'ec2:CreateNetworkInterface', | ||
'ec2:DescribeNetworkInterfaces', | ||
'ec2:DeleteNetworkInterface', | ||
'ec2:DescribeSubnets', | ||
'ec2:DescribeSecurityGroups', | ||
'ec2:DescribeDhcpOptions', | ||
'ec2:DescribeVpcs', | ||
|
||
// NOTABLY MISSING: | ||
// - Reading secrets | ||
// - Reading parameterstore | ||
], | ||
resources: ['*'], | ||
}), | ||
...props.additionalStatements ?? [], | ||
], | ||
}); | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
packages/@aws-cdk/aws-codebuild/test/test.untrusted-code-boundary.ts
This file contains 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,56 @@ | ||
import { expect, haveResourceLike, arrayWith } from '@aws-cdk/assert'; | ||
import * as iam from '@aws-cdk/aws-iam'; | ||
import * as cdk from '@aws-cdk/core'; | ||
import { Test } from 'nodeunit'; | ||
import * as codebuild from '../lib'; | ||
|
||
export = { | ||
'can attach permissions boundary to Project'(test: Test) { | ||
// GIVEN | ||
const stack = new cdk.Stack(); | ||
|
||
// WHEN | ||
const project = new codebuild.Project(stack, 'Project', { | ||
source: codebuild.Source.gitHub({ owner: 'a', repo: 'b' }), | ||
}); | ||
iam.PermissionsBoundary.of(project).apply(new codebuild.UntrustedCodeBoundaryPolicy(stack, 'Boundary')); | ||
|
||
// THEN | ||
expect(stack).to(haveResourceLike('AWS::IAM::Role', { | ||
PermissionsBoundary: { Ref: 'BoundaryEA298153' }, | ||
})); | ||
|
||
test.done(); | ||
}, | ||
|
||
'can add additional statements Boundary'(test: Test) { | ||
// GIVEN | ||
const stack = new cdk.Stack(); | ||
|
||
// WHEN | ||
const project = new codebuild.Project(stack, 'Project', { | ||
source: codebuild.Source.gitHub({ owner: 'a', repo: 'b' }), | ||
}); | ||
iam.PermissionsBoundary.of(project).apply(new codebuild.UntrustedCodeBoundaryPolicy(stack, 'Boundary', { | ||
additionalStatements: [ | ||
new iam.PolicyStatement({ | ||
actions: ['a:a'], | ||
resources: ['b'], | ||
}), | ||
], | ||
})); | ||
|
||
// THEN | ||
expect(stack).to(haveResourceLike('AWS::IAM::ManagedPolicy', { | ||
PolicyDocument: { | ||
Statement: arrayWith({ | ||
Effect: 'Allow', | ||
Action: 'a:a', | ||
Resource: 'b', | ||
}), | ||
}, | ||
})); | ||
|
||
test.done(); | ||
}, | ||
}; |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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,53 @@ | ||
import { Node, IConstruct } from 'constructs'; | ||
import { CfnRole, CfnUser } from './iam.generated'; | ||
import { IManagedPolicy } from './managed-policy'; | ||
|
||
/** | ||
* Modify the Permissions Boundaries of Users and Roles in a construct tree | ||
* | ||
* @example | ||
* | ||
* const policy = ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess'); | ||
* PermissionsBoundary.of(stack).apply(policy); | ||
*/ | ||
export class PermissionsBoundary { | ||
/** | ||
* Access the Permissions Boundaries of a construct tree | ||
*/ | ||
public static of(scope: IConstruct): PermissionsBoundary { | ||
return new PermissionsBoundary(scope); | ||
} | ||
|
||
private constructor(private readonly scope: IConstruct) { | ||
} | ||
|
||
/** | ||
* Apply the given policy as Permissions Boundary to all Roles in the scope | ||
* | ||
* Will override any Permissions Boundaries configured previously; in case | ||
* a Permission Boundary is applied in multiple scopes, the Boundary applied | ||
* closest to the Role wins. | ||
*/ | ||
public apply(boundaryPolicy: IManagedPolicy) { | ||
Node.of(this.scope).applyAspect({ | ||
visit(node: IConstruct) { | ||
if (node instanceof CfnRole || node instanceof CfnUser) { | ||
node.permissionsBoundary = boundaryPolicy.managedPolicyArn; | ||
} | ||
}, | ||
}); | ||
} | ||
|
||
/** | ||
* Remove previously applied Permissions Boundaries | ||
*/ | ||
public clear() { | ||
Node.of(this.scope).applyAspect({ | ||
visit(node: IConstruct) { | ||
if (node instanceof CfnRole || node instanceof CfnUser) { | ||
node.permissionsBoundary = undefined; | ||
} | ||
}, | ||
}); | ||
} | ||
} |
Oops, something went wrong.