Skip to content

Commit c46acd5

Browse files
authored
feat(ecr): add server-side encryption configuration (#16966)
fixes #15400 With this request you will be able to configure the encryption of your ECR Repository. Before this patch you need to use a L1-Construct and add it via: Python: ```python repo = ecr.Repository(stack, 'Repo') cfn_repo = repo.node.default_child cfn_repo.encryption_configuration = CfnRepository.EncryptionConfigurationProperty(encryption_type="KMS") ``` Now this becomes: ```python repo = ecr.Repository(stack, 'Repo', encryption_type=ecr.RepositoryEncryption.KMS) ``` When using a KMS Key, the `encryption_type` is set automatically to `KMS`. ```python kms_key = kms.Key(stack, 'Key') ecr.Repository(stack, 'Repo', encryption_key=kms_key) ``` Similar to #15571 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent b30c3f2 commit c46acd5

File tree

4 files changed

+174
-0
lines changed

4 files changed

+174
-0
lines changed

packages/@aws-cdk/aws-ecr/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,32 @@ You can set tag immutability on images in our repository using the `imageTagMuta
7979
new ecr.Repository(this, 'Repo', { imageTagMutability: ecr.TagMutability.IMMUTABLE });
8080
```
8181

82+
### Encryption
83+
84+
By default, Amazon ECR uses server-side encryption with Amazon S3-managed encryption keys which encrypts your data at rest using an AES-256 encryption algorithm. For more control over the encryption for your Amazon ECR repositories, you can use server-side encryption with KMS keys stored in AWS Key Management Service (AWS KMS). Read more about this feature in the [ECR Developer Guide](https://docs.aws.amazon.com/AmazonECR/latest/userguide/encryption-at-rest.html).
85+
86+
When you use AWS KMS to encrypt your data, you can either use the default AWS managed key, which is managed by Amazon ECR, by specifying `RepositoryEncryption.KMS` in the `encryption` property. Or specify your own customer managed KMS key, by specifying the `encryptionKey` property.
87+
88+
When `encryptionKey` is set, the `encryption` property must be `KMS` or empty.
89+
90+
In the case `encryption` is set to `KMS` but no `encryptionKey` is set, an AWS managed KMS key is used.
91+
92+
```ts
93+
new ecr.Repository(this, 'Repo', {
94+
encryption: ecr.RepositoryEncryption.KMS
95+
});
96+
```
97+
98+
Otherwise, a customer-managed KMS key is used if `encryptionKey` was set and `encryption` was optionally set to `KMS`.
99+
100+
```ts
101+
import * as kms from '@aws-cdk/aws-kms';
102+
103+
new ecr.Repository(this, 'Repo', {
104+
encryptionKey: new kms.Key(this, 'Key'),
105+
});
106+
```
107+
82108
## Automatically clean up repositories
83109

84110
You can set life cycle rules to automatically clean up old images from your

packages/@aws-cdk/aws-ecr/lib/repository.ts

+72
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EOL } from 'os';
22
import * as events from '@aws-cdk/aws-events';
33
import * as iam from '@aws-cdk/aws-iam';
4+
import * as kms from '@aws-cdk/aws-kms';
45
import { ArnFormat, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core';
56
import { IConstruct, Construct } from 'constructs';
67
import { CfnRepository } from './ecr.generated';
@@ -327,6 +328,27 @@ export interface RepositoryProps {
327328
*/
328329
readonly repositoryName?: string;
329330

331+
/**
332+
* The kind of server-side encryption to apply to this repository.
333+
*
334+
* If you choose KMS, you can specify a KMS key via `encryptionKey`. If
335+
* encryptionKey is not specified, an AWS managed KMS key is used.
336+
*
337+
* @default - `KMS` if `encryptionKey` is specified, or `AES256` otherwise.
338+
*/
339+
readonly encryption?: RepositoryEncryption;
340+
341+
/**
342+
* External KMS key to use for repository encryption.
343+
*
344+
* The 'encryption' property must be either not specified or set to "KMS".
345+
* An error will be emitted if encryption is set to "AES256".
346+
*
347+
* @default - If encryption is set to `KMS` and this property is undefined,
348+
* an AWS managed KMS key is used.
349+
*/
350+
readonly encryptionKey?: kms.IKey;
351+
330352
/**
331353
* Life cycle rules to apply to this registry
332354
*
@@ -490,6 +512,7 @@ export class Repository extends RepositoryBase {
490512
scanOnPush: true,
491513
},
492514
imageTagMutability: props.imageTagMutability || undefined,
515+
encryptionConfiguration: this.parseEncryption(props),
493516
});
494517

495518
resource.applyRemovalPolicy(props.removalPolicy);
@@ -602,6 +625,34 @@ export class Repository extends RepositoryBase {
602625
validateAnyRuleLast(ret);
603626
return ret;
604627
}
628+
629+
/**
630+
* Set up key properties and return the Repository encryption property from the
631+
* user's configuration.
632+
*/
633+
private parseEncryption(props: RepositoryProps): CfnRepository.EncryptionConfigurationProperty | undefined {
634+
635+
// default based on whether encryptionKey is specified
636+
const encryptionType = props.encryption ?? (props.encryptionKey ? RepositoryEncryption.KMS : RepositoryEncryption.AES_256);
637+
638+
// if encryption key is set, encryption must be set to KMS.
639+
if (encryptionType !== RepositoryEncryption.KMS && props.encryptionKey) {
640+
throw new Error(`encryptionKey is specified, so 'encryption' must be set to KMS (value: ${encryptionType.value})`);
641+
}
642+
643+
if (encryptionType === RepositoryEncryption.AES_256) {
644+
return undefined;
645+
}
646+
647+
if (encryptionType === RepositoryEncryption.KMS) {
648+
return {
649+
encryptionType: 'KMS',
650+
kmsKey: props.encryptionKey?.keyArn,
651+
};
652+
}
653+
654+
throw new Error(`Unexpected 'encryptionType': ${encryptionType}`);
655+
}
605656
}
606657

607658
function validateAnyRuleLast(rules: LifecycleRule[]) {
@@ -664,3 +715,24 @@ export enum TagMutability {
664715
IMMUTABLE = 'IMMUTABLE',
665716

666717
}
718+
719+
/**
720+
* Indicates whether server-side encryption is enabled for the object, and whether that encryption is
721+
* from the AWS Key Management Service (AWS KMS) or from Amazon S3 managed encryption (SSE-S3).
722+
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#SysMetadata
723+
*/
724+
export class RepositoryEncryption {
725+
/**
726+
* 'AES256'
727+
*/
728+
public static readonly AES_256 = new RepositoryEncryption('AES256');
729+
/**
730+
* 'KMS'
731+
*/
732+
public static readonly KMS = new RepositoryEncryption('KMS');
733+
734+
/**
735+
* @param value the string value of the encryption
736+
*/
737+
protected constructor(public readonly value: string) { }
738+
}

packages/@aws-cdk/aws-ecr/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@
9393
"dependencies": {
9494
"@aws-cdk/aws-events": "0.0.0",
9595
"@aws-cdk/aws-iam": "0.0.0",
96+
"@aws-cdk/aws-kms": "0.0.0",
9697
"@aws-cdk/core": "0.0.0",
9798
"constructs": "^3.3.69"
9899
},
99100
"homepage": "https://github.com/aws/aws-cdk",
100101
"peerDependencies": {
101102
"@aws-cdk/aws-events": "0.0.0",
102103
"@aws-cdk/aws-iam": "0.0.0",
104+
"@aws-cdk/aws-kms": "0.0.0",
103105
"@aws-cdk/core": "0.0.0",
104106
"constructs": "^3.3.69"
105107
},

packages/@aws-cdk/aws-ecr/test/repository.test.ts

+74
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EOL } from 'os';
22
import { Template } from '@aws-cdk/assertions';
33
import * as iam from '@aws-cdk/aws-iam';
4+
import * as kms from '@aws-cdk/aws-kms';
45
import * as cdk from '@aws-cdk/core';
56
import * as ecr from '../lib';
67

@@ -363,6 +364,79 @@ describe('repository', () => {
363364
expect(() => app.synth()).toThrow(/A PolicyStatement used in a resource-based policy must specify at least one IAM principal/);
364365
});
365366

367+
test('default encryption configuration', () => {
368+
// GIVEN
369+
const app = new cdk.App();
370+
const stack = new cdk.Stack(app, 'my-stack');
371+
372+
// WHEN
373+
new ecr.Repository(stack, 'Repo', { encryption: ecr.RepositoryEncryption.AES_256 });
374+
375+
// THEN
376+
Template.fromStack(stack).templateMatches({
377+
Resources: {
378+
Repo02AC86CF: {
379+
Type: 'AWS::ECR::Repository',
380+
DeletionPolicy: 'Retain',
381+
UpdateReplacePolicy: 'Retain',
382+
},
383+
},
384+
});
385+
});
386+
387+
test('kms encryption configuration', () => {
388+
// GIVEN
389+
const app = new cdk.App();
390+
const stack = new cdk.Stack(app, 'my-stack');
391+
392+
// WHEN
393+
new ecr.Repository(stack, 'Repo', { encryption: ecr.RepositoryEncryption.KMS });
394+
395+
// THEN
396+
Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository',
397+
{
398+
EncryptionConfiguration: {
399+
EncryptionType: 'KMS',
400+
},
401+
});
402+
});
403+
404+
test('kms encryption with custom kms configuration', () => {
405+
// GIVEN
406+
const app = new cdk.App();
407+
const stack = new cdk.Stack(app, 'my-stack');
408+
409+
// WHEN
410+
const custom_key = new kms.Key(stack, 'Key');
411+
new ecr.Repository(stack, 'Repo', { encryptionKey: custom_key });
412+
413+
// THEN
414+
Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository',
415+
{
416+
EncryptionConfiguration: {
417+
EncryptionType: 'KMS',
418+
KmsKey: {
419+
'Fn::GetAtt': [
420+
'Key961B73FD',
421+
'Arn',
422+
],
423+
},
424+
},
425+
});
426+
});
427+
428+
test('fails if with custom kms key and AES256 as encryption', () => {
429+
// GIVEN
430+
const app = new cdk.App();
431+
const stack = new cdk.Stack(app, 'my-stack');
432+
const custom_key = new kms.Key(stack, 'Key');
433+
434+
// THEN
435+
expect(() => {
436+
new ecr.Repository(stack, 'Repo', { encryption: ecr.RepositoryEncryption.AES_256, encryptionKey: custom_key });
437+
}).toThrow('encryptionKey is specified, so \'encryption\' must be set to KMS (value: AES256)');
438+
});
439+
366440
describe('events', () => {
367441
test('onImagePushed without imageTag creates the correct event', () => {
368442
const stack = new cdk.Stack();

0 commit comments

Comments
 (0)