diff --git a/src/auth/awsclient.ts b/src/auth/awsclient.ts index fdbdc411..d6dae618 100644 --- a/src/auth/awsclient.ts +++ b/src/auth/awsclient.ts @@ -72,6 +72,9 @@ export class AwsClient extends BaseExternalAccountClient { private awsRequestSigner: AwsRequestSigner | null; private region: string; + static AWS_EC2_METADATA_IPV4_ADDRESS = '169.254.169.254'; + static AWS_EC2_METADATA_IPV6_ADDRESS = 'fd00:ec2::254'; + /** * Instantiates an AwsClient instance using the provided JSON * object loaded from an external account credentials file. @@ -95,7 +98,15 @@ export class AwsClient extends BaseExternalAccountClient { options.credential_source.regional_cred_verification_url; this.imdsV2SessionTokenUrl = options.credential_source.imdsv2_session_token_url; - this.validateMetadataServerUrls(); + this.awsRequestSigner = null; + this.region = ''; + + // data validators + this.validateEnvironmentId(); + this.validateMetadataServerURLs(); + } + + private validateEnvironmentId() { const match = this.environmentId?.match(/^(aws)(\d+)$/); if (!match || !this.regionalCredVerificationUrl) { throw new Error('No valid AWS "credential_source" provided'); @@ -104,29 +115,28 @@ export class AwsClient extends BaseExternalAccountClient { `aws version "${match[2]}" is not supported in the current build.` ); } - this.awsRequestSigner = null; - this.region = ''; } - private validateMetadataServerUrls() { - this.validateMetadataServerUrlIfAny(this.regionUrl, 'region_url'); - this.validateMetadataServerUrlIfAny(this.securityCredentialsUrl, 'url'); - this.validateMetadataServerUrlIfAny( + private validateMetadataServerURLs() { + this.validateMetadataURL(this.regionUrl, 'region_url'); + this.validateMetadataURL(this.securityCredentialsUrl, 'url'); + this.validateMetadataURL( this.imdsV2SessionTokenUrl, 'imdsv2_session_token_url' ); } - private validateMetadataServerUrlIfAny( - urlString: string | undefined, - nameOfData: string - ) { - if (urlString !== undefined) { - const url = new URL(urlString); + private validateMetadataURL(value?: string, prop?: string) { + if (!value) return; + const url = new URL(value); - if (url.host !== '169.254.169.254' && url.host !== '[fd00:ec2::254]') { - throw new Error(`Invalid host "${url.host}" for "${nameOfData}"`); - } + if ( + url.hostname !== AwsClient.AWS_EC2_METADATA_IPV4_ADDRESS && + url.hostname !== `[${AwsClient.AWS_EC2_METADATA_IPV6_ADDRESS}]` + ) { + throw new RangeError( + `Invalid host "${url.hostname}" for "${prop}". Expecting ${AwsClient.AWS_EC2_METADATA_IPV4_ADDRESS} or ${AwsClient.AWS_EC2_METADATA_IPV6_ADDRESS}.` + ); } } diff --git a/test/test.awsclient.ts b/test/test.awsclient.ts index f753930a..583d8329 100644 --- a/test/test.awsclient.ts +++ b/test/test.awsclient.ts @@ -197,14 +197,31 @@ describe('AwsClient', () => { credential_source: invalidCredentialSource, }; - assert.throws(() => { - return new AwsClient(invalidOptions); - }, expectedError); + assert.throws(() => new AwsClient(invalidOptions), expectedError); }); }); - it('should throw when an unsupported url is provided', () => { - const expectedError = new Error('Invalid host "baddomain.com" for "url"'); + it('should support credential_source with a port number', () => { + const validCredentialSource = {...awsCredentialSource}; + const validURLWithPort = new URL(validCredentialSource.url); + validURLWithPort.port = '8888'; + + validCredentialSource.url = validURLWithPort.href; + const validOptions = { + type: 'external_account', + audience, + subject_token_type: 'urn:ietf:params:aws:token-type:aws4_request', + token_url: getTokenUrl(), + credential_source: validCredentialSource, + }; + + assert.doesNotThrow(() => new AwsClient(validOptions)); + }); + + it('should throw when an unsupported credential_source is provided', () => { + const expectedError = new RangeError( + 'Invalid host "baddomain.com" for "url". Expecting 169.254.169.254 or fd00:ec2::254.' + ); const invalidCredentialSource = Object.assign({}, awsCredentialSource); invalidCredentialSource.url = 'http://baddomain.com/fake'; const invalidOptions = { @@ -215,14 +232,12 @@ describe('AwsClient', () => { credential_source: invalidCredentialSource, }; - assert.throws(() => { - return new AwsClient(invalidOptions); - }, expectedError); + assert.throws(() => new AwsClient(invalidOptions), expectedError); }); it('should throw when an unsupported imdsv2_session_token_url is provided', () => { - const expectedError = new Error( - 'Invalid host "baddomain.com" for "imdsv2_session_token_url"' + const expectedError = new RangeError( + 'Invalid host "baddomain.com" for "imdsv2_session_token_url". Expecting 169.254.169.254 or fd00:ec2::254.' ); const invalidCredentialSource = Object.assign( {imdsv2_session_token_url: 'http://baddomain.com/fake'}, @@ -236,14 +251,12 @@ describe('AwsClient', () => { credential_source: invalidCredentialSource, }; - assert.throws(() => { - return new AwsClient(invalidOptions); - }, expectedError); + assert.throws(() => new AwsClient(invalidOptions), expectedError); }); it('should throw when an unsupported region_url is provided', () => { - const expectedError = new Error( - 'Invalid host "baddomain.com" for "region_url"' + const expectedError = new RangeError( + 'Invalid host "baddomain.com" for "region_url". Expecting 169.254.169.254 or fd00:ec2::254.' ); const invalidCredentialSource = Object.assign({}, awsCredentialSource); invalidCredentialSource.region_url = 'http://baddomain.com/fake'; @@ -255,9 +268,7 @@ describe('AwsClient', () => { credential_source: invalidCredentialSource, }; - assert.throws(() => { - return new AwsClient(invalidOptions); - }, expectedError); + assert.throws(() => new AwsClient(invalidOptions), expectedError); }); it('should throw when an unsupported environment ID is provided', () => { @@ -274,9 +285,7 @@ describe('AwsClient', () => { credential_source: invalidCredentialSource, }; - assert.throws(() => { - return new AwsClient(invalidOptions); - }, expectedError); + assert.throws(() => new AwsClient(invalidOptions), expectedError); }); it('should throw when an unsupported environment version is provided', () => { @@ -293,9 +302,7 @@ describe('AwsClient', () => { credential_source: invalidCredentialSource, }; - assert.throws(() => { - return new AwsClient(invalidOptions); - }, expectedError); + assert.throws(() => new AwsClient(invalidOptions), expectedError); }); it('should not throw when valid AWS options are provided', () => {