Documentation
¶
Overview ¶
Package clusterinfo authenticates the kube-public cluster-info ConfigMap and turns the authenticated payload into a CA trust bundle.
It exists as its own package for one reason: the trust-bundle helper must be reachable ONLY from the authenticated path. Inside a single package that is a convention a future caller can quietly break — an unexported marker type is still constructible by any code in the same package. Across a package boundary the compiler enforces it: Authenticated carries only unexported fields, so no outside package can build a populated one, and the only thing that returns a populated one is Authenticate.
The wire format is the Kubernetes cluster-info format: a detached JWS (RFC 7515 Appendix F) with an HMAC-SHA256 MAC keyed by the bootstrap token secret, published by kube-controller-manager's bootstrapsigner as data["jws-kubeconfig-<token-id>"] alongside data["kubeconfig"]. See the golden vector in the tests.
Index ¶
Constants ¶
const ( // KubeConfigKey and JWSSignatureKeyPrefix mirror k8s.io/cluster-bootstrap's // token/api constants. They are restated rather than imported: taking that // module for two strings and ~15 lines of signer would add 19 modules to the // require set and a permanently-unfixed advisory (GO-2024-2631) via // gopkg.in/square/go-jose.v2. KubeConfigKey = "kubeconfig" JWSSignatureKeyPrefix = "jws-kubeconfig-" )
const ( KubeletClientCAConfigMap = "stratum-kubelet-client-ca" KubeletClientCAKey = "ca.crt" // KubeletClientCAModeExtension is covered by cluster-info's detached JWS. // An absent extension identifies cluster-info from a release before the split. KubeletClientCAModeExtension = "stratum.ciq.com/kubelet-client-ca" KubeletClientCAModeLegacy = "legacy" KubeletClientCAModeSplit = "split" )
KubeletClientCAConfigMap is the kube-public ConfigMap holding a split cluster's kubelet client-CA bundle under KubeletClientCAKey.
Variables ¶
This section is empty.
Functions ¶
func Parse ¶
Parse is the one reader of the cluster-info kubeconfig document: it returns the apiserver URL the document advertises and the CA data it carries.
token.ensureClusterInfo writes the document with exactly one cluster entry, but three consumers read it back — TrustBundle on the joining node, the verified re-fetch in bootstrap, and RepinCA on the control plane — and a second entry (a VIP beside a node address, say) must not make them answer by Go map order, each under its own rule. So Parse never chooses between entries: every entry that states a server must state the same one, every entry that carries CA data must carry the same data, and an entry that states no opinion on a field is skipped. A document whose entries disagree is refused.
Parse does not require either field to be present. Each consumer checks the field it needs, so the refusal names what that consumer is missing.
func PinFor ¶
func PinFor(c *x509.Certificate) string
PinFor returns the "sha256:<hex>" pin of a certificate's SubjectPublicKeyInfo — the same value token.CACertHash computes on the control plane, so the two ends of the handshake agree.
func ValidatePin ¶
ValidatePin is validPin for callers outside this package: `stratum up --join` checks the pin before comparing it with the node's own CA, so a missing or mistyped pin gets this diagnosis there too, rather than "a different cluster".
Types ¶
type Authenticated ¶
type Authenticated struct {
// contains filtered or unexported fields
}
Authenticated is a cluster-info kubeconfig whose detached JWS has been verified against the bootstrap token. Only Authenticate returns a populated value, and the field is unexported, so no other package can manufacture one. The zero value is inert and TrustBundle rejects it.
func Authenticate ¶
func Authenticate(data map[string]string, t Token) (Authenticated, error)
Authenticate verifies the detached JWS over data["kubeconfig"] using the bootstrap token, and returns the authenticated payload.
This must run before the kubeconfig is parsed. The bytes arrive from a transport with InsecureSkipVerify, so until this returns nil they are entirely attacker-controlled and must not reach a YAML parser.
func (Authenticated) KubeletClientCASplit ¶
func (a Authenticated) KubeletClientCASplit() (bool, error)
KubeletClientCASplit reports the mode carried in the token-authenticated kubeconfig. A malformed or unknown mode is never treated as legacy.
func (Authenticated) TrustBundle ¶
func (a Authenticated) TrustBundle(pin string) ([]byte, error)
TrustBundle parses the authenticated kubeconfig, checks the pinned CA hash against the certificates it carries, and returns those certificates canonically re-encoded.
This is the single trust-bundle helper, and it is reachable only from a populated Authenticated. What it returns is every certificate the JWS covered — NOT only the pinned one. The MAC authenticates the whole document, so every block in it is equally authenticated, and narrowing to the pinned certificate would break a staged CA rotation, where a joiner legitimately needs a CA it holds no pin for.
type NoSignatureError ¶
type NoSignatureError struct{ TokenID string }
NoSignatureError reports that cluster-info carries no JWS for this token id.
Callers distinguish it because it is the one absent-signature case worth retrying: the bootstrapsigner patches the signature in a moment after the ConfigMap itself is written. A signature that is present and does NOT verify is never retried — that is the MITM signal, and it fails immediately.
func (*NoSignatureError) Error ¶
func (e *NoSignatureError) Error() string
type Token ¶
type Token struct {
// contains filtered or unexported fields
}
Token is a bootstrap token that has been validated against tokenPattern.
Its fields are unexported and ParseToken is the only constructor, so — like Authenticated on the output side — no outside package can hand Authenticate an unvalidated token. That matters twice: computeDetachedSignature builds a JSON header by concatenating ID, and an empty secret would key the MAC with the empty string, which any attacker can compute. The zero value is rejected by Authenticate.
func ParseToken ¶
ParseToken validates a bootstrap token "<id>.<secret>" against tokenPattern. Call it BEFORE any network I/O, so a malformed token never causes a request to an endpoint that cannot yet be verified.