From a9755d14df37443b04baaa71e40e10e2e83642e0 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Sat, 6 Jun 2026 16:48:58 +0530 Subject: [PATCH 1/4] feat: authenticate to Redis via AWS ElastiCache secret (Secrets Manager) Mirror the silo app's ELASTICACHE_SECRET_ARN pattern. When set (and CACHE_ENGINE=redis), the Redis auth token (and optionally host/port) is fetched from AWS Secrets Manager and injected into the connection options. A background timer re-fetches the secret every AWS_SECRET_CACHE_TTL seconds and rebuilds the client when the token rotates, so reconnections use fresh credentials without a restart. - New lib/aws-secrets.js: TTL-cached getSecret(), lazy-imports the AWS SDK - Rework lib/cache-engines/redis.js: secret resolution, build options (standard + cluster), refresh timer; set/get unchanged - Config/IFRAMELY_REDIS_HOST/PORT/TLS take precedence; TLS not forced on - Configurable secret keys (defaults match silo so the same secret works) - Add @aws-sdk/client-secrets-manager dependency (lazy-imported) - Document new env vars in config.loader.js, config.local.js.SAMPLE, SETUP.md Co-Authored-By: Claude Opus 4.8 (1M context) --- config.loader.js | 9 +++ config.local.js.SAMPLE | 22 ++++++ docs/SETUP.md | 4 ++ lib/aws-secrets.js | 40 +++++++++++ lib/cache-engines/redis.js | 135 ++++++++++++++++++++++++++++++++++--- package.json | 1 + 6 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 lib/aws-secrets.js diff --git a/config.loader.js b/config.loader.js index c79c6a4b9..9f6869e77 100644 --- a/config.loader.js +++ b/config.loader.js @@ -22,6 +22,15 @@ globalConfig = globalConfig && globalConfig.default; // IFRAMELY_REDIS_PASSWORD password (optional) // IFRAMELY_REDIS_TLS true | false (enables TLS socket) // IFRAMELY_REDIS_MODE standard | cluster (default: standard) +// +// AWS ElastiCache auth via Secrets Manager (used when CACHE_ENGINE=redis). +// Handled in lib/cache-engines/redis.js, not here — listed for reference: +// ELASTICACHE_SECRET_ARN Secrets Manager ARN with Redis creds (enables it) +// AWS_REGION region for Secrets Manager (default: us-east-1) +// AWS_SECRET_CACHE_TTL secret cache + refresh interval, seconds (default: 300) +// REDIS_AUTH_TOKEN_KEY JSON key for auth token (default: REDIS_AUTH_TOKEN) +// REDIS_HOST_KEY JSON key for host (default: REDIS_HOST) +// REDIS_PORT_KEY JSON key for port (default: REDIS_PORT) // --------------------------------------------------------------------------- var envOverrides = {}; diff --git a/config.local.js.SAMPLE b/config.local.js.SAMPLE index 29bbc0532..fe1580133 100644 --- a/config.local.js.SAMPLE +++ b/config.local.js.SAMPLE @@ -102,6 +102,28 @@ export default { }, */ + /* + // AWS ElastiCache auth via Secrets Manager (Plane fork extension). + // + // When CACHE_ENGINE is 'redis' and the ELASTICACHE_SECRET_ARN env var is set, + // the Redis auth token (and optionally host/port) is fetched from AWS Secrets + // Manager and injected into the connection options above. A background timer + // re-fetches the secret and rebuilds the client when the token rotates. + // + // Configure via environment variables (no config-file changes needed): + // + // ELASTICACHE_SECRET_ARN AWS Secrets Manager ARN with the Redis creds (enables this feature) + // AWS_REGION region for Secrets Manager (default: us-east-1) + // AWS_SECRET_CACHE_TTL secret cache + refresh interval, sec (default: 300) + // REDIS_AUTH_TOKEN_KEY JSON key for the auth token (default: REDIS_AUTH_TOKEN) + // REDIS_HOST_KEY JSON key for the host (default: REDIS_HOST) + // REDIS_PORT_KEY JSON key for the port (default: REDIS_PORT) + // + // host/port from IFRAMELY_REDIS_HOST/PORT or REDIS_OPTIONS take precedence over + // the secret; TLS is controlled by IFRAMELY_REDIS_TLS (not forced on). + // AWS credentials use the SDK default chain (IRSA / Pod Identity / keys / IMDS). + */ + /* // Memcached options. See https://github.com/3rd-Eden/node-memcached#server-locations MEMCACHED_OPTIONS: { diff --git a/docs/SETUP.md b/docs/SETUP.md index 6e8335bf1..4b654d2a3 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -57,6 +57,10 @@ In your local config file, define caching parameters: Valid cache engine values are `no-cache`, `node-cache` (default), `redis` and `memcached`. For Redis and Memcached, the connection options are also required. See sample config file. +### AWS ElastiCache auth (Secrets Manager) + +For AWS ElastiCache with an auth token, set `CACHE_ENGINE: 'redis'` and the `ELASTICACHE_SECRET_ARN` environment variable. On startup the Redis auth token (and optionally host/port) is read from AWS Secrets Manager and injected into the Redis connection; a background timer re-fetches the secret every `AWS_SECRET_CACHE_TTL` seconds and rebuilds the client when the token rotates, so no restart is needed. Supported env vars: `ELASTICACHE_SECRET_ARN`, `AWS_REGION` (default `us-east-1`), `AWS_SECRET_CACHE_TTL` (default `300`), and the configurable secret-key names `REDIS_AUTH_TOKEN_KEY` / `REDIS_HOST_KEY` / `REDIS_PORT_KEY`. AWS credentials use the SDK default chain (IRSA / Pod Identity / static keys / IMDS). TLS is controlled by `IFRAMELY_REDIS_TLS` and is not forced on. See the sample config file for details. + ## Run Server diff --git a/lib/aws-secrets.js b/lib/aws-secrets.js new file mode 100644 index 000000000..64544631e --- /dev/null +++ b/lib/aws-secrets.js @@ -0,0 +1,40 @@ + import log from '../logging.js'; + + // --------------------------------------------------------------------------- + // AWS Secrets Manager helper (Plane fork extension). + // + // Port of plane-ee/apps/silo/src/lib/aws-secrets.ts to plain ESM. Fetches a + // secret by ARN and TTL-caches the parsed JSON so repeated lookups (e.g. the + // periodic credential refresh in the redis cache engine) don't hammer the API. + // + // The AWS SDK is lazy-imported so deployments that never set a *_SECRET_ARN + // do not need `@aws-sdk/client-secrets-manager` installed/loaded. + // --------------------------------------------------------------------------- + + const secretCache = new Map(); // key: `${arn}:${region}` -> { value, fetchedAt } + + export async function getSecret(secretArn, region, forceRefresh = false) { + + const cacheTtl = parseInt(process.env.AWS_SECRET_CACHE_TTL || '300', 10) * 1000; + const key = secretArn + ':' + region; + const now = Date.now(); + + if (!forceRefresh && secretCache.has(key)) { + const entry = secretCache.get(key); + if (now - entry.fetchedAt < cacheTtl) { + return { ...entry.value }; + } + } + + const { SecretsManagerClient, GetSecretValueCommand } = + await import('@aws-sdk/client-secrets-manager'); + + const client = new SecretsManagerClient({ region }); + const response = await client.send(new GetSecretValueCommand({ SecretId: secretArn })); + const value = JSON.parse(response.SecretString || '{}'); + + secretCache.set(key, { value, fetchedAt: now }); + log(' -- Secrets Manager: refreshed secret ' + secretArn); + + return { ...value }; + }; diff --git a/lib/cache-engines/redis.js b/lib/cache-engines/redis.js index e59343485..cf3168ebb 100644 --- a/lib/cache-engines/redis.js +++ b/lib/cache-engines/redis.js @@ -1,16 +1,135 @@ import log from '../../logging.js'; import CONFIG from '../../config.loader.js'; + // --------------------------------------------------------------------------- + // Redis cache engine. + // + // Standard mode uses the `redis` client; cluster mode uses `redis-clustr`. + // + // AWS ElastiCache auth (Plane fork extension): + // When ELASTICACHE_SECRET_ARN is set, the Redis auth token (and optionally + // host/port) is fetched from AWS Secrets Manager and injected into the + // connection options. A background timer re-fetches the secret every + // AWS_SECRET_CACHE_TTL seconds and rebuilds the client when the token + // rotates, so reconnections use fresh credentials without a restart. + // Mirrors plane-ee/apps/silo/src/env.ts resolveRedisUrl()/resolveSecrets(). + // --------------------------------------------------------------------------- + var client; + var currentToken; + + // Resolve the auth token (and host/port) from AWS Secrets Manager. + // Returns null when no ELASTICACHE_SECRET_ARN is configured. + async function resolveSecretValues() { + if (!process.env.ELASTICACHE_SECRET_ARN) { + return null; + } + + var region = process.env.AWS_REGION || 'us-east-1'; + var { getSecret } = await import('../aws-secrets.js'); + var secret = await getSecret(process.env.ELASTICACHE_SECRET_ARN, region, true); + + var tokenKey = process.env.REDIS_AUTH_TOKEN_KEY || 'REDIS_AUTH_TOKEN'; + var hostKey = process.env.REDIS_HOST_KEY || 'REDIS_HOST'; + var portKey = process.env.REDIS_PORT_KEY || 'REDIS_PORT'; + + return { + token: typeof secret[tokenKey] === 'string' ? secret[tokenKey] : undefined, + host: typeof secret[hostKey] === 'string' ? secret[hostKey] : undefined, + port: secret[portKey] !== undefined ? Number(secret[portKey]) : undefined, + }; + } + + // Merge resolved secret values into the configured connection options. + // Existing config / IFRAMELY_REDIS_* values take precedence for host/port/TLS; + // only the auth token is always taken from the secret. + function buildOptions(secretVals) { + + if (CONFIG.REDIS_MODE === 'cluster') { + var clusterOptions = { ...(CONFIG.REDIS_CLUSTER_OPTIONS || {}) }; + if (secretVals && secretVals.token) { + // redis-clustr forwards `redisOptions` to node_redis v2's + // createClient (see redis-clustr@1.7.0 -> redis ^2.6.0). node_redis + // v2 uses `auth_pass`; `password` is a newer alias, so set both. + clusterOptions.redisOptions = { + ...(clusterOptions.redisOptions || {}), + auth_pass: secretVals.token, + password: secretVals.token, + }; + } + return clusterOptions; + } + + var options = { ...(CONFIG.REDIS_OPTIONS || {}) }; + var socket = { ...(options.socket || {}) }; + + if (secretVals) { + if (secretVals.token) { + options.password = secretVals.token; + } + // Fill host/port from the secret only when not already provided via + // config or IFRAMELY_REDIS_HOST/PORT (do not force TLS here). + if (socket.host === undefined && secretVals.host !== undefined) { + socket.host = secretVals.host; + } + if (socket.port === undefined && secretVals.port !== undefined) { + socket.port = secretVals.port; + } + } + + options.socket = socket; + return options; + } + + async function createAndConnect() { + var secretVals = await resolveSecretValues(); + var options = buildOptions(secretVals); - if (CONFIG.REDIS_MODE === 'cluster') { - const pkg = await import('redis-clustr'); - const RedisClustr = pkg.default; - client = new RedisClustr(CONFIG.REDIS_CLUSTER_OPTIONS); - } else { - var pkg = await import('redis'); - client = pkg.createClient(CONFIG.REDIS_OPTIONS); - await client.connect(); + var newClient; + if (CONFIG.REDIS_MODE === 'cluster') { + const pkg = await import('redis-clustr'); + const RedisClustr = pkg.default; + newClient = new RedisClustr(options); + } else { + var pkg = await import('redis'); + newClient = pkg.createClient(options); + await newClient.connect(); + } + + currentToken = secretVals && secretVals.token; + return newClient; + } + + // Initial connection. + client = await createAndConnect(); + + // Background credential refresh: rebuild the client when the token rotates. + if (process.env.ELASTICACHE_SECRET_ARN) { + var ttlMs = parseInt(process.env.AWS_SECRET_CACHE_TTL || '300', 10) * 1000; + if (ttlMs > 0) { + var refreshTimer = setInterval(async function () { + try { + var secretVals = await resolveSecretValues(); + var newToken = secretVals && secretVals.token; + if (newToken && newToken !== currentToken) { + var oldClient = client; + client = await createAndConnect(); + log(' -- Redis: rebuilt client after credential rotation'); + try { + if (oldClient && oldClient.quit) { + await oldClient.quit(); + } + } catch (quitErr) { + log(' -- Redis: error closing old client ' + quitErr); + } + } + } catch (err) { + log(' -- Redis: failed to refresh credentials ' + err); + } + }, ttlMs); + // Allow the process to exit even if the timer is still running. + refreshTimer.unref(); + } } export async function set(key, data, options) { diff --git a/package.json b/package.json index 2935f3b41..ab7eb5da3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "license": "MIT", "dependencies": { "@adobe/fetch": "^4.1.11", + "@aws-sdk/client-secrets-manager": "^3.700.0", "async": "^3.2.4", "cheerio": "^1.1.2", "cookie": "^1.0.2", From 596a585a3814fa8e8bda0bffab8da17922421fde Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Sun, 7 Jun 2026 14:08:20 +0530 Subject: [PATCH 2/4] chore: update pnpm-lock.yaml for @aws-sdk/client-secrets-manager The Dockerfile installs with --frozen-lockfile, so the new dependency must be present in the lockfile. Co-Authored-By: Claude Opus 4.8 (1M context) --- pnpm-lock.yaml | 412 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa947b0c2..df2b917b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ importers: '@adobe/fetch': specifier: ^4.1.11 version: 4.1.11 + '@aws-sdk/client-secrets-manager': + specifier: ^3.700.0 + version: 3.1063.0 async: specifier: ^3.2.4 version: 3.2.4 @@ -132,6 +135,91 @@ packages: resolution: {integrity: sha512-Zak2kPJuIdg9UQQfUgNm848vRAg2pdOqYYU+7DkCYWO+SgZiMV+qy99BpO1geDiP2rQ2M7JH5oNXRTEvEWglRQ==} engines: {node: '>=14.16'} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-secrets-manager@3.1063.0': + resolution: {integrity: sha512-oP3/vHpo93rb/0JWhNcLA7fVbV1I6fBV9DrprScf71t3EVzwMyqLnVuv+5q2/3o32sCZ58YouZmqL0CqczyQzA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.18': + resolution: {integrity: sha512-JDYCPI0j7zGrzXTDFsLB346cxss7J/AxH7+O0MzWlqppJBEyB9Qe6TQXRL6iwLUo/xZkNv9KFmBL2hqElmwW0g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.44': + resolution: {integrity: sha512-3hKJVrZ7bqXzDAXCQp+OaQ1ASN+vWstaNuEH418wQVl//cRZhqhfR9Bjk1qIWmgUGe8/D3gdO73PgidRj378EQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.46': + resolution: {integrity: sha512-VhwC9pGAZHhiQ2xSViyOPDFqvr9aRxGCAXZtADsUhU3R65nad7y//CwynE6mQnWNR+suRlqE79W36IVayL+m1g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.50': + resolution: {integrity: sha512-09Xi6ovxiK42+De/qBGF71sT5F2bWgYM+1fFyDwSOpy1xpsQ5R/naIu7MVDpH6Dic36QNc8dAv4KADtMGK2JYg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.49': + resolution: {integrity: sha512-EfJF/1Fh9mI4pZyoheU2RY9xUhTcugIZNkD63+orXMkYj/QXacJNbKVDUK90Yv5hE+aX+rt9J/EZ9Qr3vKOa7g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.52': + resolution: {integrity: sha512-7QX+PbyiWBEOVipJq8Nke/TqXT6lAPLE7fvTaopa39/IVWuLfS+Fzdy71sZJONf/mLGgmtj6aU17+REw3+aRrw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.44': + resolution: {integrity: sha512-V+UUhZpRP7QDRhi+qgBDisM9tUBnYmMje8Bk77A6MZsfeGeGdMsQXmaHP1CDYFcept0o/Rz5g2Y0TMeVlG9dzg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.49': + resolution: {integrity: sha512-9QqOYGuh5tZ76OzaT68kwI78AH+5lS/uZGGvkfxb3fc8FzRrIz2jOufNTliEBEeSAwmgK2rWLNsK+IB3zbtNPA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.49': + resolution: {integrity: sha512-IYx1lN38MnnPXv+NBLpuATu0cZakbZ321TAfjW+aVkw7HIJF38YnEwdeEO55MSl3pl7hIX1IvvnD6EmnAzmAJw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.17': + resolution: {integrity: sha512-lDRgraoTfKRawUyc176Ow93mrNrOho/x+EoK4C+lKU+vKkHWhNhzvSMVAx0WEJUJoeQxxDN5ZdKMfiGEyNejig==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.32': + resolution: {integrity: sha512-llvApLcsWtmRFhG2wT3WIp1CmDeRaIYutqty1ZZXoMzK7TiJ6MOLOimk9eXUS8PwgG4ew4pa4QAbt0lfhn++1w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1063.0': + resolution: {integrity: sha512-nYDaWWdzjKiDP5xj8k4oUgcYd4WPgzfAOgdU5vJsaqH/07Dfvm7ffisHCFJ+NEl7kUC9JEIUxh0kznvenbo3NQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.11': + resolution: {integrity: sha512-YjS0qFuECClRh4qhEyW8XagW0fwEPBeZ1cfsW/gU73Kh/ExFILxbzxOfPCmzF/2DwEvhvsHYt0b0qnvStwKYrg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.6': + resolution: {integrity: sha512-ZfHjfwSzeXj+Lg9AK5ZNmeDkXev6V+w2tn1t4kgDdRtUaRCthepTQiFwbD06EF9oNGH4LaLg+Mb6U16Ypv5bSw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.28': + resolution: {integrity: sha512-lI/l3c/vPvsxmspzV63NfS3x9q4CkMmdhJy4QiM+NThAufVkDvi/PZZQ6xETnICL0UD7jI808pY83gllf86RFg==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@mongodb-js/saslprep@1.3.2': resolution: {integrity: sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==} @@ -139,6 +227,9 @@ packages: resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} + '@nodable/entities@2.1.1': + resolution: {integrity: sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==} + '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -178,6 +269,42 @@ packages: resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==} engines: {node: '>=18'} + '@smithy/core@3.24.6': + resolution: {integrity: sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.3.8': + resolution: {integrity: sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.4.6': + resolution: {integrity: sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/node-http-handler@4.7.7': + resolution: {integrity: sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.4.6': + resolution: {integrity: sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.14.3': + resolution: {integrity: sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -238,6 +365,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} @@ -555,6 +685,13 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + + fast-xml-parser@5.7.3: + resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} + hasBin: true + feedparser@2.2.10: resolution: {integrity: sha512-WoAOooa61V8/xuKMi2pEtK86qQ3ZH/M72EEGdqlOTxxb3m6ve1NPvZcmPFs3wEDfcBbFLId2GqZ4YjsYi+h1xA==} engines: {node: '>= 10.18.1'} @@ -634,6 +771,7 @@ packages: glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -792,6 +930,7 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. lodash.has@4.5.2: resolution: {integrity: sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==} @@ -1015,6 +1154,7 @@ packages: osenv@0.1.5: resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==} + deprecated: This package is no longer supported. p-cancelable@4.0.1: resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==} @@ -1048,6 +1188,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -1221,6 +1365,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strnum@2.3.0: + resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + superagent@10.2.3: resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} engines: {node: '>=14.18.0'} @@ -1245,6 +1392,9 @@ packages: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-fest@4.32.0: resolution: {integrity: sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==} engines: {node: '>=16'} @@ -1305,6 +1455,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -1338,12 +1492,200 @@ snapshots: transitivePeerDependencies: - supports-color + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.11 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.11 + '@aws-sdk/util-locate-window': 3.965.6 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.11 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.11 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-secrets-manager@3.1063.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.18 + '@aws-sdk/credential-provider-node': 3.972.52 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/fetch-http-handler': 5.4.6 + '@smithy/node-http-handler': 4.7.7 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/core@3.974.18': + dependencies: + '@aws-sdk/types': 3.973.11 + '@aws-sdk/xml-builder': 3.972.28 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/core': 3.24.6 + '@smithy/signature-v4': 5.4.6 + '@smithy/types': 4.14.3 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.44': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.46': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/fetch-http-handler': 5.4.6 + '@smithy/node-http-handler': 4.7.7 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.50': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/credential-provider-env': 3.972.44 + '@aws-sdk/credential-provider-http': 3.972.46 + '@aws-sdk/credential-provider-login': 3.972.49 + '@aws-sdk/credential-provider-process': 3.972.44 + '@aws-sdk/credential-provider-sso': 3.972.49 + '@aws-sdk/credential-provider-web-identity': 3.972.49 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/credential-provider-imds': 4.3.8 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.49': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.52': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.44 + '@aws-sdk/credential-provider-http': 3.972.46 + '@aws-sdk/credential-provider-ini': 3.972.50 + '@aws-sdk/credential-provider-process': 3.972.44 + '@aws-sdk/credential-provider-sso': 3.972.49 + '@aws-sdk/credential-provider-web-identity': 3.972.49 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/credential-provider-imds': 4.3.8 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.44': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.49': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/token-providers': 3.1063.0 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.49': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.17': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.18 + '@aws-sdk/signature-v4-multi-region': 3.996.32 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/fetch-http-handler': 5.4.6 + '@smithy/node-http-handler': 4.7.7 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.32': + dependencies: + '@aws-sdk/types': 3.973.11 + '@smithy/signature-v4': 5.4.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1063.0': + dependencies: + '@aws-sdk/core': 3.974.18 + '@aws-sdk/nested-clients': 3.997.17 + '@aws-sdk/types': 3.973.11 + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/types@3.973.11': + dependencies: + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.6': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.28': + dependencies: + '@smithy/types': 4.14.3 + fast-xml-parser: 5.7.3 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + '@mongodb-js/saslprep@1.3.2': dependencies: sparse-bitfield: 3.0.3 '@noble/hashes@1.8.0': {} + '@nodable/entities@2.1.1': {} + '@paralleldrive/cuid2@2.3.1': dependencies: '@noble/hashes': 1.8.0 @@ -1378,6 +1720,54 @@ snapshots: '@sindresorhus/is@7.0.1': {} + '@smithy/core@3.24.6': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.3.8': + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.4.6': + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/node-http-handler@4.7.7': + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@smithy/signature-v4@5.4.6': + dependencies: + '@smithy/core': 3.24.6 + '@smithy/types': 4.14.3 + tslib: 2.8.1 + + '@smithy/types@4.14.3': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 @@ -1435,6 +1825,8 @@ snapshots: boolbase@1.0.0: {} + bowser@2.14.1: {} + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 @@ -1756,6 +2148,18 @@ snapshots: fast-safe-stringify@2.1.1: {} + fast-xml-builder@1.2.0: + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + + fast-xml-parser@5.7.3: + dependencies: + '@nodable/entities': 2.1.1 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.3.0 + feedparser@2.2.10: dependencies: addressparser: 1.0.1 @@ -2270,6 +2674,8 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.5.0: {} + path-is-absolute@1.0.1: {} path-scurry@2.0.1: @@ -2489,6 +2895,8 @@ snapshots: strip-json-comments@3.1.1: {} + strnum@2.3.0: {} + superagent@10.2.3: dependencies: component-emitter: 1.3.1 @@ -2524,6 +2932,8 @@ snapshots: dependencies: punycode: 2.3.0 + tslib@2.8.1: {} + type-fest@4.32.0: {} type-is@2.0.1: @@ -2571,6 +2981,8 @@ snapshots: wrappy@1.0.2: {} + xml-naming@0.1.0: {} + y18n@5.0.8: {} yallist@4.0.0: {} From e933d7f059d2856aad7b4a8c538d081a0695b343 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Sun, 7 Jun 2026 14:56:15 +0530 Subject: [PATCH 3/4] feat: make cluster worker memory/restart limits configurable via env The default per-worker memory cap (CLUSTER_WORKER_RESTART_ON_MEMORY_USED, 120 MB) is below the ~122 MB a worker uses just loading domains+plugins, causing an immediate restart loop. Expose it (and the periodic restart interval) via IFRAMELY_WORKER_MAX_MEMORY_MB / IFRAMELY_WORKER_RESTART_PERIOD_SEC. Co-Authored-By: Claude Opus 4.8 (1M context) --- config.loader.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/config.loader.js b/config.loader.js index 9f6869e77..02569d6d7 100644 --- a/config.loader.js +++ b/config.loader.js @@ -31,6 +31,10 @@ globalConfig = globalConfig && globalConfig.default; // REDIS_AUTH_TOKEN_KEY JSON key for auth token (default: REDIS_AUTH_TOKEN) // REDIS_HOST_KEY JSON key for host (default: REDIS_HOST) // REDIS_PORT_KEY JSON key for port (default: REDIS_PORT) +// +// Cluster worker tuning +// IFRAMELY_WORKER_MAX_MEMORY_MB per-worker memory before restart, MB (default: 120) +// IFRAMELY_WORKER_RESTART_PERIOD_SEC periodic worker restart interval, seconds (default: 28800) // --------------------------------------------------------------------------- var envOverrides = {}; @@ -91,4 +95,22 @@ if (effectiveEngine === 'redis' && ( envOverrides.REDIS_OPTIONS = redisOptions; } +// --- Cluster worker tuning --- +// The default per-worker memory cap (config.js CLUSTER_WORKER_RESTART_ON_MEMORY_USED) +// is 120 MB, which a freshly-booted worker can exceed just loading domains+plugins, +// causing a restart loop. Allow raising it (and the periodic restart) via env. +if (process.env.IFRAMELY_WORKER_MAX_MEMORY_MB) { + var maxMemMb = parseInt(process.env.IFRAMELY_WORKER_MAX_MEMORY_MB, 10); + if (!isNaN(maxMemMb)) { + envOverrides.CLUSTER_WORKER_RESTART_ON_MEMORY_USED = maxMemMb * 1024 * 1024; + } +} + +if (process.env.IFRAMELY_WORKER_RESTART_PERIOD_SEC) { + var restartSec = parseInt(process.env.IFRAMELY_WORKER_RESTART_PERIOD_SEC, 10); + if (!isNaN(restartSec)) { + envOverrides.CLUSTER_WORKER_RESTART_ON_PERIOD = restartSec * 1000; + } +} + export default {...iframelyConfig, ...globalConfig, ...envOverrides}; From 4645bb29e28ac93424571882d0225eb78037aa3a Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Sun, 7 Jun 2026 15:24:53 +0530 Subject: [PATCH 4/4] fix: only resolve ElastiCache secret when IRSA/Pod Identity is present Previously the AWS Secrets Manager fetch was gated solely on ELASTICACHE_SECRET_ARN, so any non-EKS deployment with that var set would still attempt to reach Secrets Manager (and fail/fall back). Mirror silo's resolveSecrets() gate: skip secret resolution unless IRSA or EKS Pod Identity credentials are detected via one of: - AWS_ROLE_ARN / AWS_WEB_IDENTITY_TOKEN_FILE (IRSA) - AWS_CONTAINER_CREDENTIALS_FULL_URI / AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE (EKS Pod Identity) Static AWS_ACCESS_KEY_ID creds are intentionally not accepted. Log a one-time warning when the ARN is set but no managed credentials are present. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/cache-engines/redis.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/cache-engines/redis.js b/lib/cache-engines/redis.js index cf3168ebb..174c289a0 100644 --- a/lib/cache-engines/redis.js +++ b/lib/cache-engines/redis.js @@ -18,10 +18,31 @@ var client; var currentToken; + // True only when the pod has IRSA or EKS Pod Identity credentials available. + // Mirrors silo's resolveSecrets() gate (env.ts) so that non-EKS deployments + // never attempt to reach AWS Secrets Manager even if ELASTICACHE_SECRET_ARN + // happens to be set in the environment. Static AWS_ACCESS_KEY_ID creds are + // intentionally NOT accepted here. + function hasAwsManagedCredentials() { + return Boolean( + (process.env.AWS_ROLE_ARN || '').trim() || // IRSA + process.env.AWS_WEB_IDENTITY_TOKEN_FILE || // IRSA (token file) + process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI || // EKS Pod Identity + process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE // EKS Pod Identity (token file) + ); + } + + // True when secret resolution should run at all: ARN configured AND the pod + // has IRSA / Pod Identity credentials to read it with. + function secretResolutionEnabled() { + return Boolean(process.env.ELASTICACHE_SECRET_ARN) && hasAwsManagedCredentials(); + } + // Resolve the auth token (and host/port) from AWS Secrets Manager. - // Returns null when no ELASTICACHE_SECRET_ARN is configured. + // Returns null when secret resolution is not enabled (no ARN, or no + // IRSA/Pod Identity credentials present). async function resolveSecretValues() { - if (!process.env.ELASTICACHE_SECRET_ARN) { + if (!secretResolutionEnabled()) { return null; } @@ -100,11 +121,16 @@ return newClient; } + // Surface a misconfiguration: ARN set but no managed credentials to use it. + if (process.env.ELASTICACHE_SECRET_ARN && !hasAwsManagedCredentials()) { + log(' -- Redis: ELASTICACHE_SECRET_ARN is set but no IRSA / EKS Pod Identity credentials detected — skipping AWS Secrets Manager.'); + } + // Initial connection. client = await createAndConnect(); // Background credential refresh: rebuild the client when the token rotates. - if (process.env.ELASTICACHE_SECRET_ARN) { + if (secretResolutionEnabled()) { var ttlMs = parseInt(process.env.AWS_SECRET_CACHE_TTL || '300', 10) * 1000; if (ttlMs > 0) { var refreshTimer = setInterval(async function () {