Skip to content

feat: authenticate to Redis via AWS ElastiCache secret (Secrets Manager)#3

Merged
pratapalakshmi merged 4 commits into
mainfrom
feat/elasticache-secret-redis-auth
Jun 7, 2026
Merged

feat: authenticate to Redis via AWS ElastiCache secret (Secrets Manager)#3
pratapalakshmi merged 4 commits into
mainfrom
feat/elasticache-secret-redis-auth

Conversation

@pratapalakshmi

Copy link
Copy Markdown

Context

The silo app (plane-ee/apps/silo) authenticates to AWS ElastiCache by reading an ELASTICACHE_SECRET_ARN, fetching the secret JSON from AWS Secrets Manager, and using the REDIS_AUTH_TOKEN (plus host/port) it contains to connect — refreshing on a timer so rotated tokens are picked up without a restart.

This PR brings the same pattern to iframely so the same AWS secret can be reused. It builds on the existing "Plane fork extension" env-var layer (config.loader.js) and the pluggable Redis cache engine (lib/cache-engines/redis.js).

What changed

  • lib/aws-secrets.js (new) — Port of silo's aws-secrets.ts. getSecret(arn, region, forceRefresh) does a TTL-cached GetSecretValueCommand, lazy-importing @aws-sdk/client-secrets-manager so non-AWS deployments never load it.
  • lib/cache-engines/redis.js (reworked) — When ELASTICACHE_SECRET_ARN is set, fetches the auth token (and optionally host/port) from Secrets Manager and injects it into the connection options. A .unref()'d background timer re-fetches every AWS_SECRET_CACHE_TTL seconds and rebuilds + swaps the client when the token rotates (handles live rotation despite iframely's single long-lived client). set/get are unchanged.
  • package.json — adds @aws-sdk/client-secrets-manager (lazy-imported).
  • Docsconfig.local.js.SAMPLE, docs/SETUP.md, and the config.loader.js env-var header document the new variables.

Behavior

  • Existing config / IFRAMELY_REDIS_HOST/PORT/TLS take precedence for host/port/TLS; only the auth token is always taken from the secret.
  • TLS is honored via IFRAMELY_REDIS_TLS, not forced on (differs from silo's always-rediss://).
  • Standard + cluster both supported. Cluster injects the token into redisOptions.auth_pass/password (redis-clustr@1.7.0 → node_redis v2).
  • Secret-key names are configurable and default to match silo (REDIS_AUTH_TOKEN / REDIS_HOST / REDIS_PORT).
  • AWS credentials use the SDK default chain (IRSA / Pod Identity / static keys / IMDS).

New env vars

Var Purpose Default
ELASTICACHE_SECRET_ARN Secrets Manager ARN with Redis creds; enables the feature (unset)
AWS_REGION Region for Secrets Manager us-east-1
AWS_SECRET_CACHE_TTL Secret cache + refresh interval (seconds) 300
REDIS_AUTH_TOKEN_KEY JSON key for the auth token REDIS_AUTH_TOKEN
REDIS_HOST_KEY JSON key for host REDIS_HOST
REDIS_PORT_KEY JSON key for port REDIS_PORT

Testing

  • node --check on all modified files (ESM + top-level await) — clean.
  • Confirmed redis-clustr@1.7.0 → node_redis v2 auth key (auth_pass, with password alias) from the published package.
  • 13 unit assertions on the buildOptions merge logic (secret-only, config-wins, no-ARN regression, cluster, partial-secret, TLS-not-forced) — all pass.
  • Full runtime verification (live Redis + AWS) should be run in the deployment env: regression with no ARN, standard connect with ARN, and rotation by lowering AWS_SECRET_CACHE_TTL.

🤖 Generated with Claude Code

pratapalakshmi and others added 4 commits June 6, 2026 16:48
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant