Skip to content

Commit

Permalink
Add spdxjson predicate type for attestations (#1974)
Browse files Browse the repository at this point in the history
Signed-off-by: Josh Dolitsky <josh@dolit.ski>
  • Loading branch information
jdolitsky authored Jun 8, 2022
1 parent 2ef684f commit 2ccdb3e
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 31 deletions.
24 changes: 13 additions & 11 deletions cmd/cosign/cli/options/predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,22 @@ import (
)

const (
PredicateCustom = "custom"
PredicateSLSA = "slsaprovenance"
PredicateSPDX = "spdx"
PredicateLink = "link"
PredicateVuln = "vuln"
PredicateCustom = "custom"
PredicateSLSA = "slsaprovenance"
PredicateSPDX = "spdx"
PredicateSPDXJSON = "spdxjson"
PredicateLink = "link"
PredicateVuln = "vuln"
)

// PredicateTypeMap is the mapping between the predicate `type` option to predicate URI.
var PredicateTypeMap = map[string]string{
PredicateCustom: attestation.CosignCustomProvenanceV01,
PredicateSLSA: slsa.PredicateSLSAProvenance,
PredicateSPDX: in_toto.PredicateSPDX,
PredicateLink: in_toto.PredicateLinkV1,
PredicateVuln: attestation.CosignVulnProvenanceV01,
PredicateCustom: attestation.CosignCustomProvenanceV01,
PredicateSLSA: slsa.PredicateSLSAProvenance,
PredicateSPDX: in_toto.PredicateSPDX,
PredicateSPDXJSON: in_toto.PredicateSPDX,
PredicateLink: in_toto.PredicateLinkV1,
PredicateVuln: attestation.CosignVulnProvenanceV01,
}

// PredicateOptions is the wrapper for predicate related options.
Expand All @@ -54,7 +56,7 @@ var _ Interface = (*PredicateOptions)(nil)
// AddFlags implements Interface
func (o *PredicateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Type, "type", "custom",
"specify a predicate type (slsaprovenance|link|spdx|vuln|custom) or an URI")
"specify a predicate type (slsaprovenance|link|spdx|spdxjson|vuln|custom) or an URI")
}

// ParsePredicateType parses the predicate `type` flag passed into a predicate URI, or validates `type` is a valid URI.
Expand Down
2 changes: 1 addition & 1 deletion doc/cosign_attest.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion doc/cosign_verify-attestation.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/apis/policy/v1alpha1/clusterimagepolicy_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (a *Attestation) Validate(ctx context.Context) *apis.FieldError {
}
if a.PredicateType == "" {
errs = errs.Also(apis.ErrMissingField("predicateType"))
} else if a.PredicateType != "custom" && a.PredicateType != "slsaprovenance" && a.PredicateType != "spdx" && a.PredicateType != "link" && a.PredicateType != "vuln" {
} else if a.PredicateType != "custom" && a.PredicateType != "slsaprovenance" && a.PredicateType != "spdx" && a.PredicateType != "spdxjson" && a.PredicateType != "link" && a.PredicateType != "vuln" {
// TODO(vaikas): The above should be using something like:
// if _, ok := options.PredicateTypeMap[a.PrecicateType]; !ok {
// But it causes an import loop. That refactor can be part of
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/policy/v1beta1/clusterimagepolicy_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (a *Attestation) Validate(ctx context.Context) *apis.FieldError {
}
if a.PredicateType == "" {
errs = errs.Also(apis.ErrMissingField("predicateType"))
} else if a.PredicateType != "custom" && a.PredicateType != "slsaprovenance" && a.PredicateType != "spdx" && a.PredicateType != "link" && a.PredicateType != "vuln" {
} else if a.PredicateType != "custom" && a.PredicateType != "slsaprovenance" && a.PredicateType != "spdx" && a.PredicateType != "spdxjson" && a.PredicateType != "link" && a.PredicateType != "vuln" {
// TODO(vaikas): The above should be using something like:
// if _, ok := options.PredicateTypeMap[a.PrecicateType]; !ok {
// But it causes an import loop. That refactor can be part of
Expand Down
18 changes: 14 additions & 4 deletions pkg/cosign/attestation/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type GenerateOpts struct {
}

// GenerateStatement returns an in-toto statement based on the provided
// predicate type (custom|slsaprovenance|spdx|link).
// predicate type (custom|slsaprovenance|spdx|spdxjson|link).
func GenerateStatement(opts GenerateOpts) (interface{}, error) {
predicate, err := io.ReadAll(opts.Predicate)
if err != nil {
Expand All @@ -110,7 +110,9 @@ func GenerateStatement(opts GenerateOpts) (interface{}, error) {
case "slsaprovenance":
return generateSLSAProvenanceStatement(predicate, opts.Digest, opts.Repo)
case "spdx":
return generateSPDXStatement(predicate, opts.Digest, opts.Repo)
return generateSPDXStatement(predicate, opts.Digest, opts.Repo, false)
case "spdxjson":
return generateSPDXStatement(predicate, opts.Digest, opts.Repo, true)
case "link":
return generateLinkStatement(predicate, opts.Digest, opts.Repo)
case "vuln":
Expand Down Expand Up @@ -226,11 +228,19 @@ func generateLinkStatement(rawPayload []byte, digest string, repo string) (inter
}, nil
}

func generateSPDXStatement(rawPayload []byte, digest string, repo string) (interface{}, error) {
func generateSPDXStatement(rawPayload []byte, digest string, repo string, parseJSON bool) (interface{}, error) {
var data interface{}
if parseJSON {
if err := json.Unmarshal(rawPayload, &data); err != nil {
return nil, err
}
} else {
data = string(rawPayload)
}
return in_toto.SPDXStatement{
StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateSPDX),
Predicate: CosignPredicate{
Data: string(rawPayload),
Data: data,
},
}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/policy/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func AttestationToPayloadJSON(ctx context.Context, predicateType string, verifie
if err != nil {
return nil, fmt.Errorf("marshaling ProvenanceStatement: %w", err)
}
case options.PredicateSPDX:
case options.PredicateSPDX, options.PredicateSPDXJSON:
var spdxStatement in_toto.SPDXStatement
if err := json.Unmarshal(decodedPayload, &spdxStatement); err != nil {
return nil, fmt.Errorf("unmarshaling SPDXStatement: %w", err)
Expand Down
41 changes: 30 additions & 11 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,33 @@ func TestImportSignVerifyClean(t *testing.T) {
}

func TestAttestVerify(t *testing.T) {
attestVerify(t,
"slsaprovenance",
`{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`,
`builder: id: "1"`,
`builder: id: "2"`,
)
}

func TestAttestVerifySPDXJSON(t *testing.T) {
attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.spdx.json")
if err != nil {
t.Fatal(err)
}
attestVerify(t,
"spdxjson",
string(attestationBytes),
`Data: spdxVersion: "SPDX-9.9"`,
`Data: spdxVersion: "SPDX-2.2"`,
)
}

func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue string) {
repo, stop := reg(t)
defer stop()
td := t.TempDir()

imgName := path.Join(repo, "cosign-attest-e2e")
imgName := path.Join(repo, fmt.Sprintf("cosign-attest-%s-e2e-image", predicateType))

_, _, cleanup := mkimage(t, imgName)
defer cleanup()
Expand All @@ -225,31 +247,28 @@ func TestAttestVerify(t *testing.T) {
// Fail case when using without type and policy flag
mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)

slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
attestationPath := filepath.Join(td, fmt.Sprintf("cosign-attest-%s-e2e-attestation", predicateType))
if err := os.WriteFile(attestationPath, []byte(attestation), 0600); err != nil {
t.Fatal(err)
}

// Now attest the image
ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, slsaAttestationPath, false,
"slsaprovenance", false, 30*time.Second), t)
must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, attestationPath, false,
predicateType, false, 30*time.Second), t)

// Use cue to verify attestation
policyPath := filepath.Join(td, "policy.cue")
verifyAttestation.PredicateType = "slsaprovenance"
verifyAttestation.PredicateType = predicateType
verifyAttestation.Policies = []string{policyPath}

// Fail case
cuePolicy := `builder: id: "1"`
if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
if err := os.WriteFile(policyPath, []byte(badCue), 0600); err != nil {
t.Fatal(err)
}

// Success case
cuePolicy = `builder: id: "2"`
if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
if err := os.WriteFile(policyPath, []byte(goodCue), 0600); err != nil {
t.Fatal(err)
}
must(verifyAttestation.Exec(ctx, []string{imgName}), t)
Expand Down
1 change: 1 addition & 0 deletions test/testdata/bom-go-mod.spdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"spdxVersion":"SPDX-2.2","dataLicense":"CC0-1.0","SPDXID":"SPDXRef-DOCUMENT","name":"SBOM-SPDX-34f1a7f5-03ff-4277-9021-8c04f8777803","documentNamespace":"https://spdx.org/spdxdocs/k8s-releng-bom-16f4e288-6bdf-4b89-a79a-9ffd56ad33e0","creationInfo":{"licenseListVersion":"","creators":["Organization: Kubernetes Release Engineering","Tool: sigs.k8s.io/bom/pkg/spdx"],"created":"2022-06-07T22:14:56Z","comment":""},"packages":[]}

0 comments on commit 2ccdb3e

Please sign in to comment.