Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .claude/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,50 @@ The format is based on the regulated environment requirements:

---

## [2026-04-19 21:25] - Remove bogus `vexctl validate` step from build-vex job

**Author:** Erick Bourgeois

### Changed
- `.github/workflows/build.yaml` (`build-vex` job): Deleted the `Install vexctl` step (VEXCTL_VERSION 0.3.0) and the `Run vexctl validate vex.openvex.json` step. Updated the inline comment on the adjacent `Install Grype` step to drop the now-dangling "mirrors the vexctl install step above" reference.

### Why
`vexctl` has no `validate` subcommand — it never has — and runs of `build-vex` on push-to-main were failing with `unknown command "validate" for "vexctl"`. The step was added on the assumption that vexctl mirrored the OpenVEX reference implementation's schema validator; it does not. vexctl's 0.3.0 subcommand surface is `attest / create / generate / merge / verify`. Since vexctl was not used anywhere else in the job (Cosign does the attestation, not vexctl), the install step was removed with it rather than left as dead weight. Input-side schema/enum/uniqueness validation is still performed up front by `tools/validate-vex.sh` (which runs `validate_vex.py` against `.vex/*.toml`) — that's the real defensive gate. No behaviour change on pull_request events: the whole `build-vex` job is gated by `if: github.event_name != 'pull_request'` and only runs on push-to-main + release.

### Impact
- [ ] Breaking change
- [ ] Requires cluster rollout
- [x] Config change only (CI workflow)
- [ ] Documentation only

### Follow-up (optional, not in this commit)
If a second-opinion schema check of the *assembled* `vex.openvex.json` is wanted, add a step that validates against the OpenVEX JSON Schema with a tool that actually ships a validator (e.g. `python -m jsonschema -i vex.openvex.json openvex-schema.json`). Record the dependency in `~/dev/roadmaps/5spot-vex-generation-and-signing.md § Dependencies`.

---

## [2026-04-19 21:10] - Add child-cluster MutatingAdmissionPolicy to label Nodes for kata-deploy

**Author:** Erick Bourgeois

### Changed
- `deploy/admission/child-cluster-kata-runtime-mutatingpolicy.yaml` (new): `MutatingAdmissionPolicy` (`admissionregistration.k8s.io/v1alpha1`) that matches `nodes` on `CREATE` and stamps `katacontainers.io/kata-runtime=true` onto `metadata.labels` via an `ApplyConfiguration` patch. `failurePolicy: Ignore` so a policy evaluation error can never block kubelet from registering a Node.
- `deploy/admission/child-cluster-kata-runtime-mutatingpolicybinding.yaml` (new): `MutatingAdmissionPolicyBinding` activating the policy cluster-wide on the child cluster. `matchResources: {}` covers every Node; an `objectSelector` can be added to scope to a specific pool.

### Why
The child (workload) cluster needs Nodes labelled for the upstream `kata-deploy` DaemonSet's default `nodeSelector`. Labelling at Node `CREATE` is the cleanest admission shape: kubelet updates the `Ready` condition via the `nodes/status` subresource, which the API server does not allow to mutate `metadata.labels`, so a true "on Ready" mutation is not possible. `kata-deploy` is a DaemonSet, and DaemonSet pod lifecycle already gates installation on Node readiness — matching on `CREATE` and letting the DaemonSet handle Ready yields the behaviour the user asked for without introducing a controller.

### Impact
- [ ] Breaking change
- [ ] Requires cluster rollout
- [x] Config change only (child-cluster manifests; not wired into the 5-spot controller)
- [ ] Documentation only

### Follow-up (optional, not in this commit)
- Promote to `admissionregistration.k8s.io/v1beta1` once the child-cluster floor is Kubernetes >= 1.34.
- If a subset of Nodes should opt out of kata, add an `objectSelector` to the binding rather than conditions in the policy expression.

---

## [2026-04-19 20:45] - Fix vexctl installer 404 in build.yaml

**Author:** Erick Bourgeois
Expand Down
24 changes: 2 additions & 22 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -549,26 +549,6 @@ jobs:
echo "── vex.openvex.json ─────────────────────────────────────────"
cat vex.openvex.json

# vexctl supplies `validate` as a second-opinion schema check against
# the OpenVEX reference implementation. Pinned to a tagged release; bump
# per ~/dev/roadmaps/5spot-vex-generation-and-signing.md § Dependencies.
- name: Install vexctl
env:
VEXCTL_VERSION: '0.3.0'
run: |
set -euo pipefail
# OpenVEX publishes vexctl as raw platform binaries (no tarball,
# no version in the asset name), plus detached Cosign signatures.
# Asset layout: vexctl-<os>-<arch>, vexctl-<os>-<arch>.sig,
# vexctl-<os>-<arch>.pem. See the release page for the full list.
url="https://github.com/openvex/vexctl/releases/download/v${VEXCTL_VERSION}/vexctl-linux-amd64"
curl -fsSLo vexctl "$url"
sudo install -m 0755 vexctl /usr/local/bin/vexctl
vexctl version

- name: Run vexctl validate
run: vexctl validate vex.openvex.json

# Always upload the assembled document so the `grype` scan job can
# download it via actions/download-artifact@v4 regardless of event.
- name: Upload OpenVEX document artifact
Expand Down Expand Up @@ -823,8 +803,8 @@ jobs:
name: openvex
path: ./vex

# Pinned-version install from the Anchore release tarball (mirrors the
# vexctl install step above). Bump via GRYPE_VERSION and record in
# Pinned-version install from the Anchore release tarball. Bump via
# GRYPE_VERSION and record in
# ~/dev/roadmaps/5spot-vex-generation-and-signing.md § Dependencies.
- name: Install Grype
env:
Expand Down
70 changes: 70 additions & 0 deletions deploy/admission/child-cluster-kata-runtime-mutatingpolicy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# MutatingAdmissionPolicy for child-cluster Node registration.
#
# APPLIED TO: the child (workload) cluster, NOT the management cluster.
#
# Purpose
# -------
# Stamps every Node with the label
#
# katacontainers.io/kata-runtime: "true"
#
# at kubelet registration time (CREATE on nodes). This is the upstream
# kata-deploy DaemonSet's default nodeSelector — once the label is present,
# kata-deploy schedules its installer Pod onto the Node. DaemonSet pod
# lifecycle naturally gates installation on Node Ready, which is why we do
# not (and cannot cleanly) match on a Ready-condition transition here:
# kubelet updates Node Ready via the nodes/status subresource, which does
# not allow mutating metadata.labels. Matching on CREATE + letting the
# DaemonSet handle Ready is the clean shape.
#
# Requirements
# ------------
# Kubernetes >= 1.32 (alpha: admissionregistration.k8s.io/v1alpha1).
# Feature gates on the child cluster's kube-apiserver:
# --feature-gates=MutatingAdmissionPolicy=true
# --runtime-config=admissionregistration.k8s.io/v1alpha1=true
#
# Apply with (against the child cluster kubeconfig):
# kubectl apply -f deploy/admission/child-cluster-kata-runtime-mutatingpolicy.yaml
# kubectl apply -f deploy/admission/child-cluster-kata-runtime-mutatingpolicybinding.yaml
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: MutatingAdmissionPolicy
metadata:
name: child-cluster-kata-runtime-label
labels:
app.kubernetes.io/name: 5spot
app.kubernetes.io/component: admission
app.kubernetes.io/part-of: child-cluster
app.kubernetes.io/managed-by: 5spot-controller
spec:
# Ignore rather than Fail: a transient policy evaluation error must not
# block kubelet from registering the Node. Missing the label means
# kata-deploy will not land on the Node — recoverable by re-patching —
# whereas blocking Node registration breaks the cluster.
failurePolicy: Ignore

# Do not re-invoke on the same request after other mutating plugins run;
# a single pass is sufficient to add a label.
reinvocationPolicy: Never

matchConstraints:
resourceRules:
# Core API group — Node is apiVersion: v1 (apiGroups: [""]).
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["nodes"]
operations: ["CREATE"]

mutations:
# ApplyConfiguration uses server-side-apply-style merge semantics, so
# the existing labels map is preserved and only the kata-runtime key
# is added. JSONPatch would require checking whether metadata.labels
# exists first; ApplyConfiguration handles that for us.
- patchType: ApplyConfiguration
applyConfiguration:
expression: |
Object{
metadata: Object.metadata{
labels: {"katacontainers.io/kata-runtime": "true"}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# MutatingAdmissionPolicyBinding — activates child-cluster-kata-runtime-label
# cluster-wide on the child (workload) cluster.
#
# Bindings for MutatingAdmissionPolicy have no validationActions field
# (unlike ValidatingAdmissionPolicyBinding); mutations returned by the
# policy are always applied when the binding matches.
#
# Node is a cluster-scoped resource, so a namespaceSelector has no effect
# and is omitted. If you need to restrict the label to a subset of Nodes
# (e.g. only a specific pool), add an objectSelector here rather than
# adding conditions in the policy expression.
#
# Apply after the MutatingAdmissionPolicy (against the child cluster kubeconfig):
# kubectl apply -f deploy/admission/child-cluster-kata-runtime-mutatingpolicy.yaml
# kubectl apply -f deploy/admission/child-cluster-kata-runtime-mutatingpolicybinding.yaml
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: MutatingAdmissionPolicyBinding
metadata:
name: child-cluster-kata-runtime-label-binding
labels:
app.kubernetes.io/name: 5spot
app.kubernetes.io/component: admission
app.kubernetes.io/part-of: child-cluster
app.kubernetes.io/managed-by: 5spot-controller
spec:
policyName: child-cluster-kata-runtime-label

# All Nodes in the child cluster. Replace with an objectSelector to
# scope to a specific pool, e.g.:
# matchResources:
# objectSelector:
# matchLabels:
# node-pool: kata
matchResources: {}
22 changes: 20 additions & 2 deletions docs/src/security/admission-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,40 @@ For Kubernetes 1.26–1.27, enable the feature gate on the API server:

### Apply the manifests

`deploy/admission/` ships two policies, each with its own binding:
`deploy/admission/` ships three policies, each with its own binding:

- `validatingadmissionpolicy*.yaml` — validates `ScheduledMachine` CRs.
- `controller-deployment-policy.yaml` + `controller-deployment-binding.yaml`
— validates the controller's own `Deployment`, enforcing that
`POD_NAME` is set via downward API and rejecting the deprecated
`CONTROLLER_POD_NAME` env var with a migration message.
- `child-cluster-kata-runtime-mutatingpolicy.yaml` +
`child-cluster-kata-runtime-mutatingpolicybinding.yaml` — **applied to
the child (workload) cluster, not the management cluster.** A
`MutatingAdmissionPolicy` (`admissionregistration.k8s.io/v1alpha1`,
Kubernetes >= 1.32) that stamps `katacontainers.io/kata-runtime=true`
on every Node at kubelet registration time (`CREATE`), which is the
upstream `kata-deploy` DaemonSet's default `nodeSelector`. DaemonSet
pod lifecycle naturally gates installation on Node `Ready`, so no
controller is required. `failurePolicy: Ignore` ensures a policy
error never blocks Node registration.

Apply each policy before its binding (order matters — the binding
references the policy by name):
references the policy by name). The first two go on the **management**
cluster; the `child-cluster-*` pair goes on the **child** cluster:

```bash
# Management cluster
kubectl apply -f deploy/admission/validatingadmissionpolicy.yaml
kubectl apply -f deploy/admission/validatingadmissionpolicybinding.yaml
kubectl apply -f deploy/admission/controller-deployment-policy.yaml
kubectl apply -f deploy/admission/controller-deployment-binding.yaml

# Child (workload) cluster
kubectl --kubeconfig <child-kubeconfig> apply \
-f deploy/admission/child-cluster-kata-runtime-mutatingpolicy.yaml
kubectl --kubeconfig <child-kubeconfig> apply \
-f deploy/admission/child-cluster-kata-runtime-mutatingpolicybinding.yaml
```

### Verify the policy is active
Expand Down
Loading