From fad643d8d771f3179e619cb98f5afb26f7fb1875 Mon Sep 17 00:00:00 2001 From: Tom Hennen Date: Sun, 19 Jan 2025 16:09:59 +0000 Subject: [PATCH] Modernize attest statements attest no longer uses the cosign library to generate the Statement used in the attestation. This lets us: 1. Use `gitCommit` as the digest type (which is what we were doing anyways) 2. Use the correct `_type` value for modern in-toto statements In the future it will let us add annotations on the subject. Note that this also changes the user behavior in that it: 1. Changes the things mentioned above. 2. Doesn't let users use the convenience attestation types from the command line. They must instead specify the full predicate type. refs #611 Signed-off-by: Tom Hennen --- docs/cli/gitsign_attest.md | 2 +- go.mod | 1 + internal/attest/attest.go | 39 +++++++++++++------ internal/attest/attest_test.go | 14 +++---- internal/attest/testdata/test.json.provenance | 2 +- internal/commands/attest/attest.go | 2 +- 6 files changed, 39 insertions(+), 21 deletions(-) diff --git a/docs/cli/gitsign_attest.md b/docs/cli/gitsign_attest.md index aa3fb0fc..6e4d0648 100644 --- a/docs/cli/gitsign_attest.md +++ b/docs/cli/gitsign_attest.md @@ -12,7 +12,7 @@ gitsign attest [flags] -f, --filepath string attestation filepath -h, --help help for attest --objtype string [commit | tree] - Git object type to attest (default "commit") - --type string specify a predicate type (slsaprovenance|link|spdx|spdxjson|cyclonedx|vuln|custom) or an URI (default "custom") + --type string specify a predicate type URI ``` ### SEE ALSO diff --git a/go.mod b/go.mod index c66cb786..d2d8c0f5 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-openapi/swag v0.23.0 github.com/google/go-cmp v0.6.0 + github.com/in-toto/attestation v1.1.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/jonboulle/clockwork v0.5.0 github.com/mattn/go-tty v0.0.7 diff --git a/internal/attest/attest.go b/internal/attest/attest.go index cd74b53b..8f75baa3 100644 --- a/internal/attest/attest.go +++ b/internal/attest/attest.go @@ -32,9 +32,9 @@ import ( "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage" "github.com/go-openapi/strfmt" + spb "github.com/in-toto/attestation/go/v1" "github.com/jonboulle/clockwork" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" - "github.com/sigstore/cosign/v2/pkg/cosign/attestation" "github.com/sigstore/cosign/v2/pkg/types" utils "github.com/sigstore/gitsign/internal" gitsignconfig "github.com/sigstore/gitsign/internal/config" @@ -42,6 +42,8 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" dssesig "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" ) const ( @@ -111,7 +113,7 @@ func NewNamedReader(r io.Reader, name string) Reader { // refName: What ref to write to (e.g. refs/attestations/commits) // sha: Commit SHA you are attesting to. // input: Attestation file input. -// attType: Attestation type. See [attestation.GenerateStatement] for allowed values. +// attType: Attestation (predicate) type URI corresponding to the input (predicate). func (a *Attestor) WriteAttestation(ctx context.Context, refName string, sha plumbing.Hash, input Reader, attType string) (plumbing.Hash, error) { b, err := io.ReadAll(input) if err != nil { @@ -226,18 +228,32 @@ func encode(store storage.Storer, enc Encoder) (plumbing.Hash, error) { return store.SetEncodedObject(obj) } +func generateStatement(pred []byte, attType string, sha plumbing.Hash) (*spb.Statement, error) { + sub := []*spb.ResourceDescriptor{{ + Digest: map[string]string{"gitCommit": sha.String()}, + }} + + var predPb structpb.Struct + err := json.Unmarshal(pred, &predPb) + if err != nil { + return nil, err + } + + return &spb.Statement{ + Type: spb.StatementTypeUri, + Subject: sub, + PredicateType: attType, + Predicate: &predPb, + }, nil +} + func (a *Attestor) signPayload(ctx context.Context, sha plumbing.Hash, b []byte, attType string) ([]byte, error) { - // Generate attestation - sh, err := attestation.GenerateStatement(attestation.GenerateOpts{ - Predicate: bytes.NewBuffer(b), - Type: attType, - Digest: sha.String(), - Time: clock.Now, - }) + sh, err := generateStatement(b, attType, sha) + if err != nil { return nil, err } - payload, err := json.Marshal(sh) + payload, err := protojson.Marshal(sh) if err != nil { return nil, err } @@ -248,7 +264,8 @@ func (a *Attestor) signPayload(ctx context.Context, sha plumbing.Hash, b []byte, } rekorHost, rekorBasePath := utils.StripURL(a.config.Rekor) - tc := &rekorclient.TransportConfig{ + +tc := &rekorclient.TransportConfig{ Host: rekorHost, BasePath: rekorBasePath, Schemes: []string{"https"}, diff --git a/internal/attest/attest_test.go b/internal/attest/attest_test.go index 96e3d0d1..1133d339 100644 --- a/internal/attest/attest_test.go +++ b/internal/attest/attest_test.go @@ -90,7 +90,7 @@ func TestAttestCommitRef(t *testing.T) { }, } t.Run("base", func(t *testing.T) { - attest1, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom") + attest1, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } @@ -99,7 +99,7 @@ func TestAttestCommitRef(t *testing.T) { t.Run("noop", func(t *testing.T) { // Write same attestation to the same commit - should be a no-op. - attest2, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom") + attest2, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } @@ -117,7 +117,7 @@ func TestAttestCommitRef(t *testing.T) { t.Fatal(err) } - attest3, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom") + attest3, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } @@ -170,7 +170,7 @@ func TestAttestTreeRef(t *testing.T) { }, } t.Run("base", func(t *testing.T) { - attest1, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom") + attest1, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } @@ -179,7 +179,7 @@ func TestAttestTreeRef(t *testing.T) { t.Run("noop", func(t *testing.T) { // Write same attestation to the same commit - should be a no-op. - attest2, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom") + attest2, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } @@ -197,7 +197,7 @@ func TestAttestTreeRef(t *testing.T) { } sha = resolveTree(t, repo, sha) - attest3, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom") + attest3, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } @@ -208,7 +208,7 @@ func TestAttestTreeRef(t *testing.T) { // Make a new commit, write new attestation. sha = resolveTree(t, repo, writeRepo(t, w, fs, "testdata/bar.txt")) - attest3, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom") + attest3, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } diff --git a/internal/attest/testdata/test.json.provenance b/internal/attest/testdata/test.json.provenance index bf5ca61b..d0f698cf 100644 --- a/internal/attest/testdata/test.json.provenance +++ b/internal/attest/testdata/test.json.provenance @@ -1 +1 @@ -{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://cosign.sigstore.dev/attestation/v1","subject":[{"name":"","digest":{"sha256":"{{.}}"}}],"predicate":{"Data":"{\"foo\":\"bar\"}","Timestamp":"1984-04-04T00:00:00Z"}} +{"_type":"https://in-toto.io/Statement/v1","subject":[{"digest":{"gitCommit":"{{.}}"}}],"predicateType":"custom-pred-type","predicate":{"foo":"bar"}} diff --git a/internal/commands/attest/attest.go b/internal/commands/attest/attest.go index 76a7fcbe..13791141 100644 --- a/internal/commands/attest/attest.go +++ b/internal/commands/attest/attest.go @@ -46,7 +46,7 @@ type options struct { func (o *options) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.FlagObjectType, "objtype", FlagObjectTypeCommit, "[commit | tree] - Git object type to attest") cmd.Flags().StringVarP(&o.FlagPath, "filepath", "f", "", "attestation filepath") - cmd.Flags().StringVar(&o.FlagAttestationType, "type", "", `specify a predicate type (slsaprovenance|link|spdx|spdxjson|cyclonedx|vuln|custom) or an URI (default "custom")`) + cmd.Flags().StringVar(&o.FlagAttestationType, "type", "", `specify a predicate type URI`) } func (o *options) Run(ctx context.Context) error {