Skip to content

Commit 78ec0dd

Browse files
authored
fix(NODE-3487): check for nullish aws mechanism property (#2951)
1 parent c9a962f commit 78ec0dd

File tree

7 files changed

+107
-49
lines changed

7 files changed

+107
-49
lines changed

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
"@istanbuljs/nyc-config-typescript": "^1.0.1",
4141
"@microsoft/api-extractor": "^7.18.6",
4242
"@microsoft/tsdoc-config": "^0.15.2",
43-
"@types/aws4": "^1.5.1",
4443
"@types/chai": "^4.2.14",
4544
"@types/chai-subset": "^1.3.3",
4645
"@types/kerberos": "^1.1.0",

src/cmap/auth/mongo_credentials.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,22 @@ function getDefaultAuthMechanism(ismaster?: Document): AuthMechanism {
2525
return AuthMechanism.MONGODB_CR;
2626
}
2727

28+
/** @public */
29+
export interface AuthMechanismProperties extends Document {
30+
SERVICE_NAME?: string;
31+
SERVICE_REALM?: string;
32+
CANONICALIZE_HOST_NAME?: boolean;
33+
AWS_SESSION_TOKEN?: string;
34+
}
35+
2836
/** @public */
2937
export interface MongoCredentialsOptions {
3038
username: string;
3139
password: string;
3240
source: string;
3341
db?: string;
3442
mechanism?: AuthMechanism;
35-
mechanismProperties: Document;
43+
mechanismProperties: AuthMechanismProperties;
3644
}
3745

3846
/**
@@ -49,7 +57,7 @@ export class MongoCredentials {
4957
/** The method used to authenticate */
5058
readonly mechanism: AuthMechanism;
5159
/** Special properties used by some types of auth mechanisms */
52-
readonly mechanismProperties: Document;
60+
readonly mechanismProperties: AuthMechanismProperties;
5361

5462
constructor(options: MongoCredentialsOptions) {
5563
this.username = options.username;
@@ -70,7 +78,10 @@ export class MongoCredentials {
7078
this.password = process.env.AWS_SECRET_ACCESS_KEY;
7179
}
7280

73-
if (!this.mechanismProperties.AWS_SESSION_TOKEN && process.env.AWS_SESSION_TOKEN) {
81+
if (
82+
this.mechanismProperties.AWS_SESSION_TOKEN == null &&
83+
process.env.AWS_SESSION_TOKEN != null
84+
) {
7485
this.mechanismProperties = {
7586
...this.mechanismProperties,
7687
AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN

src/cmap/auth/mongodb_aws.ts

+32-31
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { BSONSerializeOptions } from '../../bson';
1414

1515
import { aws4 } from '../../deps';
1616
import { AuthMechanism } from './defaultAuthProviders';
17+
import type { Binary } from 'bson';
1718

1819
const ASCII_N = 110;
1920
const AWS_RELATIVE_URI = 'http://169.254.170.2';
@@ -64,10 +65,19 @@ export class MongoDBAWS extends AuthProvider {
6465
return;
6566
}
6667

67-
const username = credentials.username;
68-
const password = credentials.password;
68+
const accessKeyId = credentials.username;
69+
const secretAccessKey = credentials.password;
70+
const sessionToken = credentials.mechanismProperties.AWS_SESSION_TOKEN;
71+
72+
// If all three defined, include sessionToken, else include username and pass, else no credentials
73+
const awsCredentials =
74+
accessKeyId && secretAccessKey && sessionToken
75+
? { accessKeyId, secretAccessKey, sessionToken }
76+
: accessKeyId && secretAccessKey
77+
? { accessKeyId, secretAccessKey }
78+
: undefined;
79+
6980
const db = credentials.source;
70-
const token = credentials.mechanismProperties.AWS_SESSION_TOKEN;
7181
crypto.randomBytes(32, (err, nonce) => {
7282
if (err) {
7383
callback(err);
@@ -83,7 +93,10 @@ export class MongoDBAWS extends AuthProvider {
8393
connection.command(ns(`${db}.$cmd`), saslStart, undefined, (err, res) => {
8494
if (err) return callback(err);
8595

86-
const serverResponse = BSON.deserialize(res.payload.buffer, bsonOptions);
96+
const serverResponse = BSON.deserialize(res.payload.buffer, bsonOptions) as {
97+
s: Binary;
98+
h: string;
99+
};
87100
const host = serverResponse.h;
88101
const serverNonce = serverResponse.s.buffer;
89102
if (serverNonce.length !== 64) {
@@ -123,18 +136,15 @@ export class MongoDBAWS extends AuthProvider {
123136
path: '/',
124137
body
125138
},
126-
{
127-
accessKeyId: username,
128-
secretAccessKey: password,
129-
token
130-
}
139+
awsCredentials
131140
);
132141

133-
const authorization = options.headers.Authorization;
134-
const date = options.headers['X-Amz-Date'];
135-
const payload: AWSSaslContinuePayload = { a: authorization, d: date };
136-
if (token) {
137-
payload.t = token;
142+
const payload: AWSSaslContinuePayload = {
143+
a: options.headers.Authorization,
144+
d: options.headers['X-Amz-Date']
145+
};
146+
if (sessionToken) {
147+
payload.t = sessionToken;
138148
}
139149

140150
const saslContinue = {
@@ -149,14 +159,16 @@ export class MongoDBAWS extends AuthProvider {
149159
}
150160
}
151161

152-
interface AWSCredentials {
162+
interface AWSTempCredentials {
153163
AccessKeyId?: string;
154164
SecretAccessKey?: string;
155165
Token?: string;
166+
RoleArn?: string;
167+
Expiration?: Date;
156168
}
157169

158170
function makeTempCredentials(credentials: MongoCredentials, callback: Callback<MongoCredentials>) {
159-
function done(creds: AWSCredentials) {
171+
function done(creds: AWSTempCredentials) {
160172
if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) {
161173
callback(
162174
new MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials')
@@ -183,6 +195,7 @@ function makeTempCredentials(credentials: MongoCredentials, callback: Callback<M
183195
if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
184196
request(
185197
`${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`,
198+
undefined,
186199
(err, res) => {
187200
if (err) return callback(err);
188201
done(res);
@@ -239,27 +252,15 @@ interface RequestOptions {
239252
headers?: http.OutgoingHttpHeaders;
240253
}
241254

242-
function request(uri: string, callback: Callback): void;
243-
function request(uri: string, options: RequestOptions, callback: Callback): void;
244-
function request(uri: string, _options: RequestOptions | Callback, _callback?: Callback) {
245-
let options = _options as RequestOptions;
246-
if ('function' === typeof _options) {
247-
options = {};
248-
}
249-
250-
let callback: Callback = _options as Callback;
251-
if (_callback) {
252-
callback = _callback;
253-
}
254-
255-
options = Object.assign(
255+
function request(uri: string, _options: RequestOptions | undefined, callback: Callback) {
256+
const options = Object.assign(
256257
{
257258
method: 'GET',
258259
timeout: 10000,
259260
json: true
260261
},
261262
url.parse(uri),
262-
options
263+
_options
263264
);
264265

265266
const req = http.request(options, res => {

src/deps.ts

+45-6
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,51 @@ try {
109109
saslprep = require('saslprep');
110110
} catch {} // eslint-disable-line
111111

112-
export let aws4: typeof import('aws4') | { kModuleError: MongoMissingDependencyError } =
113-
makeErrorModule(
114-
new MongoMissingDependencyError(
115-
'Optional module `aws4` not found. Please install it to enable AWS authentication'
116-
)
117-
);
112+
interface AWS4 {
113+
/**
114+
* Created these inline types to better assert future usage of this API
115+
* @param options - options for request
116+
* @param credentials - AWS credential details, sessionToken should be omitted entirely if its false-y
117+
*/
118+
sign(
119+
options: {
120+
path: '/';
121+
body: string;
122+
host: string;
123+
method: 'POST';
124+
headers: {
125+
'Content-Type': 'application/x-www-form-urlencoded';
126+
'Content-Length': number;
127+
'X-MongoDB-Server-Nonce': string;
128+
'X-MongoDB-GS2-CB-Flag': 'n';
129+
};
130+
service: string;
131+
region: string;
132+
},
133+
credentials:
134+
| {
135+
accessKeyId: string;
136+
secretAccessKey: string;
137+
sessionToken: string;
138+
}
139+
| {
140+
accessKeyId: string;
141+
secretAccessKey: string;
142+
}
143+
| undefined
144+
): {
145+
headers: {
146+
Authorization: string;
147+
'X-Amz-Date': string;
148+
};
149+
};
150+
}
151+
152+
export let aws4: AWS4 | { kModuleError: MongoMissingDependencyError } = makeErrorModule(
153+
new MongoMissingDependencyError(
154+
'Optional module `aws4` not found. Please install it to enable AWS authentication'
155+
)
156+
);
118157

119158
try {
120159
// Ensure you always wrap an optional require in the try block NODE-3199

src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,11 @@ export type {
160160
OperationTime,
161161
ResumeOptions
162162
} from './change_stream';
163-
export type { MongoCredentials, MongoCredentialsOptions } from './cmap/auth/mongo_credentials';
163+
export type {
164+
MongoCredentials,
165+
AuthMechanismProperties,
166+
MongoCredentialsOptions
167+
} from './cmap/auth/mongo_credentials';
164168
export type {
165169
WriteProtocolMessageType,
166170
Query,

src/mongo_client.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import type { AuthMechanism } from './cmap/auth/defaultAuthProviders';
2727
import type { Topology, TopologyEvents } from './sdam/topology';
2828
import type { ClientSession, ClientSessionOptions } from './sessions';
2929
import type { TagSet } from './sdam/server_description';
30-
import type { MongoCredentials } from './cmap/auth/mongo_credentials';
30+
import type { AuthMechanismProperties, MongoCredentials } from './cmap/auth/mongo_credentials';
3131
import { parseOptions } from './connection_string';
3232
import type { CompressorName } from './cmap/wire_protocol/compression';
3333
import type { TLSSocketOptions, ConnectionOptions as TLSConnectionOptions } from 'tls';
@@ -157,12 +157,7 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC
157157
/** Specify the authentication mechanism that MongoDB will use to authenticate the connection. */
158158
authMechanism?: AuthMechanism;
159159
/** Specify properties for the specified authMechanism as a comma-separated list of colon-separated key-value pairs. */
160-
authMechanismProperties?: {
161-
SERVICE_NAME?: string;
162-
CANONICALIZE_HOST_NAME?: boolean;
163-
SERVICE_REALM?: string;
164-
[key: string]: any;
165-
};
160+
authMechanismProperties?: AuthMechanismProperties;
166161
/** The size (in milliseconds) of the latency window for selecting among multiple suitable MongoDB instances. */
167162
localThresholdMS?: number;
168163
/** Specifies how long (in milliseconds) to block for server selection before throwing an exception. */

test/functional/mongodb_aws.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,13 @@ describe('MONGODB-AWS', function () {
4040
});
4141
});
4242
});
43+
44+
it('should allow empty string in authMechanismProperties.AWS_SESSION_TOKEN to override AWS_SESSION_TOKEN environment variable', function () {
45+
const client = this.configuration.newClient(this.configuration.url(), {
46+
authMechanismProperties: { AWS_SESSION_TOKEN: '' }
47+
});
48+
expect(client)
49+
.to.have.nested.property('options.credentials.mechanismProperties.AWS_SESSION_TOKEN')
50+
.that.equals('');
51+
});
4352
});

0 commit comments

Comments
 (0)