Skip to content

Commit bc9361a

Browse files
authored
Merge pull request #3267 from simonbaird/skip-att-sig-check
Add --skip-att-sig-check flag
2 parents 7c9c60f + c3eb9ee commit bc9361a

13 files changed

Lines changed: 596 additions & 71 deletions

File tree

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ feature_%: ## Run acceptance tests for a single feature file, e.g. make feature_
177177
# (Replace spaces with underscores in the scenario name.)
178178
scenario_%: build ## Run acceptance tests for a single scenario, e.g. make scenario_inline_policy
179179
@cd acceptance && go test -test.run 'TestFeatures/$*'
180+
@# With UPDATE_SNAPS=true all the other snap files will be deleted. Let's put them back.
181+
@if [ -n "$$UPDATE_SNAPS" ]; then \
182+
git ls-files --deleted -- '../features/__snapshots__/*.snap' | xargs -r git checkout --; \
183+
fi
180184

181185
benchmark/%/data.tar.gz:
182186
@cd benchmark/$*

cmd/validate/image.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
211211
},
212212
IgnoreRekor: data.ignoreRekor,
213213
SkipImageSigCheck: data.skipImageSigCheck,
214+
SkipAttSigCheck: data.skipAttSigCheck,
214215
PolicyRef: data.policyConfiguration,
215216
PublicKey: data.publicKey,
216217
RekorURL: data.rekorURL,
@@ -499,6 +500,9 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
499500
cmd.Flags().BoolVar(&data.skipImageSigCheck, "skip-image-sig-check", data.skipImageSigCheck,
500501
"Skip image signature validation checks.")
501502

503+
cmd.Flags().BoolVar(&data.skipAttSigCheck, "skip-att-sig-check", data.skipAttSigCheck,
504+
"Skip attestation signature validation checks.")
505+
502506
cmd.Flags().StringVar(&data.certificateIdentity, "certificate-identity", data.certificateIdentity,
503507
"URL of the certificate identity for keyless verification")
504508

@@ -637,6 +641,7 @@ type imageData struct {
637641
input string
638642
ignoreRekor bool
639643
skipImageSigCheck bool
644+
skipAttSigCheck bool
640645
output []string
641646
outputFile string
642647
policy policy.Policy

docs/modules/ROOT/pages/ec_validate_image.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ mark (?) sign, for example: --output text=output.txt?show-successes=false
151151
* inline JSON ('{sources: {...}, identity: {...}}')")
152152
-k, --public-key:: path to the public key. Overrides publicKey from EnterpriseContractPolicy
153153
-r, --rekor-url:: Rekor URL. Overrides rekorURL from EnterpriseContractPolicy
154+
--skip-att-sig-check:: Skip attestation signature validation checks. (Default: false)
154155
--skip-image-sig-check:: Skip image signature validation checks. (Default: false)
155156
--snapshot:: Provide the AppStudio Snapshot as a source of the images to validate, as inline
156157
JSON of the "spec" or a reference to a Kubernetes object [<namespace>/]<name>

features/__snapshots__/validate_image.snap

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5529,6 +5529,7 @@ Error: success criteria not met
55295529
---
55305530

55315531
[TestFeatures/invalid image signature with valid att sig and skip-image-sig-check flag:stderr - 1]
5532+
time="${TIMESTAMP}" level=warning msg="Image signature check skipped"
55325533

55335534
---
55345535

@@ -5594,6 +5595,7 @@ Error: success criteria not met
55945595
---
55955596

55965597
[TestFeatures/happy day with skip-image-sig-check flag:stderr - 1]
5598+
time="${TIMESTAMP}" level=warning msg="Image signature check skipped"
55975599

55985600
---
55995601

@@ -5780,3 +5782,137 @@ Error: success criteria not met
57805782
[TestFeatures/discover artifact referrers via OCI Referrers API:stderr - 1]
57815783

57825784
---
5785+
5786+
[TestFeatures/invalid att signature with valid image sig and skip-att-sig-check flag:stdout - 1]
5787+
{
5788+
"success": true,
5789+
"components": [
5790+
{
5791+
"name": "Unnamed",
5792+
"containerImage": "${REGISTRY}/acceptance/invalid-att-signature@sha256:${REGISTRY_acceptance/invalid-att-signature:latest_DIGEST}",
5793+
"source": {},
5794+
"successes": [
5795+
{
5796+
"msg": "Pass",
5797+
"metadata": {
5798+
"code": "builtin.attestation.syntax_check"
5799+
}
5800+
},
5801+
{
5802+
"msg": "Pass",
5803+
"metadata": {
5804+
"code": "builtin.image.signature_check"
5805+
}
5806+
},
5807+
{
5808+
"msg": "Pass",
5809+
"metadata": {
5810+
"code": "main.acceptor"
5811+
}
5812+
}
5813+
],
5814+
"success": true,
5815+
"signatures": [
5816+
{
5817+
"keyid": "",
5818+
"sig": "${IMAGE_SIGNATURE_acceptance/invalid-att-signature}"
5819+
}
5820+
],
5821+
"attestations": [
5822+
{
5823+
"type": "https://in-toto.io/Statement/v0.1",
5824+
"predicateType": "https://slsa.dev/provenance/v0.2",
5825+
"predicateBuildType": "https://tekton.dev/attestations/chains/pipelinerun@v2",
5826+
"signatures": [
5827+
{
5828+
"keyid": "",
5829+
"sig": "${ATTESTATION_SIGNATURE_acceptance/invalid-att-signature}"
5830+
}
5831+
]
5832+
}
5833+
]
5834+
}
5835+
],
5836+
"key": "${known_PUBLIC_KEY_JSON}",
5837+
"policy": {
5838+
"sources": [
5839+
{
5840+
"policy": [
5841+
"git::${GITHOST}/git/invalid-att-signature.git?ref=${LATEST_COMMIT}"
5842+
]
5843+
}
5844+
],
5845+
"rekorUrl": "${REKOR}",
5846+
"publicKey": "${known_PUBLIC_KEY}"
5847+
},
5848+
"ec-version": "${EC_VERSION}",
5849+
"effective-time": "${TIMESTAMP}"
5850+
}
5851+
---
5852+
5853+
[TestFeatures/invalid att signature with valid image sig and skip-att-sig-check flag:stderr - 1]
5854+
time="${TIMESTAMP}" level=warning msg="Attestation signature check skipped, fetching attestations without verification"
5855+
5856+
---
5857+
5858+
[TestFeatures/skip both image and att sig checks for debugging:stdout - 1]
5859+
{
5860+
"success": true,
5861+
"components": [
5862+
{
5863+
"name": "Unnamed",
5864+
"containerImage": "${REGISTRY}/acceptance/skip-both-sig-checks@sha256:${REGISTRY_acceptance/skip-both-sig-checks:latest_DIGEST}",
5865+
"source": {},
5866+
"successes": [
5867+
{
5868+
"msg": "Pass",
5869+
"metadata": {
5870+
"code": "builtin.attestation.syntax_check"
5871+
}
5872+
},
5873+
{
5874+
"msg": "Pass",
5875+
"metadata": {
5876+
"code": "main.acceptor"
5877+
}
5878+
}
5879+
],
5880+
"success": true,
5881+
"attestations": [
5882+
{
5883+
"type": "https://in-toto.io/Statement/v0.1",
5884+
"predicateType": "https://slsa.dev/provenance/v0.2",
5885+
"predicateBuildType": "https://tekton.dev/attestations/chains/pipelinerun@v2",
5886+
"signatures": [
5887+
{
5888+
"keyid": "",
5889+
"sig": "${ATTESTATION_SIGNATURE_acceptance/skip-both-sig-checks}"
5890+
}
5891+
]
5892+
}
5893+
]
5894+
}
5895+
],
5896+
"key": "${known_PUBLIC_KEY_JSON}",
5897+
"policy": {
5898+
"sources": [
5899+
{
5900+
"policy": [
5901+
"git::${GITHOST}/git/skip-both-sig-checks.git?ref=${LATEST_COMMIT}"
5902+
]
5903+
}
5904+
],
5905+
"rekorUrl": "${REKOR}",
5906+
"publicKey": "${known_PUBLIC_KEY}"
5907+
},
5908+
"ec-version": "${EC_VERSION}",
5909+
"effective-time": "${TIMESTAMP}"
5910+
}
5911+
---
5912+
5913+
[TestFeatures/skip both image and att sig checks for debugging:stderr - 1]
5914+
time="${TIMESTAMP}" level=warning msg="Image signature check skipped"
5915+
time="${TIMESTAMP}" level=warning msg="Attestation signature check skipped, fetching attestations without verification"
5916+
time="${TIMESTAMP}" level=warning msg="Both --skip-image-sig-check and --skip-att-sig-check are active, all cryptographic verification is disabled"
5917+
5918+
---

features/validate_image.feature

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,65 @@ Feature: evaluate enterprise contract
5656
# builtin.attestation.image_check is not found in the success output
5757
Then the output should match the snapshot
5858

59+
Scenario: invalid att signature with valid image sig and skip-att-sig-check flag
60+
Given a key pair named "known"
61+
Given a key pair named "unknown"
62+
Given an image named "acceptance/invalid-att-signature"
63+
64+
# We're going to use the "known" public key when validating, so the image
65+
# sig check should pass but the att sig check should fail (if it wasn't skipped).
66+
Given a valid image signature of "acceptance/invalid-att-signature" image signed by the "known" key
67+
Given a valid attestation of "acceptance/invalid-att-signature" signed by the "unknown" key
68+
69+
Given a git repository named "invalid-att-signature" with
70+
| main.rego | examples/happy_day.rego |
71+
Given policy configuration named "invalid-att-signature-policy" with specification
72+
"""
73+
{
74+
"sources": [
75+
{
76+
"policy": [
77+
"git::https://${GITHOST}/git/invalid-att-signature.git"
78+
]
79+
}
80+
]
81+
}
82+
"""
83+
# Notice we're using the --skip-att-sig-check flag, which is the reason this is
84+
# green even though attestation is signed with a different key to the one we're providing
85+
When ec command is run with "validate image --image ${REGISTRY}/acceptance/invalid-att-signature --policy acceptance/invalid-att-signature-policy --public-key ${known_PUBLIC_KEY} --skip-att-sig-check --rekor-url ${REKOR} --show-successes --output json"
86+
Then the exit status should be 0
87+
Then the output should match the snapshot
88+
89+
Scenario: skip both image and att sig checks for debugging
90+
Given a key pair named "known"
91+
Given a key pair named "unknown"
92+
Given an image named "acceptance/skip-both-sig-checks"
93+
94+
# Both signatures use the wrong key. Without the skip flags this would fail.
95+
Given a valid image signature of "acceptance/skip-both-sig-checks" image signed by the "unknown" key
96+
Given a valid attestation of "acceptance/skip-both-sig-checks" signed by the "unknown" key
97+
98+
Given a git repository named "skip-both-sig-checks" with
99+
| main.rego | examples/happy_day.rego |
100+
Given policy configuration named "skip-both-sig-checks-policy" with specification
101+
"""
102+
{
103+
"sources": [
104+
{
105+
"policy": [
106+
"git::https://${GITHOST}/git/skip-both-sig-checks.git"
107+
]
108+
}
109+
]
110+
}
111+
"""
112+
# This is the expected real-world debugging usage: skip all signature checks
113+
# to focus on policy evaluation when the signing key is unavailable
114+
When ec command is run with "validate image --image ${REGISTRY}/acceptance/skip-both-sig-checks --policy acceptance/skip-both-sig-checks-policy --public-key ${known_PUBLIC_KEY} --skip-image-sig-check --skip-att-sig-check --rekor-url ${REKOR} --show-successes --output json"
115+
Then the exit status should be 0
116+
Then the output should match the snapshot
117+
59118
Scenario: happy day with git config and yaml
60119
Given a key pair named "known"
61120
Given an image named "acceptance/ec-happy-day"

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,32 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
201201
return a.parseAttestationsFromBundles(layers)
202202
}
203203

204-
// Extract the signatures from the attestations here in order to also validate that
205-
// the signatures do exist in the expected format.
206-
for _, sig := range layers {
204+
return a.parseAttestationsFromSignatures(layers)
205+
}
206+
207+
// FetchAttestationsWithoutVerification fetches attestations from the registry
208+
// without performing signature verification. This is used when --skip-att-sig-check
209+
// is enabled but we still need the attestation data for policy evaluation.
210+
func (a *ApplicationSnapshotImage) FetchAttestationsWithoutVerification(ctx context.Context) error {
211+
if trace.IsEnabled() {
212+
region := trace.StartRegion(ctx, "ec:fetch-attestations-without-verification")
213+
defer region.End()
214+
}
215+
216+
sigs, err := oci.NewClient(ctx).FetchAttestationLayers(a.reference)
217+
if err != nil {
218+
return err
219+
}
220+
221+
if a.hasBundles(ctx) {
222+
return a.parseAttestationsFromBundles(sigs)
223+
}
224+
225+
return a.parseAttestationsFromSignatures(sigs)
226+
}
227+
228+
func (a *ApplicationSnapshotImage) parseAttestationsFromSignatures(sigs []cosignOCI.Signature) error {
229+
for _, sig := range sigs {
207230
att, err := attestation.ProvenanceFromSignature(sig)
208231
if err != nil {
209232
return fmt.Errorf("unable to parse untyped provenance: %w", err)
@@ -283,7 +306,8 @@ func (a *ApplicationSnapshotImage) parseAttestationsFromBundles(layers []cosignO
283306
// ValidateAttestationSyntax validates the attestations against known JSON
284307
// schemas, errors out if there are no attestations to check to prevent
285308
// successful syntax check of no inputs, must invoke
286-
// [ValidateAttestationSignature] to prefill the attestations.
309+
// [ValidateAttestationSignature] or [FetchAttestationsWithoutVerification]
310+
// to prefill the attestations.
287311
func (a ApplicationSnapshotImage) ValidateAttestationSyntax(ctx context.Context) error {
288312
if trace.IsEnabled() {
289313
region := trace.StartRegion(ctx, "ec:validate-attestation-syntax")

0 commit comments

Comments
 (0)