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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ feature_%: ## Run acceptance tests for a single feature file, e.g. make feature_
# (Replace spaces with underscores in the scenario name.)
scenario_%: build ## Run acceptance tests for a single scenario, e.g. make scenario_inline_policy
@cd acceptance && go test -test.run 'TestFeatures/$*'
@# With UPDATE_SNAPS=true all the other snap files will be deleted. Let's put them back.
@if [ -n "$$UPDATE_SNAPS" ]; then \
git ls-files --deleted -- '../features/__snapshots__/*.snap' | xargs -r git checkout --; \
fi

benchmark/%/data.tar.gz:
@cd benchmark/$*
Expand Down
5 changes: 5 additions & 0 deletions cmd/validate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
},
IgnoreRekor: data.ignoreRekor,
SkipImageSigCheck: data.skipImageSigCheck,
SkipAttSigCheck: data.skipAttSigCheck,
PolicyRef: data.policyConfiguration,
PublicKey: data.publicKey,
RekorURL: data.rekorURL,
Expand Down Expand Up @@ -499,6 +500,9 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
cmd.Flags().BoolVar(&data.skipImageSigCheck, "skip-image-sig-check", data.skipImageSigCheck,
"Skip image signature validation checks.")

cmd.Flags().BoolVar(&data.skipAttSigCheck, "skip-att-sig-check", data.skipAttSigCheck,
"Skip attestation signature validation checks.")

cmd.Flags().StringVar(&data.certificateIdentity, "certificate-identity", data.certificateIdentity,
"URL of the certificate identity for keyless verification")

Expand Down Expand Up @@ -637,6 +641,7 @@ type imageData struct {
input string
ignoreRekor bool
skipImageSigCheck bool
skipAttSigCheck bool
output []string
outputFile string
policy policy.Policy
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/ec_validate_image.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ mark (?) sign, for example: --output text=output.txt?show-successes=false
* inline JSON ('{sources: {...}, identity: {...}}')")
-k, --public-key:: path to the public key. Overrides publicKey from EnterpriseContractPolicy
-r, --rekor-url:: Rekor URL. Overrides rekorURL from EnterpriseContractPolicy
--skip-att-sig-check:: Skip attestation signature validation checks. (Default: false)
--skip-image-sig-check:: Skip image signature validation checks. (Default: false)
--snapshot:: Provide the AppStudio Snapshot as a source of the images to validate, as inline
JSON of the "spec" or a reference to a Kubernetes object [<namespace>/]<name>
Expand Down
136 changes: 136 additions & 0 deletions features/__snapshots__/validate_image.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5529,6 +5529,7 @@ Error: success criteria not met
---

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

---

Expand Down Expand Up @@ -5594,6 +5595,7 @@ Error: success criteria not met
---

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

---

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

---

[TestFeatures/invalid att signature with valid image sig and skip-att-sig-check flag:stdout - 1]
{
"success": true,
"components": [
{
"name": "Unnamed",
"containerImage": "${REGISTRY}/acceptance/invalid-att-signature@sha256:${REGISTRY_acceptance/invalid-att-signature:latest_DIGEST}",
"source": {},
"successes": [
{
"msg": "Pass",
"metadata": {
"code": "builtin.attestation.syntax_check"
}
},
{
"msg": "Pass",
"metadata": {
"code": "builtin.image.signature_check"
}
},
{
"msg": "Pass",
"metadata": {
"code": "main.acceptor"
}
}
],
"success": true,
"signatures": [
{
"keyid": "",
"sig": "${IMAGE_SIGNATURE_acceptance/invalid-att-signature}"
}
],
"attestations": [
{
"type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicateBuildType": "https://tekton.dev/attestations/chains/pipelinerun@v2",
"signatures": [
{
"keyid": "",
"sig": "${ATTESTATION_SIGNATURE_acceptance/invalid-att-signature}"
}
]
}
]
}
],
"key": "${known_PUBLIC_KEY_JSON}",
"policy": {
"sources": [
{
"policy": [
"git::${GITHOST}/git/invalid-att-signature.git?ref=${LATEST_COMMIT}"
]
}
],
"rekorUrl": "${REKOR}",
"publicKey": "${known_PUBLIC_KEY}"
},
"ec-version": "${EC_VERSION}",
"effective-time": "${TIMESTAMP}"
}
---

[TestFeatures/invalid att signature with valid image sig and skip-att-sig-check flag:stderr - 1]
time="${TIMESTAMP}" level=warning msg="Attestation signature check skipped, fetching attestations without verification"

---

[TestFeatures/skip both image and att sig checks for debugging:stdout - 1]
{
"success": true,
"components": [
{
"name": "Unnamed",
"containerImage": "${REGISTRY}/acceptance/skip-both-sig-checks@sha256:${REGISTRY_acceptance/skip-both-sig-checks:latest_DIGEST}",
"source": {},
"successes": [
{
"msg": "Pass",
"metadata": {
"code": "builtin.attestation.syntax_check"
}
},
{
"msg": "Pass",
"metadata": {
"code": "main.acceptor"
}
}
],
"success": true,
"attestations": [
{
"type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicateBuildType": "https://tekton.dev/attestations/chains/pipelinerun@v2",
"signatures": [
{
"keyid": "",
"sig": "${ATTESTATION_SIGNATURE_acceptance/skip-both-sig-checks}"
}
]
}
]
}
],
"key": "${known_PUBLIC_KEY_JSON}",
"policy": {
"sources": [
{
"policy": [
"git::${GITHOST}/git/skip-both-sig-checks.git?ref=${LATEST_COMMIT}"
]
}
],
"rekorUrl": "${REKOR}",
"publicKey": "${known_PUBLIC_KEY}"
},
"ec-version": "${EC_VERSION}",
"effective-time": "${TIMESTAMP}"
}
---

[TestFeatures/skip both image and att sig checks for debugging:stderr - 1]
time="${TIMESTAMP}" level=warning msg="Image signature check skipped"
time="${TIMESTAMP}" level=warning msg="Attestation signature check skipped, fetching attestations without verification"
time="${TIMESTAMP}" level=warning msg="Both --skip-image-sig-check and --skip-att-sig-check are active, all cryptographic verification is disabled"

---
59 changes: 59 additions & 0 deletions features/validate_image.feature
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,65 @@ Feature: evaluate enterprise contract
# builtin.attestation.image_check is not found in the success output
Then the output should match the snapshot

Scenario: invalid att signature with valid image sig and skip-att-sig-check flag
Given a key pair named "known"
Given a key pair named "unknown"
Given an image named "acceptance/invalid-att-signature"

# We're going to use the "known" public key when validating, so the image
# sig check should pass but the att sig check should fail (if it wasn't skipped).
Given a valid image signature of "acceptance/invalid-att-signature" image signed by the "known" key
Given a valid attestation of "acceptance/invalid-att-signature" signed by the "unknown" key

Given a git repository named "invalid-att-signature" with
| main.rego | examples/happy_day.rego |
Given policy configuration named "invalid-att-signature-policy" with specification
"""
{
"sources": [
{
"policy": [
"git::https://${GITHOST}/git/invalid-att-signature.git"
]
}
]
}
"""
# Notice we're using the --skip-att-sig-check flag, which is the reason this is
# green even though attestation is signed with a different key to the one we're providing
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"
Then the exit status should be 0
Then the output should match the snapshot

Scenario: skip both image and att sig checks for debugging
Given a key pair named "known"
Given a key pair named "unknown"
Given an image named "acceptance/skip-both-sig-checks"

# Both signatures use the wrong key. Without the skip flags this would fail.
Given a valid image signature of "acceptance/skip-both-sig-checks" image signed by the "unknown" key
Given a valid attestation of "acceptance/skip-both-sig-checks" signed by the "unknown" key

Given a git repository named "skip-both-sig-checks" with
| main.rego | examples/happy_day.rego |
Given policy configuration named "skip-both-sig-checks-policy" with specification
"""
{
"sources": [
{
"policy": [
"git::https://${GITHOST}/git/skip-both-sig-checks.git"
]
}
]
}
"""
# This is the expected real-world debugging usage: skip all signature checks
# to focus on policy evaluation when the signing key is unavailable
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"
Then the exit status should be 0
Then the output should match the snapshot

Scenario: happy day with git config and yaml
Given a key pair named "known"
Given an image named "acceptance/ec-happy-day"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,32 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
return a.parseAttestationsFromBundles(layers)
}

// Extract the signatures from the attestations here in order to also validate that
// the signatures do exist in the expected format.
for _, sig := range layers {
return a.parseAttestationsFromSignatures(layers)
Comment thread
simonbaird marked this conversation as resolved.
}

// FetchAttestationsWithoutVerification fetches attestations from the registry
// without performing signature verification. This is used when --skip-att-sig-check
// is enabled but we still need the attestation data for policy evaluation.
func (a *ApplicationSnapshotImage) FetchAttestationsWithoutVerification(ctx context.Context) error {
if trace.IsEnabled() {
region := trace.StartRegion(ctx, "ec:fetch-attestations-without-verification")
defer region.End()
}

sigs, err := oci.NewClient(ctx).FetchAttestationLayers(a.reference)
if err != nil {
return err
}

if a.hasBundles(ctx) {
return a.parseAttestationsFromBundles(sigs)
}

return a.parseAttestationsFromSignatures(sigs)
}
Comment thread
simonbaird marked this conversation as resolved.

func (a *ApplicationSnapshotImage) parseAttestationsFromSignatures(sigs []cosignOCI.Signature) error {
Comment thread
simonbaird marked this conversation as resolved.
for _, sig := range sigs {
att, err := attestation.ProvenanceFromSignature(sig)
if err != nil {
return fmt.Errorf("unable to parse untyped provenance: %w", err)
Expand Down Expand Up @@ -283,7 +306,8 @@ func (a *ApplicationSnapshotImage) parseAttestationsFromBundles(layers []cosignO
// ValidateAttestationSyntax validates the attestations against known JSON
// schemas, errors out if there are no attestations to check to prevent
// successful syntax check of no inputs, must invoke
// [ValidateAttestationSignature] to prefill the attestations.
// [ValidateAttestationSignature] or [FetchAttestationsWithoutVerification]
// to prefill the attestations.
func (a ApplicationSnapshotImage) ValidateAttestationSyntax(ctx context.Context) error {
if trace.IsEnabled() {
region := trace.StartRegion(ctx, "ec:validate-attestation-syntax")
Expand Down
Loading
Loading