From 86530bcfbddbcb6b3c85d5b9828e11a9d6112838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Wed, 10 Sep 2025 10:50:39 +0100 Subject: [PATCH] chore: add proxy-aware regional STS authentication for AWS IAM DB auth (#10643) https://linear.app/unleash/issue/2-3875/add-proxy-aware-regional-sts-authentication-for-aws-iam-db-auth Adds proxy-aware regional STS authentication for AWS IAM DB auth. We also added optional support for explicit IAM role assumption through the `DATABASE_AWS_ROLE_ARN` environment variable. --- package.json | 1 + .../__snapshots__/create-config.test.ts.snap | 1 + src/lib/create-config.ts | 1 + src/lib/db/aws-iam.ts | 66 ++++-- src/lib/types/option.ts | 1 + yarn.lock | 207 +++++++++++++++++- 6 files changed, 254 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 0969d16f7f..fb10c3eb13 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "pg-connection-string": "^2.5.0", "pkginfo": "^0.4.1", "prom-client": "^15.0.0", + "proxy-agent": "^6.5.0", "sanitize-filename": "^1.6.3", "semver": "^7.6.3", "serve-favicon": "^2.5.0", diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 5d0fd2b1ae..2eb4fef725 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -34,6 +34,7 @@ exports[`should create default config 1`] = ` "applicationName": "unleash", "awsIamAuth": false, "awsRegion": undefined, + "awsRoleArn": undefined, "database": "unleash_db", "disableMigration": false, "driver": "postgres", diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index f04d4cf965..a642542864 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -268,6 +268,7 @@ const defaultDbOptions: WithOptional = applicationName: process.env.DATABASE_APPLICATION_NAME || 'unleash', awsIamAuth: parseEnvVarBoolean(process.env.DATABASE_AWS_IAM, false), awsRegion: process.env.AWS_REGION, + awsRoleArn: process.env.DATABASE_AWS_ROLE_ARN, }; const defaultSessionOption = (isEnterprise: boolean): ISessionOption => ({ diff --git a/src/lib/db/aws-iam.ts b/src/lib/db/aws-iam.ts index 97c50f48bc..7dfe7ee1c7 100644 --- a/src/lib/db/aws-iam.ts +++ b/src/lib/db/aws-iam.ts @@ -1,25 +1,63 @@ import { Signer } from '@aws-sdk/rds-signer'; +import { + fromTemporaryCredentials, + fromNodeProviderChain, +} from '@aws-sdk/credential-providers'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; +import { ProxyAgent } from 'proxy-agent'; import type { IDBOption } from '../types/option.js'; type PasswordResolver = () => Promise; export const getDBPasswordResolver = (db: IDBOption): PasswordResolver => { - if (db.awsIamAuth) { - if (!db.awsRegion) - throw new Error( - 'AWS_REGION is required when DATABASE_AWS_IAM=true', - ); + if (!db.awsIamAuth) return async () => db.password; + if (!db.awsRegion) + throw new Error('AWS_REGION is required when DATABASE_AWS_IAM=true'); - const signer = new Signer({ - region: db.awsRegion, - hostname: db.host, - port: db.port, - username: db.user, - }); - return async () => signer.getAuthToken(); - } + const needProxy = Boolean( + process.env.HTTPS_PROXY || + process.env.HTTP_PROXY || + process.env.NO_PROXY, + ); + const proxyAgent = needProxy ? new ProxyAgent() : undefined; - return async () => db.password; + const requestHandler = needProxy + ? new NodeHttpHandler({ + httpAgent: proxyAgent, + httpsAgent: proxyAgent, + }) + : undefined; + + const clientConfig = { + region: db.awsRegion, + endpoint: `https://sts.${db.awsRegion}.amazonaws.com`, + requestHandler, + }; + + const baseCreds = fromNodeProviderChain({ + clientConfig, + }); + + const credentials = db.awsRoleArn + ? fromTemporaryCredentials({ + params: { + RoleArn: db.awsRoleArn, + RoleSessionName: 'unleash-db-session', + }, + clientConfig, + masterCredentials: baseCreds, + }) + : baseCreds; + + const signer = new Signer({ + region: db.awsRegion, + hostname: db.host, + port: db.port, + username: db.user, + credentials, + }); + + return async () => signer.getAuthToken(); }; export const getDBPassword = (db: IDBOption): Promise => diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index d87a368665..41e0bfdb2b 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -40,6 +40,7 @@ export interface IDBOption { applicationName?: string; awsIamAuth?: boolean; awsRegion?: string; + awsRoleArn?: string; } export interface ISessionOption { diff --git a/yarn.lock b/yarn.lock index 0064dfb494..7e4b20050c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2354,6 +2354,13 @@ __metadata: languageName: node linkType: hard +"@tootallnate/quickjs-emscripten@npm:^0.23.0": + version: 0.23.0 + resolution: "@tootallnate/quickjs-emscripten@npm:0.23.0" + checksum: 10c0/2a939b781826fb5fd3edd0f2ec3b321d259d760464cf20611c9877205aaca3ccc0b7304dea68416baa0d568e82cd86b17d29548d1e5139fa3155a4a86a2b4b49 + languageName: node + linkType: hard + "@tsconfig/node10@npm:^1.0.7": version: 1.0.9 resolution: "@tsconfig/node10@npm:1.0.9" @@ -2927,6 +2934,13 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -3090,6 +3104,15 @@ __metadata: languageName: node linkType: hard +"ast-types@npm:^0.13.4": + version: 0.13.4 + resolution: "ast-types@npm:0.13.4" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10c0/3a1a409764faa1471601a0ad01b3aa699292991aa9c8a30c7717002cabdf5d98008e7b53ae61f6e058f757fc6ba965e147967a93c13e62692c907d79cfb245f8 + languageName: node + linkType: hard + "async@npm:^3.2.4": version: 3.2.6 resolution: "async@npm:3.2.6" @@ -3133,6 +3156,13 @@ __metadata: languageName: node linkType: hard +"basic-ftp@npm:^5.0.2": + version: 5.0.5 + resolution: "basic-ftp@npm:5.0.5" + checksum: 10c0/be983a3997749856da87b839ffce6b8ed6c7dbf91ea991d5c980d8add275f9f2926c19f80217ac3e7f353815be879371d636407ca72b038cea8cab30e53928a6 + languageName: node + linkType: hard + "bcrypt-pbkdf@npm:^1.0.2": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" @@ -3747,6 +3777,13 @@ __metadata: languageName: node linkType: hard +"data-uri-to-buffer@npm:^6.0.2": + version: 6.0.2 + resolution: "data-uri-to-buffer@npm:6.0.2" + checksum: 10c0/f76922bf895b3d7d443059ff278c9cc5efc89d70b8b80cd9de0aa79b3adc6d7a17948eefb8692e30398c43635f70ece1673d6085cc9eba2878dbc6c6da5292ac + languageName: node + linkType: hard + "date-fns@npm:^2.30.0": version: 2.30.0 resolution: "date-fns@npm:2.30.0" @@ -3916,6 +3953,17 @@ __metadata: languageName: node linkType: hard +"degenerator@npm:^5.0.0": + version: 5.0.1 + resolution: "degenerator@npm:5.0.1" + dependencies: + ast-types: "npm:^0.13.4" + escodegen: "npm:^2.1.0" + esprima: "npm:^4.0.1" + checksum: 10c0/e48d8a651edeb512a648711a09afec269aac6de97d442a4bb9cf121a66877e0eec11b9727100a10252335c0666ae1c84a8bc1e3a3f47788742c975064d2c7b1c + languageName: node + linkType: hard + "del-cli@npm:6.0.0": version: 6.0.0 resolution: "del-cli@npm:6.0.0" @@ -4328,6 +4376,24 @@ __metadata: languageName: node linkType: hard +"escodegen@npm:^2.1.0": + version: 2.1.0 + resolution: "escodegen@npm:2.1.0" + dependencies: + esprima: "npm:^4.0.1" + estraverse: "npm:^5.2.0" + esutils: "npm:^2.0.2" + source-map: "npm:~0.6.1" + dependenciesMeta: + source-map: + optional: true + bin: + escodegen: bin/escodegen.js + esgenerate: bin/esgenerate.js + checksum: 10c0/e1450a1f75f67d35c061bf0d60888b15f62ab63aef9df1901cffc81cffbbb9e8b3de237c5502cf8613a017c1df3a3003881307c78835a1ab54d8c8d2206e01d3 + languageName: node + linkType: hard + "esm@npm:^3.2.25": version: 3.2.25 resolution: "esm@npm:3.2.25" @@ -4347,6 +4413,23 @@ __metadata: languageName: node linkType: hard +"esprima@npm:^4.0.1": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + "estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -4356,6 +4439,13 @@ __metadata: languageName: node linkType: hard +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + "etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" @@ -4908,6 +4998,17 @@ __metadata: languageName: node linkType: hard +"get-uri@npm:^6.0.1": + version: 6.0.5 + resolution: "get-uri@npm:6.0.5" + dependencies: + basic-ftp: "npm:^5.0.2" + data-uri-to-buffer: "npm:^6.0.2" + debug: "npm:^4.3.4" + checksum: 10c0/c7ff5d5d55de53d23ecce7c5108cc3ed0db1174db43c9aa15506d640283d36ee0956fd8ba1fc50b06a718466cc85794ae9d8860193f91318afe846e3e7010f3a + languageName: node + linkType: hard + "getopts@npm:2.3.0": version: 2.3.0 resolution: "getopts@npm:2.3.0" @@ -5146,7 +5247,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2": +"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1, http-proxy-agent@npm:^7.0.2": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" dependencies: @@ -5176,6 +5277,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + "human-signals@npm:^5.0.0": version: 5.0.0 resolution: "human-signals@npm:5.0.0" @@ -5952,6 +6063,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^7.14.1": + version: 7.18.3 + resolution: "lru-cache@npm:7.18.3" + checksum: 10c0/b3a452b491433db885beed95041eb104c157ef7794b9c9b4d647be503be91769d11206bb573849a16b4cc0d03cbd15ffd22df7960997788b74c1d399ac7a4fed + languageName: node + linkType: hard + "lru-cache@npm:^9.1.1": version: 9.1.1 resolution: "lru-cache@npm:9.1.1" @@ -6489,6 +6607,13 @@ __metadata: languageName: node linkType: hard +"netmask@npm:^2.0.2": + version: 2.0.2 + resolution: "netmask@npm:2.0.2" + checksum: 10c0/cafd28388e698e1138ace947929f842944d0f1c0b87d3fa2601a61b38dc89397d33c0ce2c8e7b99e968584b91d15f6810b91bef3f3826adf71b1833b61d4bf4f + languageName: node + linkType: hard + "next-tick@npm:1, next-tick@npm:^1.1.0": version: 1.1.0 resolution: "next-tick@npm:1.1.0" @@ -6765,6 +6890,32 @@ __metadata: languageName: node linkType: hard +"pac-proxy-agent@npm:^7.1.0": + version: 7.2.0 + resolution: "pac-proxy-agent@npm:7.2.0" + dependencies: + "@tootallnate/quickjs-emscripten": "npm:^0.23.0" + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + get-uri: "npm:^6.0.1" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.6" + pac-resolver: "npm:^7.0.1" + socks-proxy-agent: "npm:^8.0.5" + checksum: 10c0/0265c17c9401c2ea735697931a6553a0c6d8b20c4d7d4e3b3a0506080ba69a8d5ad656e2a6be875411212e2b6ed7a4d9526dd3997e08581fdfb1cbcad454c296 + languageName: node + linkType: hard + +"pac-resolver@npm:^7.0.1": + version: 7.0.1 + resolution: "pac-resolver@npm:7.0.1" + dependencies: + degenerator: "npm:^5.0.0" + netmask: "npm:^2.0.2" + checksum: 10c0/5f3edd1dd10fded31e7d1f95776442c3ee51aa098c28b74ede4927d9677ebe7cebb2636750c24e945f5b84445e41ae39093d3a1014a994e5ceb9f0b1b88ebff5 + languageName: node + linkType: hard + "package-json-from-dist@npm:^1.0.0": version: 1.0.0 resolution: "package-json-from-dist@npm:1.0.0" @@ -7147,6 +7298,22 @@ __metadata: languageName: node linkType: hard +"proxy-agent@npm:^6.5.0": + version: 6.5.0 + resolution: "proxy-agent@npm:6.5.0" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + http-proxy-agent: "npm:^7.0.1" + https-proxy-agent: "npm:^7.0.6" + lru-cache: "npm:^7.14.1" + pac-proxy-agent: "npm:^7.1.0" + proxy-from-env: "npm:^1.1.0" + socks-proxy-agent: "npm:^8.0.5" + checksum: 10c0/7fd4e6f36bf17098a686d4aee3b8394abfc0b0537c2174ce96b0a4223198b9fafb16576c90108a3fcfc2af0168bd7747152bfa1f58e8fee91d3780e79aab7fd8 + languageName: node + linkType: hard + "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" @@ -7885,6 +8052,17 @@ __metadata: languageName: node linkType: hard +"socks-proxy-agent@npm:^8.0.5": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + "socks@npm:^2.7.1": version: 2.7.1 resolution: "socks@npm:2.7.1" @@ -7895,6 +8073,16 @@ __metadata: languageName: node linkType: hard +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + "source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" @@ -7912,7 +8100,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0": +"source-map@npm:^0.6.0, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -8459,6 +8647,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.0.1, tslib@npm:^2.6.2": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + "tslib@npm:^2.1.0": version: 2.6.2 resolution: "tslib@npm:2.6.2" @@ -8466,13 +8661,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.6.2": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 - languageName: node - linkType: hard - "tsscmp@npm:1.0.6": version: 1.0.6 resolution: "tsscmp@npm:1.0.6" @@ -8731,6 +8919,7 @@ __metadata: pg-connection-string: "npm:^2.5.0" pkginfo: "npm:^0.4.1" prom-client: "npm:^15.0.0" + proxy-agent: "npm:^6.5.0" proxyquire: "npm:2.1.3" sanitize-filename: "npm:^1.6.3" semver: "npm:^7.6.3"