diff --git a/lib/assets/aws-destination.ts b/lib/assets/aws-destination.ts index 6ccc382..67c366a 100644 --- a/lib/assets/aws-destination.ts +++ b/lib/assets/aws-destination.ts @@ -22,4 +22,15 @@ export interface AwsDestination { * @default - No ExternalId will be supplied */ readonly assumeRoleExternalId?: string; + + /** + * Additional options to pass to STS when assuming the role. + * + * - `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly assumeRoleAdditionalOptions?: { [key: string]: any }; } diff --git a/lib/cloud-assembly/artifact-schema.ts b/lib/cloud-assembly/artifact-schema.ts index 67e8484..f820d01 100644 --- a/lib/cloud-assembly/artifact-schema.ts +++ b/lib/cloud-assembly/artifact-schema.ts @@ -16,6 +16,17 @@ export interface BootstrapRole { */ readonly assumeRoleExternalId?: string; + /** + * Additional options to pass to STS when assuming the role. + * + * - `RoleArn` should not be used. Use the dedicated `arn` property instead. + * - `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly assumeRoleAdditionalOptions?: { [key: string]: any }; + /** * Version of bootstrap stack required to use this role * @@ -81,6 +92,17 @@ export interface AwsCloudFormationStackProperties { */ readonly assumeRoleExternalId?: string; + /** + * Additional options to pass to STS when assuming the role. + * + * - `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly assumeRoleAdditionalOptions?: { [key: string]: any }; + /** * The role that is passed to CloudFormation to execute the change set * diff --git a/lib/cloud-assembly/context-queries.ts b/lib/cloud-assembly/context-queries.ts index b599bb7..49541a1 100644 --- a/lib/cloud-assembly/context-queries.ts +++ b/lib/cloud-assembly/context-queries.ts @@ -61,16 +61,16 @@ export enum ContextProvider { } /** - * Query to AMI context provider + * Options for context lookup roles. */ -export interface AmiContextQuery { +export interface ContextLookupRoleOptions { /** - * Account to query + * Query account */ readonly account: string; /** - * Region to query + * Query region */ readonly region: string; @@ -82,61 +82,50 @@ export interface AmiContextQuery { readonly lookupRoleArn?: string; /** - * Owners to DescribeImages call + * The ExternalId that needs to be supplied while assuming this role * - * @default - All owners + * @default - No ExternalId will be supplied */ - readonly owners?: string[]; + readonly lookupRoleExternalId?: string; /** - * Filters to DescribeImages call + * Additional options to pass to STS when assuming the lookup role. + * + * - `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. */ - readonly filters: { [key: string]: string[] }; + readonly assumeRoleAdditionalOptions?: { [key: string]: any }; } /** - * Query to availability zone context provider + * Query to AMI context provider */ -export interface AvailabilityZonesContextQuery { - /** - * Query account - */ - readonly account: string; - +export interface AmiContextQuery extends ContextLookupRoleOptions { /** - * Query region + * Owners to DescribeImages call + * + * @default - All owners */ - readonly region: string; + readonly owners?: string[]; /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None + * Filters to DescribeImages call */ - readonly lookupRoleArn?: string; + readonly filters: { [key: string]: string[] }; } /** - * Query to hosted zone context provider + * Query to availability zone context provider */ -export interface HostedZoneContextQuery { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; +export interface AvailabilityZonesContextQuery extends ContextLookupRoleOptions {} +/** + * Query to hosted zone context provider + */ +export interface HostedZoneContextQuery extends ContextLookupRoleOptions { /** * The domain name e.g. example.com to lookup */ @@ -163,24 +152,7 @@ export interface HostedZoneContextQuery { /** * Query to SSM Parameter Context Provider */ -export interface SSMParameterContextQuery { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; - +export interface SSMParameterContextQuery extends ContextLookupRoleOptions { /** * Parameter name to query */ @@ -190,24 +162,7 @@ export interface SSMParameterContextQuery { /** * Query input for looking up a VPC */ -export interface VpcContextQuery { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; - +export interface VpcContextQuery extends ContextLookupRoleOptions { /** * Filters to apply to the VPC * @@ -249,24 +204,7 @@ export interface VpcContextQuery { /** * Query to endpoint service context provider */ -export interface EndpointServiceAvailabilityZonesContextQuery { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; - +export interface EndpointServiceAvailabilityZonesContextQuery extends ContextLookupRoleOptions { /** * Query service name */ @@ -291,7 +229,7 @@ export enum LoadBalancerType { /** * Filters for selecting load balancers */ -export interface LoadBalancerFilter { +export interface LoadBalancerFilter extends ContextLookupRoleOptions { /** * Filter load balancers by their type */ @@ -313,24 +251,7 @@ export interface LoadBalancerFilter { /** * Query input for looking up a load balancer */ -export interface LoadBalancerContextQuery extends LoadBalancerFilter { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; -} +export interface LoadBalancerContextQuery extends LoadBalancerFilter {} /** * The protocol for connections from clients to the load balancer @@ -371,23 +292,6 @@ export enum LoadBalancerListenerProtocol { * Query input for looking up a load balancer listener */ export interface LoadBalancerListenerContextQuery extends LoadBalancerFilter { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; - /** * Find by listener's arn * @default - does not find by listener arn @@ -410,24 +314,7 @@ export interface LoadBalancerListenerContextQuery extends LoadBalancerFilter { /** * Query input for looking up a security group */ -export interface SecurityGroupContextQuery { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; - +export interface SecurityGroupContextQuery extends ContextLookupRoleOptions { /** * Security group id * @@ -453,24 +340,7 @@ export interface SecurityGroupContextQuery { /** * Query input for looking up a KMS Key */ -export interface KeyContextQuery { - /** - * Query account - */ - readonly account: string; - - /** - * Query region - */ - readonly region: string; - - /** - * The ARN of the role that should be used to look up the missing values - * - * @default - None - */ - readonly lookupRoleArn?: string; - +export interface KeyContextQuery extends ContextLookupRoleOptions { /** * Alias name used to search the Key */ diff --git a/lib/manifest.ts b/lib/manifest.ts index f129ca3..562d4ee 100644 --- a/lib/manifest.ts +++ b/lib/manifest.ts @@ -147,11 +147,7 @@ export class Manifest { return this.loadAssemblyManifest(filePath); } - private static validate( - manifest: { version: string }, - schema: jsonschema.Schema, - options?: LoadManifestOptions - ) { + private static validate(manifest: any, schema: jsonschema.Schema, options?: LoadManifestOptions) { function parseVersion(version: string) { const ver = semver.valid(version); if (!ver) { @@ -179,7 +175,8 @@ export class Manifest { nestedErrors: true, allowUnknownAttributes: false, - } as any); + preValidateProperty: Manifest.validateAssumeRoleAdditionalOptions, + }); let errors = result.errors; if (options?.skipEnumCheck) { @@ -250,6 +247,34 @@ export class Manifest { ); } + /** + * Validates that `assumeRoleAdditionalOptions` doesn't contain nor `ExternalId` neither `RoleArn`, as they + * should have dedicated properties preceding this (e.g `assumeRoleArn` and `assumeRoleExternalId`). + */ + private static validateAssumeRoleAdditionalOptions( + instance: any, + key: string, + _schema: jsonschema.Schema, + _options: jsonschema.Options, + _ctx: jsonschema.SchemaContext + ) { + if (key !== 'assumeRoleAdditionalOptions') { + // note that this means that if we happen to have a property named like this, but that + // does want to allow 'RoleArn' or 'ExternalId', this code will have to change to consider the full schema path. + // I decided to make this less granular for now on purpose because it fits our needs and avoids having messy + // validation logic due to various schema paths. + return; + } + + const assumeRoleOptions = instance[key]; + if (assumeRoleOptions?.RoleArn) { + throw new Error(`RoleArn is not allowed inside '${key}'`); + } + if (assumeRoleOptions?.ExternalId) { + throw new Error(`ExternalId is not allowed inside '${key}'`); + } + } + /** * See explanation on `patchStackTagsOnRead` * diff --git a/schema/assets.schema.json b/schema/assets.schema.json index 980fbaf..f6cabb2 100644 --- a/schema/assets.schema.json +++ b/schema/assets.schema.json @@ -97,6 +97,11 @@ "assumeRoleExternalId": { "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the role.\n\n- `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -241,6 +246,11 @@ "assumeRoleExternalId": { "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the role.\n\n- `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ diff --git a/schema/cloud-assembly.schema.json b/schema/cloud-assembly.schema.json index 279dfbe..3801042 100644 --- a/schema/cloud-assembly.schema.json +++ b/schema/cloud-assembly.schema.json @@ -362,6 +362,11 @@ "description": "External ID to use when assuming role for cloudformation deployments (Default - No external ID)", "type": "string" }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the role.\n\n- `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} + }, "cloudFormationExecutionRoleArn": { "description": "The role that is passed to CloudFormation to execute the change set (Default - No role is passed (currently assumed role/credentials are used))", "type": "string" @@ -403,6 +408,11 @@ "description": "External ID to use when assuming the bootstrap role (Default - No external ID)", "type": "string" }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the role.\n\n- `RoleArn` should not be used. Use the dedicated `arn` property instead.\n- `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} + }, "requiresBootstrapStackVersion": { "description": "Version of bootstrap stack required to use this role (Default - No bootstrap stack required)", "type": "number" @@ -548,18 +558,6 @@ "description": "Query to AMI context provider", "type": "object", "properties": { - "account": { - "description": "Account to query", - "type": "string" - }, - "region": { - "description": "Region to query", - "type": "string" - }, - "lookupRoleArn": { - "description": "The ARN of the role that should be used to look up the missing values (Default - None)", - "type": "string" - }, "owners": { "description": "Owners to DescribeImages call (Default - All owners)", "type": "array", @@ -576,6 +574,27 @@ "type": "string" } } + }, + "account": { + "description": "Query account", + "type": "string" + }, + "region": { + "description": "Query region", + "type": "string" + }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", + "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -599,6 +618,15 @@ "lookupRoleArn": { "description": "The ARN of the role that should be used to look up the missing values (Default - None)", "type": "string" + }, + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", + "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -610,6 +638,19 @@ "description": "Query to hosted zone context provider", "type": "object", "properties": { + "domainName": { + "description": "The domain name e.g. example.com to lookup", + "type": "string" + }, + "privateZone": { + "description": "True if the zone you want to find is a private hosted zone", + "default": false, + "type": "boolean" + }, + "vpcId": { + "description": "The VPC ID to that the private zone must be associated with\n\nIf you provide VPC ID and privateZone is false, this will return no results\nand raise an error. (Default - Required if privateZone=true)", + "type": "string" + }, "account": { "description": "Query account", "type": "string" @@ -622,18 +663,14 @@ "description": "The ARN of the role that should be used to look up the missing values (Default - None)", "type": "string" }, - "domainName": { - "description": "The domain name e.g. example.com to lookup", + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", "type": "string" }, - "privateZone": { - "description": "True if the zone you want to find is a private hosted zone", - "default": false, - "type": "boolean" - }, - "vpcId": { - "description": "The VPC ID to that the private zone must be associated with\n\nIf you provide VPC ID and privateZone is false, this will return no results\nand raise an error. (Default - Required if privateZone=true)", - "type": "string" + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -646,6 +683,10 @@ "description": "Query to SSM Parameter Context Provider", "type": "object", "properties": { + "parameterName": { + "description": "Parameter name to query", + "type": "string" + }, "account": { "description": "Query account", "type": "string" @@ -658,9 +699,14 @@ "description": "The ARN of the role that should be used to look up the missing values (Default - None)", "type": "string" }, - "parameterName": { - "description": "Parameter name to query", + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -673,18 +719,6 @@ "description": "Query input for looking up a VPC", "type": "object", "properties": { - "account": { - "description": "Query account", - "type": "string" - }, - "region": { - "description": "Query region", - "type": "string" - }, - "lookupRoleArn": { - "description": "The ARN of the role that should be used to look up the missing values (Default - None)", - "type": "string" - }, "filter": { "description": "Filters to apply to the VPC\n\nFilter parameters are the same as passed to DescribeVpcs.", "type": "object", @@ -704,6 +738,27 @@ "returnVpnGateways": { "description": "Whether to populate the `vpnGatewayId` field of the `VpcContextResponse`,\nwhich contains the VPN Gateway ID, if one exists. You can explicitly\ndisable this in order to avoid the lookup if you know the VPC does not have\na VPN Gatway attached. (Default true)", "type": "boolean" + }, + "account": { + "description": "Query account", + "type": "string" + }, + "region": { + "description": "Query region", + "type": "string" + }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", + "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -716,6 +771,10 @@ "description": "Query to endpoint service context provider", "type": "object", "properties": { + "serviceName": { + "description": "Query service name", + "type": "string" + }, "account": { "description": "Query account", "type": "string" @@ -728,9 +787,14 @@ "description": "The ARN of the role that should be used to look up the missing values (Default - None)", "type": "string" }, - "serviceName": { - "description": "Query service name", + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -743,18 +807,6 @@ "description": "Query input for looking up a load balancer", "type": "object", "properties": { - "account": { - "description": "Query account", - "type": "string" - }, - "region": { - "description": "Query region", - "type": "string" - }, - "lookupRoleArn": { - "description": "The ARN of the role that should be used to look up the missing values (Default - None)", - "type": "string" - }, "loadBalancerType": { "$ref": "#/definitions/LoadBalancerType", "description": "Filter load balancers by their type" @@ -769,6 +821,27 @@ "items": { "$ref": "#/definitions/Tag" } + }, + "account": { + "description": "Query account", + "type": "string" + }, + "region": { + "description": "Query region", + "type": "string" + }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", + "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -789,18 +862,6 @@ "description": "Query input for looking up a load balancer listener", "type": "object", "properties": { - "account": { - "description": "Query account", - "type": "string" - }, - "region": { - "description": "Query region", - "type": "string" - }, - "lookupRoleArn": { - "description": "The ARN of the role that should be used to look up the missing values (Default - None)", - "type": "string" - }, "listenerArn": { "description": "Find by listener's arn (Default - does not find by listener arn)", "type": "string" @@ -835,6 +896,27 @@ "items": { "$ref": "#/definitions/Tag" } + }, + "account": { + "description": "Query account", + "type": "string" + }, + "region": { + "description": "Query region", + "type": "string" + }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", + "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -847,6 +929,18 @@ "description": "Query input for looking up a security group", "type": "object", "properties": { + "securityGroupId": { + "description": "Security group id (Default - None)", + "type": "string" + }, + "securityGroupName": { + "description": "Security group name (Default - None)", + "type": "string" + }, + "vpcId": { + "description": "VPC ID (Default - None)", + "type": "string" + }, "account": { "description": "Query account", "type": "string" @@ -859,17 +953,14 @@ "description": "The ARN of the role that should be used to look up the missing values (Default - None)", "type": "string" }, - "securityGroupId": { - "description": "Security group id (Default - None)", - "type": "string" - }, - "securityGroupName": { - "description": "Security group name (Default - None)", + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", "type": "string" }, - "vpcId": { - "description": "VPC ID (Default - None)", - "type": "string" + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ @@ -881,6 +972,10 @@ "description": "Query input for looking up a KMS Key", "type": "object", "properties": { + "aliasName": { + "description": "Alias name used to search the Key", + "type": "string" + }, "account": { "description": "Query account", "type": "string" @@ -893,9 +988,14 @@ "description": "The ARN of the role that should be used to look up the missing values (Default - None)", "type": "string" }, - "aliasName": { - "description": "Alias name used to search the Key", + "lookupRoleExternalId": { + "description": "The ExternalId that needs to be supplied while assuming this role (Default - No ExternalId will be supplied)", "type": "string" + }, + "assumeRoleAdditionalOptions": { + "description": "Additional options to pass to STS when assuming the lookup role.\n\n- `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead.\n- `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. (Default - No additional options.)", + "type": "object", + "additionalProperties": {} } }, "required": [ diff --git a/test/assets.test.ts b/test/assets.test.ts index 24ddd46..03cf28f 100644 --- a/test/assets.test.ts +++ b/test/assets.test.ts @@ -181,6 +181,56 @@ describe('File asset', () => { }); }); +test('assumeRoleAdditionalOptions.RoleArn is validated', () => { + expect(() => { + validate({ + version: Manifest.version(), + files: { + asset: { + source: { + path: 'a/b/c', + }, + destinations: { + dest: { + region: 'us-north-20', + bucketName: 'Bouquet', + objectKey: 'key', + assumeRoleAdditionalOptions: { + RoleArn: 'foo', + }, + }, + }, + }, + }, + }); + }).toThrow(`RoleArn is not allowed inside 'assumeRoleAdditionalOptions'`); +}); + +test('assumeRoleAdditionalOptions.ExternalId is validated', () => { + expect(() => { + validate({ + version: Manifest.version(), + files: { + asset: { + source: { + path: 'a/b/c', + }, + destinations: { + dest: { + region: 'us-north-20', + bucketName: 'Bouquet', + objectKey: 'key', + assumeRoleAdditionalOptions: { + ExternalId: 'foo', + }, + }, + }, + }, + }, + }); + }).toThrow(`ExternalId is not allowed inside 'assumeRoleAdditionalOptions'`); +}); + function validate(manifest: any) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'assets.test.')); const filePath = path.join(dir, 'manifest.json'); diff --git a/test/manifest.test.ts b/test/manifest.test.ts index 83842c2..b443951 100644 --- a/test/manifest.test.ts +++ b/test/manifest.test.ts @@ -2,7 +2,13 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as semver from 'semver'; -import { AssemblyManifest, Manifest, StackTagsMetadataEntry } from '../lib'; +import { + ArtifactType, + AssemblyManifest, + ContextProvider, + Manifest, + StackTagsMetadataEntry, +} from '../lib'; const FIXTURES = path.join(__dirname, 'fixtures'); @@ -31,6 +37,102 @@ test('manifest save', () => { }); }); +test('assumeRoleAdditionalOptions.RoleArn is validated in stack artifact', () => { + expect(() => { + Manifest.saveAssemblyManifest( + { + version: 'version', + artifacts: { + 'aws-cdk-sqs': { + type: ArtifactType.AWS_CLOUDFORMATION_STACK, + properties: { + directoryName: 'dir', + file: 'file', + templateFile: 'template', + assumeRoleAdditionalOptions: { + RoleArn: 'foo', + }, + }, + }, + }, + }, + 'somewhere' + ); + }).toThrow(`RoleArn is not allowed inside 'assumeRoleAdditionalOptions'`); +}); + +test('assumeRoleAdditionalOptions.ExternalId is validated in stack artifact', () => { + expect(() => { + Manifest.saveAssemblyManifest( + { + version: 'version', + artifacts: { + 'aws-cdk-sqs': { + type: ArtifactType.AWS_CLOUDFORMATION_STACK, + properties: { + directoryName: 'dir', + file: 'file', + templateFile: 'template', + assumeRoleAdditionalOptions: { + ExternalId: 'external-id', + }, + }, + }, + }, + }, + 'somewhere' + ); + }).toThrow(`ExternalId is not allowed inside 'assumeRoleAdditionalOptions'`); +}); + +test('assumeRoleAdditionalOptions.RoleArn is validated in missing context', () => { + expect(() => { + Manifest.saveAssemblyManifest( + { + version: 'version', + missing: [ + { + key: 'key', + provider: ContextProvider.AMI_PROVIDER, + props: { + account: '123456789012', + region: 'us-east-1', + assumeRoleAdditionalOptions: { + RoleArn: 'role', + }, + }, + }, + ], + }, + 'somewhere' + ); + }).toThrow(`RoleArn is not allowed inside 'assumeRoleAdditionalOptions'`); +}); + +test('assumeRoleAdditionalOptions.ExternalId is validated in missing context', () => { + expect(() => { + Manifest.saveAssemblyManifest( + { + version: 'version', + missing: [ + { + key: 'key', + provider: ContextProvider.AMI_PROVIDER, + props: { + account: '123456789012', + region: 'us-east-1', + assumeRoleAdditionalOptions: { + ExternalId: 'external-id', + }, + }, + }, + ], + }, + 'somewhere' + ); + }).toThrow(`ExternalId is not allowed inside 'assumeRoleAdditionalOptions'`); +}); + test('manifest load', () => { const loaded = Manifest.loadAssemblyManifest(fixture('only-version')); expect(loaded).toMatchSnapshot();