Documentation
¶
Overview ¶
Cross-node orchestration for `stratum certs rotate-ca --cluster`. It drives the three CA-rotation phases across every node PHASE-MAJOR — every node completes phase N before any node starts phase N+1 — which is what keeps etcd peers and kubelet clients from ever facing a cert signed by a CA a peer doesn't yet trust.
Reuses the same mechanism as `stratum upgrade --cluster`: a privileged, node-pinned Job that nsenters PID 1 and runs the on-host `stratum` binary. It does NOT drain or cordon (CA rotation is not workload-disruptive). New CA material is generated once on the initiator and distributed to the other control planes via the existing encrypted stratum-certs Secret (keyed by a transient stratum-carotate Secret).
CA rotation (`stratum certs rotate-ca`) — the on-demand, cluster-wide rotation of the trust anchors the leaf rotation deliberately leaves alone: the root CA, the front-proxy CA, and the ServiceAccount signing keypair. It runs in three phases that must each be applied to EVERY node before the next (RotateCACluster, or a careful operator, enforces the ordering):
trust-new every node trusts old‖new (bundles); signer still old sign-new control planes promote the new signer + reissue all leaves finalize every node drops the old CA; only new remains
This file is the single-node executor for one phase; the cross-node orchestrator lives in cacluster.go.
Package certs orchestrates control-plane certificate rotation on a node: inspect expiry (check), and force-reissue the leaf certs from the existing CA + rewrite the kubeconfigs + restart the control-plane units with health gates (rotate). Leaf-only — it never touches the root/front-proxy CAs or the SA signing keypair (those are the heavier cluster-wide "CA rotation" flow).
The per-node half of `stratum certs migrate-etcd-ca`: one phase, on this node, followed by the restart the phase requires.
internal/pki owns the file surgery and every safety guard; this file owns the two things a primitive deliberately does not — deciding whether the phase is already applied, and restarting the component whose in-memory state the write does not reach. pki.MigrateEtcdTrustBoth's doc comment states the second obligation outright: "Nothing in-tree owns that restart yet ... So the restart is the caller's responsibility." This is that caller.
The cluster driver (etcdcacluster.go) invokes this same code path on every control plane, through a Job, so a `--cluster` run and three hand-run `--phase` invocations do exactly the same thing on each node.
Cross-node orchestration for `stratum certs migrate-etcd-ca --cluster` walks trust-both → issue-new → drop-root across every control plane PHASE-MAJOR — every node completes phase N before any node starts phase N+1 — which is what stops a member presenting a leaf its peers do not yet trust and losing quorum. internal/pki/etcdca.go states the requirement.
Same mechanism as `stratum certs rotate-ca --cluster` and `stratum upgrade --cluster`: a privileged, node-pinned Job that nsenters PID 1 and runs the on-host `stratum` binary. It does NOT drain or cordon — no workload is disrupted — and it touches control planes only, because a worker holds no etcd leaf and no etcd CA. The etcd CA is minted once on the initiator and distributed to the other control planes over the existing encrypted stratum-certs Secret, keyed by the transient stratum-carotate Secret.
A cluster-scoped custom resource carrying a command would grant root on every node, so the per-node Job is the house pattern; this reuses it rather than introducing a CR.
Where the migration state lives ¶
Nowhere. There is no journal, no CR, no ConfigMap and no phase counter. State is re-derived at the start of every run, and again before the gate, by asking each node's RUNNING etcd what it trusts (pki.ProbeEtcdTrust). A second store could disagree with what etcd actually enforces, and the disagreement that matters — a bundle written whose restart never took — is precisely the one a counter would record as success.
Mounted ServiceAccount tokens and the old signing key. Finalize drops the old ServiceAccount key from the apiserver's verification set, so a token still signed by it gets 401 from that moment. After sign-new every token the apiserver issues carries the new key's id, but one that was issued before (a kubelet restarted while another control plane still signed with the old key) stays mounted until the kubelet re-issues it. This file finds those tokens on a node, which is what the finalize gate runs on every node.
In-cluster API clients and CA rotation. A client that loads the cluster CA once, at start, fails with x509 errors from sign-new until it restarts. It can restart safely only after kube-controller-manager has republished kube-root-ca.crt as old‖new, which it does once trust-new is on every node, so the restart goes between trust-new and sign-new. This file finds those clients, waits for the republish, rolls what Stratum owns, and gates on what it does not.
Index ¶
- Constants
- Variables
- func CARotateKeyStaged() (bool, error)
- func CheckRollback(w io.Writer) error
- func CheckRollbackCluster(ctx context.Context, w io.Writer, buildClient ...clientBuilder) error
- func FinalizePreflight(ctx context.Context, w io.Writer, cs kubernetes.Interface, nodes []cnode) error
- func GuardRekey() error
- func GuardRekeyWithClient(cs kubernetes.Interface) error
- func IncompleteRotation() (present, expired bool)
- func IsRollbackUnsafe(err error) bool
- func MigrateEtcdCA(w io.Writer, o EtcdCAOptions) error
- func MigrateEtcdCACluster(o EtcdCAOptions) error
- func MigrateEtcdCAClusterContext(ctx context.Context, w io.Writer, o EtcdCAOptions, ...) error
- func MigrateKubeletClientCA(w io.Writer, o KubeletCAOptions) error
- func MigrateKubeletClientCACluster(w io.Writer, o KubeletCAOptions) error
- func MigrateKubeletClientCAClusterContext(ctx context.Context, w io.Writer, o KubeletCAOptions, ...) error
- func RenewalRecordPath() string
- func RequireNodeProfile(op string) error
- func RollbackCA(w io.Writer, o CARotateOptions) error
- func Rotate(w io.Writer, o Options) (bool, error)
- func RotateCA(w io.Writer, o CARotateOptions) error
- func RotateCACluster(o CARotateOptions) error
- func RotateCAClusterContext(ctx context.Context, w io.Writer, o CARotateOptions, ...) error
- func RotateContext(ctx context.Context, w io.Writer, o Options) (bool, error)
- func RotationDetail() []string
- func RotationPhase() string
- func SaveRenewalRecord(r RenewalRecord) error
- func ScanTokens(w io.Writer, oldKid string, restart bool) error
- func SubjectDrift() ([]string, error)
- type CARotateFlags
- type CARotateOptions
- type CertStatus
- type EtcdCAFlags
- type EtcdCAOptions
- type KubeletCAFlags
- type KubeletCAOptions
- type Options
- type RenewalRecord
Constants ¶
const ( PhaseTrustNew = "trust-new" PhaseSignNew = "sign-new" PhaseFinalize = "finalize" )
Phase names for CA rotation.
const ( CARotateClusterWarning = "WARNING: workloads that use in-cluster API config (operators, cert-manager, ingress controllers, Cilium)\n" + " keep the CA they started with, so from sign-new they fail with x509 errors until restarted.\n" + " Stratum restarts its own add-ons before sign-new. Your Deployments, DaemonSets and StatefulSets\n" + " are restarted before sign-new with --restart-workloads; with --skip-workload-check they are\n" + " not, and you restart them once, when this command completes.\n" + " A client that re-reads its service-account token needs no restart after sign-new, if it was\n" + " restarted before it.\n" + " Finalize refuses while any node still mounts a token signed with the old key. That scan reads\n" + " mounted token files, so it cannot see a client that caches its token in memory: that client\n" + " holds an old-key token even after a restart before sign-new, and is rejected with 401 once\n" + " finalize drops the key. Restart it after sign-new has completed on every control plane and\n" + " before finalize. This command runs finalize itself, so use --phase runs for that window;\n" + " otherwise restart it when this command completes.\n" + " Tokens from kubernetes.io/service-account-token Secrets do not rotate: recreate those\n" + " Secrets (and any kubeconfig built from them) after finalize; a restart does not help.\n\n" CARotatePhaseWarning = "WARNING: workloads that use in-cluster API config (operators, cert-manager, ingress controllers, Cilium)\n" + " keep the CA they started with, so from sign-new they fail with x509 errors until restarted.\n" + " Restart all of them, Stratum's own add-ons included, after trust-new has completed on every\n" + " node and before sign-new; sign-new refuses until they have, unless you pass --skip-workload-check.\n" + " A client that re-reads its service-account token needs no restart after sign-new, if it was\n" + " restarted before it.\n" + " A client that caches its service-account token in memory holds an old-key token even after\n" + " that restart, and is rejected with 401 once finalize drops the key. Restart it after sign-new\n" + " has completed on every control plane and before finalize.\n" + " --phase=finalize on a control plane refuses while that node mounts a token signed with the\n" + " old key. It checks that control plane only, and finalize on a worker does not check: scan\n" + " every node before the first control plane finalizes. The scan reads mounted token files, so\n" + " it does not see a client that caches its token in memory.\n" + " Tokens from kubernetes.io/service-account-token Secrets do not rotate: recreate those\n" + " Secrets (and any kubeconfig built from them) after finalize; a restart does not help.\n\n" )
The restart warnings are printed by the CLI and returned in the MCP result before a rotating (not rollback) rotate-ca run. Workloads using in-cluster API config cache the CA they loaded at start, so from sign-new they fail with x509 errors until restarted: the restart belongs after trust-new publishes the new CA and before sign-new. --cluster does it itself for the add-ons Stratum ships, and for the user's Deployments, DaemonSets and StatefulSets with --restart-workloads; without it --cluster refuses to start while there are any, unless --skip-workload-check leaves them to the operator (one restart, once the run completes). A --phase run rolls nothing, so --phase=sign-new refuses until every workload has restarted, Stratum's own included. A pod restarted before sign-new gets an old-key token, which its client re-reads when the kubelet refreshes it, so nothing needs restarting after sign-new for those. The residual is a client that caches its token in memory, which still holds an old-key token after a restart before sign-new and which the finalize gate cannot see (it scans the tokens mounted as files): it needs a restart after sign-new has completed on every control plane and before finalize. --cluster runs finalize itself, so only --phase runs leave that window, and a --cluster operator restarts it after the run. The gate is cluster-wide under --cluster, but a --phase=finalize run checks only its own control plane, and a worker's finalize not at all. The other residual is the tokens of legacy kubernetes.io/service-account-token Secrets, which never rotate and which a restart never fixes. The per-node Jobs of a --cluster run execute --phase, so their logs carry the --phase text.
const EtcdPhaseReAddRoot = "re-add-root"
EtcdPhaseReAddRoot is the first pass of a forced un-migration: widen trust cluster-wide before any leaf moves. It is a certs-level phase rather than a pki one because it exists purely to give the ORCHESTRATOR an ordering it can make safe; a single node has no use for it.
const ExitRollbackUnsafe = 11
ExitRollbackUnsafe is the exit status of `rotate-ca --rollback --check` on a node that cannot roll back, so the cluster-wide check tells a refusal from a Job that failed to run.
const ExitStaleTokens = 10
ExitStaleTokens is the exit status of `rotate-ca --scan-tokens` on a node that mounts a token signed by the old key. The scan Job's answer is its exit status, as the upgrade --check Job's is (internal/upgrade.ExitUpgradeNeeded).
const KubeletClientName = "kubelet-client"
KubeletClientName is the `certs check` row for a worker's CSR-issued kubelet client cert.
Variables ¶
var CARotatePhases = []string{PhaseTrustNew, PhaseSignNew, PhaseFinalize}
CARotatePhases are the CA-rotation phases, in the only order they may run. Both front ends name the phases from this list, so a phase that is added, dropped or renamed here changes what every front end accepts and prints.
var ErrStaleTokens = errors.New("this node mounts a token signed by the old service-account key")
ErrStaleTokens is ScanTokens' error when it found such a token.
var EtcdCAAllPhases = []string{ pki.EtcdPhaseTrustBoth, pki.EtcdPhaseIssueNew, pki.EtcdPhaseDropRoot, pki.EtcdPhaseRollback, }
EtcdCAAllPhases is every phase a single-node migrate-etcd-ca accepts: the forward phases plus rollback, the way back. The cluster orchestrator runs only EtcdCAPhases, in EtcdCAPhases' order; rollback is never a scheduled step, and re-add-root exists only inside a forced --cluster un-migration.
var EtcdCAPhases = []string{pki.EtcdPhaseTrustBoth, pki.EtcdPhaseIssueNew, pki.EtcdPhaseDropRoot}
EtcdCAPhases are the forward phases, in the only order they may be run.
var KubeletCAPhases = []string{pki.KubeletPhaseTrustBoth, pki.KubeletPhaseIssueNew, pki.KubeletPhaseDropRoot}
KubeletCAPhases are the forward phases of `certs migrate-kubelet-client-ca`, in order.
var KubeletCARollbackPhases = []string{pki.KubeletPhaseReAddRoot, pki.KubeletPhaseReissueRoot, pki.KubeletPhaseRemoveCA}
KubeletCARollbackPhases undo them, in order.
Functions ¶
func CARotateKeyStaged ¶
CARotateKeyStaged reports whether the transient stratum-carotate Secret is present in the cluster. `certs rotate-ca --cluster` and the etcd-CA migration create it to hand follower control planes the key that decrypts stratum-certs; while it exists, re-encrypting stratum-certs under a new key would strand those followers. Unlike RotationPhase() (this node's local PKI files, which read "idle" on a follower the initiator has not reached yet), this is a cluster-wide signal visible from any control plane — what `certs check` reads to flag a key that outlived its run. The re-key guard reads the Secret and the shared rotation record itself (GuardRekey).
func CheckRollback ¶
CheckRollback reports whether THIS node can roll back, and changes nothing. A control plane answers with cpRollbackCheck, the whole of RollbackCA's refusal logic; a worker holds no CA and refreshes its trust from the control planes, so it can unless workerRollbackCheck refuses. It first prints the node's rollbackState.
func CheckRollbackCluster ¶
CheckRollbackCluster asks every node, control planes first, whether it can roll back, and refuses if one cannot. It takes no lease and changes nothing on any node: the point is to find a node that lost an old key before any node rolls back, because the healthy nodes would return to the old root while that one can only go forward, and the cluster would be split across two roots. Run it from a control plane, before `--rollback --yes` on any node.
func FinalizePreflight ¶
func FinalizePreflight(ctx context.Context, w io.Writer, cs kubernetes.Interface, nodes []cnode) error
FinalizePreflight refuses to drop the old CA unless the cluster is provably healthy on the new signer: every node Ready, no pending CSRs (a worker awaiting its new-CA client cert), every CP's apiserver serving, and every kubelet serving a cert the new CA signed (checkKubeletServingTrusted). Because the orchestrator halts on the first node failure, reaching here already implies every node completed sign-new; this is the explicit safety re-check. The mounted-token arm of the preflight (finalizeTokenGateForKey) runs after it.
func GuardRekey ¶
func GuardRekey() error
GuardRekey is the one re-key entry point for every path that mints a new certificate-key and re-encrypts stratum-certs — `certs rotate --rekey-shared`, `token create --control-plane` and the MCP token_create tool. A re-key while a cluster CA rotation or etcd-CA migration is in flight strands the key staged in stratum-carotate that the flow's follower control planes decrypt stratum-certs with, so the mint must refuse.
The guard answers from two signals, the cheap one first: this node's rotation phase (local PKI files, which catch a rotation staged on THIS node), and — because RotationPhase() reads "idle" on a follower the initiator has not reached yet — the cluster-wide stratum-carotate Secret or an unfinished stratum-ca-rotation record, visible from any control plane: the record also covers a halted run whose key is gone, which no one is waiting out, so that refusal names the resume and the record to clear after a completed rollback. An unreadable staged-key check refuses the mint rather than wave it through: an apiserver failure is not evidence that nothing is staged.
The orchestrators' own re-uploads (rotate-ca --cluster, migrate-etcd-ca) do not consult this guard: they run under the shared maintenance lease, stage the key themselves and re-encrypt deliberately.
func GuardRekeyWithClient ¶
func GuardRekeyWithClient(cs kubernetes.Interface) error
GuardRekeyWithClient uses the caller's credentials for cluster-wide checks.
func IncompleteRotation ¶
func IncompleteRotation() (present, expired bool)
IncompleteRotation reports whether a leaf rotation reissued leaves but has not yet restarted every component onto them, and whether that rotation began from expired leaves. A marker that cannot be read still counts as present.
func IsRollbackUnsafe ¶
IsRollbackUnsafe reports whether err is a refusal to roll back, from this node's check or from the cluster-wide one, so a front end can exit with ExitRollbackUnsafe.
func MigrateEtcdCA ¶
func MigrateEtcdCA(w io.Writer, o EtcdCAOptions) error
MigrateEtcdCA runs one phase on this node.
The node maintenance lock comes before anything is decided about the phase — before the node identity, the applied-phase check, the shared-CA fetch or the first write — so an invocation beside a live upgrade or other maintenance operation refuses with the operation-naming lock message, and the defer releases the lock on every exit. The lockable entry is the exported one: the cluster driver's in-process initiator and every node Job (which run the same CLI command) reach it, while the lock-free migrateEtcdCAWith stays the seam the cluster tests drive.
func MigrateEtcdCACluster ¶
func MigrateEtcdCACluster(o EtcdCAOptions) error
MigrateEtcdCACluster drives the etcd-CA migration across every control plane from one of them.
func MigrateEtcdCAClusterContext ¶
func MigrateEtcdCAClusterContext(ctx context.Context, w io.Writer, o EtcdCAOptions, buildClient ...clientBuilder) error
MigrateEtcdCAClusterContext runs the migration with the caller's context, without installing a signal handler or detaching from parent cancellation.
buildClient is the client builder the run acquires its one API client through (see clientBuilder); callers that pass none get the package's admin.conf builder. The front ends inject their own, so the credential decision stays with the caller.
func MigrateKubeletClientCA ¶
func MigrateKubeletClientCA(w io.Writer, o KubeletCAOptions) error
MigrateKubeletClientCA runs one phase on this node. Its progress goes through the writer the caller hands it — os.Stdout for the CLI, the MCP handler's result buffer for the tool.
func MigrateKubeletClientCACluster ¶
func MigrateKubeletClientCACluster(w io.Writer, o KubeletCAOptions) error
MigrateKubeletClientCACluster runs every phase on every node from this control plane: to KubeletClientCAName, or with Rollback back to the root.
func MigrateKubeletClientCAClusterContext ¶
func MigrateKubeletClientCAClusterContext(ctx context.Context, w io.Writer, o KubeletCAOptions, buildClient ...clientBuilder) error
MigrateKubeletClientCAClusterContext is MigrateKubeletClientCACluster with the caller's context. Its progress goes through w, and like the other cluster orchestrators it acquires its API client through an injectable builder: the front ends hand it the node credential's builder, the default stays the package's admin seam.
func RenewalRecordPath ¶
func RenewalRecordPath() string
RenewalRecordPath is where the renewal record persists, beside node.yaml in the node's state area.
func RequireNodeProfile ¶
RequireNodeProfile refuses a certificate operation on a node whose saved profile this binary does not register, before the operation writes anything. The renderers an operation reaches later — the static-pod restarts, the finalize preflight — stop on the same profile, but mid-run, with leaves reissued and kubeconfigs rewritten. A node with no state yet runs no profile. op names the operation the way its command is spelled.
func RollbackCA ¶
func RollbackCA(w io.Writer, o CARotateOptions) error
RollbackCA reverts a partial rotation on THIS node. It restores the pre-rotation CA/SA + bundles (pki.RollbackCA); if the signer had already been promoted (sign-new reached, detected via the .pre-carotate backup), it also reissues the leaves under the restored old CA so the node is internally consistent, then restarts. Workers hold no CA — they just refresh trust from cluster-info.
func Rotate ¶
Rotate reissues the leaf certs from the existing CA, rewrites the kubeconfigs preserving the current endpoint, and restarts the control-plane units. It reports whether the run reissued anything: (false, nil) when the window check found nothing to rotate, (true, nil) after a completed rotation, and false beside the error on every refusal or failure.
The cluster fence and host lock come before any change, so a run beside a live upgrade or other maintenance operation refuses having reissued nothing, and the defer releases it on every exit.
func RotateCA ¶
func RotateCA(w io.Writer, o CARotateOptions) error
RotateCA runs one CA-rotation phase on THIS node. Control-plane nodes rotate the CA/SA material and reissue leaves; workers refresh their trust bundle (trust-new, finalize) and renew the kubelet client cert under the new CA (sign-new).
The node maintenance lock comes first, before the phase is even decided — ahead of the staged-rotation guard, the first pre-change refusal on the control-plane path — so an invocation beside a live upgrade or other maintenance operation refuses with the operation-naming lock message and leaves a staged rotation untouched. The defer releases the lock on every exit, including a phase that fails after the lock.
func RotateCACluster ¶
func RotateCACluster(o CARotateOptions) error
RotateCACluster orchestrates a full CA rotation across the cluster from a control plane. It runs trust-new → sign-new → finalize, each phase completed on the initiator, then every other control plane (serial, quorum-safe), then every worker — before advancing. Halts on the first node failure, leaving the cluster in the stable, still-working state reached so far.
func RotateCAClusterContext ¶
func RotateCAClusterContext(ctx context.Context, w io.Writer, o CARotateOptions, buildClient ...clientBuilder) error
RotateCAClusterContext runs the cluster rotation with the caller's context. The CLI supplies its parent-derived signal context; this entry point does not install another signal handler or detach from the caller's cancellation.
buildClient is the client builder the run acquires every API client through (see clientBuilder): the client at entry, and the two mid-run rebuilds after trust-new and after sign-new, where the admin certificate has been reissued under the new CA. Callers that pass none get the package's admin.conf builder; the front ends inject their own, so the credential decision stays with the caller and a rebuild cannot quietly reload admin.conf.
func RotateContext ¶
RotateContext reissues the leaf certs from the existing CA, rewrites the kubeconfigs preserving the current endpoint, and restarts the control-plane units, bounded by ctx: a rotation whose context ends mid-run stops at the next step boundary — the step in flight (usually the RSA reissue) finishes, and no later step starts. The run group joins the renewal loop at shutdown, so this is what keeps that join from waiting out a whole rotation and what keeps systemd's KillMode=process from cutting one down mid-step instead. The cluster fence and host lock come before any change, so a run beside a live upgrade or other maintenance operation refuses having reissued nothing, and the defers release them on every exit. Like Rotate, it reports whether the run reissued anything: false when the window check found nothing to rotate or on any error, true only after the restart sequence completed.
func RotationDetail ¶
func RotationDetail() []string
RotationDetail is what the halt messages send an operator to `certs check` for: whether the rotation was forced, which forbids a rollback, whether a staged pair signs in place of a lost key, for each of the root CA, the service-account key and the front-proxy CA, and whether this node can roll back. Empty when no rotation is open.
func RotationPhase ¶
func RotationPhase() string
RotationPhase describes this node's CA-rotation state for `stratum certs check`.
func SaveRenewalRecord ¶
func SaveRenewalRecord(r RenewalRecord) error
SaveRenewalRecord persists the renewal record atomically through the shared atomic writer (internal/atomicfile): a failed or crashed save leaves either the old record or the new one, never a truncated one and never a stray temp copy beside it.
func ScanTokens ¶
ScanTokens is `certs rotate-ca --scan-tokens`, the per-node scan Job: it prints one line per mounted token signed by the key with id oldKid and returns ErrStaleTokens if there were any. With restart it restarts the kubelet first when there are, and reports what is left.
func SubjectDrift ¶
SubjectDrift is pki.SubjectDrift for this node: the leaves on disk whose subject this release no longer issues, as leaves an earlier release issued in system:masters.
Types ¶
type CARotateFlags ¶
type CARotateFlags struct {
Cluster bool
Phase string
Force bool
Rollback bool
// Check, with Rollback, reports whether this node (with Cluster, every
// node) can roll back, and changes nothing.
Check bool
RestartWorkloads bool
SkipWorkloadCheck bool
SkipTokenCheck bool
}
CARotateFlags is the raw flag set `stratum certs rotate-ca` was given: what the CLI's cobra flags and the MCP tool's input struct both carry.
func (CARotateFlags) Options ¶
func (f CARotateFlags) Options() CARotateOptions
Options turns validated flags into the options the entry points take, so a front end cannot assemble CARotateOptions by hand and drift from what the validator just accepted.
func (CARotateFlags) Validate ¶
func (f CARotateFlags) Validate() error
Validate refuses contradictory or incomplete argument combinations rather than silently picking one, on a command that restarts the control plane on every node.
It lives here, not in pkg/cli, because both front ends must refuse the same requests with the same words: the MCP tool returns this error verbatim, so it spells the CLI's flag names (--cluster, --phase) and an AI client can map its own arguments onto what the refusal asks for.
type CARotateOptions ¶
type CARotateOptions struct {
Force bool // rotate even though an old CA or SA key is gone (compromise recovery); ca.crt is still required
Phase string // trust-new | sign-new | finalize
Cluster bool // orchestrate across all nodes (see RotateCACluster)
Quiet bool
// RestartWorkloads (--cluster) has Stratum restart the user Deployments,
// DaemonSets and StatefulSets that use in-cluster API credentials, between
// trust-new and sign-new. SkipWorkloadCheck turns off the checks on those
// workloads (before trust-new, and before sign-new on a node).
RestartWorkloads bool
SkipWorkloadCheck bool
// SkipTokenCheck finalizes without checking that no mounted ServiceAccount
// token is still signed by the old key (see satoken.go). The operator's
// --skip-token-check, for compromise recovery, needs Force (CheckWorkloadFlags);
// the orchestrator sets it directly, having checked every node itself.
SkipTokenCheck bool
// RunID carries the cluster rotation identity to each phase, including
// remote node Jobs. Manual phases leave it empty and consult the shared
// record only when finalized material needs to be scoped to an active run.
RunID string
// contains filtered or unexported fields
}
CARotateOptions configures a CA rotation.
func (CARotateOptions) CheckWorkloadFlags ¶
func (o CARotateOptions) CheckWorkloadFlags() error
CheckWorkloadFlags refuses the workload flag combinations that make no sense. One check, for the CLI and the MCP tool both.
type CertStatus ¶
type CertStatus struct {
Name string
NotAfter time.Time
DaysLeft int
IsCA bool
Expiring bool // within the warn window
Rotatable bool // a leaf we can rotate (false for CAs)
Kubelet bool // the kubelet's CSR-issued client cert, which the kubelet renews itself
RenewalOverdue bool // the kubelet client cert is past its latest expected renewal point
}
CertStatus is one row of `stratum certs check`.
func Check ¶
func Check(o Options) ([]CertStatus, error)
Check reports expiry for managed certificates and the kubelet client cert. Any present managed or kubelet cert that cannot be read or parsed is an error for operators to address; absent ones are skipped.
type EtcdCAFlags ¶
EtcdCAFlags is the raw flag set `stratum certs migrate-etcd-ca` was given.
func (EtcdCAFlags) Options ¶
func (f EtcdCAFlags) Options() EtcdCAOptions
Options turns validated flags into the options the two entry points take.
func (EtcdCAFlags) Validate ¶
func (f EtcdCAFlags) Validate() error
Validate rejects contradictory, incomplete or misspelled arguments rather than silently picking one, on a command that restarts etcd on every control plane. Both front ends run it before anything touches the host: the CLI's RunE and the MCP tool's handler return its error verbatim, so a refusal spells the CLI's flag names (--cluster, --phase) and no front end can rebuild the rules, or their wording, by hand.
It lives here, not in pkg/cli, because the cluster driver builds the per-node Job argv for THIS command (etcdNodeArgv) and the Job runs it through the same CLI — so the rules and the argv have to agree, and a rule that lives only in pkg/cli cannot be checked against the argv that must satisfy it. An earlier version had them disagree: the argv carried `--phase rollback --force` while the CLI accepted --force only alongside the separate --rollback bool, so every follower Job failed flag validation. The initiator does not go through cobra (the driver runs it in-process), so it succeeded — splitting the cluster's trust rather than failing cleanly.
type EtcdCAOptions ¶
type EtcdCAOptions struct {
Phase string // trust-both | issue-new | drop-root | rollback
Resume bool // --cluster: proceed on a cluster whose nodes are at different phases
Force bool // --phase=rollback: un-migrate a node that already completed drop-root
Quiet bool
}
EtcdCAOptions configures one per-node invocation.
type KubeletCAFlags ¶
KubeletCAFlags is the flag set `certs migrate-kubelet-client-ca` was given.
func (KubeletCAFlags) Options ¶
func (f KubeletCAFlags) Options() KubeletCAOptions
Options turns validated flags into options.
func (KubeletCAFlags) Validate ¶
func (f KubeletCAFlags) Validate() error
Validate refuses contradictory flags.
type KubeletCAOptions ¶
KubeletCAOptions configures one invocation.
type Options ¶
type Options struct {
IfExpiringDays int // >0: no-op unless a leaf is within this many days of expiry (timer path). 0 = force.
WarnDays int // window for the "expiring" flag in Check (default version.CertRenewWindowDays)
Quiet bool // suppress the per-step step logs (timer path)
// NodeLockHeld is the held-lock capability: the caller already holds the
// node maintenance lock (internal/nodelock) for the whole call and Rotate
// must not reacquire it — a second Acquire from the same process refuses
// against its own fence, because flock conflicts between open file
// descriptions. The caller releases the lock on its own exits and must
// also hold the cluster fence when a renewal is needed.
NodeLockHeld bool
// KubeVipRequired fails the rotation when kube-vip cannot be restarted,
// or when kubelet has not replaced the process that ran before the
// restart within kubeVipReplaceWait, where otherwise that restart is
// best-effort: a caller moving kube-vip off a credential it must no
// longer hold cannot count a rotation that left the running kube-vip on
// it as done.
KubeVipRequired bool
}
Options for RotateNode.
type RenewalRecord ¶
type RenewalRecord struct {
// AttemptedAt is when the loop started its last rotation attempt.
AttemptedAt time.Time `json:"attemptedAt"`
// Result is that attempt's rotation error, empty when it succeeded.
Result string `json:"result,omitempty"`
}
RenewalRecord is the scheduled renewal loop's LAST attempt on this node, persisted under the node's state area so `stratum certs check` can show a dead or failing loop before the leaves expire — nothing else on the node says when renewal last ran or how it went.
func LoadRenewalRecord ¶
func LoadRenewalRecord() (RenewalRecord, error)
LoadRenewalRecord reads the persisted renewal record. A record that does not exist is reported as an error wrapping fs.ErrNotExist, which callers read as no attempt recorded yet.