Skip to content

Commit

Permalink
fix: Validate url domain for aws metadata urls (#1484)
Browse files Browse the repository at this point in the history
* fix: Validate url domain for aws metadata urls

* lint

* refactor and add test for ipv6

* update undefined check
  • Loading branch information
sai-sunder-s authored Nov 3, 2022
1 parent 89daaac commit 6dc4e58
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 0 deletions.
23 changes: 23 additions & 0 deletions src/auth/awsclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class AwsClient extends BaseExternalAccountClient {
options.credential_source.regional_cred_verification_url;
this.imdsV2SessionTokenUrl =
options.credential_source.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');
Expand All @@ -107,6 +108,28 @@ export class AwsClient extends BaseExternalAccountClient {
this.region = '';
}

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 (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.
Expand Down
90 changes: 90 additions & 0 deletions test/test.awsclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,63 @@ 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'
Expand Down Expand Up @@ -266,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(
Expand Down

0 comments on commit 6dc4e58

Please sign in to comment.