diff --git a/backend/.gitignore b/backend/.gitignore index 1521c8b765..00425e090d 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1 +1,3 @@ dist + +/wallet diff --git a/backend/package-lock.json b/backend/package-lock.json index a871c38b16..025a0fe221 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -155,6 +155,7 @@ "@types/lodash.isequal": "^4.5.8", "@types/node": "^20.19.0", "@types/nodemailer": "^6.4.14", + "@types/oracledb": "^6.10.0", "@types/passport-google-oauth20": "^2.0.14", "@types/pg": "^8.10.9", "@types/picomatch": "^2.3.3", @@ -15367,6 +15368,16 @@ "@types/node": "*" } }, + "node_modules/@types/oracledb": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-6.10.0.tgz", + "integrity": "sha512-dRaEYKRkJhSSM/uKrscE7zXC5D75JSkBgdye5kSxzTRwrMAUI5V675cD3fqCdMuSOiGo2K9Ng3CjjRI4rzeuAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/passport": { "version": "1.0.17", "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", diff --git a/backend/package.json b/backend/package.json index aa97de2ed4..c65e61a8a5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -101,6 +101,7 @@ "@types/lodash.isequal": "^4.5.8", "@types/node": "^20.19.0", "@types/nodemailer": "^6.4.14", + "@types/oracledb": "^6.10.0", "@types/passport-google-oauth20": "^2.0.14", "@types/pg": "^8.10.9", "@types/picomatch": "^2.3.3", diff --git a/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts b/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts index ca50bae8a3..d97ea45568 100644 --- a/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts +++ b/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts @@ -1,4 +1,6 @@ +import fs from "fs"; import knex, { Knex } from "knex"; +import oracledb from "oracledb"; import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; @@ -79,12 +81,51 @@ const getConnectionConfig = ({ } }; +const ORACLE_WALLET_REGEX = /^[a-z0-9]+_[a-z]+$/i; + +const isOracleWalletConnection = (app: AppConnection, database: string): boolean => { + return app === AppConnection.OracleDB && ORACLE_WALLET_REGEX.test(database); +}; + +const getOracleWalletKnexClient = ( + credentials: Pick +): Knex => { + if (!process.env.TNS_ADMIN || !fs.existsSync(process.env.TNS_ADMIN)) { + throw new BadRequestError({ + message: + "Oracle wallet is not configured correctly. See documentation for instructions: https://infisical.com/docs/integrations/app-connections/oracledb#mtls-wallet" + }); + } + if (oracledb.thin) { + try { + oracledb.initOracleClient(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err); + throw new BadRequestError({ + message: `Failed to initialize Oracle client: ${errorMessage}. See documentation for instructions: https://infisical.com/docs/integrations/app-connections/oracledb#mtls-wallet` + }); + } + } + return knex({ + client: "oracledb", + connection: { + user: credentials.username, + password: credentials.password, + connectString: credentials.database + } + }); +}; + export const getSqlConnectionClient = async (appConnection: Pick) => { const { app, credentials: { host: baseHost, database, port, password, username } } = appConnection; + if (isOracleWalletConnection(app, database)) { + return getOracleWalletKnexClient({ username, password, database }); + } + const [host] = await verifyHostInputValidity(baseHost); const client = knex({ @@ -119,21 +160,30 @@ export const executeWithPotentialGateway = async ( targetPort: credentials.port }); + const createClient = (proxyPort: number): Knex => { + const { database, username, password } = credentials; + if (isOracleWalletConnection(app, database)) { + return getOracleWalletKnexClient({ username, password, database }); + } + + return knex({ + client: SQL_CONNECTION_CLIENT_MAP[app], + connection: { + database: credentials.database, + port: proxyPort, + host: "localhost", + user: credentials.username, + password: credentials.password, + connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT, + ...getConnectionConfig({ app, credentials }) + } + }); + }; + if (platformConnectionDetails) { return withGatewayV2Proxy( async (proxyPort) => { - const client = knex({ - client: SQL_CONNECTION_CLIENT_MAP[app], - connection: { - database: credentials.database, - port: proxyPort, - host: "localhost", - user: credentials.username, - password: credentials.password, - connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT, - ...getConnectionConfig({ app, credentials }) - } - }); + const client = createClient(proxyPort); try { return await operation(client); } finally { @@ -154,18 +204,7 @@ export const executeWithPotentialGateway = async ( return withGatewayProxy( async (proxyPort) => { - const client = knex({ - client: SQL_CONNECTION_CLIENT_MAP[app], - connection: { - database: credentials.database, - port: proxyPort, - host: "localhost", - user: credentials.username, - password: credentials.password, - connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT, - ...getConnectionConfig({ app, credentials }) - } - }); + const client = createClient(proxyPort); try { return await operation(client); } finally { diff --git a/docs/integrations/app-connections/oracledb.mdx b/docs/integrations/app-connections/oracledb.mdx index 47aa33356f..d570af91d9 100644 --- a/docs/integrations/app-connections/oracledb.mdx +++ b/docs/integrations/app-connections/oracledb.mdx @@ -44,17 +44,95 @@ Infisical supports connecting to OracleDB using a database user. - You'll need the following information to create your Oracle Database connection: - - `host` - The hostname or IP address of your Oracle Database server - - `port` - The port number your Oracle Database server is listening on (default: 1521) - - `database` - The Oracle Service Name or SID (System Identifier) for the database you are connecting to. For example: `ORCL`, `FREEPDB1`, `XEPDB1` - - `username` - The user name of the login created in the steps above - - `password` - The user password of the login created in the steps above - - `sslCertificate` (optional) - The SSL certificate required for connection (if configured) + + + You'll need the following information to create your Oracle Database connection: + - `host` - The hostname or IP address of your Oracle Database server + - `port` - The port number your Oracle Database server is listening on (default: 1521) + - `database` - The Oracle Service Name or SID (System Identifier) for the database you are connecting to. For example: `ORCL`, `FREEPDB1`, `XEPDB1` + - `username` - The user name of the login created in the steps above + - `password` - The user password of the login created in the steps above + - `sslCertificate` (optional) - The SSL certificate required for connection (if configured) - - If you are self-hosting Infisical and intend to connect to an internal/private IP address, be sure to set the `ALLOW_INTERNAL_IP_CONNECTIONS` environment variable to `true`. - + + If you are self-hosting Infisical and intend to connect to an internal/private IP address, be sure to set the `ALLOW_INTERNAL_IP_CONNECTIONS` environment variable to `true`. + + + + + This configuration can only be done on self-hosted or dedicated instances of Infisical. + + + To connect to an Oracle Database using mTLS with a wallet, you'll need to modify your self-hosted Infisical instance's Docker image. + + 1. Place your Oracle Wallet folder, which must be named `wallet`, inside the `/backend` directory of your Infisical installation source code. + + 2. Add the following instructions to your `Dockerfile`. These instructions install the Oracle Instant Client and configure the environment for the wallet. Choose the tab that matches your server's architecture. + + + + ```Dockerfile +# Install dependencies for Oracle Instant Client +RUN apt-get update && apt-get install -y \ + libaio1 \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +# Download and install Oracle Instant Client for x86_64 +RUN wget https://download.oracle.com/otn_software/linux/instantclient/2326000/instantclient-basic-linux.x64-23.26.0.0.0.zip && \ + unzip instantclient-basic-linux.x64-23.26.0.0.0.zip -d /opt/oracle && \ + rm instantclient-basic-linux.x64-23.26.0.0.0.zip && \ + echo /opt/oracle/instantclient_23_26 > /etc/ld.so.conf.d/oracle-instantclient.conf && \ + ldconfig + +# Configure environment variables for Oracle Instant Client and the wallet +ENV LD_LIBRARY_PATH=/opt/oracle/instantclient_23_26:$LD_LIBRARY_PATH +ENV TNS_ADMIN=/app/wallet + +# Update sqlnet.ora to point to the correct wallet directory +RUN sed -i 's|DIRECTORY="?/network/admin"|DIRECTORY="/app/wallet"|g' /app/wallet/sqlnet.ora + ``` + + + ```Dockerfile +# Install dependencies for Oracle Instant Client +RUN apt-get update && apt-get install -y \ + libaio1t64 \ + unzip \ + && ln -s /lib/aarch64-linux-gnu/libaio.so.1t64 /lib/aarch64-linux-gnu/libaio.so.1 \ + && rm -rf /var/lib/apt/lists/* + +# Download and install Oracle Instant Client for ARM64 +RUN wget https://download.oracle.com/otn_software/linux/instantclient/2326000/instantclient-basic-linux.arm64-23.26.0.0.0.zip && \ + unzip instantclient-basic-linux.arm64-23.26.0.0.0.zip -d /opt/oracle && \ + rm instantclient-basic-linux.arm64-23.26.0.0.0.zip && \ + echo /opt/oracle/instantclient_23_26 > /etc/ld.so.conf.d/oracle-instantclient.conf && \ + ldconfig + +# Configure environment variables for Oracle Instant Client and the wallet +ENV LD_LIBRARY_PATH=/opt/oracle/instantclient_23_26:$LD_LIBRARY_PATH +ENV TNS_ADMIN=/app/wallet + +# Update sqlnet.ora to point to the correct wallet directory +RUN sed -i 's|DIRECTORY="?/network/admin"|DIRECTORY="/app/wallet"|g' /app/wallet/sqlnet.ora + ``` + + + + 3. After rebuilding and deploying your custom Docker image, you'll need the following information to create the connection in Infisical: + - `host` - The hostname or IP address of your Oracle Database server. This is required for the Infisical Gateway to function. + - `port` - The port number your Oracle Database server is listening on. This is required for the Infisical Gateway to function. + - `database` - The connection alias for your Oracle Database from your `tnsnames.ora` file. (e.g. `tyk9ovdixe1dvaj8_high`) + - `username` - The user name of the login created in the steps above. + - `password` - The user password of the login created in the steps above. + + Note that when a wallet is being used, any configured SSL settings are ignored. + + + If you are self-hosting Infisical and intend to connect to an internal/private IP address, be sure to set the `ALLOW_INTERNAL_IP_CONNECTIONS` environment variable to `true`. + + +