Skip to content

Commit ed30a36

Browse files
chore: add files
1 parent 6fd568b commit ed30a36

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { expect } from 'chai';
2+
import { get } from 'http';
3+
4+
import { Document } from '../../mongodb';
5+
6+
const BASE_URL = new URL(`http://127.0.0.1:8080/metadata/identity/oauth2/token`);
7+
8+
async function mockServerIsSetup() {
9+
const url = (() => {
10+
const copiedURL = new URL(BASE_URL);
11+
12+
// minimum configuration for the mock server not to throw an error when responding.
13+
copiedURL.searchParams.append('api-version', '2018-02-01');
14+
copiedURL.searchParams.append('resource', 'https://vault.azure.net');
15+
return copiedURL;
16+
})();
17+
return new Promise<void>((resolve, reject) => {
18+
get(url, res => {
19+
if (res.statusCode === 200) {
20+
return resolve();
21+
}
22+
return reject('server not running');
23+
})
24+
.on('error', error => reject(error))
25+
.end();
26+
});
27+
}
28+
29+
class KMSRequestOptions {
30+
url: URL = BASE_URL;
31+
headers: Document;
32+
constructor(testCase?: 'empty-json' | 'bad-json' | '404' | '500' | 'slow') {
33+
if (testCase) {
34+
this.headers = {
35+
'X-MongoDB-HTTP-TestParams': `case=${testCase}`
36+
};
37+
}
38+
}
39+
}
40+
41+
context.only('it works', function () {
42+
let fetchAzureKMSToken: (options: {
43+
url: URL;
44+
headers: Document;
45+
}) => Promise<{ accessToken: string }>;
46+
let KMSRequestFailedError: Error;
47+
48+
const AZURE_PROSE_TESTING_SYMBOL = Symbol.for('@@mdb.azureKMSRefreshProseTest');
49+
beforeEach(async function () {
50+
try {
51+
await mockServerIsSetup();
52+
} catch {
53+
this.currentTest.skipReason = 'Test requires mock azure identity endpoint to be running.';
54+
this.test?.skip();
55+
}
56+
57+
fetchAzureKMSToken = this.configuration.mongodbClientEncryption[AZURE_PROSE_TESTING_SYMBOL];
58+
KMSRequestFailedError =
59+
this.configuration.mongodbClientEncryption.MongoCryptAzureKMSRequestError;
60+
});
61+
context('Case 1: Success', function () {
62+
// Do not set an ``X-MongoDB-HTTP-TestParams`` header.
63+
64+
// Upon receiving a response from ``fake_azure``, the driver must decode the
65+
// following information:
66+
67+
// 1. HTTP status will be ``200 Okay``.
68+
// 2. The HTTP body will be a valid JSON string.
69+
// 3. The access token will be the string ``"magic-cookie"``.
70+
// 4. The expiry duration of the token will be seventy seconds.
71+
// 5. The token will have a resource of ``"https://vault.azure.net"``
72+
73+
it('returns a properly formatted access token', async () => {
74+
const credentials = await fetchAzureKMSToken(new KMSRequestOptions());
75+
expect(credentials).to.have.property('accessToken', 'magic-cookie');
76+
});
77+
});
78+
context('Case 2: Empty JSON', function () {
79+
// This case addresses a server returning valid JSON with invalid content.
80+
// Set ``X-MongoDB-HTTP-TestParams`` to ``case=empty-json``.
81+
// Upon receiving a response:
82+
// 1. HTTP status will be ``200 Okay``
83+
// 2. The HTTP body will be a valid JSON string.
84+
// 3. There will be no access token, expiry duration, or resource.
85+
// The test case should ensure that this error condition is handled gracefully.
86+
87+
it('returns an error', async () => {
88+
const credentials = await fetchAzureKMSToken(new KMSRequestOptions('empty-json')).catch(
89+
e => e
90+
);
91+
92+
expect(credentials).to.be.instanceof(KMSRequestFailedError);
93+
});
94+
});
95+
context('Case 3: Bad JSON', function () {
96+
// This case addresses a server returning malformed JSON.
97+
// Set ``X-MongoDB-HTTP-TestParams`` to ``case=bad-json``.
98+
// Upon receiving a response:
99+
// 1. HTTP status will be ``200 Okay``
100+
// 2. The response body will contain a malformed JSON string.
101+
// The test case should ensure that this error condition is handled gracefully.
102+
103+
it('returns an error', async () => {
104+
const credentials = await fetchAzureKMSToken(new KMSRequestOptions('bad-json')).catch(e => e);
105+
106+
expect(credentials).to.be.instanceof(KMSRequestFailedError);
107+
});
108+
});
109+
context('Case 4: HTTP 404', function () {
110+
// This case addresses a server returning a "Not Found" response. This is
111+
// documented to occur spuriously within an Azure environment.
112+
// Set ``X-MongoDB-HTTP-TestParams`` to ``case=404``.
113+
// Upon receiving a response:
114+
// 1. HTTP status will be ``404 Not Found``.
115+
// 2. The response body is unspecified.
116+
// The test case should ensure that this error condition is handled gracefully.
117+
it('returns an error', async () => {
118+
const credentials = await fetchAzureKMSToken(new KMSRequestOptions('404')).catch(e => e);
119+
120+
expect(credentials).to.be.instanceof(KMSRequestFailedError);
121+
});
122+
});
123+
context('Case 5: HTTP 500', function () {
124+
// This case addresses an IMDS server reporting an internal error. This is
125+
// documented to occur spuriously within an Azure environment.
126+
// Set ``X-MongoDB-HTTP-TestParams`` to ``case=500``.
127+
// Upon receiving a response:
128+
// 1. HTTP status code will be ``500``.
129+
// 2. The response body is unspecified.
130+
// The test case should ensure that this error condition is handled gracefully.
131+
it('returns an error', async () => {
132+
const credentials = await fetchAzureKMSToken(new KMSRequestOptions('500')).catch(e => e);
133+
134+
expect(credentials).to.be.instanceof(KMSRequestFailedError);
135+
});
136+
});
137+
context('Case 6: Slow Response', function () {
138+
// This case addresses an IMDS server responding very slowly. Drivers should not
139+
// halt the application waiting on a peer to communicate.
140+
// Set ``X-MongoDB-HTTP-TestParams`` to ``case=slow``.
141+
// The HTTP response from the ``fake_azure`` server will take at least 1000 seconds
142+
// to complete. The request should fail with a timeout.
143+
it('returns an error after the request times out', async () => {
144+
const credentials = await fetchAzureKMSToken(new KMSRequestOptions('slow')).catch(e => e);
145+
146+
expect(credentials).to.be.instanceof(KMSRequestFailedError);
147+
});
148+
});
149+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { expect } from 'chai';
2+
import { env } from 'process';
3+
4+
import { Binary } from '../../mongodb';
5+
6+
const metadata: MongoDBMetadataUI = {
7+
requires: {
8+
clientSideEncryption: true
9+
}
10+
} as const;
11+
12+
const dataKeyOptions = {
13+
masterKey: {
14+
keyVaultEndpoint: 'https://keyvault-drivers-2411.vault.azure.net/keys/',
15+
keyName: 'KEY-NAME'
16+
}
17+
};
18+
19+
describe('19. On-demand GCP Credentials', () => {
20+
let clientEncryption: import('mongodb-client-encryption').ClientEncryption;
21+
let keyVaultClient;
22+
let MongoCryptAzureKMSRequestError;
23+
24+
beforeEach(async function () {
25+
keyVaultClient = this.configuration.newClient();
26+
27+
const { ClientEncryption } = this.configuration.mongodbClientEncryption;
28+
MongoCryptAzureKMSRequestError = this.configuration.mongodbClientEncryption;
29+
30+
clientEncryption = new ClientEncryption(keyVaultClient, {
31+
keyVaultClient,
32+
keyVaultNamespace: 'keyvault.datakeys',
33+
kmsProviders: { azure: {} }
34+
});
35+
});
36+
37+
afterEach(async () => {
38+
await keyVaultClient?.close();
39+
});
40+
41+
it('Case 1: Failure', metadata, async function () {
42+
if (env.EXPECTED_AZUREKMS_OUTCOME !== 'failure') {
43+
this.skipReason = 'This test is supposed to run in the environment where failure is expected';
44+
this.skip();
45+
}
46+
47+
const error = await clientEncryption
48+
.createDataKey('azure', dataKeyOptions)
49+
.catch(error => error);
50+
expect(error).to.be.instanceOf(MongoCryptAzureKMSRequestError);
51+
});
52+
53+
it('Case 2: Success', metadata, async function () {
54+
if (env.EXPECTED_AZUREKMS_OUTCOME !== 'success') {
55+
this.skipReason = 'This test is supposed to run in the environment where success is expected';
56+
this.skip();
57+
}
58+
59+
const dk = await clientEncryption.createDataKey('azure', dataKeyOptions);
60+
expect(dk).to.be.instanceOf(Binary);
61+
});
62+
});

0 commit comments

Comments
 (0)