Signing and tamper detection (experimental)
A user-defined profile is an allow-list — whoever can edit it controls what the runtime treats as "normal". Same goes for the CEL rules1 and configuration. Which is why we decided to make them signable and bundlable.
For up-to-date examples, consult the
component tests Test_29 (a signed profile is accepted), Test_30 (tampering invalidates the
signature), and Test_31 (the R1016 alert fires). This implementation will change in the future2.
Implemented as annotation
The signature travels with the object, as an annotation.
signature.kubescape.io/certificate: LS0tLS1CRUdJTiBDRVJUSUZJ…(base64)
signature.kubescape.io/identity: local-key
signature.kubescape.io/issuer: local
signature.kubescape.io/signature: MEQCIDEsKzuY5LQM6TO1kNb4…(base64)
signature.kubescape.io/timestamp: "1783454246"
Try it yourself
Prerequisites: a cluster with Kubescape sbob-rc3 installed (see the
quickstart install), plus kubectl, docker, jq, and yq.
export SIGN_OBJECT="ghcr.io/k8sstormcenter/sign-object:v0.0.3@sha256:f3d4e321fa62e0a4ca421ba59a3fce3f2ff88714aaf87a7d160322cb8ec2f92b"
sign_object() { docker run --rm -v "$PWD:/work" "$SIGN_OBJECT" "$@"; }
mkdir -p sbob-signing && cd sbob-signing
1. Generate a keypair.
2. Author a profile.
cat > my-profile.yaml <<'EOF'
apiVersion: spdx.softwarecomposition.kubescape.io/v1beta1
kind: ApplicationProfile
metadata:
name: signed-demo
namespace: sig-demo
spec:
architectures: ["amd64"]
containers:
- name: app
execs:
- { path: /bin/sleep, args: ["/bin/sleep", "infinity"] }
EOF
3. Sign it.
sign_object sign --file my-profile.yaml --output my-profile.signed.yaml \
--key cosign.key --type applicationprofile
4. Apply the signed profile first, then a workload that references it by label.
kubectl create namespace sig-demo
kubectl apply -f my-profile.signed.yaml
sleep 5
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: { name: signed-demo, namespace: sig-demo }
spec:
replicas: 1
selector: { matchLabels: { app: signed-demo } }
template:
metadata:
labels:
app: signed-demo
kubescape.io/user-defined-profile: signed-demo
kubescape.io/user-defined-network: signed-demo
spec:
containers:
- { name: app, image: busybox:1.36, command: ["/bin/sleep", "infinity"] }
EOF
kubectl -n sig-demo rollout status deploy/signed-demo
node-agent binds and verifies the signature — no alert. Confirm it's quiet
5. Tamper. Add /bin/sh to the signed profile, keeping the old signature. Any in-place edit works — kubectl patch, edit, apply, replace, or delete + re-apply. The signature no longer matches the spec:
kubectl -n sig-demo patch applicationprofile signed-demo --type=json \
-p '[{"op":"add","path":"/spec/containers/0/execs/-","value":{"path":"/bin/sh","args":["/bin/sh"]}}]'
6. Re-bind the pod, then read R1016. The signature is verified at bind time, so a running pod isn't re-checked until it re-binds2 — restart it:
Read the alert from the node-agent pods
kubectl -n kubescape logs -l app=node-agent --tail=-1 | grep '"RuleID":"R1016"' \
| jq -c '{RuleID, alert: .BaseRuntimeMetadata.alertName, severity: .BaseRuntimeMetadata.severity, workload: .RuntimeK8sDetails.workloadName}'
Clean up: cd .. && kubectl delete namespace sig-demo.
Next
- What is a Bill of Behavior — the concept and the custom resources.
- Quickstart — a live Log4Shell detection against a signed profile.
- Node Agent Rule Library — the full rule catalog, including R1016.
-
Rules are signable (
sign-object --type rules), but verification is gated onenableSignatureVerification(default off, unset by the chart). If set toONa tampered rule is rejected (fail-closed skip, loggedskippedByVerification; not an R1016 alert), and unsigned rules are rejected too — the default library drops to 0 enabled unless every rule is signed. ↩ -
We currently have a backlog where the late-binding of profiles will be allowed. This requires a lot of testing and process-design, which is why currently, you need to restart a pod to bind the whole process to a new profile (irrespective of signature). This does not apply to the rules and config - they can be changed in real-time. Stay tuned for updates 🤓. ↩↩
