Skip to content

Commit d32baf6

Browse files
feat(ecs): add tls property to a ServiceConnectService (#32605)
### Issue # (if applicable) Closes #32583 ### Reason for this change ServiceConnectService in ECS did not support the `tls` property. ### Description of changes - Added `tls` property to ServiceConnectService(interface) in ECS(BaseService) - modified implementation to allow specifying ServiceConnectService tls in the `enableServiceConnect` method ### Description of how you validated changes Added unit tests. ### 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 7b5f5a5 commit d32baf6

File tree

3 files changed

+181
-0
lines changed

3 files changed

+181
-0
lines changed

packages/aws-cdk-lib/aws-ecs/README.md

+37
Original file line numberDiff line numberDiff line change
@@ -1938,3 +1938,40 @@ taskDefinition.addContainer('TheContainer', {
19381938
}],
19391939
});
19401940
```
1941+
1942+
## Service Connect TLS
1943+
1944+
Service Connect TLS is a feature that allows you to secure the communication between services using TLS.
1945+
1946+
You can specify the `tls` option in the `services` array of the `serviceConnectConfiguration` property.
1947+
1948+
The `tls` property is an object with the following properties:
1949+
1950+
- `role`: The IAM role that's associated with the Service Connect TLS.
1951+
- `awsPcaAuthorityArn`: The ARN of the certificate root authority that secures your service.
1952+
- `kmsKey`: The KMS key used for encryption and decryption.
1953+
1954+
```ts
1955+
declare const cluster: ecs.Cluster;
1956+
declare const taskDefinition: ecs.TaskDefinition;
1957+
declare const kmsKey: kms.IKey;
1958+
declare const role: iam.IRole;
1959+
1960+
const service = new ecs.FargateService(this, 'FargateService', {
1961+
cluster,
1962+
taskDefinition,
1963+
serviceConnectConfiguration: {
1964+
services: [
1965+
{
1966+
tls: {
1967+
role,
1968+
kmsKey,
1969+
awsPcaAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/123456789012',
1970+
},
1971+
portMappingName: 'api',
1972+
},
1973+
],
1974+
namespace: 'sample namespace',
1975+
},
1976+
});
1977+
```

packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts

+49
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as ec2 from '../../../aws-ec2';
77
import * as elb from '../../../aws-elasticloadbalancing';
88
import * as elbv2 from '../../../aws-elasticloadbalancingv2';
99
import * as iam from '../../../aws-iam';
10+
import * as kms from '../../../aws-kms';
1011
import * as cloudmap from '../../../aws-servicediscovery';
1112
import {
1213
Annotations,
@@ -254,6 +255,39 @@ export interface ServiceConnectService {
254255
* @default - Duration.seconds(15)
255256
*/
256257
readonly perRequestTimeout?: Duration;
258+
259+
/**
260+
* A reference to an object that represents a Transport Layer Security (TLS) configuration.
261+
*
262+
* @default - none
263+
*/
264+
readonly tls?: ServiceConnectTlsConfiguration;
265+
}
266+
267+
/**
268+
* TLS configuration for Service Connect service
269+
*/
270+
export interface ServiceConnectTlsConfiguration {
271+
/**
272+
* The ARN of the certificate root authority that secures your service.
273+
*
274+
* @default - none
275+
*/
276+
readonly awsPcaAuthorityArn?: string;
277+
278+
/**
279+
* The KMS key used for encryption and decryption.
280+
*
281+
* @default - none
282+
*/
283+
readonly kmsKey?: kms.IKey;
284+
285+
/**
286+
* The IAM role that's associated with the Service Connect TLS.
287+
*
288+
* @default - none
289+
*/
290+
readonly role?: iam.IRole;
257291
}
258292

259293
/**
@@ -920,12 +954,21 @@ export abstract class BaseService extends Resource
920954
dnsName: svc.dnsName,
921955
};
922956

957+
const tls: CfnService.ServiceConnectTlsConfigurationProperty | undefined = svc.tls ? {
958+
issuerCertificateAuthority: {
959+
awsPcaAuthorityArn: svc.tls.awsPcaAuthorityArn,
960+
},
961+
kmsKey: svc.tls.kmsKey?.keyArn,
962+
roleArn: svc.tls.role?.roleArn,
963+
} : undefined;
964+
923965
return {
924966
portName: svc.portMappingName,
925967
discoveryName: svc.discoveryName,
926968
ingressPortOverride: svc.ingressPortOverride,
927969
clientAliases: [alias],
928970
timeout: this.renderTimeout(svc.idleTimeout, svc.perRequestTimeout),
971+
tls,
929972
} as CfnService.ServiceConnectServiceProperty;
930973
});
931974

@@ -996,6 +1039,12 @@ export abstract class BaseService extends Resource
9961039
!this.isValidPort(serviceConnectService.port)) {
9971040
throw new Error(`Client Alias port ${serviceConnectService.port} is not valid.`);
9981041
}
1042+
1043+
// tls.awsPcaAuthorityArn should be an ARN
1044+
const awsPcaAuthorityArn = serviceConnectService.tls?.awsPcaAuthorityArn;
1045+
if (awsPcaAuthorityArn && !Token.isUnresolved(awsPcaAuthorityArn) && !awsPcaAuthorityArn.startsWith('arn:')) {
1046+
throw new Error(`awsPcaAuthorityArn must start with "arn:" and have at least 6 components; received ${awsPcaAuthorityArn}`);
1047+
}
9991048
});
10001049
}
10011050

packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts

+95
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Template, Match } from '../../assertions';
22
import * as ec2 from '../../aws-ec2';
33
import * as iam from '../../aws-iam';
4+
import * as kms from '../../aws-kms';
45
import * as cdk from '../../core';
56
import { App, Stack } from '../../core';
67
import * as cxapi from '../../cx-api';
@@ -79,6 +80,100 @@ describe('When import an ECS Service', () => {
7980
],
8081
});
8182
});
83+
84+
test('should add tls configuration to service connect service', () => {
85+
// GIVEN
86+
const vpc = new ec2.Vpc(stack, 'Vpc');
87+
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
88+
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
89+
const kmsKey = new kms.Key(stack, 'KmsKey');
90+
const role = new iam.Role(stack, 'Role', {
91+
assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'),
92+
});
93+
taskDefinition.addContainer('Web', {
94+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
95+
portMappings: [
96+
{
97+
name: 'api',
98+
containerPort: 80,
99+
},
100+
],
101+
});
102+
const service = new ecs.FargateService(stack, 'Service', {
103+
cluster,
104+
taskDefinition,
105+
});
106+
107+
// WHEN
108+
service.enableServiceConnect({
109+
services: [
110+
{
111+
tls: {
112+
awsPcaAuthorityArn:
113+
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/123456789012',
114+
kmsKey,
115+
role,
116+
},
117+
portMappingName: 'api',
118+
},
119+
],
120+
namespace: 'test namespace',
121+
});
122+
123+
// THEN
124+
Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', {
125+
ServiceConnectConfiguration: {
126+
Services: [
127+
{
128+
Tls: {
129+
IssuerCertificateAuthority: {
130+
AwsPcaAuthorityArn:
131+
'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/123456789012',
132+
},
133+
KmsKey: stack.resolve(kmsKey.keyArn),
134+
RoleArn: stack.resolve(role.roleArn),
135+
},
136+
},
137+
],
138+
},
139+
});
140+
});
141+
142+
test('throws an error when awsPcaAuthorityArn is not an ARN', () => {
143+
// GIVEN
144+
const vpc = new ec2.Vpc(stack, 'Vpc');
145+
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
146+
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
147+
taskDefinition.addContainer('Web', {
148+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
149+
portMappings: [
150+
{
151+
name: 'api',
152+
containerPort: 80,
153+
},
154+
],
155+
});
156+
157+
// WHEN
158+
const createFargateService = () => new ecs.FargateService(stack, 'Service', {
159+
cluster,
160+
taskDefinition,
161+
serviceConnectConfiguration: {
162+
services: [
163+
{
164+
tls: {
165+
awsPcaAuthorityArn: 'invalid-arn',
166+
},
167+
portMappingName: 'api',
168+
},
169+
],
170+
namespace: 'test namespace',
171+
},
172+
});
173+
174+
// THEN
175+
expect(() => createFargateService()).toThrow(/awsPcaAuthorityArn must start with "arn:" and have at least 6 components; received invalid-arn/);
176+
});
82177
});
83178

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

0 commit comments

Comments
 (0)