Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix comparison in replace option for attestation #1366

Merged
merged 6 commits into from
Feb 3, 2022
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
29 changes: 22 additions & 7 deletions pkg/cosign/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,23 +127,38 @@ func (r *ro) Replace(signatures oci.Signatures, o oci.Signature) (oci.Signatures
}

for _, s := range sigs {
var payloadData map[string]interface{}

var signaturePayload map[string]interface{}
p, err := s.Payload()
if err != nil {
return nil, fmt.Errorf("could not get payload: %w", err)
}

err = json.Unmarshal(p, &payloadData)
err = json.Unmarshal(p, &signaturePayload)
if err != nil {
return nil, fmt.Errorf("unmarshal payload data: %w", err)
}

if r.predicateURI == payloadData["payloadType"] {
fmt.Fprintln(os.Stderr, "Attestation already present, not adding new one.")
val, ok := signaturePayload["payload"]
if !ok {
return nil, fmt.Errorf("could not find 'payload' in payload data")
}
decodedPayload, err := base64.StdEncoding.DecodeString(val.(string))
if err != nil {
return nil, fmt.Errorf("could not decode 'payload': %w", err)
}

var payloadData map[string]interface{}
if err := json.Unmarshal(decodedPayload, &payloadData); err != nil {
return nil, fmt.Errorf("unmarshal payloadData: %w", err)
}
val, ok = payloadData["predicateType"]
if !ok {
return nil, fmt.Errorf("could not find 'predicateType' in payload data")
}
if r.predicateURI == val {
fmt.Fprintln(os.Stderr, "Replacing attestation predicate:", r.predicateURI)
continue
} else {
fmt.Fprintln(os.Stderr, "Attestation not found, adding new attestation.")
fmt.Fprintln(os.Stderr, "Not replacing attestation predicate:", val)
sigsCopy = append(sigsCopy, s)
}
}
Expand Down
58 changes: 56 additions & 2 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func TestAttestVerify(t *testing.T) {
// Fail case when using without type and policy flag
mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)

slsaAttestation := `{ "builder": { "id": "2" }, "recipe": {} }`
slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
t.Fatal(err)
Expand All @@ -233,7 +233,7 @@ func TestAttestVerify(t *testing.T) {
// Now attest the image
ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false,
"custom", false, 30*time.Second), t)
"slsaprovenance", false, 30*time.Second), t)

// Use cue to verify attestation
policyPath := filepath.Join(td, "policy.cue")
Expand All @@ -257,6 +257,60 @@ func TestAttestVerify(t *testing.T) {
mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t)
}

func TestAttestationReplace(t *testing.T) {
// This test is currently failing, see https://github.com/sigstore/cosign/issues/1378
// The replace option for attest does not appear to work on random.Image() generated images
t.Skip()

repo, stop := reg(t)
defer stop()
td := t.TempDir()

imgName := path.Join(repo, "cosign-attest-replace-e2e")

_, _, cleanup := mkimage(t, imgName)
defer cleanup()

_, privKeyPath, _ := keypair(t, td)
ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}

ctx := context.Background()

slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
t.Fatal(err)
}

// Attest once with with replace=false creating an attestation
must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false,
"slsaprovenance", false, 30*time.Second), t)
// Attest again with replace=true, replacing the previous attestation
must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false,
"slsaprovenance", true, 30*time.Second), t)
// Attest once more replace=true using a different predicate, to ensure it adds a new attestation
must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false,
"custom", true, 30*time.Second), t)

// Download and count the attestations
ref, err := name.ParseReference(imgName)
if err != nil {
t.Fatal(err)
}
regOpts := options.RegistryOptions{}
ociremoteOpts, err := regOpts.ClientOpts(ctx)
if err != nil {
t.Fatal(err)
}
attestations, err := cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...)
if err != nil {
t.Fatal(err)
}
if len(attestations) != 2 {
t.Fatal(fmt.Errorf("Expected len(attestations) == 2, got %d", len(attestations)))
}
}

func TestRekorBundle(t *testing.T) {
// turn on the tlog
defer setenv(t, options.ExperimentalEnv, "1")()
Expand Down