Skip to content

Commit

Permalink
refactor: Validate AWS Metadata URLs (#1486)
Browse files Browse the repository at this point in the history
Notes:
- Validates a URL's `hostname` rather than`host`
- Added test for URL with port
- Uses static variables for AWS metadata IP Addresses
- `RangeError` rather than `Error`
- Separated validators from setters in `constructor`
  • Loading branch information
danielbankhead committed Nov 8, 2022
1 parent 8706abc commit 515441f
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 40 deletions.
42 changes: 26 additions & 16 deletions src/auth/awsclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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');
Expand All @@ -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}.`
);
}
}

Expand Down
55 changes: 31 additions & 24 deletions test/test.awsclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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'},
Expand All @@ -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';
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand Down

0 comments on commit 515441f

Please sign in to comment.