Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Validate AWS Metadata URLs #1486

Merged
merged 1 commit into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not able to find any official aws documentatiton that says what ports are allowed. @lsirac any idea?

Copy link
Member Author

@danielbankhead danielbankhead Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see anything either; perhaps we can verify the hostname for now and if customers set a port we can allow it for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that works 👍


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