Skip to content

Commit 4977cde

Browse files
committed
Add Centralized Configuration Provider support (Oracle Database 23ai)
1 parent d3277b4 commit 4977cde

File tree

8 files changed

+733
-1
lines changed

8 files changed

+733
-1
lines changed

doc/src/release_notes.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ For deprecated and desupported features, see :ref:`Deprecations and desupported
1010
node-oracledb `v6.6.0 <https://github.com/oracle/node-oracledb/compare/v6.5.1...v6.6.0>`__ (TBD)
1111
---------------------------------------------------------------------------------------------------------
1212

13+
Common Changes
14+
++++++++++++++
15+
16+
#) Added support for Centralized Configuration Providers (Azure App Configuration Store and OCI
17+
Object Storage).
18+
Node-oracledb extracts configuration information from the the supported provider and uses it to
19+
connect to the database.
20+
1321
Thin Mode Changes
1422
+++++++++++++++++
1523

examples/azureProviderSample.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
3+
/******************************************************************************
4+
*
5+
* This software is dual-licensed to you under the Universal Permissive License
6+
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
7+
* 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
8+
* either license.
9+
*
10+
* If you elect to accept the software under the Apache License, Version 2.0,
11+
* the following applies:
12+
*
13+
* Licensed under the Apache License, Version 2.0 (the "License");
14+
* you may not use this file except in compliance with the License.
15+
* You may obtain a copy of the License at
16+
*
17+
* https://www.apache.org/licenses/LICENSE-2.0
18+
*
19+
* Unless required by applicable law or agreed to in writing, software
20+
* distributed under the License is distributed on an "AS IS" BASIS,
21+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
* See the License for the specific language governing permissions and
23+
* limitations under the License.
24+
*
25+
* NAME
26+
* azureProviderSample.js
27+
*
28+
* DESCRIPTION
29+
* Sample program to connect to the database using DB connectstring, username and password
30+
* which are fetched from the Azure Configuration Store using the connectstring which consists of
31+
* required parameters to authenticate azure configuration store.
32+
*
33+
*****************************************************************************/
34+
35+
'use strict';
36+
37+
Error.stackTraceLimit = 50;
38+
39+
const oracledb = require('oracledb');
40+
41+
// This example runs in both node-oracledb Thin and Thick modes.
42+
//
43+
// Optionally run in node-oracledb Thick mode
44+
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
45+
46+
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
47+
// On Windows and macOS Intel you can specify the directory containing the
48+
// libraries at runtime or before Node.js starts. On other platforms (where
49+
// Oracle libraries are available) the system library search path must always
50+
// include the Oracle library path before Node.js starts. If the search path
51+
// is not correct, you will get a DPI-1047 error. See the node-oracledb
52+
// installation documentation.
53+
let clientOpts = {};
54+
// On Windows and macOS Intel platforms, set the environment
55+
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
56+
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
57+
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
58+
}
59+
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
60+
}
61+
62+
console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode');
63+
64+
async function run() {
65+
// replace xxxx with the corresponding values of the parameter
66+
let connection;
67+
const options = {
68+
connectString: 'config-azure://testappconfig.azconfig.io/?key=testapp/testkey/&azure_client_id=xxxx&azure_client_certificate_path=xxxx&azure_tenant_id=xxxx'
69+
};
70+
try {
71+
// Get a non-pooled connection
72+
connection = await oracledb.getConnection(options);
73+
74+
console.log('Connection was successful!');
75+
76+
} catch (err) {
77+
console.error(err);
78+
} finally {
79+
if (connection) {
80+
try {
81+
await connection.close();
82+
} catch (err) {
83+
console.error(err);
84+
}
85+
}
86+
}
87+
}
88+
89+
run();

examples/ociProviderSample.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
3+
/******************************************************************************
4+
*
5+
* This software is dual-licensed to you under the Universal Permissive License
6+
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
7+
* 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
8+
* either license.
9+
*
10+
* If you elect to accept the software under the Apache License, Version 2.0,
11+
* the following applies:
12+
*
13+
* Licensed under the Apache License, Version 2.0 (the "License");
14+
* you may not use this file except in compliance with the License.
15+
* You may obtain a copy of the License at
16+
*
17+
* https://www.apache.org/licenses/LICENSE-2.0
18+
*
19+
* Unless required by applicable law or agreed to in writing, software
20+
* distributed under the License is distributed on an "AS IS" BASIS,
21+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
* See the License for the specific language governing permissions and
23+
* limitations under the License.
24+
*
25+
* NAME
26+
* ociProviderSample.js
27+
*
28+
* DESCRIPTION
29+
* Sample program to connect to the database using DB connectstring, username and password
30+
* which are fetched from the OCI Object Store using the connectstring which consists of
31+
* required parameters to authenticate OCI Object Store.
32+
*
33+
*****************************************************************************/
34+
35+
'use strict';
36+
37+
Error.stackTraceLimit = 50;
38+
39+
const oracledb = require('oracledb');
40+
41+
// This example runs in both node-oracledb Thin and Thick modes.
42+
//
43+
// Optionally run in node-oracledb Thick mode
44+
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
45+
46+
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
47+
// On Windows and macOS Intel you can specify the directory containing the
48+
// libraries at runtime or before Node.js starts. On other platforms (where
49+
// Oracle libraries are available) the system library search path must always
50+
// include the Oracle library path before Node.js starts. If the search path
51+
// is not correct, you will get a DPI-1047 error. See the node-oracledb
52+
// installation documentation.
53+
let clientOpts = {};
54+
// On Windows and macOS Intel platforms, set the environment
55+
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
56+
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
57+
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
58+
}
59+
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
60+
}
61+
62+
console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode');
63+
64+
async function run() {
65+
//replace xxxx with correct values of the parameter
66+
let connection;
67+
const options = {
68+
connectString: 'config-ociobject://test.us-phoenix-1.oraclecloud.com/n/testnamespace/b/testbucket/o/testobject?oci_tenancy=xxxx&oci_user=xxxx&oci_fingerprint=xxxx&oci_key_file=xxxx',
69+
};
70+
try {
71+
// Get a non-pooled connection
72+
connection = await oracledb.getConnection(options);
73+
74+
console.log('Connection was successful!');
75+
76+
} catch (err) {
77+
console.error(err);
78+
} finally {
79+
if (connection) {
80+
try {
81+
await connection.close();
82+
} catch (err) {
83+
console.error(err);
84+
}
85+
}
86+
}
87+
}
88+
89+
run();

lib/configProviders/azure.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
3+
//-----------------------------------------------------------------------------
4+
//
5+
// This software is dual-licensed to you under the Universal Permissive License
6+
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
7+
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
8+
// either license.
9+
//
10+
// If you elect to accept the software under the Apache License, Version 2.0,
11+
// the following applies:
12+
//
13+
// Licensed under the Apache License, Version 2.0 (the "License");
14+
// you may not use this file except in compliance with the License.
15+
// You may obtain a copy of the License at
16+
//
17+
// https://www.apache.org/licenses/LICENSE-2.0
18+
//
19+
// Unless required by applicable law or agreed to in writing, software
20+
// distributed under the License is distributed on an "AS IS" BASIS,
21+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
// See the License for the specific language governing permissions and
23+
// limitations under the License.
24+
//
25+
//-----------------------------------------------------------------------------
26+
27+
'use strict';
28+
29+
let AppConfigurationClient;
30+
let ClientSecretCredential, ClientCertificateCredential, ChainedTokenCredential, ManagedIdentityCredential, EnvironmentCredential;
31+
const errors = require("../errors.js");
32+
const { base } = require("./base.js");
33+
34+
class AzureProvider extends base {
35+
constructor(provider_arg, urlExtendedPart) {
36+
super(urlExtendedPart);
37+
this._addParam("appconfigname", provider_arg);
38+
}
39+
40+
//---------------------------------------------------------------------------
41+
// init()
42+
//
43+
// Require/import modules from azure
44+
//---------------------------------------------------------------------------
45+
init() {
46+
({AppConfigurationClient } = require("@azure/app-configuration"));
47+
({ClientSecretCredential, ClientCertificateCredential, ChainedTokenCredential, ManagedIdentityCredential, EnvironmentCredential} = require("@azure/identity"));
48+
}
49+
50+
//---------------------------------------------------------------------------
51+
// _withChainedTokenCredential()
52+
//
53+
// Use of ChainedTokenCredential class which provides the ability to link
54+
// together multiple credential instances to be tried sequentially when authenticating.
55+
// Default authentication to try when no authentication parameter is given by the user
56+
//---------------------------------------------------------------------------
57+
_withChainedTokenCredential() {
58+
const tokens = [];
59+
if ((this.paramMap.get("azure_client_secret")))
60+
tokens.push(new ClientSecretCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_secret")));
61+
if ((this.paramMap.get("azure_client_certificate_path")))
62+
tokens.push(new ClientCertificateCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_certificate_path")));
63+
if ((this.paramMap.get('azure_managed_identity_client_id')))
64+
tokens.push(this.paramMap.get('azure_managed_identity_client_id'));
65+
tokens.push(new EnvironmentCredential());
66+
const credential = new ChainedTokenCredential(...tokens);
67+
return credential;
68+
}
69+
70+
//---------------------------------------------------------------------------
71+
// _returnCredential()
72+
//
73+
// Returns credential to access Azure Config Store on the basis of
74+
// authentication parameters given by the user.
75+
//---------------------------------------------------------------------------
76+
_returnCredential() {
77+
let auth = null;
78+
if (this.paramMap.get('authentication')) {
79+
auth = this.paramMap.get('authentication').toUpperCase();
80+
}
81+
if (auth && !(auth == 'AZURE_DEFAULT')) {
82+
// do the given authentication
83+
if (auth == 'AZURE_SERVICE_PRINCIPAL') {
84+
if (this.paramMap.get("azure_client_certificate_path"))
85+
return new ClientCertificateCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_certificate_path"));
86+
else if (this.paramMap.get("azure_client_secret"))
87+
return new ClientSecretCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_secret"));
88+
else
89+
errors.throwErr(errors.ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED);
90+
} else if (auth == 'AZURE_MANAGED_IDENTITY') {
91+
return new ManagedIdentityCredential(this.paramMap.get('azure_managed_identity_client_id'));
92+
} else {
93+
errors.throwErr(errors.ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED, auth);
94+
}
95+
} else {
96+
//return default token credential
97+
return this._withChainedTokenCredential();
98+
}
99+
}
100+
101+
//---------------------------------------------------------------------------
102+
// _getConfigurationSetting()
103+
//
104+
// Get configuration setting from the config provider given a key
105+
//and an optional label
106+
//---------------------------------------------------------------------------
107+
async _getConfigurationSetting(client, key, label) {
108+
return await client.getConfigurationSetting({ key: key, label: label });
109+
110+
}
111+
112+
async returnConfig() {
113+
const configObject = {};
114+
const label = this.paramMap.get("label");
115+
const credential = this._returnCredential();
116+
// azure config store
117+
const client = new AppConfigurationClient(
118+
"https://" + this.paramMap.get("appconfigname"), // ex: <https://<your appconfig resource>.azconfig.io>
119+
credential
120+
);
121+
// retrieve connect_description
122+
configObject.connectString = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'connect_descriptor', label)).value;
123+
124+
// retrieve node-oracledb parameters
125+
try {
126+
const params = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'node-oracledb', label)).value;
127+
const obj = JSON.parse(params);
128+
for (const key in obj) {
129+
var val = obj[key];
130+
configObject[key] = val;
131+
}
132+
} catch {
133+
configObject['node-oracledb'] = null;
134+
}
135+
try {
136+
// retrieve user
137+
configObject.user = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'user', label)).value;
138+
} catch {
139+
configObject.user = null;
140+
}
141+
//retrieve password
142+
let pwdJson = null;
143+
try {
144+
pwdJson = await this._getConfigurationSetting(client, this.paramMap.get("key") + 'password', label);
145+
} catch {
146+
configObject.password = null;
147+
}
148+
if (pwdJson) {
149+
let obj;
150+
try {
151+
obj = JSON.parse(pwdJson.value);
152+
} catch {
153+
obj = pwdJson.value;
154+
}
155+
if (obj.uri) {
156+
const { SecretClient } = require("@azure/keyvault-secrets");
157+
const vault_detail = await this._parsePwd(obj.uri);
158+
const client1 = new SecretClient(vault_detail[0], credential);
159+
configObject.password = (await client1.getSecret(vault_detail[1])).value;
160+
} else {
161+
configObject.password = pwdJson.value;
162+
}
163+
}
164+
return configObject;
165+
}
166+
}
167+
module.exports = AzureProvider;
168+

0 commit comments

Comments
 (0)