Skip to content

Commit

Permalink
Modernize attest statements (#620)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* gofmt

Signed-off-by: Tom Hennen <[email protected]>

* Restructure test to allow deep comparison of the JSON

The prior setup needed the contents to be exactly equal.
Unfortunately protojson explicitly doesn't maintain
byte-for-byte equality. To address that the test now
deeply examines the 'attestation' (.sig) file produced
parses the payload JSON into a struct and then compares
that.

This provides somewhat easier to understand error diffs
when things go wrong, but most importantly it appears
stable even if field order or spacing change.

Signed-off-by: Tom Hennen <[email protected]>

* remove byte conversion to make linter happy

Signed-off-by: Tom Hennen <[email protected]>

* use gitCommit/gitTree as appropriate

Signed-off-by: Tom Hennen <[email protected]>

* remove old provenance template

Signed-off-by: Tom Hennen <[email protected]>

---------

Signed-off-by: Tom Hennen <[email protected]>
  • Loading branch information
TomHennen authored Jan 22, 2025
1 parent 62ea5c3 commit 6e07836
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 109 deletions.
2 changes: 1 addition & 1 deletion docs/cli/gitsign_attest.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 42 additions & 21 deletions internal/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,25 @@ 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"
rekorclient "github.com/sigstore/rekor/pkg/generated/client"
"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 (
CommitRef = "refs/attestations/commits"
TreeRef = "refs/attestations/trees"
CommitRef = "refs/attestations/commits"
TreeRef = "refs/attestations/trees"
DigestTypeCommit = "gitCommit"
DigestTypeTree = "gitTree"
)

var (
Expand All @@ -57,18 +61,20 @@ var (
type rekorUpload func(ctx context.Context, rekorClient *rekorclient.Rekor, signature []byte, pemBytes []byte) (*models.LogEntryAnon, error)

type Attestor struct {
repo *git.Repository
sv *sign.SignerVerifier
rekorFn rekorUpload
config *gitsignconfig.Config
repo *git.Repository
sv *sign.SignerVerifier
rekorFn rekorUpload
config *gitsignconfig.Config
digestType string
}

func NewAttestor(repo *git.Repository, sv *sign.SignerVerifier, rekorFn rekorUpload, config *gitsignconfig.Config) *Attestor {
func NewAttestor(repo *git.Repository, sv *sign.SignerVerifier, rekorFn rekorUpload, config *gitsignconfig.Config, digestType string) *Attestor {
return &Attestor{
repo: repo,
sv: sv,
rekorFn: rekorFn,
config: config,
repo: repo,
sv: sv,
rekorFn: rekorFn,
config: config,
digestType: digestType,
}
}

Expand Down Expand Up @@ -111,7 +117,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 {
Expand Down Expand Up @@ -226,18 +232,32 @@ func encode(store storage.Storer, enc Encoder) (plumbing.Hash, error) {
return store.SetEncodedObject(obj)
}

func generateStatement(pred []byte, attType string, digestType string, sha plumbing.Hash) (*spb.Statement, error) {
sub := []*spb.ResourceDescriptor{{
Digest: map[string]string{digestType: 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, a.digestType, sha)

if err != nil {
return nil, err
}
payload, err := json.Marshal(sh)
payload, err := protojson.Marshal(sh)
if err != nil {
return nil, err
}
Expand All @@ -248,6 +268,7 @@ func (a *Attestor) signPayload(ctx context.Context, sha plumbing.Hash, b []byte,
}

rekorHost, rekorBasePath := utils.StripURL(a.config.Rekor)

tc := &rekorclient.TransportConfig{
Host: rekorHost,
BasePath: rekorBasePath,
Expand Down
Loading

0 comments on commit 6e07836

Please sign in to comment.