envoyproxy / envoyproxy/gateway

Serving listener TLS certificates from a provider outside the cluster (extension-based)

Open
#10,008 2 comments 0 reactions 0 assignees View on GitHub
triage
Dominant language
Go
Stars
3k
Forks
864
Avg merge
2d 2h
Merged PRs (30d)
140

Description

## Background

This follows up on #9421, where I asked for a way to let a TLS listener reference a certificate that is not a Kubernetes `Secret` — resolved by name at the data plane instead.

The steer from that discussion was to avoid a core-API change and do this through the extension mechanism. The idea is to support the same capability, built entirely on `ExtensionManager` and a new xDS hook, with no change to how core handles Secrets. Opening it fresh because the approach is different enough from #9421 to warrant its own discussion.

## Problem

A listener's `tls.certificateRefs` can only name a `Secret`, so the private key must live in the cluster and travel inline in the xDS stream. That excludes any provider holding certificates outside Kubernetes that will not export the key: a hosted certificate service or a data plane that already holds its own material.

## Proposal

1. Register the kinds an extension owns. New field on ExtensionManager:
```
extensionManager:
certificateResources:
- group: example.io
version: v1alpha1
kind: ExampleCertificate
```
Envoy Gateway watches these kinds and accepts them in certificateRefs. An unregistered kind stays an InvalidCertificateRef, which is the rejection Gateway API already requires for an unknown group and kind.

2. Reference one from a listener.
```
certificateRefs:
- group: example.io
kind: ExampleCertificate
name: prod-wildcard
```

3. A new xDS hook resolves it. `PostTLSCertificateResolve`, called once per such reference with the certificate object and the Gateway and listener it is for, returns an `SdsSecretConfig` that Envoy Gateway places on the filter chain verbatim. The provider decides its shape, from its own secret store / by whatever mechanism it implements

Name only, no sds_config

```
tls_certificate_sds_secret_configs:
- name: "provider-certificate-handle"
```

Name plus sds_config
```
tls_certificate_sds_secret_configs:
- name: "provider-certificate-handle"
sds_config: { ... } # exactly what the hook returned
```
Either way Envoy Gateway emits no Secret of its own and substitutes no default — what the provider returns is exactly what Envoy sees.

4. Watch the registered kinds. Envoy Gateway watches every kind in `certificateResources`, so any change to such a resource triggers a retranslation and re-invokes the hook — the same watch mechanism the other registered kinds already use, except the generation-change filter is dropped, since a certificate often becomes usable through a status-only update.

This closes the loop and makes the feature fail closed. While a certificate is not usable the hook returns failure_reason, Envoy Gateway omits it, and the listener serves nothing; when the provider updates the resource the watch fires, the hook is re-asked, and the chain appears. The hook is the only thing that judges usability, and this holds whether or not piece 5 is adopted: an unusable certificate is never served. Envoy Gateway passes the object to the hook opaquely and reads no content of its own.

5. Optionally, gate on a Ready condition for accurate status. Piece 4 already works, but its status can mislead: a waiting listener reports Programmed=True even though its port is closed, because the hook runs too late in translation to correct the listener status. If Envoy Gateway reads a Ready condition on the certificate first, it can instead report Programmed=False with Gateway API's existing Pending reason while the certificate is not yet usable.
```
status:
consumers:
- consumerRef: { kind: Gateway, namespace: team-a, name: web }
conditions: [{ type: Ready, status: "True" }]
- consumerRef: { kind: Gateway, namespace: team-b, name: api }
conditions: [{ type: Ready, status: "False" }]

```

### Proposed change

1. feat(api) — register certificate kinds on ExtensionManager
2. feat(proto) — add the PostTLSCertificateResolve RPC
3. feat(extension) — register the hook and make it reachable from translation
4. feat(ir) — carry a certificate reference instead of key material
5. feat(gatewayapi) — admit a ref to a registered kind and gate it on readiness
6. feat(xds) — resolve through the owning extension, and fail closed when it does not

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by tracing ExtensionManager, the proposed PostTLSCertificateResolve hook, and the translation and xDS entry points described in the issue. Done means the registered certificate kinds can be watched and resolved through the extension path, with retranslation on updates and fail-closed behavior when the hook reports an unusable certificate.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, kubernetes
Domain
api, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.