Skip to content

Gateway: template expressions unusable in non-string policy params #2871

Description

@renuka-fernando

Description

A policy parameter declared as integer, number, or boolean cannot be set to a Go-template expression ({{ env "X" }}, {{ secret "X" }}). The gateway-controller resolves templates before validation, but the resolved value is always a string, so it fails in two places: JSON-schema validation rejects it as a type mismatch, and — even if validation passed — the string flows unchanged to the policy engine, which type-asserts the value and breaks. Literal numeric/boolean values work; only templated ones fail. This defeats the purpose of resolving templates in the gateway for any non-string param.

Example: the advanced-ratelimit limit/burst params (integers) and slugify-body's maxLength (integer) cannot use {{ env … }}.

Root cause (traced)

  1. templateengine.RenderSpec resolves templates before validation (by design), but renderValue returns every rendered leaf as a stringgateway/gateway-controller/pkg/templateengine/spec.go:152-162. So limit: "{{ env \"LIMIT\" }}" becomes the string "100", never the number 100.
  2. Validation order: render then validate — gateway/gateway-controller/pkg/utils/api_deployment.go:316 (render) → :326 (validate).
  3. validatePolicyParams passes params straight to gojsonschema with no coerciongateway/gateway-controller/pkg/config/policy_validator.go:278-286. A JSON string "100" fails type: integer/number.
  4. Even past validation, the transform copies params through untouched — gateway/gateway-controller/pkg/transform/restapi.go:632-650 — into RuntimeDeployConfig (pkg/models/runtime_deploy_config.go:114) → xDS → the policy engine, which hands the bare map[string]interface{} to the policy factory (gateway-runtime/policy-engine/internal/registry/registry.go:84-122). Policies then type-assert values (e.g. gateway/sample-policies/transform-payload-case/transformpayloadcase.go:47), so a string where a number is expected breaks at runtime.

Proposed solution

Add a schema-aware, recursive coercion of resolved policy-param values to their declared JSON-schema type, applied after RenderSpec and before Validate, so both validation and everything downstream (xDS, policy engine) receive correctly typed values:

  • The policy definition schema is already reachable at this stage — PolicyValidator and RestAPITransformer both hold policyDefinitions (pkg/models/policy_definition.go:27), so no new plumbing is needed to look up a param's declared type.
  • For each leaf whose schema type is integer/number/boolean, convert a clean scalar string to the typed value; leave everything else untouched.
  • It must walk the schema recursively (properties/items), because the real target (advanced-ratelimit.limit) is nested under quotas[].limits[]. This mirrors the algorithm the AI Workspace frontend already has in portals/ai-workspace/.../schemaUtils.ts:320 (coerceValuesToSchemaTypes).
  • Coerce then validate: this preserves real validation — if a template resolves to a non-numeric string (e.g. env var "abc" for an integer), coercion can't produce a number and gojsonschema still rejects it.

Alternative considered: schema-unaware re-typing in templateengine (JSON-parse a pure-template scalar leaf back to number/bool). Rejected as riskier — it would over-coerce genuine string params that happen to resolve to numeric-looking strings.

Version

No response

Related Issue

#2872

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions