From 72aa747986c35620492d03d18c22b548889b941e Mon Sep 17 00:00:00 2001 From: jatin Date: Fri, 12 Jun 2026 18:53:25 -0400 Subject: [PATCH 1/5] fix(rr): stop agentSandbox externalSecret.name from hijacking postgres; require its encryption key setting rr.agentSandbox.externalSecret.name (intended for the JWT keypair) silently (a) routed the agent sandbox postgres connection to that secret's postgres-url key and (b) coupled the encryption key to it. an existing deployment that just wanted to reuse its backend postgres crashed at runtime with getaddrinfo ENOTFOUND. - postgres now only reads from externalSecret when postgres.fromExternalSecret: true (default false); otherwise it inherits the backend's config.postgresql connection, even when externalSecret.name is set for the JWT - require the agent sandbox encryption key (validateSecrets, mirroring the JWT keys): the proxy derives the sandbox-iframe asset-token HMAC key from AGENT_SANDBOX_ENCRYPTION_KEY and throws when serving a sandbox without it (agent_executor/proxy/src/config.ts getAssetTokenHmacSecret), so 'optional' would surface as a green pod that fails at first sandbox use. fail loudly at render instead. - updated both validateSecrets fail hints to mention postgres.fromExternalSecret: true - values.yaml (both copies): add postgres.fromExternalSecret, mark encryptionKey required, note 64 hex - bump chart 6.11.1 -> 6.11.2; update ci fixtures to supply the now-required encryption key and add fromExternalSecret: true to the Option-4 sandbox fixture --- .../ci/test-agent-sandbox-enabled-option.yaml | 3 +++ ...t-agent-sandbox-inherit-postgres-option.yaml | 2 ++ ...st-agent-sandbox-postgres-fields-option.yaml | 2 ++ charts/retool/ci/test-rr-enabled-option.yaml | 2 ++ charts/retool/templates/_helpers.tpl | 17 +++++++++++------ charts/retool/values.yaml | 11 +++++++---- values.yaml | 11 +++++++---- 7 files changed, 34 insertions(+), 14 deletions(-) diff --git a/charts/retool/ci/test-agent-sandbox-enabled-option.yaml b/charts/retool/ci/test-agent-sandbox-enabled-option.yaml index 32ba7f6..f4c7a28 100644 --- a/charts/retool/ci/test-agent-sandbox-enabled-option.yaml +++ b/charts/retool/ci/test-agent-sandbox-enabled-option.yaml @@ -31,6 +31,9 @@ rr: name: agent-sandbox-secrets postgres: + # Option 4 is opt-in: route Postgres to the external secret's postgres-url key. + # externalSecret.name alone only covers the JWT/encryption keys, not Postgres. + fromExternalSecret: true schema: agent_executor poolMax: 10 diff --git a/charts/retool/ci/test-agent-sandbox-inherit-postgres-option.yaml b/charts/retool/ci/test-agent-sandbox-inherit-postgres-option.yaml index c5d09a4..0dc2c47 100644 --- a/charts/retool/ci/test-agent-sandbox-inherit-postgres-option.yaml +++ b/charts/retool/ci/test-agent-sandbox-inherit-postgres-option.yaml @@ -20,3 +20,5 @@ rr: jwtPublicKey: '-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEljtqa2nhBwe/PqNhWgPHhj0jv8AI\nY+QUCicYtfv9wLGcEGPQuXoBQtuoIuOwXOdbEWgrQyLdIEb0YjegAW3miA==\n-----END PUBLIC KEY-----' jwtPrivateKey: '-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMFXLiN/YsJv89D2YkEZ6/Dj5fujghENmYTOilwdChU3oAoGCCqGSM49\nAwEHoUQDQgAEljtqa2nhBwe/PqNhWgPHhj0jv8AIY+QUCicYtfv9wLGcEGPQuXoB\nQtuoIuOwXOdbEWgrQyLdIEb0YjegAW3miA==\n-----END EC PRIVATE KEY-----' + # encryption key is required (proxy derives the asset-token HMAC key from it) + encryptionKey: a12b01429fe0fe69a80da94e9e837ab2f1e9bda378ed8a25905a238f6fea6b7a diff --git a/charts/retool/ci/test-agent-sandbox-postgres-fields-option.yaml b/charts/retool/ci/test-agent-sandbox-postgres-fields-option.yaml index 2b4e25b..3e9c12a 100644 --- a/charts/retool/ci/test-agent-sandbox-postgres-fields-option.yaml +++ b/charts/retool/ci/test-agent-sandbox-postgres-fields-option.yaml @@ -21,6 +21,8 @@ rr: jwtPublicKey: '-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEljtqa2nhBwe/PqNhWgPHhj0jv8AI\nY+QUCicYtfv9wLGcEGPQuXoBQtuoIuOwXOdbEWgrQyLdIEb0YjegAW3miA==\n-----END PUBLIC KEY-----' jwtPrivateKey: '-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMFXLiN/YsJv89D2YkEZ6/Dj5fujghENmYTOilwdChU3oAoGCCqGSM49\nAwEHoUQDQgAEljtqa2nhBwe/PqNhWgPHhj0jv8AIY+QUCicYtfv9wLGcEGPQuXoB\nQtuoIuOwXOdbEWgrQyLdIEb0YjegAW3miA==\n-----END EC PRIVATE KEY-----' + # encryption key is required (proxy derives the asset-token HMAC key from it) + encryptionKey: a12b01429fe0fe69a80da94e9e837ab2f1e9bda378ed8a25905a238f6fea6b7a # Option 2: host + user + database, password via PGPASSWORD secretKeyRef. postgres: diff --git a/charts/retool/ci/test-rr-enabled-option.yaml b/charts/retool/ci/test-rr-enabled-option.yaml index aae0c9d..b61ecc7 100644 --- a/charts/retool/ci/test-rr-enabled-option.yaml +++ b/charts/retool/ci/test-rr-enabled-option.yaml @@ -18,6 +18,8 @@ rr: # be single-line (\n-escaped) or templating breaks. jwtPublicKey: '-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEljtqa2nhBwe/PqNhWgPHhj0jv8AIY+QUCicYtfv9wLGcEGPQuXoBQtuoIuOwXOdbEWgrQyLdIEb0YjegAW3miA==\n-----END PUBLIC KEY-----' jwtPrivateKey: '-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMFXLiN/YsJv89D2YkEZ6/Dj5fujghENmYTOilwdChU3oAoGCCqGSM49AwEHoUQDQgAEljtqa2nhBwe/PqNhWgPHhj0jv8AIY+QUCicYtfv9wLGcEGPQuXoBQtuoIuOwXOdbEWgrQyLdIEb0YjegAW3miA==\n-----END EC PRIVATE KEY-----' + # encryption key is required (proxy derives the asset-token HMAC key from it) + encryptionKey: a12b01429fe0fe69a80da94e9e837ab2f1e9bda378ed8a25905a238f6fea6b7a postgres: url: postgres://retool:retool@agent-sandbox-db.example.internal:5432/agent_sandbox schema: agent_executor diff --git a/charts/retool/templates/_helpers.tpl b/charts/retool/templates/_helpers.tpl index 2989dc3..040c0be 100644 --- a/charts/retool/templates/_helpers.tpl +++ b/charts/retool/templates/_helpers.tpl @@ -676,14 +676,14 @@ or the catch-all externalSecret.name. No-op when agentSandbox is disabled. {{- if eq (include "retool.rr.componentEnabled" (dict "root" . "component" "agentSandbox")) "1" -}} {{- $as := .Values.rr.agentSandbox -}} {{- $ext := $as.externalSecret.name -}} -{{- $explicitPg := or $as.postgres.url $as.postgres.urlSecretName $as.postgres.host $ext -}} +{{- $explicitPg := or $as.postgres.url $as.postgres.urlSecretName $as.postgres.host (and $ext $as.postgres.fromExternalSecret) -}} {{- if not $explicitPg -}} {{- /* No explicit source: inherit the backend's Postgres connection. */ -}} {{- if not (include "retool.postgresql.host" . | trimAll "\"") -}} -{{- fail "agentSandbox.enabled defaults to reusing the backend's Postgres connection, but config.postgresql resolved no host. Set agentSandbox.postgres.url / .host / .urlSecretName / externalSecret.name, or configure config.postgresql." -}} +{{- fail "agentSandbox.enabled defaults to reusing the backend's Postgres connection, but config.postgresql resolved no host. Set agentSandbox.postgres.url / .host / .urlSecretName, or agentSandbox.externalSecret.name together with agentSandbox.postgres.fromExternalSecret: true (externalSecret.name alone is only used for the JWT/encryption keys, not Postgres), or configure config.postgresql." -}} {{- end -}} {{- if not (or .Values.postgresql.enabled .Values.config.postgresql.passwordSecretName (eq (include "shouldIncludeConfigSecretsEnvVars" . | trim) "1")) -}} -{{- fail "agentSandbox.postgres is unset so it would inherit the backend's Postgres password, but that password is supplied via external secrets (envFrom) and cannot be referenced from a separate pod. Set agentSandbox.postgres.url / .urlSecretName / .host (+ passwordSecretName), or agentSandbox.externalSecret.name." -}} +{{- fail "agentSandbox.postgres is unset so it would inherit the backend's Postgres password, but that password is supplied via external secrets (envFrom) and cannot be referenced from a separate pod. Set agentSandbox.postgres.url / .urlSecretName / .host (+ passwordSecretName), or agentSandbox.externalSecret.name together with agentSandbox.postgres.fromExternalSecret: true (externalSecret.name alone is only used for the JWT/encryption keys, not Postgres)." -}} {{- end -}} {{- end -}} {{- if $as.postgres.host -}} @@ -714,6 +714,9 @@ or the catch-all externalSecret.name. No-op when agentSandbox is disabled. {{- if not (or $as.jwtPrivateKey $ext) -}} {{- fail "agentSandbox.enabled requires a JWT private key (the backend signs sandbox tokens with it). Set agentSandbox.jwtPrivateKey or agentSandbox.externalSecret.name." -}} {{- end -}} +{{- if not (or $as.encryptionKey $ext) -}} +{{- fail "agentSandbox.enabled requires an encryption key: the proxy derives the sandbox-iframe asset-token HMAC key from it and throws when serving a sandbox without it, and the backend must use the same value. Set agentSandbox.encryptionKey (64 hex chars, openssl rand -hex 32) or agentSandbox.externalSecret.name (with an encryption-key entry)." -}} +{{- end -}} {{- end -}} {{- end -}} @@ -721,8 +724,10 @@ or the catch-all externalSecret.name. No-op when agentSandbox is disabled. Render the AGENT_SANDBOX_POSTGRES_URL env entry for the controller/proxy (plus a PGPASSWORD entry when assembling from fields). validateSecrets guarantees one of these applies, in order: postgres.url -> postgres.host -> postgres.urlSecretName --> externalSecret.name -> inherit the backend's config.postgresql connection -(the default when nothing agent-specific is set). +-> externalSecret.name (only when postgres.fromExternalSecret is true) -> inherit +the backend's config.postgresql connection (the default when nothing +agent-specific is set, including when externalSecret.name is set only for the JWT +keypair -- it must NOT silently hijack the Postgres source). For the host path the password is passed via PGPASSWORD rather than embedded in the URL: node-postgres reads PGPASSWORD when the connection string omits the @@ -761,7 +766,7 @@ Usage: {{- include "retool.agentSandbox.postgresUrlEnv" . | nindent 12 }} secretKeyRef: name: {{ $pg.urlSecretName }} key: {{ $pg.urlSecretKey | default "postgres-url" }} -{{- else if $ext }} +{{- else if and $ext $pg.fromExternalSecret }} - name: AGENT_SANDBOX_POSTGRES_URL valueFrom: secretKeyRef: diff --git a/charts/retool/values.yaml b/charts/retool/values.yaml index f1efd1e..9fe406a 100644 --- a/charts/retool/values.yaml +++ b/charts/retool/values.yaml @@ -894,7 +894,7 @@ rr: jwtPublicKey: '' # REQUIRED (ES256) unless provided via externalSecret jwtPrivateKey: '' # REQUIRED (ES256) unless provided via externalSecret - encryptionKey: '' # optional: hex 256-bit; must match backend AGENT_SANDBOX_ENCRYPTION_KEY + encryptionKey: '' # REQUIRED: 64 hex chars (openssl rand -hex 32, NOT base64). Proxy throws when serving a sandbox without it; must match the backend's AGENT_SANDBOX_ENCRYPTION_KEY. May instead be supplied via externalSecret.name (encryption-key entry). apiSecret: '' # optional: admin/test endpoints # === Postgres state backend ============================================= @@ -928,9 +928,12 @@ rr: urlSecretKey: 'postgres-url' # -- Option 4: reuse externalSecret.name (its postgres-url key) -- - # Selected by setting rr.agentSandbox.externalSecret.name (in the Secrets - # section above), not by anything here. Used when options 1-3 are blank. - # + # Opt-in ONLY: set fromExternalSecret: true to read postgres-url from the + # Secret named in rr.agentSandbox.externalSecret.name. Setting externalSecret.name + # for the JWT keypair alone does NOT route Postgres here -- it still inherits + # config.postgresql (below), so an existing deployment needs no DB config. + fromExternalSecret: false + # If options 1-4 are ALL unset, the default (inherit config.postgresql) # applies -- see the note at the top of this block. diff --git a/values.yaml b/values.yaml index f1efd1e..9fe406a 100644 --- a/values.yaml +++ b/values.yaml @@ -894,7 +894,7 @@ rr: jwtPublicKey: '' # REQUIRED (ES256) unless provided via externalSecret jwtPrivateKey: '' # REQUIRED (ES256) unless provided via externalSecret - encryptionKey: '' # optional: hex 256-bit; must match backend AGENT_SANDBOX_ENCRYPTION_KEY + encryptionKey: '' # REQUIRED: 64 hex chars (openssl rand -hex 32, NOT base64). Proxy throws when serving a sandbox without it; must match the backend's AGENT_SANDBOX_ENCRYPTION_KEY. May instead be supplied via externalSecret.name (encryption-key entry). apiSecret: '' # optional: admin/test endpoints # === Postgres state backend ============================================= @@ -928,9 +928,12 @@ rr: urlSecretKey: 'postgres-url' # -- Option 4: reuse externalSecret.name (its postgres-url key) -- - # Selected by setting rr.agentSandbox.externalSecret.name (in the Secrets - # section above), not by anything here. Used when options 1-3 are blank. - # + # Opt-in ONLY: set fromExternalSecret: true to read postgres-url from the + # Secret named in rr.agentSandbox.externalSecret.name. Setting externalSecret.name + # for the JWT keypair alone does NOT route Postgres here -- it still inherits + # config.postgresql (below), so an existing deployment needs no DB config. + fromExternalSecret: false + # If options 1-4 are ALL unset, the default (inherit config.postgresql) # applies -- see the note at the top of this block. From b0619b4be699fd81aacb8e6f16eca1c0d8b73099 Mon Sep 17 00:00:00 2001 From: Ryan Artecona Date: Fri, 12 Jun 2026 12:10:00 -0700 Subject: [PATCH 2/5] Respect postgres.passwordSecretName in presence of rr.agentSandbox.externalSecret (cherry picked from commit 38304c00b34ff6ca36240b0a0689946ced3e3c40) --- charts/retool/templates/_helpers.tpl | 7 +++++++ charts/retool/values.yaml | 6 ++++-- values.yaml | 6 ++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/charts/retool/templates/_helpers.tpl b/charts/retool/templates/_helpers.tpl index 040c0be..cbd2668 100644 --- a/charts/retool/templates/_helpers.tpl +++ b/charts/retool/templates/_helpers.tpl @@ -772,6 +772,13 @@ Usage: {{- include "retool.agentSandbox.postgresUrlEnv" . | nindent 12 }} secretKeyRef: name: {{ $ext }} key: postgres-url +{{- if $pg.passwordSecretName }} +- name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ $pg.passwordSecretName }} + key: {{ $pg.passwordSecretKey | default "password" }} +{{- end }} {{- else }} {{- /* Default: inherit the backend's Postgres connection (config.postgresql or the diff --git a/charts/retool/values.yaml b/charts/retool/values.yaml index 9fe406a..2311da1 100644 --- a/charts/retool/values.yaml +++ b/charts/retool/values.yaml @@ -887,8 +887,10 @@ rr: # === Secrets ============================================================ # Provide each secret as a plaintext value below, OR set externalSecret.name # to a pre-existing Secret with keys jwt-public-key, jwt-private-key, - # encryption-key, api-secret, postgres-url. A plaintext value always wins over - # the external secret for that key. + # encryption-key, api-secret, postgres-url. A plaintext value always wins + # over the external secret for that key. The postgres-url key can either + # include or omit an embedded password; if the password is omitted, it can + # be provided separately via rr.agentSandbox.postgres.passwordSecretName. externalSecret: name: '' # optional: existing Secret holding all keys below diff --git a/values.yaml b/values.yaml index 9fe406a..2311da1 100644 --- a/values.yaml +++ b/values.yaml @@ -887,8 +887,10 @@ rr: # === Secrets ============================================================ # Provide each secret as a plaintext value below, OR set externalSecret.name # to a pre-existing Secret with keys jwt-public-key, jwt-private-key, - # encryption-key, api-secret, postgres-url. A plaintext value always wins over - # the external secret for that key. + # encryption-key, api-secret, postgres-url. A plaintext value always wins + # over the external secret for that key. The postgres-url key can either + # include or omit an embedded password; if the password is omitted, it can + # be provided separately via rr.agentSandbox.postgres.passwordSecretName. externalSecret: name: '' # optional: existing Secret holding all keys below From 8fd12d9a31d63faf00a386e04d4380655bf5092e Mon Sep 17 00:00:00 2001 From: jatin Date: Fri, 12 Jun 2026 20:21:26 -0400 Subject: [PATCH 3/5] refactor(rr): replace postgres.fromExternalSecret with postgres.urlSecretName the fromExternalSecret boolean was redundant with the existing Option-3 postgres.urlSecretName/urlSecretKey: 'read postgres-url from externalSecret.name' is identical to pointing urlSecretName at that same secret (urlSecretKey already defaults to postgres-url). drop the bespoke flag and the externalSecret postgres branch entirely, so externalSecret.name covers ONLY the JWT/encryption keys and never sources postgres. - remove rr.agentSandbox.postgres.fromExternalSecret (both values.yaml copies) - postgresUrlEnv: delete the externalSecret postgres branch; move the passwordSecretName PGPASSWORD support (#332) into the urlSecretName branch, so 'DSN from a secret + auto-rotated password from another secret' uses Option 3 - validateSecrets: drop the externalSecret term from $explicitPg; repoint both fail hints to postgres.urlSecretName - ci: test-agent-sandbox-enabled-option uses postgres.urlSecretName (Option 3) pointed at its JWT secret instead of the removed flag --- .../ci/test-agent-sandbox-enabled-option.yaml | 15 ++++++----- charts/retool/templates/_helpers.tpl | 26 +++++++++---------- charts/retool/values.yaml | 26 +++++++++---------- values.yaml | 26 +++++++++---------- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/charts/retool/ci/test-agent-sandbox-enabled-option.yaml b/charts/retool/ci/test-agent-sandbox-enabled-option.yaml index f4c7a28..792c46c 100644 --- a/charts/retool/ci/test-agent-sandbox-enabled-option.yaml +++ b/charts/retool/ci/test-agent-sandbox-enabled-option.yaml @@ -7,9 +7,10 @@ rr: # the image-prepuller + seccomp DaemonSets, the smarter-device-manager device # plugin DaemonSet, the NetworkPolicies, and both PDBs. # - # Secret/Postgres sourcing here uses externalSecret.name (Postgres OPTION 4: - # the secret's postgres-url key). The other secret/Postgres precedence paths and - # the same-origin (no-ingress) proxy mode are covered by sibling files: + # Secrets come from externalSecret.name (JWT/encryption); Postgres reads its DSN + # from that same Secret via postgres.urlSecretName (Option 3). The other + # secret/Postgres precedence paths and the same-origin (no-ingress) proxy mode + # are covered by sibling files: # - test-agent-sandbox-inline-secrets-option.yaml (inline secrets, plaintext DSN, same-origin/no ingress, hostPath tun) # - test-agent-sandbox-postgres-fields-option.yaml (assemble DSN from fields + PGPASSWORD secret) # - test-agent-sandbox-postgres-url-secret-option.yaml (full DSN from an existing secret) @@ -31,9 +32,11 @@ rr: name: agent-sandbox-secrets postgres: - # Option 4 is opt-in: route Postgres to the external secret's postgres-url key. - # externalSecret.name alone only covers the JWT/encryption keys, not Postgres. - fromExternalSecret: true + # Option 3: read the DSN from the same Secret that holds the JWT/encryption + # keys by pointing urlSecretName at it. externalSecret.name itself never + # sources Postgres. + urlSecretName: agent-sandbox-secrets + urlSecretKey: postgres-url schema: agent_executor poolMax: 10 diff --git a/charts/retool/templates/_helpers.tpl b/charts/retool/templates/_helpers.tpl index cbd2668..60521e9 100644 --- a/charts/retool/templates/_helpers.tpl +++ b/charts/retool/templates/_helpers.tpl @@ -676,14 +676,14 @@ or the catch-all externalSecret.name. No-op when agentSandbox is disabled. {{- if eq (include "retool.rr.componentEnabled" (dict "root" . "component" "agentSandbox")) "1" -}} {{- $as := .Values.rr.agentSandbox -}} {{- $ext := $as.externalSecret.name -}} -{{- $explicitPg := or $as.postgres.url $as.postgres.urlSecretName $as.postgres.host (and $ext $as.postgres.fromExternalSecret) -}} +{{- $explicitPg := or $as.postgres.url $as.postgres.urlSecretName $as.postgres.host -}} {{- if not $explicitPg -}} {{- /* No explicit source: inherit the backend's Postgres connection. */ -}} {{- if not (include "retool.postgresql.host" . | trimAll "\"") -}} -{{- fail "agentSandbox.enabled defaults to reusing the backend's Postgres connection, but config.postgresql resolved no host. Set agentSandbox.postgres.url / .host / .urlSecretName, or agentSandbox.externalSecret.name together with agentSandbox.postgres.fromExternalSecret: true (externalSecret.name alone is only used for the JWT/encryption keys, not Postgres), or configure config.postgresql." -}} +{{- fail "agentSandbox.enabled defaults to reusing the backend's Postgres connection, but config.postgresql resolved no host. Set agentSandbox.postgres.url / .host / .urlSecretName (point .urlSecretName at your externalSecret to reuse its postgres-url key; externalSecret.name alone only covers the JWT/encryption keys), or configure config.postgresql." -}} {{- end -}} {{- if not (or .Values.postgresql.enabled .Values.config.postgresql.passwordSecretName (eq (include "shouldIncludeConfigSecretsEnvVars" . | trim) "1")) -}} -{{- fail "agentSandbox.postgres is unset so it would inherit the backend's Postgres password, but that password is supplied via external secrets (envFrom) and cannot be referenced from a separate pod. Set agentSandbox.postgres.url / .urlSecretName / .host (+ passwordSecretName), or agentSandbox.externalSecret.name together with agentSandbox.postgres.fromExternalSecret: true (externalSecret.name alone is only used for the JWT/encryption keys, not Postgres)." -}} +{{- fail "agentSandbox.postgres is unset so it would inherit the backend's Postgres password, but that password is supplied via external secrets (envFrom) and cannot be referenced from a separate pod. Set agentSandbox.postgres.url / .urlSecretName / .host (+ passwordSecretName) -- .urlSecretName can point at your externalSecret's postgres-url key (externalSecret.name alone only covers the JWT/encryption keys)." -}} {{- end -}} {{- end -}} {{- if $as.postgres.host -}} @@ -724,10 +724,10 @@ or the catch-all externalSecret.name. No-op when agentSandbox is disabled. Render the AGENT_SANDBOX_POSTGRES_URL env entry for the controller/proxy (plus a PGPASSWORD entry when assembling from fields). validateSecrets guarantees one of these applies, in order: postgres.url -> postgres.host -> postgres.urlSecretName --> externalSecret.name (only when postgres.fromExternalSecret is true) -> inherit -the backend's config.postgresql connection (the default when nothing -agent-specific is set, including when externalSecret.name is set only for the JWT -keypair -- it must NOT silently hijack the Postgres source). +-> inherit the backend's config.postgresql connection (the default when nothing +agent-specific is set). externalSecret.name covers only the JWT/encryption keys +-- it never sources Postgres. To read a DSN from that same secret, point +postgres.urlSecretName at it (its postgres-url key is the urlSecretKey default). For the host path the password is passed via PGPASSWORD rather than embedded in the URL: node-postgres reads PGPASSWORD when the connection string omits the @@ -742,7 +742,6 @@ Usage: {{- include "retool.agentSandbox.postgresUrlEnv" . | nindent 12 }} */}} {{- define "retool.agentSandbox.postgresUrlEnv" -}} {{- $pg := .Values.rr.agentSandbox.postgres -}} -{{- $ext := .Values.rr.agentSandbox.externalSecret.name -}} {{- if $pg.url }} - name: AGENT_SANDBOX_POSTGRES_URL value: {{ $pg.url | quote }} @@ -766,12 +765,11 @@ Usage: {{- include "retool.agentSandbox.postgresUrlEnv" . | nindent 12 }} secretKeyRef: name: {{ $pg.urlSecretName }} key: {{ $pg.urlSecretKey | default "postgres-url" }} -{{- else if and $ext $pg.fromExternalSecret }} -- name: AGENT_SANDBOX_POSTGRES_URL - valueFrom: - secretKeyRef: - name: {{ $ext }} - key: postgres-url +{{- /* + The DSN may omit the password; supply it separately via passwordSecretName so + an auto-rotated password (e.g. the backend's RDS secret) isn't duplicated into + the DSN secret. node-postgres reads PGPASSWORD when the URL omits the password. +*/}} {{- if $pg.passwordSecretName }} - name: PGPASSWORD valueFrom: diff --git a/charts/retool/values.yaml b/charts/retool/values.yaml index 2311da1..74934fd 100644 --- a/charts/retool/values.yaml +++ b/charts/retool/values.yaml @@ -887,12 +887,12 @@ rr: # === Secrets ============================================================ # Provide each secret as a plaintext value below, OR set externalSecret.name # to a pre-existing Secret with keys jwt-public-key, jwt-private-key, - # encryption-key, api-secret, postgres-url. A plaintext value always wins - # over the external secret for that key. The postgres-url key can either - # include or omit an embedded password; if the password is omitted, it can - # be provided separately via rr.agentSandbox.postgres.passwordSecretName. + # encryption-key, api-secret. A plaintext value always wins over the external + # secret for that key. externalSecret.name covers ONLY these app secrets -- + # it does not source Postgres. To read a DSN from that same Secret, point + # postgres.urlSecretName at it (see Postgres Option 3 below). externalSecret: - name: '' # optional: existing Secret holding all keys below + name: '' # optional: existing Secret holding the keys below jwtPublicKey: '' # REQUIRED (ES256) unless provided via externalSecret jwtPrivateKey: '' # REQUIRED (ES256) unless provided via externalSecret @@ -925,18 +925,16 @@ rr: passwordSecretName: '' passwordSecretKey: 'password' - # -- Option 3: existing Secret holding the full DSN -- + # -- Option 3: DSN from an existing Secret -- + # Set urlSecretName (urlSecretKey defaults to postgres-url). To reuse the + # Secret in rr.agentSandbox.externalSecret.name, just point urlSecretName at + # it -- externalSecret.name itself never sources Postgres. + # The DSN may omit the password; supply it separately via passwordSecretName + # (above) so an auto-rotated password isn't duplicated into the DSN secret. urlSecretName: '' urlSecretKey: 'postgres-url' - # -- Option 4: reuse externalSecret.name (its postgres-url key) -- - # Opt-in ONLY: set fromExternalSecret: true to read postgres-url from the - # Secret named in rr.agentSandbox.externalSecret.name. Setting externalSecret.name - # for the JWT keypair alone does NOT route Postgres here -- it still inherits - # config.postgresql (below), so an existing deployment needs no DB config. - fromExternalSecret: false - - # If options 1-4 are ALL unset, the default (inherit config.postgresql) + # If options 1-3 are ALL unset, the default (inherit config.postgresql) # applies -- see the note at the top of this block. # -- Optional tuning (defaults shown) -- diff --git a/values.yaml b/values.yaml index 2311da1..74934fd 100644 --- a/values.yaml +++ b/values.yaml @@ -887,12 +887,12 @@ rr: # === Secrets ============================================================ # Provide each secret as a plaintext value below, OR set externalSecret.name # to a pre-existing Secret with keys jwt-public-key, jwt-private-key, - # encryption-key, api-secret, postgres-url. A plaintext value always wins - # over the external secret for that key. The postgres-url key can either - # include or omit an embedded password; if the password is omitted, it can - # be provided separately via rr.agentSandbox.postgres.passwordSecretName. + # encryption-key, api-secret. A plaintext value always wins over the external + # secret for that key. externalSecret.name covers ONLY these app secrets -- + # it does not source Postgres. To read a DSN from that same Secret, point + # postgres.urlSecretName at it (see Postgres Option 3 below). externalSecret: - name: '' # optional: existing Secret holding all keys below + name: '' # optional: existing Secret holding the keys below jwtPublicKey: '' # REQUIRED (ES256) unless provided via externalSecret jwtPrivateKey: '' # REQUIRED (ES256) unless provided via externalSecret @@ -925,18 +925,16 @@ rr: passwordSecretName: '' passwordSecretKey: 'password' - # -- Option 3: existing Secret holding the full DSN -- + # -- Option 3: DSN from an existing Secret -- + # Set urlSecretName (urlSecretKey defaults to postgres-url). To reuse the + # Secret in rr.agentSandbox.externalSecret.name, just point urlSecretName at + # it -- externalSecret.name itself never sources Postgres. + # The DSN may omit the password; supply it separately via passwordSecretName + # (above) so an auto-rotated password isn't duplicated into the DSN secret. urlSecretName: '' urlSecretKey: 'postgres-url' - # -- Option 4: reuse externalSecret.name (its postgres-url key) -- - # Opt-in ONLY: set fromExternalSecret: true to read postgres-url from the - # Secret named in rr.agentSandbox.externalSecret.name. Setting externalSecret.name - # for the JWT keypair alone does NOT route Postgres here -- it still inherits - # config.postgresql (below), so an existing deployment needs no DB config. - fromExternalSecret: false - - # If options 1-4 are ALL unset, the default (inherit config.postgresql) + # If options 1-3 are ALL unset, the default (inherit config.postgresql) # applies -- see the note at the top of this block. # -- Optional tuning (defaults shown) -- From 27d11152c3f2a6ff2e7b3e2b026d75ed11852a3d Mon Sep 17 00:00:00 2001 From: jatin Date: Fri, 12 Jun 2026 20:39:32 -0400 Subject: [PATCH 4/5] fix(rr): render blobStorage env onto the workflow worker the agentExecutor / snapshotRetention temporal activities run on the WORKFLOW_TEMPORAL_WORKER (registered in workflowsExecutor/onpremWorker) and read snapshot blob storage via getBlobStoreForSnapshots (RR_SNAPSHOTS_* with an RR_DEFAULT_* fallback). but gitServer.commonEnv was only injected onto the main backend and the standalone git-server pod, so the worker had no RR_DEFAULT_* and snapshot blob access failed there -- forcing operators to hand-plumb RR_SNAPSHOTS_* via env. include gitServer.commonEnv on the worker when rr.gitServer.enabled (no git-server host/port split -- the worker is a blob-storage client, not the git server). self-guards on rr.blobStorage being set, so it no-ops otherwise. --- charts/retool/templates/_workers.tpl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/charts/retool/templates/_workers.tpl b/charts/retool/templates/_workers.tpl index 36c1e7e..b82b439 100644 --- a/charts/retool/templates/_workers.tpl +++ b/charts/retool/templates/_workers.tpl @@ -230,6 +230,17 @@ spec: value: http://{{ template "retool.jsExecutor.name" $ }} {{- end }} {{- include "retool.agentSandbox.backendEnvVars" $ | nindent 10 }} + {{- if $.Values.rr.gitServer.enabled }} + {{- /* + Snapshot blob storage: the agentExecutor / snapshotRetention temporal + activities run on this worker and read RR_SNAPSHOTS_* with an + RR_DEFAULT_* fallback (backend getBlobStoreForSnapshots). Render the + same blobStorage env the backend and git-server get, so the fallback + resolves here too. No git-server host/port split is needed -- the + worker is a blob-storage client, not the git server itself. + */}} + {{- include "retool.gitServer.commonEnv" $ | nindent 10 }} + {{- end }} {{- include "retool.telemetry.includeEnvVars" $ | nindent 10 }} From f3e4341d9b88ba2dc97a57d53a705c2bc0bb170e Mon Sep 17 00:00:00 2001 From: jatin Date: Fri, 12 Jun 2026 20:42:39 -0400 Subject: [PATCH 5/5] chore(rr): bump chart version to 6.11.3 (rebased past #333) --- charts/retool/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/retool/Chart.yaml b/charts/retool/Chart.yaml index b14c54f..d3ced8c 100644 --- a/charts/retool/Chart.yaml +++ b/charts/retool/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: retool description: A Helm chart for Kubernetes type: application -version: 6.11.2 +version: 6.11.3 maintainers: - name: Retool Engineering email: engineering+helm@retool.com