Skip to content

Commit a1c349e

Browse files
committed
Add support for attesting with complete statement
Signed-off-by: Cody Soyland <[email protected]>
1 parent 37740f0 commit a1c349e

File tree

7 files changed

+97
-40
lines changed

7 files changed

+97
-40
lines changed

cmd/cosign/cli/attest.go

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func Attest() *cobra.Command {
9494
CertPath: o.Cert,
9595
CertChainPath: o.CertChain,
9696
NoUpload: o.NoUpload,
97+
StatementPath: o.Predicate.Statement,
9798
PredicatePath: o.Predicate.Path,
9899
PredicateType: o.Predicate.Type,
99100
Replace: o.Replace,

cmd/cosign/cli/attest/attest.go

+37-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
_ "crypto/sha256" // for `crypto.SHA256`
2222
"encoding/json"
2323
"fmt"
24+
"io"
2425
"os"
2526
"time"
2627

@@ -74,6 +75,7 @@ type AttestCommand struct {
7475
CertPath string
7576
CertChainPath string
7677
NoUpload bool
78+
StatementPath string
7779
PredicatePath string
7880
PredicateType string
7981
Replace bool
@@ -140,26 +142,44 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
140142
wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType)
141143
dd := cremote.NewDupeDetector(sv)
142144

143-
predicate, err := predicateReader(c.PredicatePath)
144-
if err != nil {
145-
return fmt.Errorf("getting predicate reader: %w", err)
146-
}
147-
defer predicate.Close()
145+
var payload []byte
148146

149-
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
150-
Predicate: predicate,
151-
Type: c.PredicateType,
152-
Digest: h.Hex,
153-
Repo: digest.Repository.String(),
154-
})
155-
if err != nil {
156-
return err
157-
}
147+
if c.StatementPath != "" {
148+
statement, err := predicateReader(c.StatementPath)
149+
if err != nil {
150+
return fmt.Errorf("getting statement reader: %w", err)
151+
}
152+
defer statement.Close()
153+
payload, err = io.ReadAll(statement)
154+
if err != nil {
155+
return fmt.Errorf("could not read statement: %w", err)
156+
}
157+
if err := validateStatement(payload); err != nil {
158+
return fmt.Errorf("invalid statement: %w", err)
159+
}
160+
} else {
161+
predicate, err := predicateReader(c.PredicatePath)
162+
if err != nil {
163+
return fmt.Errorf("getting predicate reader: %w", err)
164+
}
165+
defer predicate.Close()
158166

159-
payload, err := json.Marshal(sh)
160-
if err != nil {
161-
return err
167+
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
168+
Predicate: predicate,
169+
Type: c.PredicateType,
170+
Digest: h.Hex,
171+
Repo: digest.Repository.String(),
172+
})
173+
if err != nil {
174+
return err
175+
}
176+
177+
payload, err = json.Marshal(sh)
178+
if err != nil {
179+
return err
180+
}
162181
}
182+
163183
signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx))
164184
if err != nil {
165185
return fmt.Errorf("signing: %w", err)

cmd/cosign/cli/attest/attest_blob.go

+46-20
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333

3434
"errors"
3535

36+
"github.com/in-toto/in-toto-golang/in_toto"
3637
"github.com/secure-systems-lab/go-securesystemslib/dsse"
3738
"google.golang.org/protobuf/encoding/protojson"
3839

@@ -62,6 +63,7 @@ type AttestBlobCommand struct {
6263

6364
ArtifactHash string
6465

66+
StatementPath string
6567
PredicatePath string
6668
PredicateType string
6769

@@ -82,8 +84,8 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
8284
return &options.KeyParseError{}
8385
}
8486

85-
if c.PredicatePath == "" {
86-
return fmt.Errorf("predicate cannot be empty")
87+
if options.NOf(c.PredicatePath, c.StatementPath) != 1 {
88+
return fmt.Errorf("exactly one predicate or statement path must be set")
8789
}
8890

8991
if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" {
@@ -126,12 +128,6 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
126128
hexDigest = c.ArtifactHash
127129
}
128130

129-
predicate, err := predicateReader(c.PredicatePath)
130-
if err != nil {
131-
return fmt.Errorf("getting predicate reader: %w", err)
132-
}
133-
defer predicate.Close()
134-
135131
sv, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts)
136132
if err != nil {
137133
return fmt.Errorf("getting signer: %w", err)
@@ -141,19 +137,41 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
141137

142138
base := path.Base(artifactPath)
143139

144-
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
145-
Predicate: predicate,
146-
Type: c.PredicateType,
147-
Digest: hexDigest,
148-
Repo: base,
149-
})
150-
if err != nil {
151-
return err
152-
}
140+
var payload []byte
153141

154-
payload, err := json.Marshal(sh)
155-
if err != nil {
156-
return err
142+
if c.StatementPath != "" {
143+
statement, err := predicateReader(c.StatementPath)
144+
if err != nil {
145+
return fmt.Errorf("getting statement reader: %w", err)
146+
}
147+
defer statement.Close()
148+
payload, err = io.ReadAll(statement)
149+
if err != nil {
150+
return fmt.Errorf("could not read statement: %w", err)
151+
}
152+
if err := validateStatement(payload); err != nil {
153+
return fmt.Errorf("invalid statement: %w", err)
154+
}
155+
156+
} else {
157+
predicate, err := predicateReader(c.PredicatePath)
158+
if err != nil {
159+
return fmt.Errorf("getting predicate reader: %w", err)
160+
}
161+
defer predicate.Close()
162+
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
163+
Predicate: predicate,
164+
Type: c.PredicateType,
165+
Digest: hexDigest,
166+
Repo: base,
167+
})
168+
if err != nil {
169+
return err
170+
}
171+
payload, err = json.Marshal(sh)
172+
if err != nil {
173+
return err
174+
}
157175
}
158176

159177
sig, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx))
@@ -367,3 +385,11 @@ func makeNewBundle(sv *sign.SignerVerifier, rekorEntry *models.LogEntryAnon, pay
367385

368386
return contents, nil
369387
}
388+
389+
func validateStatement(payload []byte) error {
390+
var statement *in_toto.Statement
391+
if err := json.Unmarshal(payload, &statement); err != nil {
392+
return fmt.Errorf("invalid statement: %w", err)
393+
}
394+
return nil
395+
}

cmd/cosign/cli/attest_blob.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ func AttestBlob() *cobra.Command {
4747
# supply attestation via stdin
4848
echo <PAYLOAD> | cosign attest-blob --predicate - --yes`,
4949

50-
Args: cobra.ExactArgs(1),
5150
PersistentPreRun: options.BindViper,
5251
RunE: func(cmd *cobra.Command, args []string) error {
52+
if o.Predicate.Statement != "" && len(args) != 1 {
53+
return cobra.ExactArgs(1)(cmd, args)
54+
}
5355
oidcClientSecret, err := o.OIDC.ClientSecret()
5456
if err != nil {
5557
return err
@@ -83,6 +85,7 @@ func AttestBlob() *cobra.Command {
8385
TlogUpload: o.TlogUpload,
8486
PredicateType: o.Predicate.Type,
8587
PredicatePath: o.Predicate.Path,
88+
StatementPath: o.Predicate.Statement,
8689
OutputSignature: o.OutputSignature,
8790
OutputAttestation: o.OutputAttestation,
8891
OutputCertificate: o.OutputCertificate,

cmd/cosign/cli/options/predicate.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ func ParsePredicateType(t string) (string, error) {
8383
// PredicateLocalOptions is the wrapper for predicate related options.
8484
type PredicateLocalOptions struct {
8585
PredicateOptions
86-
Path string
86+
Path string
87+
Statement string
8788
}
8889

8990
var _ Interface = (*PredicateLocalOptions)(nil)
@@ -94,7 +95,11 @@ func (o *PredicateLocalOptions) AddFlags(cmd *cobra.Command) {
9495

9596
cmd.Flags().StringVar(&o.Path, "predicate", "",
9697
"path to the predicate file.")
97-
_ = cmd.MarkFlagRequired("predicate")
98+
99+
cmd.Flags().StringVar(&o.Path, "statement", "",
100+
"path to the statement file.")
101+
102+
cmd.MarkFlagsOneRequired("predicate", "statement")
98103
}
99104

100105
// PredicateRemoteOptions is the wrapper for remote predicate related options.

doc/cosign_attest-blob.md

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/cosign_attest.md

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)