Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecs): add tls property to a ServiceConnectService #32605

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
62e9a4e
feat: add `tls` property to a ServiceConnectService
ren-yamanashi Dec 20, 2024
d34145e
feat: add unit test
ren-yamanashi Dec 20, 2024
672a95c
feat: add validation method and unit test
ren-yamanashi Dec 20, 2024
a871a30
feat: change props type
ren-yamanashi Dec 20, 2024
e995ff3
feat: update README.md
ren-yamanashi Dec 20, 2024
1b184a2
feat: update JsDoc
ren-yamanashi Dec 20, 2024
61ec526
feat: add integ test
ren-yamanashi Dec 21, 2024
a376f00
feat: add integ test
ren-yamanashi Dec 21, 2024
1486b0d
feat: update unit test
ren-yamanashi Dec 21, 2024
6f8bf3d
feat: update unit test
ren-yamanashi Dec 21, 2024
fcc8472
feat: add integ test
ren-yamanashi Dec 21, 2024
4d06220
feat: add integ test
ren-yamanashi Dec 21, 2024
e7335fa
feat: update snapshot
ren-yamanashi Dec 21, 2024
609c57d
feat: update snapshot
ren-yamanashi Dec 21, 2024
05daacf
Merge branch 'main' of https://github.com/aws/aws-cdk into 32583-feat…
ren-yamanashi Dec 21, 2024
9333404
Merge branch 'main' of https://github.com/aws/aws-cdk into 32583-feat…
ren-yamanashi Jan 12, 2025
92db83e
feat: delete integ test
ren-yamanashi Jan 12, 2025
f18c1ae
feat: update unit test
ren-yamanashi Jan 12, 2025
40b8c6e
Merge branch 'main' of https://github.com/aws/aws-cdk into 32583-feat…
ren-yamanashi Jan 12, 2025
881de2d
docs: update README.md
ren-yamanashi Jan 12, 2025
be5951f
Merge branch 'main' into 32583-feat-support-tls-on-service-connect-se…
mergify[bot] Jan 30, 2025
5fc4535
Merge branch 'main' into 32583-feat-support-tls-on-service-connect-se…
mergify[bot] Jan 31, 2025
18b5934
Merge branch 'main' into 32583-feat-support-tls-on-service-connect-se…
mergify[bot] Jan 31, 2025
c4fb13f
Merge branch 'main' into 32583-feat-support-tls-on-service-connect-se…
mergify[bot] Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions packages/aws-cdk-lib/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1938,3 +1938,40 @@ taskDefinition.addContainer('TheContainer', {
}],
});
```

## Service Connect TLS

Service Connect TLS is a feature that allows you to secure the communication between services using TLS.

You can specify the `tls` option in the `services` array of the `serviceConnectConfiguration` property.

The `tls` property is an object with the following properties:

- `role`: The IAM role that's associated with the Service Connect TLS.
- `awsPcaAuthorityArn`: The ARN of the certificate root authority that secures your service.
- `kmsKey`: The KMS key used for encryption and decryption.

```ts
declare const cluster: ecs.Cluster;
declare const taskDefinition: ecs.TaskDefinition;
declare const kmsKey: kms.IKey;
declare const role: iam.IRole;

const service = new ecs.FargateService(this, 'FargateService', {
cluster,
taskDefinition,
serviceConnectConfiguration: {
services: [
{
tls: {
role,
kmsKey,
awsPcaAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/123456789012',
},
portMappingName: 'api',
},
],
namespace: 'sample namespace',
},
});
```
49 changes: 49 additions & 0 deletions packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as ec2 from '../../../aws-ec2';
import * as elb from '../../../aws-elasticloadbalancing';
import * as elbv2 from '../../../aws-elasticloadbalancingv2';
import * as iam from '../../../aws-iam';
import * as kms from '../../../aws-kms';
import * as cloudmap from '../../../aws-servicediscovery';
import {
Annotations,
Expand Down Expand Up @@ -254,6 +255,39 @@ export interface ServiceConnectService {
* @default - Duration.seconds(15)
*/
readonly perRequestTimeout?: Duration;

/**
* A reference to an object that represents a Transport Layer Security (TLS) configuration.
*
* @default - none
*/
readonly tls?: ServiceConnectTlsConfiguration;
}

/**
* TLS configuration for Service Connect service
*/
export interface ServiceConnectTlsConfiguration {
/**
* The ARN of the certificate root authority that secures your service.
*
* @default - none
*/
readonly awsPcaAuthorityArn?: string;

/**
* The KMS key used for encryption and decryption.
*
* @default - none
*/
readonly kmsKey?: kms.IKey;

/**
* The IAM role that's associated with the Service Connect TLS.
*
* @default - none
*/
readonly role?: iam.IRole;
}

/**
Expand Down Expand Up @@ -920,12 +954,21 @@ export abstract class BaseService extends Resource
dnsName: svc.dnsName,
};

const tls: CfnService.ServiceConnectTlsConfigurationProperty | undefined = svc.tls ? {
issuerCertificateAuthority: {
awsPcaAuthorityArn: svc.tls.awsPcaAuthorityArn,
},
kmsKey: svc.tls.kmsKey?.keyArn,
roleArn: svc.tls.role?.roleArn,
} : undefined;

return {
portName: svc.portMappingName,
discoveryName: svc.discoveryName,
ingressPortOverride: svc.ingressPortOverride,
clientAliases: [alias],
timeout: this.renderTimeout(svc.idleTimeout, svc.perRequestTimeout),
tls,
} as CfnService.ServiceConnectServiceProperty;
});

Expand Down Expand Up @@ -996,6 +1039,12 @@ export abstract class BaseService extends Resource
!this.isValidPort(serviceConnectService.port)) {
throw new Error(`Client Alias port ${serviceConnectService.port} is not valid.`);
}

// tls.awsPcaAuthorityArn should be an ARN
const awsPcaAuthorityArn = serviceConnectService.tls?.awsPcaAuthorityArn;
if (awsPcaAuthorityArn && !Token.isUnresolved(awsPcaAuthorityArn) && !awsPcaAuthorityArn.startsWith('arn:')) {
throw new Error(`awsPcaAuthorityArn must start with "arn:" and have at least 6 components; received ${awsPcaAuthorityArn}`);
}
});
}

Expand Down
95 changes: 95 additions & 0 deletions packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Template, Match } from '../../assertions';
import * as ec2 from '../../aws-ec2';
import * as iam from '../../aws-iam';
import * as kms from '../../aws-kms';
import * as cdk from '../../core';
import { App, Stack } from '../../core';
import * as cxapi from '../../cx-api';
Expand Down Expand Up @@ -79,6 +80,100 @@ describe('When import an ECS Service', () => {
],
});
});

test('should add tls configuration to service connect service', () => {
// GIVEN
const vpc = new ec2.Vpc(stack, 'Vpc');
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
const kmsKey = new kms.Key(stack, 'KmsKey');
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'),
});
taskDefinition.addContainer('Web', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
portMappings: [
{
name: 'api',
containerPort: 80,
},
],
});
const service = new ecs.FargateService(stack, 'Service', {
cluster,
taskDefinition,
});

// WHEN
service.enableServiceConnect({
services: [
{
tls: {
awsPcaAuthorityArn:
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/123456789012',
kmsKey,
role,
},
portMappingName: 'api',
},
],
namespace: 'test namespace',
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', {
ServiceConnectConfiguration: {
Services: [
{
Tls: {
IssuerCertificateAuthority: {
AwsPcaAuthorityArn:
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/123456789012',
},
KmsKey: stack.resolve(kmsKey.keyArn),
RoleArn: stack.resolve(role.roleArn),
},
},
],
},
});
});

test('throws an error when awsPcaAuthorityArn is not an ARN', () => {
// GIVEN
const vpc = new ec2.Vpc(stack, 'Vpc');
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
taskDefinition.addContainer('Web', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
portMappings: [
{
name: 'api',
containerPort: 80,
},
],
});

// WHEN
const createFargateService = () => new ecs.FargateService(stack, 'Service', {
cluster,
taskDefinition,
serviceConnectConfiguration: {
services: [
{
tls: {
awsPcaAuthorityArn: 'invalid-arn',
},
portMappingName: 'api',
},
],
namespace: 'test namespace',
},
});

// THEN
expect(() => createFargateService()).toThrow(/awsPcaAuthorityArn must start with "arn:" and have at least 6 components; received invalid-arn/);
});
});

describe('For alarm-based rollbacks', () => {
Expand Down