diff --git a/kubernetes/helm/gateway-helm-chart/templates/gateway/gateway-config.yaml b/kubernetes/helm/gateway-helm-chart/templates/gateway/gateway-config.yaml index 26bb9fcb58..6ff259d35f 100644 --- a/kubernetes/helm/gateway-helm-chart/templates/gateway/gateway-config.yaml +++ b/kubernetes/helm/gateway-helm-chart/templates/gateway/gateway-config.yaml @@ -1,4 +1,5 @@ {{- $gc := .Values.gateway.config.controller -}} +{{- $controller := .Values.gateway.controller -}} {{- $router := .Values.gateway.config.router -}} {{- $pe := .Values.gateway.config.policy_engine -}} {{- $pg := $gc.storage.postgres -}} @@ -75,6 +76,20 @@ data: {{- if eq $gc.storage.type "postgres" }} [controller.storage.postgres] {{- if $pg.dsn }} + {{- /* + A full DSN is used verbatim by the controller (buildPostgresDSN returns it + as-is when set) — the password from gateway.controller.postgres.passwordSecretRef + is NOT merged into it. Setting both, unless the DSN itself references the + injected password env token, means the secret is silently ignored and the + controller connects with no password. Fail loudly instead. The secure way to + combine a DSN with the secret is to embed the token in the DSN. Match the full + `{{ env "VAR" }}` interpolation token (tolerating the {{- trim marker, spacing, + and the "" default arg), not just the bare variable name, so a DSN that merely + mentions the name elsewhere (e.g. in the dbname) still fails the guard. + */ -}} + {{- if and $controller.postgres.passwordSecretRef.name (not (regexMatch `\{\{-? *env +"APIP_GW_CONTROLLER_STORAGE_POSTGRES_PASSWORD"` $pg.dsn)) }} + {{- fail "gateway.config.controller.storage.postgres.dsn is set together with gateway.controller.postgres.passwordSecretRef, but the DSN does not reference the injected password. A full DSN is used verbatim; the separately-injected password is ignored. Either embed the token in the DSN, e.g. dsn: 'postgres://user:{{ env \"APIP_GW_CONTROLLER_STORAGE_POSTGRES_PASSWORD\" }}@host:5432/db?sslmode=require', or unset gateway.controller.postgres.passwordSecretRef and put the password directly in the DSN." }} + {{- end }} dsn = {{ $pg.dsn | quote }} {{- else }} host = {{ required "gateway.config.controller.storage.postgres.host is required when storage.type is \"postgres\" and dsn is unset" $pg.host | quote }} @@ -96,6 +111,19 @@ data: [controller.storage.database] driver = {{ default "sqlserver" $db.driver | quote }} {{- if $db.dsn }} + {{- /* + A full DSN is used verbatim by the controller (buildSQLServerDSN returns it + as-is when set) — the password from gateway.controller.sqlserver.passwordSecretRef + is NOT merged into it. Setting both, unless the DSN itself references the + injected password env token, means the secret is silently ignored. Fail loudly + instead; embed the token in the DSN to combine a DSN with the secret. Match the + full `{{ env "VAR" }}` interpolation token (tolerating the {{- trim marker, + spacing, and the "" default arg), not just the bare variable name, so a DSN that + merely mentions the name elsewhere (e.g. in the dbname) still fails the guard. + */ -}} + {{- if and $controller.sqlserver.passwordSecretRef.name (not (regexMatch `\{\{-? *env +"APIP_GW_CONTROLLER_STORAGE_DATABASE_PASSWORD"` $db.dsn)) }} + {{- fail "gateway.config.controller.storage.database.dsn is set together with gateway.controller.sqlserver.passwordSecretRef, but the DSN does not reference the injected password. A full DSN is used verbatim; the separately-injected password is ignored. Either embed the token in the DSN, e.g. dsn: 'sqlserver://user:{{ env \"APIP_GW_CONTROLLER_STORAGE_DATABASE_PASSWORD\" }}@host:1433?database=gateway', or unset gateway.controller.sqlserver.passwordSecretRef and put the password directly in the DSN." }} + {{- end }} dsn = {{ $db.dsn | quote }} {{- else }} host = {{ required "gateway.config.controller.storage.database.host is required when storage.type is \"sqlserver\" and dsn is unset" $db.host | quote }} diff --git a/kubernetes/helm/gateway-helm-chart/values.yaml b/kubernetes/helm/gateway-helm-chart/values.yaml index 7b028de112..f3708ae0f7 100644 --- a/kubernetes/helm/gateway-helm-chart/values.yaml +++ b/kubernetes/helm/gateway-helm-chart/values.yaml @@ -133,11 +133,19 @@ gateway: sqlite: path: ./data/gateway.db - # PostgreSQL configuration (used when type=postgres) - # Password is injected separately via gateway.controller.postgres.passwordSecretRef + # PostgreSQL configuration (used when type=postgres). Configure EITHER the + # individual fields (host/port/database/user) with the password injected from a + # Secret via gateway.controller.postgres.passwordSecretRef, OR a full dsn below — + # the two are mutually exclusive, not complementary. postgres: - # Full DSN takes precedence over individual fields when set. - # Example: "postgres://user:password@host:5432/dbname?sslmode=require" + # Full DSN. When set, the individual fields below are ignored and the DSN is + # used VERBATIM — the passwordSecretRef password is NOT merged in, so the DSN + # must carry its own credentials. A literal password here is written into the + # ConfigMap in plaintext; to keep it in a Secret while using a DSN, reference + # the injected token in the DSN and keep passwordSecretRef set, e.g.: + # dsn: 'postgres://user:{{ env "APIP_GW_CONTROLLER_STORAGE_POSTGRES_PASSWORD" }}@host:5432/dbname?sslmode=require' + # (The chart fails to render if a DSN is set alongside passwordSecretRef but + # does not reference the token.) dsn: "" host: "" @@ -156,13 +164,21 @@ gateway: conn_max_idle_time: 5m application_name: gateway-controller - # Global database configuration (used for type=sqlserver). - # Password is injected separately via gateway.controller.sqlserver.passwordSecretRef. - # This shape maps to [controller.storage.database] in config.toml. + # Global database configuration (used for type=sqlserver). Configure EITHER the + # individual fields (host/port/database/user) with the password injected from a + # Secret via gateway.controller.sqlserver.passwordSecretRef, OR a full dsn below — + # the two are mutually exclusive. This shape maps to [controller.storage.database] + # in config.toml. database: driver: sqlserver - # Full DSN takes precedence over individual fields when set. - # Example: "sqlserver://gateway@host:1433?database=gateway" + # Full DSN. When set, the individual fields below are ignored and the DSN is + # used VERBATIM — the passwordSecretRef password is NOT merged in, so the DSN + # must carry its own credentials. A literal password here is written into the + # ConfigMap in plaintext; to keep it in a Secret while using a DSN, reference + # the injected token in the DSN and keep passwordSecretRef set, e.g.: + # dsn: 'sqlserver://user:{{ env "APIP_GW_CONTROLLER_STORAGE_DATABASE_PASSWORD" }}@host:1433?database=gateway' + # (The chart fails to render if a DSN is set alongside passwordSecretRef but + # does not reference the token.) dsn: "" host: "" @@ -726,14 +742,20 @@ gateway: secretName: "" # Mount path inside the container — must match controller.encryption.providers[].keys[].file paths mountPath: /app/data/aesgcm-keys - # PostgreSQL password secret reference (used when gateway.config.controller.storage.type=postgres). - # The password is injected from this Secret rather than stored in the ConfigMap (see README). + # PostgreSQL password secret reference (used when gateway.config.controller.storage.type=postgres + # and gateway.config.controller.storage.postgres.dsn is NOT set). The password is injected from + # this Secret rather than stored in the ConfigMap (see README). It has NO effect when a dsn is set + # unless the dsn references the injected token {{ env "APIP_GW_CONTROLLER_STORAGE_POSTGRES_PASSWORD" }}; + # the chart fails to render if a dsn is set alongside this without referencing the token. postgres: passwordSecretRef: name: "" key: password - # SQL Server password secret reference (used when gateway.config.controller.storage.type=sqlserver). - # The password is injected from this Secret rather than stored in the ConfigMap (see README). + # SQL Server password secret reference (used when gateway.config.controller.storage.type=sqlserver + # and gateway.config.controller.storage.database.dsn is NOT set). The password is injected from + # this Secret rather than stored in the ConfigMap (see README). It has NO effect when a dsn is set + # unless the dsn references the injected token {{ env "APIP_GW_CONTROLLER_STORAGE_DATABASE_PASSWORD" }}; + # the chart fails to render if a dsn is set alongside this without referencing the token. sqlserver: passwordSecretRef: name: ""