From 37bbf315b20477c4bb108fb7adf71c2f25166ea1 Mon Sep 17 00:00:00 2001 From: Sai Sunder Srinivasan Date: Tue, 1 Nov 2022 00:36:42 +0000 Subject: [PATCH 1/4] fix: Validate url domain for aws metadata urls --- src/auth/awsclient.ts | 16 ++++++++++++ test/test.awsclient.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/auth/awsclient.ts b/src/auth/awsclient.ts index 7a61fd17..41eb1bc7 100644 --- a/src/auth/awsclient.ts +++ b/src/auth/awsclient.ts @@ -88,13 +88,16 @@ export class AwsClient extends BaseExternalAccountClient { // This is only required if the AWS region is not available in the // AWS_REGION or AWS_DEFAULT_REGION environment variables. this.regionUrl = options.credential_source.region_url; + this.validateMetadataServerUrlIfAny(this.regionUrl, "region_url"); // This is only required if AWS security credentials are not available in // environment variables. this.securityCredentialsUrl = options.credential_source.url; + this.validateMetadataServerUrlIfAny(this.securityCredentialsUrl, "url"); this.regionalCredVerificationUrl = options.credential_source.regional_cred_verification_url; this.imdsV2SessionTokenUrl = options.credential_source.imdsv2_session_token_url; + this.validateMetadataServerUrlIfAny(this.imdsV2SessionTokenUrl, "imdsv2_session_token_url"); const match = this.environmentId?.match(/^(aws)(\d+)$/); if (!match || !this.regionalCredVerificationUrl) { throw new Error('No valid AWS "credential_source" provided'); @@ -107,6 +110,19 @@ export class AwsClient extends BaseExternalAccountClient { this.region = ''; } + validateMetadataServerUrlIfAny(urlString: string | undefined, nameOfData: string) + { + if (typeof urlString != 'undefined') + { + const url = new URL(urlString); + + if (url.host !== '169.254.169.254' && url.host !== 'fd00:ec2::254') + { + throw new Error(`Invalid host "${url.host}" for "${nameOfData}"`); + } + } + } + /** * Triggered when an external subject token is needed to be exchanged for a * GCP access token via GCP STS endpoint. diff --git a/test/test.awsclient.ts b/test/test.awsclient.ts index 496d6599..2cdf16d8 100644 --- a/test/test.awsclient.ts +++ b/test/test.awsclient.ts @@ -203,6 +203,64 @@ describe('AwsClient', () => { }); }); + it('should throw when an unsupported url is provided', () => { + const expectedError = new Error( + 'Invalid host "baddomain.com" for "url"' + ); + const invalidCredentialSource = Object.assign({}, awsCredentialSource); + invalidCredentialSource.url = 'http://baddomain.com/fake'; + const invalidOptions = { + type: 'external_account', + audience, + subject_token_type: 'urn:ietf:params:aws:token-type:aws4_request', + token_url: getTokenUrl(), + credential_source: invalidCredentialSource, + }; + + assert.throws(() => { + return 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 invalidCredentialSource = Object.assign( + {imdsv2_session_token_url: 'http://baddomain.com/fake'}, + awsCredentialSource); + const invalidOptions = { + type: 'external_account', + audience, + subject_token_type: 'urn:ietf:params:aws:token-type:aws4_request', + token_url: getTokenUrl(), + credential_source: invalidCredentialSource, + }; + + assert.throws(() => { + return 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 invalidCredentialSource = Object.assign({}, awsCredentialSource); + invalidCredentialSource.region_url = 'http://baddomain.com/fake'; + const invalidOptions = { + type: 'external_account', + audience, + subject_token_type: 'urn:ietf:params:aws:token-type:aws4_request', + token_url: getTokenUrl(), + credential_source: invalidCredentialSource, + }; + + assert.throws(() => { + return new AwsClient(invalidOptions); + }, expectedError); + }); + it('should throw when an unsupported environment ID is provided', () => { const expectedError = new Error( 'No valid AWS "credential_source" provided' From 8c143692e4afa62c940af4218d862b0c5acf6a89 Mon Sep 17 00:00:00 2001 From: Sai Sunder Srinivasan Date: Tue, 1 Nov 2022 03:36:48 +0000 Subject: [PATCH 2/4] lint --- src/auth/awsclient.ts | 21 ++++++++++++--------- test/test.awsclient.ts | 7 +++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/auth/awsclient.ts b/src/auth/awsclient.ts index 41eb1bc7..9b6bfa66 100644 --- a/src/auth/awsclient.ts +++ b/src/auth/awsclient.ts @@ -88,16 +88,19 @@ export class AwsClient extends BaseExternalAccountClient { // This is only required if the AWS region is not available in the // AWS_REGION or AWS_DEFAULT_REGION environment variables. this.regionUrl = options.credential_source.region_url; - this.validateMetadataServerUrlIfAny(this.regionUrl, "region_url"); + this.validateMetadataServerUrlIfAny(this.regionUrl, 'region_url'); // This is only required if AWS security credentials are not available in // environment variables. this.securityCredentialsUrl = options.credential_source.url; - this.validateMetadataServerUrlIfAny(this.securityCredentialsUrl, "url"); + this.validateMetadataServerUrlIfAny(this.securityCredentialsUrl, 'url'); this.regionalCredVerificationUrl = options.credential_source.regional_cred_verification_url; this.imdsV2SessionTokenUrl = options.credential_source.imdsv2_session_token_url; - this.validateMetadataServerUrlIfAny(this.imdsV2SessionTokenUrl, "imdsv2_session_token_url"); + this.validateMetadataServerUrlIfAny( + this.imdsV2SessionTokenUrl, + 'imdsv2_session_token_url' + ); const match = this.environmentId?.match(/^(aws)(\d+)$/); if (!match || !this.regionalCredVerificationUrl) { throw new Error('No valid AWS "credential_source" provided'); @@ -110,14 +113,14 @@ export class AwsClient extends BaseExternalAccountClient { this.region = ''; } - validateMetadataServerUrlIfAny(urlString: string | undefined, nameOfData: string) - { - if (typeof urlString != 'undefined') - { + validateMetadataServerUrlIfAny( + urlString: string | undefined, + nameOfData: string + ) { + if (typeof urlString !== 'undefined') { const url = new URL(urlString); - if (url.host !== '169.254.169.254' && url.host !== 'fd00:ec2::254') - { + if (url.host !== '169.254.169.254' && url.host !== 'fd00:ec2::254') { throw new Error(`Invalid host "${url.host}" for "${nameOfData}"`); } } diff --git a/test/test.awsclient.ts b/test/test.awsclient.ts index 2cdf16d8..5bff756c 100644 --- a/test/test.awsclient.ts +++ b/test/test.awsclient.ts @@ -204,9 +204,7 @@ describe('AwsClient', () => { }); it('should throw when an unsupported url is provided', () => { - const expectedError = new Error( - 'Invalid host "baddomain.com" for "url"' - ); + const expectedError = new Error('Invalid host "baddomain.com" for "url"'); const invalidCredentialSource = Object.assign({}, awsCredentialSource); invalidCredentialSource.url = 'http://baddomain.com/fake'; const invalidOptions = { @@ -228,7 +226,8 @@ describe('AwsClient', () => { ); const invalidCredentialSource = Object.assign( {imdsv2_session_token_url: 'http://baddomain.com/fake'}, - awsCredentialSource); + awsCredentialSource + ); const invalidOptions = { type: 'external_account', audience, From 242ff3226804f5fd5bec817605fcb5c06121b81b Mon Sep 17 00:00:00 2001 From: Sai Sunder Srinivasan Date: Wed, 2 Nov 2022 23:36:10 +0000 Subject: [PATCH 3/4] refactor and add test for ipv6 --- src/auth/awsclient.ts | 20 ++++++++++++-------- test/test.awsclient.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/auth/awsclient.ts b/src/auth/awsclient.ts index 9b6bfa66..68ac1d64 100644 --- a/src/auth/awsclient.ts +++ b/src/auth/awsclient.ts @@ -88,19 +88,14 @@ export class AwsClient extends BaseExternalAccountClient { // This is only required if the AWS region is not available in the // AWS_REGION or AWS_DEFAULT_REGION environment variables. this.regionUrl = options.credential_source.region_url; - this.validateMetadataServerUrlIfAny(this.regionUrl, 'region_url'); // This is only required if AWS security credentials are not available in // environment variables. this.securityCredentialsUrl = options.credential_source.url; - this.validateMetadataServerUrlIfAny(this.securityCredentialsUrl, 'url'); this.regionalCredVerificationUrl = options.credential_source.regional_cred_verification_url; this.imdsV2SessionTokenUrl = options.credential_source.imdsv2_session_token_url; - this.validateMetadataServerUrlIfAny( - this.imdsV2SessionTokenUrl, - 'imdsv2_session_token_url' - ); + this.validateMetadataServerUrls(); const match = this.environmentId?.match(/^(aws)(\d+)$/); if (!match || !this.regionalCredVerificationUrl) { throw new Error('No valid AWS "credential_source" provided'); @@ -113,14 +108,23 @@ export class AwsClient extends BaseExternalAccountClient { this.region = ''; } - validateMetadataServerUrlIfAny( + private validateMetadataServerUrls() { + this.validateMetadataServerUrlIfAny(this.regionUrl, 'region_url'); + this.validateMetadataServerUrlIfAny(this.securityCredentialsUrl, 'url'); + this.validateMetadataServerUrlIfAny( + this.imdsV2SessionTokenUrl, + 'imdsv2_session_token_url' + ); + } + + private validateMetadataServerUrlIfAny( urlString: string | undefined, nameOfData: string ) { if (typeof urlString !== 'undefined') { const url = new URL(urlString); - if (url.host !== '169.254.169.254' && url.host !== 'fd00:ec2::254') { + if (url.host !== '169.254.169.254' && url.host !== '[fd00:ec2::254]') { throw new Error(`Invalid host "${url.host}" for "${nameOfData}"`); } } diff --git a/test/test.awsclient.ts b/test/test.awsclient.ts index 5bff756c..f753930a 100644 --- a/test/test.awsclient.ts +++ b/test/test.awsclient.ts @@ -323,6 +323,39 @@ describe('AwsClient', () => { scope.done(); }); + it('should resolve on success with ipv6', async () => { + const ipv6baseUrl = 'http://[fd00:ec2::254]'; + const ipv6CredentialSource = { + environment_id: 'aws1', + region_url: `${ipv6baseUrl}/latest/meta-data/placement/availability-zone`, + url: `${ipv6baseUrl}/latest/meta-data/iam/security-credentials`, + regional_cred_verification_url: + 'https://sts.{region}.amazonaws.com?' + + 'Action=GetCallerIdentity&Version=2011-06-15', + }; + const ipv6Options = { + type: 'external_account', + audience, + subject_token_type: 'urn:ietf:params:aws:token-type:aws4_request', + token_url: getTokenUrl(), + credential_source: ipv6CredentialSource, + }; + + const scope = nock(ipv6baseUrl) + .get('/latest/meta-data/placement/availability-zone') + .reply(200, `${awsRegion}b`) + .get('/latest/meta-data/iam/security-credentials') + .reply(200, awsRole) + .get(`/latest/meta-data/iam/security-credentials/${awsRole}`) + .reply(200, awsSecurityCredentials); + + const client = new AwsClient(ipv6Options); + const subjectToken = await client.retrieveSubjectToken(); + + assert.deepEqual(subjectToken, expectedSubjectToken); + scope.done(); + }); + it('should resolve on success with imdsv2 session token', async () => { const scopes: nock.Scope[] = []; scopes.push( From 8042397c87f84bd09598f3f67633342c41f0d670 Mon Sep 17 00:00:00 2001 From: Sai Sunder Srinivasan Date: Thu, 3 Nov 2022 18:17:30 +0000 Subject: [PATCH 4/4] update undefined check --- src/auth/awsclient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/awsclient.ts b/src/auth/awsclient.ts index 68ac1d64..fdbdc411 100644 --- a/src/auth/awsclient.ts +++ b/src/auth/awsclient.ts @@ -121,7 +121,7 @@ export class AwsClient extends BaseExternalAccountClient { urlString: string | undefined, nameOfData: string ) { - if (typeof urlString !== 'undefined') { + if (urlString !== undefined) { const url = new URL(urlString); if (url.host !== '169.254.169.254' && url.host !== '[fd00:ec2::254]') {