UNPKG

oracle-nosqldb

Version:

Node.js driver for Oracle NoSQL Database

199 lines (175 loc) 7.34 kB
/*- * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl/ */ /** * ResourcePrincipalProvider * <p> * The authentication profile provider used to call service API from other OCI * resource such as function. It authenticates with resource principal and uses * security token issued by IAM to do the actual request signing. * <p> * It's constructed in accordance with the following environment variables: * <ul> * * <li>OCI_RESOURCE_PRINCIPAL_VERSION: permitted values are "2.2" * </li> * * <li>OCI_RESOURCE_PRINCIPAL_RPST: * <p> * If this is an absolute path, then the filesystem-supplied resource * principal session token will be retrieved from that location. This mode * supports token refresh (if the environment replaces the RPST in the * filesystem). Otherwise, the environment variable is taken to hold the raw * value of an RPST. Under these circumstances, the RPST cannot be refreshed; * consequently, this mode is only usable for short-lived executables. * </li> * <li>OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM: * If this is an absolute path, then the filesystem-supplied private key will * be retrieved from that location. As with the OCI_RESOURCE_PRINCIPAL_RPST, * this mode supports token refresh if the environment can update the file * contents. Otherwise, the value is interpreted as the direct injection of a * private key. The same considerations as to the lifetime of this value apply * when directly injecting a key. * </li> * <li>OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE: * <p> * This is optional. If set, it contains either the location (as an absolute * path) or the value of the passphrase associated with the private key. * </li> * <li>OCI_RESOURCE_PRINCIPAL_REGION: * <p> * If set, this holds the canonical form of the local region. This is intended * to enable executables to locate their "local" OCI service endpoints.</p> * </li> * </ul> */ const assert = require('assert'); const path = require('path'); const fs = require('fs'); const process = require('process'); const promisified = require('../../utils').promisified; const NoSQLAuthorizationError = require('../../error').NoSQLAuthorizationError; const Utils = require('./utils'); const isPosInt32OrZero = require('../../utils').isPosInt32OrZero; const CachedProfileProvider = require('./cached_profile').CachedProfileProvider; /* Environment variable names used to fetch artifacts */ const OCI_RESOURCE_PRINCIPAL_VERSION = 'OCI_RESOURCE_PRINCIPAL_VERSION'; const RP_VERSION_2_2 = '2.2'; const OCI_RESOURCE_PRINCIPAL_RPST = 'OCI_RESOURCE_PRINCIPAL_RPST'; const OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM = 'OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM'; const OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE = 'OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE'; const OCI_RESOURCE_PRINCIPAL_REGION = 'OCI_RESOURCE_PRINCIPAL_REGION'; //Claim keys for compartment and tenant id in resource principal //security tokens. const COMPARTMENT_ID_CLAIM_KEY = 'res_compartment'; const TENANT_ID_CLAIM_KEY = 'res_tenant'; //Make sure the values for the above claim keys, if available, are returned as //strings (which they should be, but we check just in case). Our typescript //declaration for ResourcePrincipalClaims expects them as such. function _chkAsString(val) { return val != null ? String(val) : undefined; } class ResourcePrincipalProvider extends CachedProfileProvider { constructor(opt, cfg) { super(opt); assert(opt != null); const ver = process.env[OCI_RESOURCE_PRINCIPAL_VERSION]; if (ver == null) { throw NoSQLAuthorizationError.invalidArg(`Missing environment \ variable ${OCI_RESOURCE_PRINCIPAL_VERSION}`, null, cfg); } if (ver !== RP_VERSION_2_2) { throw NoSQLAuthorizationError.invalidArg(`Unknown value for \ environment variable ${OCI_RESOURCE_PRINCIPAL_VERSION}`, null, cfg); } this._pk = process.env[OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM]; this._pkPass = process.env[OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE]; if (this._pk == null) { throw NoSQLAuthorizationError.invalidArg(`Missing environment \ variable ${OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM}`, null, cfg); } if (path.isAbsolute(this._pk)) { if (this._pkPass != null && !path.isAbsolute(this._pkPass)) { throw NoSQLAuthorizationError.invalidArg(`Cannot mix path \ and constant settings for ${OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM} and \ ${OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE}`, null, cfg); } this._pkInFile = true; } else { this._privateKey = Utils.privateKeyFromPEM(this._pk, this._pkPass); } this._rpst = process.env[OCI_RESOURCE_PRINCIPAL_RPST]; if (this._rpst == null) { throw NoSQLAuthorizationError.invalidArg(`Missing environment \ variable ${OCI_RESOURCE_PRINCIPAL_RPST}`, null, cfg); } if (path.isAbsolute(this._rpst)) { this._rpstInFile = true; } else { this._token = Utils.parseSecurityToken(this._rpst); } this._region = process.env[OCI_RESOURCE_PRINCIPAL_REGION]; if (this._region == null) { throw NoSQLAuthorizationError.invalidArg(`Missing environment \ variable ${OCI_RESOURCE_PRINCIPAL_REGION}`, null, cfg); } this._useRPSTCompartment = opt.useResourcePrincipalCompartment; this._expireBeforeMs = opt.securityTokenExpireBeforeMs; assert(isPosInt32OrZero(this._expireBeforeMs)); } async _rpstFromFile() { let rpst; try { rpst = await promisified(null, fs.readFile, this._rpst, 'utf8'); } catch(err) { throw NoSQLAuthorizationError.invalidArg(`Error reading security \ token from file ${this._rpst}`, err); } return Utils.parseSecurityToken(rpst); } //only called when this._profile exists _isCurrentProfileValid() { assert(this._token != null && this._privateKey != null); return Utils.isSecurityTokenValid(this._token, this._expireBeforeMs); } async _doRefresh() { if (this._rpstInFile) { this._token = await this._rpstFromFile(); } if (this._pkInFile) { this._privateKey = await Utils.privateKeyFromPEMFile(this._pk, this._pkPass, true); } assert(this._token != null); assert(this._privateKey != null); this._profile = { keyId: 'ST$' + this._token.value, privateKey: this._privateKey }; if (this._useRPSTCompartment) { this._profile.compartmentId = _chkAsString( this._token.claims[COMPARTMENT_ID_CLAIM_KEY]); } } getRegion() { return this._region; } async getRPSTClaims() { await this._doRefresh(); return { tenantId: _chkAsString(this._token.claims[TENANT_ID_CLAIM_KEY]), compartmentId: _chkAsString( this._token.claims[COMPARTMENT_ID_CLAIM_KEY]) }; } } module.exports = ResourcePrincipalProvider;