Files
egress/cluster.go
zooqueen 46297b6264 egress serves calls, not keys
A caller sends a request with no vendor credential. Egress authenticates the
workload behind it, checks that workload may spend on that provider, rewrites
the call onto an address it chose itself, and attaches the credential it holds
in memory. The credential is never returned and never written down.

Three allowlists, none of them reachable from a request field: the provider
table fixes the destination, the operation set fixes which calls exist, and the
grant table fixes who may spend on what. A caller names a provider; it cannot
name a host, so the credential can never sign a request to somewhere else.

Callers present the projected service-account token the kubelet mints for them.
Egress asks the cluster what one means rather than verifying it here, which
keeps revocation instant and leaves no signature checking to get wrong.

Credentials resolve from KMS at boot and refresh on a timer, so a value
rewritten there is in use within the interval with no redeploy. A read names the
full coordinate - path, name and environment - because a bare name resolves to a
different partition and a missing environment to a different environment, and
both return "not found" for a record that exists.
2026-08-14 22:46:55 -07:00

65 lines
2.1 KiB
Go

package egress
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"os"
"strings"
"time"
)
// Where the kubelet places this pod's identity and the cluster's certificate
// authority. Both are tmpfs, written by the kubelet, and the token is rotated
// under us — which is why it is read at use rather than at startup.
const (
accountDir = "/var/run/secrets/kubernetes.io/serviceaccount"
caFile = accountDir + "/ca.crt"
tokenFile = accountDir + "/token"
)
// Cluster returns a client that trusts the cluster's certificate authority and
// presents this pod's own identity.
//
// It exists for one call: asking the cluster to authenticate a caller's token.
// The API server answers that for an identity it knows, so egress must say who
// it is in order to learn who anyone else is.
//
// It is deliberately NOT the client used to reach KMS or a vendor. A client that
// attaches this pod's cluster identity should only ever talk to the cluster.
func Cluster() (*http.Client, error) {
pem, err := os.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("cluster: read authority: %w", err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(pem) {
return nil, fmt.Errorf("cluster: authority is not a certificate")
}
return &http.Client{
Timeout: 10 * time.Second,
Transport: &identified{
base: &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12},
TLSHandshakeTimeout: 10 * time.Second,
},
},
}, nil
}
// identified attaches this pod's current token to each request. The kubelet
// rewrites that file as the token nears expiry, so it is read per request rather
// than captured once — a token cached at startup stops working within the hour.
type identified struct{ base http.RoundTripper }
func (i *identified) RoundTrip(r *http.Request) (*http.Response, error) {
token, err := os.ReadFile(tokenFile)
if err != nil {
return nil, fmt.Errorf("cluster: read identity: %w", err)
}
r = r.Clone(r.Context())
r.Header.Set("Authorization", "Bearer "+strings.TrimSpace(string(token)))
return i.base.RoundTrip(r)
}