API Call Execution in Kyverno#
Overview#
The pkg/engine/apicall/ package handles Kyverno's outbound HTTP requests for policy context entries of type APICall. It manages the full lifecycle: variable substitution in URLs and data, request construction, header injection, TLS configuration, response size enforcement, and storing results back into the evaluation context.
Entry point: New() constructs an apiCall struct backed by an Executor. Call FetchAndLoad() to execute a call and store the result, or Fetch() + Store() individually.
Call Dispatch: K8s API vs. Service Calls#
Executor.Execute() routes on whether APICall.URLPath is set:
URLPathset βexecuteK8sAPICall(): delegates toclient.RawAbsPath()(the in-cluster Kubernetes client). RBACForbidden/Unauthorizederrors are surfaced with explicit "permission denied" messages.Serviceset βexecuteServiceCall(): builds a standalone HTTP client and fires an outbound request to the configured URL.
Only GET and POST are accepted for service calls; other methods are rejected at request-build time .
Request Construction#
buildHTTPRequest() assembles the *http.Request:
- Body encoding β For
POSTrequests,buildRequestData()flattens[]kyvernov1.RequestData(key-value pairs) into a JSON object viajson.NewEncoder.GETrequests send no body. - Context propagation β The request is created with
http.NewRequestWithContext, passing the caller'scontext.Contextfor cancellation/timeout support . - Headers β
addHTTPHeaders()iterates overservice.Headersand adds each viareq.Header.Add().
Variable substitution in the URL, method, and data fields happens before execution in Fetch() via variables.SubstituteAllInType().
Authorization Token Injection#
addHTTPHeaders() automatically injects a Bearer token when no Authorization header has been explicitly set :
if req.Header.Get("Authorization") == "" {
if token, ok := readScopedToken(); ok && token != "" {
req.Header.Add("Authorization", "Bearer "+token)
}
}
readScopedToken() reads a projected ServiceAccount token from disk. The default path is /var/run/secrets/kyverno/apicall/token , overridable via the KYVERNO_SCOPED_TOKEN_PATH environment variable . If the file is missing or unreadable, a warning is logged once (via sync.Once) and the call proceeds without an Authorization header β it does not fail .
This token carries a custom OIDC audience (default: kyverno-svc.kyverno.io) configured in the Helm chart via .Values.apiCallToken.audience. A leaked scoped token cannot be replayed against the Kubernetes API server because its audience won't match .
The same readScopedToken() is used by scopedTokenClient (created via NewScopedTokenClient()), which wires the same automatic injection for outbound CEL HTTP calls across all policy compilers.
TLS and HTTP Client#
buildHTTPClient() returns:
- A plain
http.Client{Timeout: timeout}when noCABundleis configured. - A TLS-hardened client (minimum TLS 1.2, custom CA pool from
service.CABundlePEM) wrapped with OpenTelemetry tracing transport when a CA bundle is provided .
Configuration#
APICallConfiguration controls two parameters:
| Field | Purpose |
|---|---|
maxAPICallResponseLength | Enforces a max byte limit via http.MaxBytesReader. 0 disables the check . |
timeout | Passed as http.Client.Timeout. 0 means no timeout . |
Constructed via NewAPICallConfiguration(maxLen, timeout).
Namespace Scoping for K8s API Calls#
Namespaced policies enforce namespace isolation in Fetch(): if policyNamespace is non-empty, the resolved URLPath must contain a namespace segment matching policyNamespace. Cross-namespace access and cluster-scoped resource access from namespaced policies are both rejected .
Result Handling#
After fetching, transformAndStore() either:
- Stores raw JSON directly into the engine context under the entry name (no JMESPath configured), or
- Applies an optional JMESPath filter, marshals the result, and stores it.
If execution fails and a Default value is configured on the ContextAPICall, the default is returned instead of propagating the error .
Key Files#
| File | Purpose |
|---|---|
pkg/engine/apicall/apiCall.go | Public API: New, Fetch, Store, FetchAndLoad, namespace enforcement |
pkg/engine/apicall/executor.go | HTTP request construction, dispatch, TLS client, header injection |
pkg/engine/apicall/httpclient.go | Scoped token reading, scopedTokenClient for CEL HTTP calls |
pkg/engine/apicall/config.go | APICallConfiguration: response size limit and timeout |
pkg/engine/apicall/apiCall_test.go | Integration tests: GET/POST, chunked responses, header propagation, namespace enforcement, cancellation |