Skip to content

Commit

Permalink
fix(region-info): incorrect codedeploy service principals (#18505)
Browse files Browse the repository at this point in the history
In "special" regions (GovCloud, US-ISO), the CodeDeploy service
principal uses the `amazonaws.com` DNS suffix. In fact, that is true of
all regions, in all partitions, with the notable exception of `aws-cn`
which uses the `amazonaws.com.cn` suffix instead.


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Romain Marcadier authored Jan 25, 2022
1 parent 36d356d commit 16db963
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,15 @@ describe('CodeDeploy Lambda DeploymentGroup', () => {
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: { 'Fn::Join': ['', ['codedeploy.', { Ref: 'AWS::Region' }, '.', { Ref: 'AWS::URLSuffix' }]] },
Service: {
'Fn::FindInMap': [
'ServiceprincipalMap',
{
Ref: 'AWS::Region',
},
'codedeploy',
],
},
},
}],
Version: '2012-10-17',
Expand Down
33 changes: 16 additions & 17 deletions packages/@aws-cdk/region-info/lib/aws-entities.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
// Rule prefix
const RULE_ = 'RULE_';

/**
* After this point, SSM only creates regional principals
*/
export const RULE_SSM_PRINCIPALS_ARE_REGIONAL = `${RULE_}SSM_PRINCIPALS_ARE_REGIONAL`;
export const RULE_SSM_PRINCIPALS_ARE_REGIONAL = Symbol('SSM_PRINCIPALS_ARE_REGIONAL');

/**
* After this point, S3 website domains look like `s3-website.REGION.s3.amazonaws.com`
*
* Before this point, S3 website domains look like `s3-website-REGION.s3.amazonaws.com`.
*/
export const RULE_S3_WEBSITE_REGIONAL_SUBDOMAIN = `${RULE_}S3_WEBSITE_REGIONAL_SUBDOMAIN`;
export const RULE_S3_WEBSITE_REGIONAL_SUBDOMAIN = Symbol('S3_WEBSITE_REGIONAL_SUBDOMAIN');

/**
* List of AWS region, ordered by launch date (oldest to newest)
Expand All @@ -21,13 +18,13 @@ export const RULE_S3_WEBSITE_REGIONAL_SUBDOMAIN = `${RULE_}S3_WEBSITE_REGIONAL_S
* regions are left as-is.
*
* We mix the list of regions with a list of rules that were introduced over
* time (rules are strings starting with `RULE_`).
* time (rules are symbols).
*
* Therefore, if we want to know if a rule applies to a certain region, we
* only need to check its position in the list and compare it to when a
* rule was introduced.
*/
export const AWS_REGIONS_AND_RULES = [
export const AWS_REGIONS_AND_RULES: readonly (string | symbol)[] = [
'us-east-1', // US East (N. Virginia)
'eu-west-1', // Europe (Ireland)
'us-west-1', // US West (N. California)
Expand Down Expand Up @@ -68,15 +65,15 @@ export const AWS_REGIONS_AND_RULES = [
* Not in the list ==> no built-in data for that region.
*/
export const AWS_REGIONS = AWS_REGIONS_AND_RULES
.filter((x) => !x.startsWith(RULE_))
.sort();
.filter((x) => typeof x === 'string')
.sort() as readonly string[];

/**
* Possibly non-exaustive list of all service names, used to locate service principals.
*
* Not in the list ==> default service principal mappings.
*/
export const AWS_SERVICES = [
export const AWS_SERVICES: readonly string[] = [
'application-autoscaling',
'autoscaling',
'codedeploy',
Expand All @@ -96,10 +93,10 @@ export const AWS_SERVICES = [
*
* Unknown region => we have to assume no.
*/
export function before(region: string, ruleOrRegion: string) {
export function before(region: string, ruleOrRegion: string | symbol) {
const ruleIx = AWS_REGIONS_AND_RULES.indexOf(ruleOrRegion);
if (ruleIx === -1) {
throw new Error(`Unknown rule: ${ruleOrRegion}`);
throw new Error(`Unknown rule: ${String(ruleOrRegion)}`);
}
const regionIx = AWS_REGIONS_AND_RULES.indexOf(region);
return regionIx === -1 ? false : regionIx < ruleIx;
Expand All @@ -108,17 +105,19 @@ export function before(region: string, ruleOrRegion: string) {
/**
* Return all regions before a given rule was introduced (or region)
*/
export function regionsBefore(ruleOrRegion: string): string[] {
export function regionsBefore(ruleOrRegion: string | symbol): string[] {
const ruleIx = AWS_REGIONS_AND_RULES.indexOf(ruleOrRegion);
if (ruleIx === -1) {
throw new Error(`Unknown rule: ${ruleOrRegion}`);
throw new Error(`Unknown rule: ${String(ruleOrRegion)}`);
}
return AWS_REGIONS_AND_RULES.filter((_, i) => i < ruleIx).sort();
return AWS_REGIONS_AND_RULES.slice(0, ruleIx)
.filter((entry) => typeof entry === 'string')
.sort() as string[];
}

export interface Region { partition: string, domainSuffix: string }
export interface Region { readonly partition: string, readonly domainSuffix: string }

const PARTITION_MAP: { [region: string]: Region } = {
const PARTITION_MAP: {readonly [region: string]: Region } = {
'default': { partition: 'aws', domainSuffix: 'amazonaws.com' },
'cn-': { partition: 'aws-cn', domainSuffix: 'amazonaws.com.cn' },
'us-gov-': { partition: 'aws-us-gov', domainSuffix: 'amazonaws.com' },
Expand Down
134 changes: 74 additions & 60 deletions packages/@aws-cdk/region-info/lib/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export class Default {
* @param urlSuffix deprecated and ignored.
*/
public static servicePrincipal(serviceFqn: string, region: string, urlSuffix: string): string {
const service = extractSimpleName(serviceFqn);
if (!service) {
const serviceName = extractSimpleName(serviceFqn);
if (!serviceName) {
// Return "service" if it does not look like any of the following:
// - s3
// - s3.amazonaws.com
Expand All @@ -34,72 +34,86 @@ export class Default {
return serviceFqn;
}

// Exceptions for Service Principals in us-iso-*
const US_ISO_EXCEPTIONS = new Set([
'cloudhsm',
'config',
'states',
'workspaces',
]);

// Account for idiosyncratic Service Principals in `us-iso-*` regions
if (region.startsWith('us-iso-') && US_ISO_EXCEPTIONS.has(service)) {
switch (service) {
// Services with universal principal
case ('states'):
return `${service}.amazonaws.com`;

// Services with a partitional principal
default:
return `${service}.${urlSuffix}`;
function determineConfiguration(service: string): (service: string, region: string, urlSuffix: string) => string {
function universal(s: string) { return `${s}.amazonaws.com`; };
function partitional(s: string, _: string, u: string) { return `${s}.${u}`; };
function regional(s: string, r: string) { return `${s}.${r}.amazonaws.com`; };
function regionalPartitional(s: string, r: string, u: string) { return `${s}.${r}.${u}`; };

// Exceptions for Service Principals in us-iso-*
const US_ISO_EXCEPTIONS = new Set([
'cloudhsm',
'config',
'states',
'workspaces',
]);

// Account for idiosyncratic Service Principals in `us-iso-*` regions
if (region.startsWith('us-iso-') && US_ISO_EXCEPTIONS.has(service)) {
switch (service) {
// Services with universal principal
case ('states'):
return universal;

// Services with a partitional principal
default:
return partitional;
}
}
}

// Exceptions for Service Principals in us-isob-*
const US_ISOB_EXCEPTIONS = new Set([
'dms',
'states',
]);
// Exceptions for Service Principals in us-isob-*
const US_ISOB_EXCEPTIONS = new Set([
'dms',
'states',
]);

// Account for idiosyncratic Service Principals in `us-isob-*` regions
if (region.startsWith('us-isob-') && US_ISOB_EXCEPTIONS.has(service)) {
switch (service) {
// Services with universal principal
case ('states'):
return universal;

// Services with a partitional principal
default:
return partitional;
}
}

// Account for idiosyncratic Service Principals in `us-isob-*` regions
if (region.startsWith('us-isob-') && US_ISOB_EXCEPTIONS.has(service)) {
switch (service) {
// Services with universal principal
case ('states'):
return `${service}.amazonaws.com`;
// SSM turned from global to regional at some point
case 'ssm':
return before(region, RULE_SSM_PRINCIPALS_ARE_REGIONAL)
? universal
: regional;

// CodeDeploy is regional+partitional in CN, only regional everywhere else
case 'codedeploy':
return region.startsWith('cn-')
? regionalPartitional
: regional;

// Services with a regional AND partitional principal
case 'logs':
return regionalPartitional;

// Services with a regional principal
case 'states':
return regional;

// Services with a partitional principal
default:
return `${service}.${urlSuffix}`;
}
}

// SSM turned from global to regional at some point
if (service === 'ssm') {
return before(region, RULE_SSM_PRINCIPALS_ARE_REGIONAL)
? `${service}.amazonaws.com`
: `${service}.${region}.amazonaws.com`;
}

switch (service) {
// Services with a regional AND partitional principal
case 'codedeploy':
case 'logs':
return `${service}.${region}.${urlSuffix}`;

// Services with a regional principal
case 'states':
return `${service}.${region}.amazonaws.com`;
case 'ec2':
return partitional;

// Services with a partitional principal
case 'ec2':
return `${service}.${urlSuffix}`;
// Services with a universal principal across all regions/partitions (the default case)
default:
return universal;

// Services with a universal principal across all regions/partitions (the default case)
default:
return `${service}.amazonaws.com`;
}
};

}
const configuration = determineConfiguration(serviceName);
return configuration(serviceName, region, urlSuffix);
}

private constructor() { }
Expand All @@ -108,4 +122,4 @@ export class Default {
function extractSimpleName(serviceFqn: string) {
const matches = serviceFqn.match(/^([^.]+)(?:(?:\.amazonaws\.com(?:\.cn)?)|(?:\.c2s\.ic\.gov)|(?:\.sc2s\.sgov\.gov))?$/);
return matches ? matches[1] : undefined;
}
}
3 changes: 2 additions & 1 deletion packages/@aws-cdk/region-info/lib/fact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export class Fact {
* may not be an exhaustive list of all available AWS regions.
*/
public static get regions(): string[] {
return AWS_REGIONS;
// Return by copy to ensure no modifications can be made to the undelying constant.
return Array.from(AWS_REGIONS);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ Object {
"servicePrincipals": Object {
"application-autoscaling": "application-autoscaling.amazonaws.com",
"autoscaling": "autoscaling.amazonaws.com",
"codedeploy": "codedeploy.us-iso-east-1.c2s.ic.gov",
"codedeploy": "codedeploy.us-iso-east-1.amazonaws.com",
"ec2": "ec2.c2s.ic.gov",
"events": "events.amazonaws.com",
"lambda": "lambda.amazonaws.com",
Expand Down Expand Up @@ -826,7 +826,7 @@ Object {
"servicePrincipals": Object {
"application-autoscaling": "application-autoscaling.amazonaws.com",
"autoscaling": "autoscaling.amazonaws.com",
"codedeploy": "codedeploy.us-iso-west-1.c2s.ic.gov",
"codedeploy": "codedeploy.us-iso-west-1.amazonaws.com",
"ec2": "ec2.c2s.ic.gov",
"events": "events.amazonaws.com",
"lambda": "lambda.amazonaws.com",
Expand Down Expand Up @@ -857,7 +857,7 @@ Object {
"servicePrincipals": Object {
"application-autoscaling": "application-autoscaling.amazonaws.com",
"autoscaling": "autoscaling.amazonaws.com",
"codedeploy": "codedeploy.us-isob-east-1.sc2s.sgov.gov",
"codedeploy": "codedeploy.us-isob-east-1.amazonaws.com",
"ec2": "ec2.sc2s.sgov.gov",
"events": "events.amazonaws.com",
"lambda": "lambda.amazonaws.com",
Expand Down
10 changes: 8 additions & 2 deletions packages/@aws-cdk/region-info/test/default.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ const urlSuffix = '.nowhere.null';

describe('servicePrincipal', () => {
for (const suffix of ['', '.amazonaws.com', '.amazonaws.com.cn']) {
for (const service of ['states', 'ssm']) {
for (const service of ['codedeploy', 'states', 'ssm']) {
test(`${service}${suffix}`, () => {
expect(Default.servicePrincipal(`${service}${suffix}`, region, urlSuffix)).toBe(`${service}.${region}.amazonaws.com`);
});
}
for (const service of ['codedeploy', 'logs']) {
for (const service of ['logs']) {
test(`${service}${suffix}`, () => {
expect(Default.servicePrincipal(`${service}${suffix}`, region, urlSuffix)).toBe(`${service}.${region}.${urlSuffix}`);
});
Expand Down Expand Up @@ -48,6 +48,12 @@ describe('servicePrincipal', () => {
});
}

for (const cnRegion of ['cn-north-1', 'cn-northwest-1']) {
test(`Exceptions: codedeploy in ${cnRegion}`, () => {
expect(Default.servicePrincipal('codedeploy', cnRegion, 'amazonaws.com.cn')).toBe(`codedeploy.${cnRegion}.amazonaws.com.cn`);
});
}

});


Expand Down

0 comments on commit 16db963

Please sign in to comment.