Skip to content

Commit 2741dfb

Browse files
authored
feat(s3tables-alpha): add TablePolicy support to L2 construct library (#35223)
### Issue # (if applicable) Related to #33054 ### Reason for this change This PR includes backward-compatible changes being made to add L2 support for the [CfnTable](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3tables.CfnTable.html) and [CfnTablePolicy](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3tables.CfnTablePolicy.html) constructs with a consistent user interface, recommended defaults, and in-built validations for managing Table level IAM resource policies. ### Description of changes **New L2 Construct**: TablePolicy: defines an underlying [CfnTablePolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3tables-tablepolicy.html) resource **New methods added to Table construct**: - `addToResourcePolicy`: Attaches a policy statement to the Table's IAM policy - `grantRead`: Grants read access to the table for the given principal - `grantWrite`: Grants write access to the table for the given principal - `grantReadWrite`: Grants read and write access to the table for the given principal ### Describe any new or updated permissions being added Method | IAM Actions | Description -- | -- | -- table.grantRead | s3tables:Get* | Grants read permission to S3 Table table.grantWrite | s3tables:PutTableData<br>s3tables:UpdateTableMetadataLocation<br>s3tables:RenameTable | Grants write permission to S3 Table table.grantReadWrite | s3tables:Get*<br>s3tables:PutTableData<br>s3tables:UpdateTableMetadataLocation<br>s3tables:CreateTable | Grants read and write permissions to S3 Table ### Description of how you validated changes - Unit tests - Passing Integration tests with snapshots and assertions via API calls ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 628425c commit 2741dfb

24 files changed

+33537
-4
lines changed

packages/@aws-cdk/aws-s3tables-alpha/README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,36 @@ const encryptedBucketAuto = new TableBucket(scope, 'EncryptedTableBucketAuto', {
159159
});
160160
```
161161

162+
### Controlling Table Permissions
163+
164+
```ts
165+
// Grant the principal read permissions to the table
166+
const accountId = '123456789012'
167+
table.grantRead(new iam.AccountPrincipal(accountId));
168+
169+
// Grant the role write permissions to the table
170+
const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('sample') });
171+
table.grantWrite(role);
172+
173+
// Grant the user read and write permissions to the table
174+
table.grantReadWrite(new iam.User(stack, 'MyUser'));
175+
176+
// Grant an account permissions to the table
177+
table.grantReadWrite(new iam.AccountPrincipal(accountId));
178+
179+
// Add custom resource policy statements
180+
const permissions = new iam.PolicyStatement({
181+
effect: iam.Effect.ALLOW,
182+
actions: ['s3tables:*'],
183+
principals: [ new iam.ServicePrincipal('example.aws.internal') ],
184+
resources: ['*']
185+
});
186+
187+
table.addToResourcePolicy(permissions);
188+
```
189+
162190
## Coming Soon
163191

164192
L2 Construct support for:
165193

166-
- Table Policy
167194
- KMS encryption support for Tables

packages/@aws-cdk/aws-s3tables-alpha/awslint.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"duration-prop-name:@aws-cdk/aws-s3tables-alpha.UnreferencedFileRemoval.noncurrentDays",
66
"duration-prop-type:@aws-cdk/aws-s3tables-alpha.UnreferencedFileRemoval.unreferencedDays",
77
"duration-prop-name:@aws-cdk/aws-s3tables-alpha.UnreferencedFileRemoval.unreferencedDays",
8-
"attribute-tag:@aws-cdk/aws-s3tables-alpha.TableBucket.tableBucketPolicy"
8+
"attribute-tag:@aws-cdk/aws-s3tables-alpha.TableBucket.tableBucketPolicy",
9+
"attribute-tag:@aws-cdk/aws-s3tables-alpha.Table.tablePolicy",
10+
"props-physical-name:@aws-cdk/aws-s3tables-alpha.TablePolicyProps"
911
]
1012
}

packages/@aws-cdk/aws-s3tables-alpha/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './table-bucket';
77
export * from './table-bucket-policy';
88
export * from './namespace';
99
export * from './table';
10+
export * from './table-policy';

packages/@aws-cdk/aws-s3tables-alpha/lib/permissions.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Table Bucket
12
// Read priveleges
23
export const TABLE_BUCKET_READ_ACCESS = [
34
's3tables:Get*',
@@ -21,6 +22,22 @@ export const TABLE_BUCKET_READ_WRITE_ACCESS = [...new Set([
2122
...TABLE_BUCKET_WRITE_ACCESS,
2223
])];
2324

25+
// Table
26+
// Read priveleges
27+
export const TABLE_READ_ACCESS = [
28+
's3tables:Get*',
29+
];
30+
// Write priveleges
31+
export const TABLE_WRITE_ACCESS = [
32+
's3tables:PutTableData',
33+
's3tables:UpdateTableMetadataLocation',
34+
's3tables:RenameTable',
35+
];
36+
export const TABLE_READ_WRITE_ACCESS = [...new Set([
37+
...TABLE_READ_ACCESS,
38+
...TABLE_WRITE_ACCESS,
39+
])];
40+
2441
// Permissions for user defined KMS Keys
2542
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-permissions.html
2643
export const KEY_READ_ACCESS = [
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Construct } from 'constructs';
2+
import { CfnTablePolicy } from 'aws-cdk-lib/aws-s3tables';
3+
import * as iam from 'aws-cdk-lib/aws-iam';
4+
import { RemovalPolicy, Resource } from 'aws-cdk-lib/core';
5+
import { ITable } from './table';
6+
import { addConstructMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
7+
import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
8+
9+
/**
10+
* Parameters for constructing a TablePolicy
11+
*/
12+
export interface TablePolicyProps {
13+
/**
14+
* The associated table
15+
*/
16+
readonly table: ITable;
17+
/**
18+
* The policy document for the table's resource policy
19+
* @default undefined An empty iam.PolicyDocument will be initialized
20+
*/
21+
readonly resourcePolicy?: iam.PolicyDocument;
22+
/**
23+
* Policy to apply when the policy is removed from this stack.
24+
*
25+
* @default - RemovalPolicy.DESTROY.
26+
*/
27+
readonly removalPolicy?: RemovalPolicy;
28+
}
29+
30+
/**
31+
* A Policy for S3 Tables.
32+
*
33+
* You will almost never need to use this construct directly.
34+
* Instead, Table.addToResourcePolicy can be used to add more policies to your table directly
35+
*/
36+
@propertyInjectable
37+
export class TablePolicy extends Resource {
38+
/** Uniquely identifies this class. */
39+
public static readonly PROPERTY_INJECTION_ID: string = '@aws-cdk.aws-s3tables-alpha.TablePolicy';
40+
/**
41+
* The IAM PolicyDocument containing permissions represented by this policy.
42+
*/
43+
public readonly document: iam.PolicyDocument;
44+
/**
45+
* @internal The underlying policy resource.
46+
*/
47+
private readonly _resource: CfnTablePolicy;
48+
49+
constructor(scope: Construct, id: string, props: TablePolicyProps) {
50+
super(scope, id);
51+
// Enhanced CDK Analytics Telemetry
52+
addConstructMetadata(this, props);
53+
54+
// Use default policy if not provided with props
55+
this.document = props.resourcePolicy || new iam.PolicyDocument({});
56+
57+
this._resource = new CfnTablePolicy(this, id, {
58+
tableArn: props.table.tableArn,
59+
resourcePolicy: this.document,
60+
});
61+
62+
if (props.removalPolicy) {
63+
this._resource.applyRemovalPolicy(props.removalPolicy);
64+
}
65+
}
66+
}

packages/@aws-cdk/aws-s3tables-alpha/lib/table.ts

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import {
66
Token,
77
} from 'aws-cdk-lib/core';
88
import { INamespace } from './namespace';
9-
import { CfnTable } from 'aws-cdk-lib/aws-s3tables';
9+
import { CfnTable, CfnTablePolicy } from 'aws-cdk-lib/aws-s3tables';
1010
import { addConstructMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
1111
import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
1212
import { Construct } from 'constructs';
13+
import * as iam from 'aws-cdk-lib/aws-iam';
14+
import * as perms from './permissions';
1315
import { EOL } from 'os';
1416

1517
/**
@@ -39,6 +41,58 @@ export interface ITable extends IResource {
3941
* @attribute
4042
*/
4143
readonly region?: string;
44+
45+
/**
46+
* Adds a statement to the resource policy for a principal (i.e.
47+
* account/role/service) to perform actions on this table.
48+
*
49+
* Note that the policy statement may or may not be added to the policy.
50+
* For example, when an `ITable` is created from an existing table,
51+
* it's not possible to tell whether the table already has a policy
52+
* attached, let alone to re-use that policy to add more statements to it.
53+
* So it's safest to do nothing in these cases.
54+
*
55+
* @param statement the policy statement to be added to the table's
56+
* policy.
57+
* @returns metadata about the execution of this method. If the policy
58+
* was not added, the value of `statementAdded` will be `false`. You
59+
* should always check this value to make sure that the operation was
60+
* actually carried out. Otherwise, synthesis and deploy will terminate
61+
* silently, which may be confusing.
62+
*/
63+
addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult;
64+
65+
/**
66+
* Grant read permissions for this table to an IAM principal (Role/Group/User).
67+
*
68+
* If the parent TableBucket of this table has encryption,
69+
* you should grant kms:Decrypt permission to use this key to the same principal.
70+
*
71+
* @param identity The principal to allow read permissions to
72+
*/
73+
grantRead(identity: iam.IGrantable): iam.Grant;
74+
75+
/**
76+
* Grant write permissions for this table to an IAM principal (Role/Group/User).
77+
*
78+
* If the parent TableBucket of this table has encryption,
79+
* you should grant kms:GenerateDataKey and kms:Decrypt permission
80+
* to use this key to the same principal.
81+
*
82+
* @param identity The principal to allow write permissions to
83+
*/
84+
grantWrite(identity: iam.IGrantable): iam.Grant;
85+
86+
/**
87+
* Grant read and write permissions for this table to an IAM principal (Role/Group/User).
88+
*
89+
* If the parent TableBucket of this table has encryption,
90+
* you should grant kms:GenerateDataKey and kms:Decrypt permission
91+
* to use this key to the same principal.
92+
*
93+
* @param identity The principal to allow read and write permissions to
94+
*/
95+
grantReadWrite(identity: iam.IGrantable): iam.Grant;
4296
}
4397

4498
/**
@@ -47,6 +101,83 @@ export interface ITable extends IResource {
47101
abstract class TableBase extends Resource implements ITable {
48102
public abstract readonly tableName: string;
49103
public abstract readonly tableArn: string;
104+
105+
/**
106+
* The resource policy associated with this table.
107+
*
108+
* If `autoCreatePolicy` is true, a `TablePolicy` will be created upon the
109+
* first call to addToResourcePolicy(s).
110+
*/
111+
public abstract tablePolicy?: CfnTablePolicy;
112+
113+
/**
114+
* Indicates if a table resource policy should automatically created upon
115+
* the first call to `addToResourcePolicy`.
116+
*/
117+
protected abstract autoCreatePolicy: boolean;
118+
119+
public addToResourcePolicy(
120+
statement: iam.PolicyStatement,
121+
): iam.AddToResourcePolicyResult {
122+
if (!this.tablePolicy && this.autoCreatePolicy) {
123+
this.tablePolicy = new CfnTablePolicy(this, 'DefaultPolicy', {
124+
tableArn: this.tableArn,
125+
resourcePolicy: new iam.PolicyDocument({}),
126+
});
127+
}
128+
129+
if (this.tablePolicy) {
130+
this.tablePolicy.resourcePolicy.addStatements(statement);
131+
return { statementAdded: true, policyDependable: this.tablePolicy };
132+
}
133+
134+
return { statementAdded: false };
135+
}
136+
137+
public grantRead(identity: iam.IGrantable) {
138+
return this.grant(
139+
identity,
140+
perms.TABLE_READ_ACCESS,
141+
this.tableArn,
142+
);
143+
}
144+
145+
public grantWrite(identity: iam.IGrantable) {
146+
return this.grant(
147+
identity,
148+
perms.TABLE_WRITE_ACCESS,
149+
this.tableArn,
150+
);
151+
}
152+
153+
public grantReadWrite(identity: iam.IGrantable) {
154+
return this.grant(
155+
identity,
156+
perms.TABLE_READ_WRITE_ACCESS,
157+
this.tableArn,
158+
);
159+
}
160+
161+
/**
162+
* Grants the given s3tables permissions to the provided principal
163+
* @returns Grant object
164+
*/
165+
private grant(
166+
grantee: iam.IGrantable,
167+
tableActions: string[],
168+
resourceArn: string,
169+
...otherResourceArns: (string | undefined)[]) {
170+
const resources = [resourceArn, ...otherResourceArns].filter(arn => arn != undefined);
171+
172+
const grant = iam.Grant.addToPrincipalOrResource({
173+
grantee,
174+
actions: tableActions,
175+
resourceArns: resources,
176+
resource: this,
177+
});
178+
179+
return grant;
180+
}
50181
}
51182

52183
/**
@@ -260,6 +391,7 @@ export class Table extends TableBase {
260391
class Import extends TableBase {
261392
public readonly tableName = attrs.tableName;
262393
public readonly tableArn = tableArn;
394+
public readonly tablePolicy?: CfnTablePolicy;
263395
protected autoCreatePolicy: boolean = false;
264396

265397
/**
@@ -350,6 +482,13 @@ export class Table extends TableBase {
350482
*/
351483
public readonly namespace: INamespace;
352484

485+
/**
486+
* The resource policy for this table.
487+
*/
488+
public readonly tablePolicy?: CfnTablePolicy;
489+
490+
protected autoCreatePolicy: boolean = true;
491+
353492
constructor(scope: Construct, id: string, props: TableProps) {
354493
super(scope, id, {});
355494

packages/@aws-cdk/aws-s3tables-alpha/rosetta/default.ts-fixture

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ class Fixture extends Stack {
1212
});
1313
const namespace = new Namespace(scope, 'ExampleNamespace', {
1414
tableBucket: tableBucket,
15-
namespaceName: 'example-namespace-1',
15+
namespaceName: 'example_namespace_1',
1616
});
17+
const table = new Table(scope, 'ExampleTable', {
18+
tableName: 'example_table',
19+
namespace,
20+
openTableFormat: OpenTableFormat.ICEBERG,
21+
});
22+
1723
const stack = this;
1824
/// here
1925
}

packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-with-grants.js.snapshot/TableWithGrantIntegTestDefaultTestDeployAssertE9880469.assets.json

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)