Skip to content

Commit a106750

Browse files
authored
Add image signature preflight pattern (#71)
1 parent 3259ea1 commit a106750

File tree

2 files changed

+177
-0
lines changed

2 files changed

+177
-0
lines changed

patterns/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,7 @@ By trigger the github actions, you can integrate the compatibility testing into
6262
### Multiple Chart Orchestration
6363

6464
- [Multiple Chart Orchestration](multi-chart-orchestration/README.md)
65+
66+
### Validating Images Signatures in a Preflight Check
67+
68+
- [Validating Images Signatures in a Preflight Check](images-signature-preflight/README.md)
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Verifying Image Signatures with Replicated Preflight Checks
2+
3+
A question came up this morning about checking image signatures when distributing software through the Replicated Platform. This is something I've tackled before with a solution I'm particularly fond of.
4+
5+
While end customers might reach for a Kyverno policy to enforce image signature verification, Kyverno isn't something you typically ship with commercial software. The real insight here is that for software distribution, what matters most is verifying signatures at the critical moments: installation and upgrade time. This calls for a more lightweight approach, and preflight checks fit perfectly.
6+
7+
## Verification with Known Keys
8+
9+
The preflight check is pretty straightforward, differing depending on how you sign your images. For key-based signing, the approach centers on embedding your public key directly in the check and running cosign verification against your target images.
10+
11+
The preflight runs a pod containing cosign and your public key, then attempts verification against the specified images. If verification succeeds, you get a clear "verified" output that the analyzer can parse for a passing result. The security context ensures the validation runs with minimal privileges—no root access, read-only filesystem, and dropped capabilities.
12+
13+
```yaml
14+
apiVersion: troubleshoot.sh/v1beta2
15+
kind: Preflight
16+
metadata:
17+
name: cosign-signature-check
18+
spec:
19+
collectors:
20+
- runPod:
21+
name: image-signature
22+
namespace: default
23+
podSpec:
24+
containers:
25+
- name: validator
26+
image: nixery.dev/shell/coreutils/openssl/jq/cosign
27+
imagePullPolicy: IfNotPresent
28+
env:
29+
- name: COSIGN_KEY
30+
value: |
31+
# Replace with your own signing key
32+
-----BEGIN PUBLIC KEY-----
33+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETPIGSVFx2AgTBcr+bZ8DNAMql4fW
34+
kxjqKKsbcy9uQw7HjhsqPnZniQNAUwl1wDkUQ3Y7w6zK4OV9k38DT9bmow==
35+
-----END PUBLIC KEY-----
36+
command:
37+
- bash
38+
- -c
39+
- |
40+
set -x
41+
echo "${COSIGN_KEY}" > /cosign-keys/cosign.pub
42+
if cosign verify --key /cosign-keys/cosign.pub $0 $@ ; then
43+
echo "verified"
44+
else
45+
echo "failed"
46+
fi
47+
args:
48+
- ttl.sh/shell:1h
49+
- ttl.sh/cosign:1h
50+
resources:
51+
requests:
52+
cpu: 100m
53+
memory: 64Mi
54+
securityContext:
55+
allowPrivilegeEscalation: false
56+
privileged: false
57+
readOnlyRootFilesystem: true
58+
runAsNonRoot: true
59+
runAsUser: 65534
60+
capabilities:
61+
drop:
62+
- ALL
63+
volumeMounts:
64+
- name: sigstore
65+
mountPath: /.sigstore
66+
readOnly: false
67+
- name: cosign-key
68+
mountPath: /cosign-keys
69+
readOnly: false
70+
volumes:
71+
- name: sigstore
72+
emptyDir: {}
73+
- name: cosign-key
74+
emptyDir: {}
75+
analyzers:
76+
- textAnalyze:
77+
checkName: Image Signature Verification
78+
fileName: image-signature/image-signature.log
79+
regex: 'verified'
80+
outcomes:
81+
- pass:
82+
when: "true"
83+
message: Images signed by the expected signature
84+
- fail:
85+
when: "false"
86+
message: Images are not signed by the expected signature
87+
```
88+
89+
## Keyless Signing Takes a Different Path
90+
91+
If you're using keyless signing, you don't have a known key to validate against. Instead, you're verifying against identity certificates tied to OIDC providers. We can't specify a key, so we specify the exact identity and issuer used during signing.
92+
93+
The preflight adapts by replacing the embedded key with environment variables that define the expected signing identity. The cosign command switches from key-based verification to certificate identity verification, checking both the signer's identity and the OIDC issuer that authenticated them.
94+
95+
```yaml
96+
apiVersion: troubleshoot.sh/v1beta2
97+
kind: Preflight
98+
metadata:
99+
name: cosign-keyless-signature-check
100+
spec:
101+
collectors:
102+
- runPod:
103+
name: image-signature
104+
namespace: default
105+
podSpec:
106+
containers:
107+
- name: validator
108+
image: nixery.dev/shell/coreutils/openssl/jq/cosign
109+
imagePullPolicy: IfNotPresent
110+
env:
111+
- name: COSIGN_CERTIFICATE_IDENTITY
112+
113+
- name: COSIGN_CERTIFICATE_OIDC_ISSUER
114+
value: "https://github.com/login/oauth"
115+
command:
116+
- bash
117+
- -c
118+
- |
119+
set -x
120+
if cosign verify \
121+
--certificate-identity="${COSIGN_CERTIFICATE_IDENTITY}" \
122+
--certificate-oidc-issuer="${COSIGN_CERTIFICATE_OIDC_ISSUER}" \
123+
$0 $@ ; then
124+
echo "verified"
125+
else
126+
echo "failed"
127+
fi
128+
args:
129+
- ttl.sh/shell:1h
130+
- ttl.sh/cosign:1h
131+
resources:
132+
requests:
133+
cpu: 100m
134+
memory: 64Mi
135+
securityContext:
136+
allowPrivilegeEscalation: false
137+
privileged: false
138+
readOnlyRootFilesystem: true
139+
runAsNonRoot: true
140+
runAsUser: 65534
141+
capabilities:
142+
drop:
143+
- ALL
144+
volumeMounts:
145+
- name: sigstore
146+
mountPath: /.sigstore
147+
readOnly: false
148+
volumes:
149+
- name: sigstore
150+
emptyDir: {}
151+
analyzers:
152+
- textAnalyze:
153+
checkName: Image Signature Verification
154+
fileName: image-signature/image-signature.log
155+
regex: 'verified'
156+
outcomes:
157+
- pass:
158+
when: "true"
159+
message: Images signed by the expected keyless signature
160+
- fail:
161+
when: "false"
162+
message: Images are not signed by the expected keyless signature
163+
```
164+
165+
## Air-gapped Environments Need Special Consideration
166+
167+
As usual, air-gapped deployments introduce their own constraints. Keyless signing becomes impractical since you can't reach external OIDC providers for identity validation. This means you should sign your image with a known key. You'll also need to include the signatures themselves in your airgap bundle by adding them as [additional images in your Replicated configuration](https://docs.replicated.com/vendor/operator-defining-additional-images).
168+
169+
## Why I Use This Approach
170+
171+
The approach feel elegant in its timing and simplicity. Rather than running continuous policy enforcement, these preflight checks activate precisely when signature verification matters most. Customers get immediate, actionable feedback about image integrity without deploying or managing policy engines in their clusters.
172+
173+
This solution adapts to different signing strategies while integrating seamlessly with your existing preflight checks. Whether you choose keyless signing for its CI/CD advantages or key-based signing for tighter organizational control, you're delivering the security assurance customers need exactly when they need it. That's the kind of lightweight, targeted approach that works well in commercial software distribution.

0 commit comments

Comments
 (0)