Add Centralized Configuration Provider support (Oracle Database 23ai)
This commit is contained in:
parent
d3277b428e
commit
4977cdeef4
|
@ -10,6 +10,14 @@ For deprecated and desupported features, see :ref:`Deprecations and desupported
|
|||
node-oracledb `v6.6.0 <https://github.com/oracle/node-oracledb/compare/v6.5.1...v6.6.0>`__ (TBD)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Common Changes
|
||||
++++++++++++++
|
||||
|
||||
#) Added support for Centralized Configuration Providers (Azure App Configuration Store and OCI
|
||||
Object Storage).
|
||||
Node-oracledb extracts configuration information from the the supported provider and uses it to
|
||||
connect to the database.
|
||||
|
||||
Thin Mode Changes
|
||||
+++++++++++++++++
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2024, Oracle and/or its affiliates.
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* This software is dual-licensed to you under the Universal Permissive License
|
||||
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
* 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||
* either license.
|
||||
*
|
||||
* If you elect to accept the software under the Apache License, Version 2.0,
|
||||
* the following applies:
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* NAME
|
||||
* azureProviderSample.js
|
||||
*
|
||||
* DESCRIPTION
|
||||
* Sample program to connect to the database using DB connectstring, username and password
|
||||
* which are fetched from the Azure Configuration Store using the connectstring which consists of
|
||||
* required parameters to authenticate azure configuration store.
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
'use strict';
|
||||
|
||||
Error.stackTraceLimit = 50;
|
||||
|
||||
const oracledb = require('oracledb');
|
||||
|
||||
// This example runs in both node-oracledb Thin and Thick modes.
|
||||
//
|
||||
// Optionally run in node-oracledb Thick mode
|
||||
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
|
||||
|
||||
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
|
||||
// On Windows and macOS Intel you can specify the directory containing the
|
||||
// libraries at runtime or before Node.js starts. On other platforms (where
|
||||
// Oracle libraries are available) the system library search path must always
|
||||
// include the Oracle library path before Node.js starts. If the search path
|
||||
// is not correct, you will get a DPI-1047 error. See the node-oracledb
|
||||
// installation documentation.
|
||||
let clientOpts = {};
|
||||
// On Windows and macOS Intel platforms, set the environment
|
||||
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
|
||||
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
|
||||
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
|
||||
}
|
||||
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
|
||||
}
|
||||
|
||||
console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode');
|
||||
|
||||
async function run() {
|
||||
// replace xxxx with the corresponding values of the parameter
|
||||
let connection;
|
||||
const options = {
|
||||
connectString: 'config-azure://testappconfig.azconfig.io/?key=testapp/testkey/&azure_client_id=xxxx&azure_client_certificate_path=xxxx&azure_tenant_id=xxxx'
|
||||
};
|
||||
try {
|
||||
// Get a non-pooled connection
|
||||
connection = await oracledb.getConnection(options);
|
||||
|
||||
console.log('Connection was successful!');
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.close();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2024, Oracle and/or its affiliates.
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* This software is dual-licensed to you under the Universal Permissive License
|
||||
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
* 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||
* either license.
|
||||
*
|
||||
* If you elect to accept the software under the Apache License, Version 2.0,
|
||||
* the following applies:
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* NAME
|
||||
* ociProviderSample.js
|
||||
*
|
||||
* DESCRIPTION
|
||||
* Sample program to connect to the database using DB connectstring, username and password
|
||||
* which are fetched from the OCI Object Store using the connectstring which consists of
|
||||
* required parameters to authenticate OCI Object Store.
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
'use strict';
|
||||
|
||||
Error.stackTraceLimit = 50;
|
||||
|
||||
const oracledb = require('oracledb');
|
||||
|
||||
// This example runs in both node-oracledb Thin and Thick modes.
|
||||
//
|
||||
// Optionally run in node-oracledb Thick mode
|
||||
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
|
||||
|
||||
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
|
||||
// On Windows and macOS Intel you can specify the directory containing the
|
||||
// libraries at runtime or before Node.js starts. On other platforms (where
|
||||
// Oracle libraries are available) the system library search path must always
|
||||
// include the Oracle library path before Node.js starts. If the search path
|
||||
// is not correct, you will get a DPI-1047 error. See the node-oracledb
|
||||
// installation documentation.
|
||||
let clientOpts = {};
|
||||
// On Windows and macOS Intel platforms, set the environment
|
||||
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
|
||||
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
|
||||
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
|
||||
}
|
||||
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
|
||||
}
|
||||
|
||||
console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode');
|
||||
|
||||
async function run() {
|
||||
//replace xxxx with correct values of the parameter
|
||||
let connection;
|
||||
const options = {
|
||||
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',
|
||||
};
|
||||
try {
|
||||
// Get a non-pooled connection
|
||||
connection = await oracledb.getConnection(options);
|
||||
|
||||
console.log('Connection was successful!');
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.close();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright (c) 2024, Oracle and/or its affiliates.
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// This software is dual-licensed to you under the Universal Permissive License
|
||||
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||
// either license.
|
||||
//
|
||||
// If you elect to accept the software under the Apache License, Version 2.0,
|
||||
// the following applies:
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'use strict';
|
||||
|
||||
let AppConfigurationClient;
|
||||
let ClientSecretCredential, ClientCertificateCredential, ChainedTokenCredential, ManagedIdentityCredential, EnvironmentCredential;
|
||||
const errors = require("../errors.js");
|
||||
const { base } = require("./base.js");
|
||||
|
||||
class AzureProvider extends base {
|
||||
constructor(provider_arg, urlExtendedPart) {
|
||||
super(urlExtendedPart);
|
||||
this._addParam("appconfigname", provider_arg);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// init()
|
||||
//
|
||||
// Require/import modules from azure
|
||||
//---------------------------------------------------------------------------
|
||||
init() {
|
||||
({AppConfigurationClient } = require("@azure/app-configuration"));
|
||||
({ClientSecretCredential, ClientCertificateCredential, ChainedTokenCredential, ManagedIdentityCredential, EnvironmentCredential} = require("@azure/identity"));
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// _withChainedTokenCredential()
|
||||
//
|
||||
// Use of ChainedTokenCredential class which provides the ability to link
|
||||
// together multiple credential instances to be tried sequentially when authenticating.
|
||||
// Default authentication to try when no authentication parameter is given by the user
|
||||
//---------------------------------------------------------------------------
|
||||
_withChainedTokenCredential() {
|
||||
const tokens = [];
|
||||
if ((this.paramMap.get("azure_client_secret")))
|
||||
tokens.push(new ClientSecretCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_secret")));
|
||||
if ((this.paramMap.get("azure_client_certificate_path")))
|
||||
tokens.push(new ClientCertificateCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_certificate_path")));
|
||||
if ((this.paramMap.get('azure_managed_identity_client_id')))
|
||||
tokens.push(this.paramMap.get('azure_managed_identity_client_id'));
|
||||
tokens.push(new EnvironmentCredential());
|
||||
const credential = new ChainedTokenCredential(...tokens);
|
||||
return credential;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// _returnCredential()
|
||||
//
|
||||
// Returns credential to access Azure Config Store on the basis of
|
||||
// authentication parameters given by the user.
|
||||
//---------------------------------------------------------------------------
|
||||
_returnCredential() {
|
||||
let auth = null;
|
||||
if (this.paramMap.get('authentication')) {
|
||||
auth = this.paramMap.get('authentication').toUpperCase();
|
||||
}
|
||||
if (auth && !(auth == 'AZURE_DEFAULT')) {
|
||||
// do the given authentication
|
||||
if (auth == 'AZURE_SERVICE_PRINCIPAL') {
|
||||
if (this.paramMap.get("azure_client_certificate_path"))
|
||||
return new ClientCertificateCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_certificate_path"));
|
||||
else if (this.paramMap.get("azure_client_secret"))
|
||||
return new ClientSecretCredential(this.paramMap.get("azure_tenant_id"), this.paramMap.get("azure_client_id"), this.paramMap.get("azure_client_secret"));
|
||||
else
|
||||
errors.throwErr(errors.ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED);
|
||||
} else if (auth == 'AZURE_MANAGED_IDENTITY') {
|
||||
return new ManagedIdentityCredential(this.paramMap.get('azure_managed_identity_client_id'));
|
||||
} else {
|
||||
errors.throwErr(errors.ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED, auth);
|
||||
}
|
||||
} else {
|
||||
//return default token credential
|
||||
return this._withChainedTokenCredential();
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// _getConfigurationSetting()
|
||||
//
|
||||
// Get configuration setting from the config provider given a key
|
||||
//and an optional label
|
||||
//---------------------------------------------------------------------------
|
||||
async _getConfigurationSetting(client, key, label) {
|
||||
return await client.getConfigurationSetting({ key: key, label: label });
|
||||
|
||||
}
|
||||
|
||||
async returnConfig() {
|
||||
const configObject = {};
|
||||
const label = this.paramMap.get("label");
|
||||
const credential = this._returnCredential();
|
||||
// azure config store
|
||||
const client = new AppConfigurationClient(
|
||||
"https://" + this.paramMap.get("appconfigname"), // ex: <https://<your appconfig resource>.azconfig.io>
|
||||
credential
|
||||
);
|
||||
// retrieve connect_description
|
||||
configObject.connectString = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'connect_descriptor', label)).value;
|
||||
|
||||
// retrieve node-oracledb parameters
|
||||
try {
|
||||
const params = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'node-oracledb', label)).value;
|
||||
const obj = JSON.parse(params);
|
||||
for (const key in obj) {
|
||||
var val = obj[key];
|
||||
configObject[key] = val;
|
||||
}
|
||||
} catch {
|
||||
configObject['node-oracledb'] = null;
|
||||
}
|
||||
try {
|
||||
// retrieve user
|
||||
configObject.user = (await this._getConfigurationSetting(client, this.paramMap.get("key") + 'user', label)).value;
|
||||
} catch {
|
||||
configObject.user = null;
|
||||
}
|
||||
//retrieve password
|
||||
let pwdJson = null;
|
||||
try {
|
||||
pwdJson = await this._getConfigurationSetting(client, this.paramMap.get("key") + 'password', label);
|
||||
} catch {
|
||||
configObject.password = null;
|
||||
}
|
||||
if (pwdJson) {
|
||||
let obj;
|
||||
try {
|
||||
obj = JSON.parse(pwdJson.value);
|
||||
} catch {
|
||||
obj = pwdJson.value;
|
||||
}
|
||||
if (obj.uri) {
|
||||
const { SecretClient } = require("@azure/keyvault-secrets");
|
||||
const vault_detail = await this._parsePwd(obj.uri);
|
||||
const client1 = new SecretClient(vault_detail[0], credential);
|
||||
configObject.password = (await client1.getSecret(vault_detail[1])).value;
|
||||
} else {
|
||||
configObject.password = pwdJson.value;
|
||||
}
|
||||
}
|
||||
return configObject;
|
||||
}
|
||||
}
|
||||
module.exports = AzureProvider;
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) 2024, Oracle and/or its affiliates.
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// This software is dual-licensed to you under the Universal Permissive License
|
||||
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||
// either license.
|
||||
//
|
||||
// If you elect to accept the software under the Apache License, Version 2.0,
|
||||
// the following applies:
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'use strict';
|
||||
|
||||
class base {
|
||||
constructor(url) {
|
||||
const params = new URLSearchParams(url);
|
||||
this.paramMap = new URLSearchParams([...params].map(([key, value]) => [key.toLowerCase(), value])); //parse the extended part and store parameters in Map
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets precedence for different parameters in cloudConfig/userConfig
|
||||
* @param {cloudConfig} object - object retreived from cloud
|
||||
* @param {userConfig} object - user input Object
|
||||
*/
|
||||
modifyOptionsPrecedence(cloudConfig, userConfig) {
|
||||
// create a copy of userConfig object
|
||||
userConfig = { ...userConfig };
|
||||
if (!userConfig.user)
|
||||
userConfig.user = cloudConfig.user;
|
||||
if (!userConfig.password)
|
||||
userConfig.password = cloudConfig.password;
|
||||
if (cloudConfig.connectString) {
|
||||
userConfig.connectString = cloudConfig.connectString;
|
||||
userConfig.connectionString = undefined;
|
||||
}
|
||||
if (cloudConfig.poolMin)
|
||||
userConfig.poolMin = cloudConfig.poolMin;
|
||||
if (cloudConfig.poolMax)
|
||||
userConfig.poolMax = cloudConfig.poolMax;
|
||||
if (cloudConfig.poolIncrement)
|
||||
userConfig.poolIncrement = cloudConfig.poolIncrement;
|
||||
if (cloudConfig.poolTimeout)
|
||||
userConfig.poolTimeout = cloudConfig.poolTimeout;
|
||||
if (cloudConfig.poolPingInterval)
|
||||
userConfig.poolPingInterval = cloudConfig.poolPingInterval;
|
||||
if (cloudConfig.poolPingTimeout)
|
||||
userConfig.poolPingTimeout = cloudConfig.poolPingTimeout;
|
||||
if (cloudConfig.stmtCacheSize)
|
||||
userConfig.stmtCacheSize = cloudConfig.stmtCacheSize;
|
||||
if (cloudConfig.prefetchRows)
|
||||
userConfig.prefetchRows = cloudConfig.prefetchRows;
|
||||
if (cloudConfig.lobPrefetch)
|
||||
userConfig.lobPrefetch = cloudConfig.lobPrefetch;
|
||||
|
||||
return userConfig;
|
||||
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// _addParam()
|
||||
// Adds key,value pairs to the Map
|
||||
//---------------------------------------------------------------------------
|
||||
_addParam(key, value) {
|
||||
const aliasKeyName = key.toLowerCase();
|
||||
this.paramMap.set(aliasKeyName, value);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// _parsePwd()
|
||||
// Parse password which is in url format
|
||||
// “uri”:“https://mykeyvault.vault.azure.net/secrets/secretkey”}
|
||||
//---------------------------------------------------------------------------
|
||||
_parsePwd(str) {
|
||||
const vault_uri = new RegExp("(?<vault_url>https://[A-Za-z0-9._-]+)/secrets/(?<secretKey>[A-Za-z][A-Za-z0-9-]*)$", 'g');
|
||||
const vault_detail = [];
|
||||
for (const match of str.matchAll(vault_uri)) {
|
||||
vault_detail[0] = match.groups.vault_url;
|
||||
vault_detail[1] = match.groups.secretKey;
|
||||
}
|
||||
return vault_detail;
|
||||
}
|
||||
|
||||
}
|
||||
module.exports = {base};
|
||||
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright (c) 2024, Oracle and/or its affiliates.
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// This software is dual-licensed to you under the Universal Permissive License
|
||||
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||
// either license.
|
||||
//
|
||||
// If you elect to accept the software under the Apache License, Version 2.0,
|
||||
// the following applies:
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'use strict';
|
||||
const errors = require("../errors.js");
|
||||
const { base } = require("./base.js");
|
||||
const fs = require('fs');
|
||||
|
||||
const cloud_net_naming_pattern_oci = new RegExp("(?<objservername>[A-Za-z0-9._-]+)/n/" + "(?<namespace>[A-Za-z0-9._-]+)/b/" + "(?<bucketname>[A-Za-z0-9._-]+)/o/" + "(?<filename>[A-Za-z0-9._-]+)" + "(/c/(?<alias>.+))?$");
|
||||
// object to store module references that will be populated by init()
|
||||
const oci = {};
|
||||
|
||||
class OCIProvider extends base {
|
||||
constructor(provider_arg, urlExtendedPart) {
|
||||
super(urlExtendedPart);
|
||||
const match = provider_arg.match(cloud_net_naming_pattern_oci);
|
||||
if (match) {
|
||||
this._addParam("objservername", match.groups.objservername);
|
||||
this._addParam("namespace", match.groups.namespace);
|
||||
this._addParam("bucketname", match.groups.bucketname);
|
||||
this._addParam("filename", match.groups.filename);
|
||||
if (match.groups.alias)
|
||||
this._addParam("alias", match.groups.alias);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// init()
|
||||
//
|
||||
// Require/import modules from ociobject
|
||||
//---------------------------------------------------------------------------
|
||||
init() {
|
||||
oci.common = require('oci-common');
|
||||
oci.objectstorage = require('oci-objectstorage');
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// _streamToString()
|
||||
//
|
||||
// Converts data stored in a Readable stream to string
|
||||
//---------------------------------------------------------------------------
|
||||
async _streamToString(stream) {
|
||||
const chunks = [];
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
return Buffer.concat(chunks).toString("utf-8");
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// _returnCredential()
|
||||
//
|
||||
// Returns credential to access OCI Object Store on the basis of
|
||||
// authentication parameters given by the user.
|
||||
//---------------------------------------------------------------------------
|
||||
async _returnCredential() {
|
||||
let provider = null;
|
||||
let auth = null;
|
||||
if (this.paramMap.get('authentication')) {
|
||||
auth = this.paramMap.get('authentication').toUpperCase();
|
||||
}
|
||||
// authentication parameter given and its not OCI_DEFAULT
|
||||
if (auth && !(auth == 'OCI_DEFAULT')) {
|
||||
if (auth == 'OCI_INSTANCE_PRINCIPAL') {
|
||||
provider = await new oci.common.InstancePrincipalsAuthenticationDetailsProviderBuilder().build();
|
||||
} else if (auth == 'OCI_RESOURCE_PRINCIPAL') {
|
||||
provider = await new oci.common.ResourcePrincipalAuthenticationDetailsProvider.builder();
|
||||
} else
|
||||
errors.throwErr(errors.ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED, auth);
|
||||
} else {
|
||||
// default authentication
|
||||
try {
|
||||
//authentication parameters exist in the configurationFile
|
||||
provider = new oci.common.ConfigFileAuthenticationDetailsProvider(
|
||||
this.paramMap.get("oci_profile_path"), //default path ~/.oci/config
|
||||
this.paramMap.get("oci_profile")
|
||||
);
|
||||
} catch (err) {
|
||||
//throw error for wrong profile or wrong path
|
||||
if (!this.paramMap.get("oci_tenancy") || !this.paramMap.get("oci_user")) {
|
||||
throw (err);
|
||||
}
|
||||
// authentication parameters are directly given in the connectString
|
||||
const publicKey = fs.readFileSync(this.paramMap.get('oci_key_file'), { encoding: "utf8" });
|
||||
const region = this.retrieveRegion(this.paramMap.get('objservername'));
|
||||
provider = new oci.common.SimpleAuthenticationDetailsProvider(
|
||||
this.paramMap.get("oci_tenancy"),
|
||||
this.paramMap.get("oci_user"),
|
||||
this.paramMap.get("oci_fingerprint"),
|
||||
publicKey,
|
||||
undefined,
|
||||
oci.common.Region[region]
|
||||
);
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// returnConfig()
|
||||
//
|
||||
// Returns config stored in the OCI Object Store and
|
||||
// parses and gets password field stored in OCI/Azure Vault
|
||||
//---------------------------------------------------------------------------
|
||||
async returnConfig() {
|
||||
const configObject = {};
|
||||
const credential = await this._returnCredential();
|
||||
// oci object store
|
||||
const client_oci = new (oci.objectstorage).ObjectStorageClient({
|
||||
authenticationDetailsProvider: credential
|
||||
});
|
||||
const getObjectRequest = {
|
||||
objectName: this.paramMap.get('filename'),
|
||||
bucketName: this.paramMap.get('bucketname'),
|
||||
namespaceName: this.paramMap.get('namespace')
|
||||
};
|
||||
let credential1;
|
||||
const getObjectResponse = await client_oci.getObject(getObjectRequest);
|
||||
const resp = await this._streamToString(getObjectResponse.value);
|
||||
// Entire object we get from OCI Object Storage
|
||||
let obj = JSON.parse(resp);
|
||||
const userAlias = this.paramMap.get('alias');
|
||||
if (userAlias) {
|
||||
obj = obj[userAlias];
|
||||
}
|
||||
const pmSection = 'node-oracledb';
|
||||
const params = obj[pmSection];
|
||||
for (const key in params) {
|
||||
var val = params[key];
|
||||
configObject[key] = val;
|
||||
}
|
||||
configObject.connectString = obj.connect_descriptor;
|
||||
configObject.user = obj.user;
|
||||
if (obj.password) {
|
||||
if (obj.password.type == "vault-azure") {
|
||||
if (obj.password.authentication) {
|
||||
const { SecretClient } = require("@azure/keyvault-secrets");
|
||||
const {ClientSecretCredential, ClientCertificateCredential} = require("@azure/identity");
|
||||
if (obj.password.authentication.azure_client_secret)
|
||||
credential1 = new ClientSecretCredential(obj.password.authentication.azure_tenant_id, obj.password.authentication.azure_client_id, obj.password.authentication.azure_client_secret);
|
||||
else if (obj.password.authentication.azure_client_certificate_path)
|
||||
credential1 = new ClientCertificateCredential(obj.password.authentication.azure_tenant_id, obj.password.authentication.azure_client_id, obj.password.authentication.azure_client_certificate_path);
|
||||
else
|
||||
errors.throwErr(errors.ERR_AZURE_VAULT_AUTH_FAILED);
|
||||
const vault_detail = await this._parsePwd(obj.password.value);
|
||||
const client1 = new SecretClient(vault_detail[0], credential1);
|
||||
configObject.password = (await client1.getSecret(vault_detail[1])).value;
|
||||
} else {
|
||||
errors.throwErr(errors.ERR_AZURE_VAULT_AUTH_FAILED);
|
||||
}
|
||||
} else if (obj.password.type == "vault-oci") {
|
||||
const secrets = require('oci-secrets');
|
||||
const secretClientOci = new secrets.SecretsClient({
|
||||
authenticationDetailsProvider: credential
|
||||
});
|
||||
const getSecretBundleRequest = {
|
||||
secretId: obj.password.value
|
||||
};
|
||||
const getSecretBundleResponse = await secretClientOci.getSecretBundle(getSecretBundleRequest);
|
||||
configObject.password = getSecretBundleResponse.secretBundle.secretBundleContent.content;
|
||||
} else if (obj.password) {
|
||||
configObject.password = obj.password;
|
||||
}
|
||||
} else {
|
||||
configObject.password = null;
|
||||
}
|
||||
return configObject;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// retrieveRegion(objservername)
|
||||
//
|
||||
// returns region from the given objservername.
|
||||
//---------------------------------------------------------------------------
|
||||
retrieveRegion(objservername) {
|
||||
const arr = objservername.split(".");
|
||||
return arr[1].toUpperCase().replaceAll('-', '_');
|
||||
}
|
||||
}
|
||||
module.exports = OCIProvider;
|
||||
|
|
@ -179,6 +179,13 @@ const ERR_INVALID_SERVICE_NAME = 518;
|
|||
const ERR_INVALID_SID = 519;
|
||||
const ERR_TNS_NAMES_FILE_MISSING = 520;
|
||||
const ERR_CONNECTION_EOF = 521;
|
||||
const ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED = 522;
|
||||
const ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG = 523;
|
||||
const ERR_CONFIG_PROVIDER_NOT_SUPPORTED = 524;
|
||||
const ERR_CONFIG_PROVIDER_LOAD_FAILED = 525;
|
||||
const ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED = 526;
|
||||
const ERR_AZURE_VAULT_AUTH_FAILED = 527;
|
||||
const ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED = 528;
|
||||
|
||||
// Oracle SUCCESS_WITH_INFO warning start from 700
|
||||
const WRN_COMPILATION_CREATE = 700;
|
||||
|
@ -479,7 +486,20 @@ messages.set(ERR_TNS_NAMES_FILE_MISSING, // NJS-520
|
|||
'cannot connect to Oracle Database. File tnsnames.ora not found in %s');
|
||||
messages.set(ERR_CONNECTION_EOF, // NJS-521
|
||||
'connection to host %s port %d received end-of-file on communication channel. (CONNECTION_ID=%s)');
|
||||
|
||||
messages.set(ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED, // NJS-522
|
||||
'Azure Authentication Failed: The authentication parameter value %s may be incorrect');
|
||||
messages.set(ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG, // NJS-523
|
||||
'Failed to retrieve configuration from Centralized Configuration Provider:\n %s');
|
||||
messages.set(ERR_CONFIG_PROVIDER_NOT_SUPPORTED, // NJS-524
|
||||
'Configuration Provider not supported: %s');
|
||||
messages.set(ERR_CONFIG_PROVIDER_LOAD_FAILED, // NJS-525
|
||||
'Centralized Config Provider failed to load required libraries. Please install the required libraries.\n %s');
|
||||
messages.set(ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED, // NJS-526
|
||||
'OCI authentication failed: The authentication parameter value %s may be incorrect');
|
||||
messages.set(ERR_AZURE_VAULT_AUTH_FAILED, // NJS-527
|
||||
'Azure Vault: Provide correct azure vault authentication details');
|
||||
messages.set(ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED, // NJS-528
|
||||
'Azure service principal authentication requires either a client certificate path or a client secret string');
|
||||
// Oracle SUCCESS_WITH_INFO warning
|
||||
|
||||
messages.set(WRN_COMPILATION_CREATE, // NJS-700
|
||||
|
@ -789,6 +809,13 @@ module.exports = {
|
|||
ERR_NO_CONFIG_DIR,
|
||||
ERR_TNS_ENTRY_NOT_FOUND,
|
||||
ERR_CONNECTION_EOF,
|
||||
ERR_AZURE_CONFIG_PROVIDER_AUTH_FAILED,
|
||||
ERR_OCIOBJECT_CONFIG_PROVIDER_AUTH_FAILED,
|
||||
ERR_AZURE_VAULT_AUTH_FAILED,
|
||||
ERR_AZURE_SERVICE_PRINCIPAL_AUTH_FAILED,
|
||||
ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG,
|
||||
ERR_CONFIG_PROVIDER_NOT_SUPPORTED,
|
||||
ERR_CONFIG_PROVIDER_LOAD_FAILED,
|
||||
ERR_INVALID_BIND_NAME,
|
||||
ERR_WRONG_NUMBER_OF_POSITIONAL_BINDS,
|
||||
ERR_BUFFER_LENGTH_INSUFFICIENT,
|
||||
|
|
|
@ -129,6 +129,7 @@ async function _verifyOptions(options, inCreatePool) {
|
|||
// define normalized options (value returned to caller)
|
||||
const outOptions = {};
|
||||
|
||||
options = await _checkConfigProvider(options);
|
||||
// only one of "user" and "username" may be specified (and must be strings)
|
||||
if (options.user !== undefined) {
|
||||
errors.assertParamPropValue(typeof options.user === 'string', 1, "user");
|
||||
|
@ -767,6 +768,51 @@ async function shutdown(a1, a2) {
|
|||
await conn.close();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// _checkConfigProvider()
|
||||
//
|
||||
// Look for the config provider in the connection string and retreives
|
||||
// object stored in the config Provider.
|
||||
// Returns object based on precedence between input object and the one retrieved
|
||||
// from Config Provider.
|
||||
//-----------------------------------------------------------------------------
|
||||
async function _checkConfigProvider(options) {
|
||||
const url = options.connectString || options.connectionString;
|
||||
if (!url)
|
||||
return options;
|
||||
let parsedUrl = url;
|
||||
let urlExtendedPart;
|
||||
const baseRegex = new RegExp("^config-(?<provider>[A-Za-z0-9]+)(://)(?<provider_arg>[^?]+)");
|
||||
if (url.indexOf('?') != -1) {
|
||||
parsedUrl = url.substring(0, url.indexOf('?'));
|
||||
urlExtendedPart = url.substring(url.indexOf('?'), url.length); //extended part
|
||||
}
|
||||
const match = parsedUrl.match(baseRegex);
|
||||
if (match) {
|
||||
const provider = match.groups.provider;
|
||||
const provider_arg = match.groups.provider_arg;
|
||||
let configPckg;
|
||||
try {
|
||||
configPckg = require('./configProviders/' + provider);
|
||||
} catch (err) {
|
||||
errors.throwErr(errors.ERR_CONFIG_PROVIDER_NOT_SUPPORTED, provider);
|
||||
}
|
||||
const configProvider = new configPckg(provider_arg, urlExtendedPart);
|
||||
try {
|
||||
configProvider.init();
|
||||
} catch (err) {
|
||||
errors.throwErr(errors.ERR_CONFIG_PROVIDER_LOAD_FAILED, err.message);
|
||||
}
|
||||
let secondOpts;
|
||||
try {
|
||||
secondOpts = await configProvider.returnConfig();
|
||||
} catch (err) {
|
||||
errors.throwErr(errors.ERR_CONFIG_PROVIDER_FAILED_TO_RETRIEVE_CONFIG, err.message);
|
||||
}
|
||||
options = configProvider.modifyOptionsPrecedence(secondOpts, options);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// startup()
|
||||
|
|
Loading…
Reference in New Issue