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

Add spdxjson predicate type for attestations #1974

Merged
merged 1 commit into from
Jun 8, 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
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.

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":[]}