Skip to content

Commit 536d6dd

Browse files
remove aws temporary credential code from aws auth provider
1 parent 8b91c30 commit 536d6dd

File tree

2 files changed

+166
-127
lines changed

2 files changed

+166
-127
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { type AWSCredentials, getAwsCredentialProvider } from '../../deps';
2+
import { MongoAWSError } from '../../error';
3+
import { request } from '../../utils';
4+
5+
const AWS_RELATIVE_URI = 'http://169.254.170.2';
6+
const AWS_EC2_URI = 'http://169.254.169.254';
7+
const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials';
8+
9+
/** @internal */
10+
export interface AWSTempCredentials {
11+
AccessKeyId?: string;
12+
SecretAccessKey?: string;
13+
SessionToken?: string;
14+
RoleArn?: string;
15+
Expiration?: Date;
16+
}
17+
18+
/** @internal */
19+
export abstract class AWSTemporaryCredentialProvider {
20+
abstract getCredentials(): Promise<AWSTempCredentials>;
21+
static credentialProvider: ReturnType<typeof getAwsCredentialProvider>;
22+
static isAWSSDKInstalled: boolean;
23+
static {
24+
this.credentialProvider = getAwsCredentialProvider();
25+
this.isAWSSDKInstalled = 'kModuleError' in this.credentialProvider;
26+
}
27+
}
28+
29+
/** @internal */
30+
export class AWSSDKCredentialProvider extends AWSTemporaryCredentialProvider {
31+
private _provider?: () => Promise<AWSCredentials>;
32+
private get provider(): () => Promise<AWSCredentials> {
33+
if ('kModuleError' in AWSTemporaryCredentialProvider.credentialProvider) {
34+
throw AWSTemporaryCredentialProvider.credentialProvider.kModuleError;
35+
}
36+
if (this._provider) {
37+
return this._provider;
38+
}
39+
let { AWS_STS_REGIONAL_ENDPOINTS = '', AWS_REGION = '' } = process.env;
40+
AWS_STS_REGIONAL_ENDPOINTS = AWS_STS_REGIONAL_ENDPOINTS.toLowerCase();
41+
AWS_REGION = AWS_REGION.toLowerCase();
42+
43+
/** The option setting should work only for users who have explicit settings in their environment, the driver should not encode "defaults" */
44+
const awsRegionSettingsExist =
45+
AWS_REGION.length !== 0 && AWS_STS_REGIONAL_ENDPOINTS.length !== 0;
46+
47+
/**
48+
* The following regions use the global AWS STS endpoint, sts.amazonaws.com, by default
49+
* https://docs.aws.amazon.com/sdkref/latest/guide/feature-sts-regionalized-endpoints.html
50+
*/
51+
const LEGACY_REGIONS = new Set([
52+
'ap-northeast-1',
53+
'ap-south-1',
54+
'ap-southeast-1',
55+
'ap-southeast-2',
56+
'aws-global',
57+
'ca-central-1',
58+
'eu-central-1',
59+
'eu-north-1',
60+
'eu-west-1',
61+
'eu-west-2',
62+
'eu-west-3',
63+
'sa-east-1',
64+
'us-east-1',
65+
'us-east-2',
66+
'us-west-1',
67+
'us-west-2'
68+
]);
69+
/**
70+
* If AWS_STS_REGIONAL_ENDPOINTS is set to regional, users are opting into the new behavior of respecting the region settings
71+
*
72+
* If AWS_STS_REGIONAL_ENDPOINTS is set to legacy, then "old" regions need to keep using the global setting.
73+
* Technically the SDK gets this wrong, it reaches out to 'sts.us-east-1.amazonaws.com' when it should be 'sts.amazonaws.com'.
74+
* That is not our bug to fix here. We leave that up to the SDK.
75+
*/
76+
const useRegionalSts =
77+
AWS_STS_REGIONAL_ENDPOINTS === 'regional' ||
78+
(AWS_STS_REGIONAL_ENDPOINTS === 'legacy' && !LEGACY_REGIONS.has(AWS_REGION));
79+
80+
this._provider ??=
81+
awsRegionSettingsExist && useRegionalSts
82+
? AWSTemporaryCredentialProvider.credentialProvider.fromNodeProviderChain({
83+
clientConfig: { region: AWS_REGION }
84+
})
85+
: AWSTemporaryCredentialProvider.credentialProvider.fromNodeProviderChain();
86+
87+
return this._provider;
88+
}
89+
90+
override async getCredentials(): Promise<AWSTempCredentials> {
91+
/*
92+
* Creates a credential provider that will attempt to find credentials from the
93+
* following sources (listed in order of precedence):
94+
*
95+
* - Environment variables exposed via process.env
96+
* - SSO credentials from token cache
97+
* - Web identity token credentials
98+
* - Shared credentials and config ini files
99+
* - The EC2/ECS Instance Metadata Service
100+
*/
101+
try {
102+
const creds = await this.provider();
103+
return {
104+
AccessKeyId: creds.accessKeyId,
105+
SecretAccessKey: creds.secretAccessKey,
106+
SessionToken: creds.sessionToken,
107+
Expiration: creds.expiration
108+
};
109+
} catch (error) {
110+
throw new MongoAWSError(error.message);
111+
}
112+
}
113+
}
114+
115+
/** @internal */
116+
export class LegacyAWSTemporaryCredentialProvider extends AWSTemporaryCredentialProvider {
117+
override async getCredentials(): Promise<AWSTempCredentials> {
118+
// If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
119+
// is set then drivers MUST assume that it was set by an AWS ECS agent
120+
if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
121+
return request(`${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`);
122+
}
123+
124+
// Otherwise assume we are on an EC2 instance
125+
126+
// get a token
127+
const token = await request(`${AWS_EC2_URI}/latest/api/token`, {
128+
method: 'PUT',
129+
json: false,
130+
headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 }
131+
});
132+
133+
// get role name
134+
const roleName = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}`, {
135+
json: false,
136+
headers: { 'X-aws-ec2-metadata-token': token }
137+
});
138+
139+
// get temp credentials
140+
const creds = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`, {
141+
headers: { 'X-aws-ec2-metadata-token': token }
142+
});
143+
144+
return creds;
145+
}
146+
}

src/cmap/auth/mongodb_aws.ts

Lines changed: 20 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,23 @@
1-
import * as process from 'process';
2-
31
import type { Binary, BSONSerializeOptions } from '../../bson';
42
import * as BSON from '../../bson';
5-
import { aws4, type AWSCredentials, getAwsCredentialProvider } from '../../deps';
3+
import { aws4 } from '../../deps';
64
import {
7-
MongoAWSError,
85
MongoCompatibilityError,
96
MongoMissingCredentialsError,
107
MongoRuntimeError
118
} from '../../error';
12-
import { ByteUtils, maxWireVersion, ns, randomBytes, request } from '../../utils';
9+
import { ByteUtils, maxWireVersion, ns, randomBytes } from '../../utils';
1310
import { type AuthContext, AuthProvider } from './auth_provider';
11+
import {
12+
AWSSDKCredentialProvider,
13+
type AWSTempCredentials,
14+
AWSTemporaryCredentialProvider,
15+
LegacyAWSTemporaryCredentialProvider
16+
} from './aws_temporary_credentials';
1417
import { MongoCredentials } from './mongo_credentials';
1518
import { AuthMechanism } from './providers';
1619

17-
/**
18-
* The following regions use the global AWS STS endpoint, sts.amazonaws.com, by default
19-
* https://docs.aws.amazon.com/sdkref/latest/guide/feature-sts-regionalized-endpoints.html
20-
*/
21-
const LEGACY_REGIONS = new Set([
22-
'ap-northeast-1',
23-
'ap-south-1',
24-
'ap-southeast-1',
25-
'ap-southeast-2',
26-
'aws-global',
27-
'ca-central-1',
28-
'eu-central-1',
29-
'eu-north-1',
30-
'eu-west-1',
31-
'eu-west-2',
32-
'eu-west-3',
33-
'sa-east-1',
34-
'us-east-1',
35-
'us-east-2',
36-
'us-west-1',
37-
'us-west-2'
38-
]);
3920
const ASCII_N = 110;
40-
const AWS_RELATIVE_URI = 'http://169.254.170.2';
41-
const AWS_EC2_URI = 'http://169.254.169.254';
42-
const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials';
4321
const bsonOptions: BSONSerializeOptions = {
4422
useBigInt64: false,
4523
promoteLongs: true,
@@ -55,40 +33,13 @@ interface AWSSaslContinuePayload {
5533
}
5634

5735
export class MongoDBAWS extends AuthProvider {
58-
static credentialProvider: ReturnType<typeof getAwsCredentialProvider>;
59-
provider?: () => Promise<AWSCredentials>;
60-
36+
private credentialFetcher: AWSTemporaryCredentialProvider;
6137
constructor() {
6238
super();
63-
MongoDBAWS.credentialProvider ??= getAwsCredentialProvider();
64-
65-
let { AWS_STS_REGIONAL_ENDPOINTS = '', AWS_REGION = '' } = process.env;
66-
AWS_STS_REGIONAL_ENDPOINTS = AWS_STS_REGIONAL_ENDPOINTS.toLowerCase();
67-
AWS_REGION = AWS_REGION.toLowerCase();
68-
69-
/** The option setting should work only for users who have explicit settings in their environment, the driver should not encode "defaults" */
70-
const awsRegionSettingsExist =
71-
AWS_REGION.length !== 0 && AWS_STS_REGIONAL_ENDPOINTS.length !== 0;
72-
73-
/**
74-
* If AWS_STS_REGIONAL_ENDPOINTS is set to regional, users are opting into the new behavior of respecting the region settings
75-
*
76-
* If AWS_STS_REGIONAL_ENDPOINTS is set to legacy, then "old" regions need to keep using the global setting.
77-
* Technically the SDK gets this wrong, it reaches out to 'sts.us-east-1.amazonaws.com' when it should be 'sts.amazonaws.com'.
78-
* That is not our bug to fix here. We leave that up to the SDK.
79-
*/
80-
const useRegionalSts =
81-
AWS_STS_REGIONAL_ENDPOINTS === 'regional' ||
82-
(AWS_STS_REGIONAL_ENDPOINTS === 'legacy' && !LEGACY_REGIONS.has(AWS_REGION));
8339

84-
if ('fromNodeProviderChain' in MongoDBAWS.credentialProvider) {
85-
this.provider =
86-
awsRegionSettingsExist && useRegionalSts
87-
? MongoDBAWS.credentialProvider.fromNodeProviderChain({
88-
clientConfig: { region: AWS_REGION }
89-
})
90-
: MongoDBAWS.credentialProvider.fromNodeProviderChain();
91-
}
40+
this.credentialFetcher = AWSTemporaryCredentialProvider.isAWSSDKInstalled
41+
? new AWSSDKCredentialProvider()
42+
: new LegacyAWSTemporaryCredentialProvider();
9243
}
9344

9445
override async auth(authContext: AuthContext): Promise<void> {
@@ -109,7 +60,10 @@ export class MongoDBAWS extends AuthProvider {
10960
}
11061

11162
if (!authContext.credentials.username) {
112-
authContext.credentials = await makeTempCredentials(authContext.credentials, this.provider);
63+
authContext.credentials = await makeTempCredentials(
64+
authContext.credentials,
65+
this.credentialFetcher
66+
);
11367
}
11468

11569
const { credentials } = authContext;
@@ -202,17 +156,9 @@ export class MongoDBAWS extends AuthProvider {
202156
}
203157
}
204158

205-
interface AWSTempCredentials {
206-
AccessKeyId?: string;
207-
SecretAccessKey?: string;
208-
Token?: string;
209-
RoleArn?: string;
210-
Expiration?: Date;
211-
}
212-
213159
async function makeTempCredentials(
214160
credentials: MongoCredentials,
215-
provider?: () => Promise<AWSCredentials>
161+
awsCredentialFetcher: AWSTemporaryCredentialProvider
216162
): Promise<MongoCredentials> {
217163
function makeMongoCredentialsFromAWSTemp(creds: AWSTempCredentials) {
218164
// The AWS session token (creds.Token) may or may not be set.
@@ -226,66 +172,13 @@ async function makeTempCredentials(
226172
source: credentials.source,
227173
mechanism: AuthMechanism.MONGODB_AWS,
228174
mechanismProperties: {
229-
AWS_SESSION_TOKEN: creds.Token
175+
AWS_SESSION_TOKEN: creds.SessionToken
230176
}
231177
});
232178
}
179+
const temporaryCredentials = await awsCredentialFetcher.getCredentials();
233180

234-
// Check if the AWS credential provider from the SDK is present. If not,
235-
// use the old method.
236-
if (provider && !('kModuleError' in MongoDBAWS.credentialProvider)) {
237-
/*
238-
* Creates a credential provider that will attempt to find credentials from the
239-
* following sources (listed in order of precedence):
240-
*
241-
* - Environment variables exposed via process.env
242-
* - SSO credentials from token cache
243-
* - Web identity token credentials
244-
* - Shared credentials and config ini files
245-
* - The EC2/ECS Instance Metadata Service
246-
*/
247-
try {
248-
const creds = await provider();
249-
return makeMongoCredentialsFromAWSTemp({
250-
AccessKeyId: creds.accessKeyId,
251-
SecretAccessKey: creds.secretAccessKey,
252-
Token: creds.sessionToken,
253-
Expiration: creds.expiration
254-
});
255-
} catch (error) {
256-
throw new MongoAWSError(error.message);
257-
}
258-
} else {
259-
// If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
260-
// is set then drivers MUST assume that it was set by an AWS ECS agent
261-
if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
262-
return makeMongoCredentialsFromAWSTemp(
263-
await request(`${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`)
264-
);
265-
}
266-
267-
// Otherwise assume we are on an EC2 instance
268-
269-
// get a token
270-
const token = await request(`${AWS_EC2_URI}/latest/api/token`, {
271-
method: 'PUT',
272-
json: false,
273-
headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 }
274-
});
275-
276-
// get role name
277-
const roleName = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}`, {
278-
json: false,
279-
headers: { 'X-aws-ec2-metadata-token': token }
280-
});
281-
282-
// get temp credentials
283-
const creds = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`, {
284-
headers: { 'X-aws-ec2-metadata-token': token }
285-
});
286-
287-
return makeMongoCredentialsFromAWSTemp(creds);
288-
}
181+
return makeMongoCredentialsFromAWSTemp(temporaryCredentials);
289182
}
290183

291184
function deriveRegion(host: string) {

0 commit comments

Comments
 (0)