Skip to content

Commit bac7923

Browse files
committed
Adds initial Attestor implementation.
This is the initial implementation of Attestors, which uses generics to link chains components together with strict typing. To start, this adds Attestor implementations of OCI signing and v1 SLSA attestations. These Attestors are NOT wired up to the controller yet, since they don't yet support the full range of config options (and there's likely a few tweaks we need to make in order to help reuse components like signers between Attestors).
1 parent dd3620e commit bac7923

File tree

18 files changed

+579
-158
lines changed

18 files changed

+579
-158
lines changed

pkg/artifacts/signable.go

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ type Signable interface {
6060
Enabled(cfg config.Config) bool
6161
}
6262

63+
// Extractor extracts a given type T from a Tekton object.
64+
type Extractor[T any] interface {
65+
Extract(ctx context.Context, obj objects.TektonObject) ([]T, error)
66+
}
67+
6368
type TaskRunArtifact struct{}
6469

6570
var _ Signable = &TaskRunArtifact{}
@@ -150,7 +155,32 @@ type image struct {
150155

151156
func (oa *OCIArtifact) ExtractObjects(ctx context.Context, obj objects.TektonObject) []interface{} {
152157
log := logging.FromContext(ctx)
153-
objs := []interface{}{}
158+
digests, err := oa.Extract(ctx, obj)
159+
if err != nil {
160+
log.Error(err)
161+
return nil
162+
}
163+
164+
// Convert to interface
165+
objs := []any{}
166+
for _, d := range digests {
167+
objs = append(objs, d)
168+
}
169+
return objs
170+
}
171+
172+
var (
173+
defaultOCI = OCIArtifact{}
174+
)
175+
176+
func ExtractOCI(ctx context.Context, obj objects.TektonObject) ([]name.Digest, error) {
177+
return defaultOCI.Extract(ctx, obj)
178+
}
179+
180+
func (OCIArtifact) Extract(ctx context.Context, obj objects.TektonObject) ([]name.Digest, error) {
181+
log := logging.FromContext(ctx)
182+
183+
var out []name.Digest
154184

155185
// TODO: Not applicable to PipelineRuns, should look into a better way to separate this out
156186
if tr, ok := obj.GetObject().(*v1beta1.TaskRun); ok {
@@ -182,21 +212,25 @@ func (oa *OCIArtifact) ExtractObjects(ctx context.Context, obj objects.TektonObj
182212
log.Error(err)
183213
continue
184214
}
185-
objs = append(objs, dgst)
215+
out = append(out, dgst)
186216
}
187217
}
188218

189219
// Now check TaskResults
190-
resultImages := ExtractOCIImagesFromResults(ctx, obj)
191-
objs = append(objs, resultImages...)
220+
digests, err := extractOCIImagesFromResults(ctx, obj)
221+
if err != nil {
222+
log.Warnf("error extracting digests from results: %v", err)
223+
return nil, err
224+
}
225+
out = append(out, digests...)
192226

193-
return objs
227+
return out, nil
194228
}
195229

196-
func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) []interface{} {
230+
func extractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) ([]name.Digest, error) {
197231
logger := logging.FromContext(ctx)
198-
objs := []interface{}{}
199232

233+
out := []name.Digest{}
200234
extractor := structuredSignableExtractor{
201235
uriSuffix: "IMAGE_URL",
202236
digestSuffix: "IMAGE_DIGEST",
@@ -209,7 +243,7 @@ func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject)
209243
continue
210244
}
211245

212-
objs = append(objs, dgst)
246+
out = append(out, dgst)
213247
}
214248

215249
// look for a comma separated list of images
@@ -229,11 +263,10 @@ func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject)
229263
logger.Errorf("error getting digest for img %s: %v", trimmed, err)
230264
continue
231265
}
232-
objs = append(objs, dgst)
266+
out = append(out, dgst)
233267
}
234268
}
235-
236-
return objs
269+
return out, nil
237270
}
238271

239272
// ExtractSignableTargetFromResults extracts signable targets that aim to generate intoto provenance as materials within TaskRun results and store them as StructuredSignable.

pkg/artifacts/signable_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -331,16 +331,19 @@ func TestExtractOCIImagesFromResults(t *testing.T) {
331331
},
332332
}
333333
obj := objects.NewTaskRunObject(tr)
334-
want := []interface{}{
334+
want := []name.Digest{
335335
createDigest(t, fmt.Sprintf("img1@%s", digest1)),
336336
createDigest(t, fmt.Sprintf("img2@%s", digest2)),
337337
createDigest(t, fmt.Sprintf("img3@%s", digest1)),
338338
}
339339
ctx := logtesting.TestContextWithLogger(t)
340-
got := ExtractOCIImagesFromResults(ctx, obj)
340+
got, err := extractOCIImagesFromResults(ctx, obj)
341+
if err != nil {
342+
t.Fatal(err)
343+
}
341344
sort.Slice(got, func(i, j int) bool {
342-
a := got[i].(name.Digest)
343-
b := got[j].(name.Digest)
345+
a := got[i]
346+
b := got[j]
344347
return a.String() < b.String()
345348
})
346349
if !cmp.Equal(got, want, ignore...) {

pkg/chains/formats/format.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,21 @@ import (
2121
)
2222

2323
// Payloader is an interface to generate a chains Payload from a TaskRun
24+
// Deprecated: Use Formatter instead.
2425
type Payloader interface {
2526
CreatePayload(ctx context.Context, obj interface{}) (interface{}, error)
2627
Type() config.PayloadType
2728
Wrap() bool
2829
}
2930

31+
// Formatter transforms an extracted Input artifact into an Output
32+
// artifact suitable for signing + storage.
33+
type Formatter[Input any, Output any] interface {
34+
// Effectively the same as CreatePayload, but using a different name so that
35+
// this interface can coexist with Payloader.
36+
FormatPayload(ctx context.Context, in Input) (Output, error)
37+
}
38+
3039
const (
3140
PayloadTypeTekton config.PayloadType = "tekton"
3241
PayloadTypeSimpleSigning config.PayloadType = "simplesigning"

pkg/chains/formats/simple/simple.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package simple
1515

1616
import (
1717
"context"
18+
"encoding/json"
1819
"fmt"
1920

2021
"github.com/sigstore/sigstore/pkg/signature/payload"
@@ -66,6 +67,20 @@ func (i SimpleContainerImage) ImageName() string {
6667
return fmt.Sprintf("%s@%s", i.Critical.Identity.DockerReference, i.Critical.Image.DockerManifestDigest)
6768
}
6869

70+
func (i SimpleContainerImage) MarshalBinary() ([]byte, error) {
71+
return json.Marshal(i)
72+
}
73+
6974
func (i *SimpleSigning) Type() config.PayloadType {
7075
return formats.PayloadTypeSimpleSigning
7176
}
77+
78+
var (
79+
_ formats.Formatter[name.Digest, SimpleContainerImage] = &SimpleSigningPayloader{}
80+
)
81+
82+
type SimpleSigningPayloader SimpleSigning
83+
84+
func (SimpleSigningPayloader) FormatPayload(_ context.Context, v name.Digest) (SimpleContainerImage, error) {
85+
return NewSimpleStruct(v), nil
86+
}

pkg/chains/formats/slsa/extract/extract.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"fmt"
2222
"strings"
2323

24-
"github.com/google/go-containerregistry/pkg/name"
2524
intoto "github.com/in-toto/in-toto-golang/in_toto"
2625
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
2726
"github.com/tektoncd/chains/internal/backport"
@@ -95,16 +94,17 @@ func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []i
9594
logger := logging.FromContext(ctx)
9695
var subjects []intoto.Subject
9796

98-
imgs := artifacts.ExtractOCIImagesFromResults(ctx, obj)
99-
for _, i := range imgs {
100-
if d, ok := i.(name.Digest); ok {
101-
subjects = artifact.AppendSubjects(subjects, intoto.Subject{
102-
Name: d.Repository.Name(),
103-
Digest: common.DigestSet{
104-
"sha256": strings.TrimPrefix(d.DigestStr(), "sha256:"),
105-
},
106-
})
107-
}
97+
imgs, err := artifacts.ExtractOCI(ctx, obj)
98+
if err != nil {
99+
logger.Warnf("error extracting OCI artifacts: %v", err)
100+
}
101+
for _, d := range imgs {
102+
subjects = artifact.AppendSubjects(subjects, intoto.Subject{
103+
Name: d.Repository.Name(),
104+
Digest: common.DigestSet{
105+
"sha256": strings.TrimPrefix(d.DigestStr(), "sha256:"),
106+
},
107+
})
108108
}
109109

110110
sts := artifacts.ExtractSignableTargetFromResults(ctx, obj)

pkg/chains/formats/slsa/v1/intotoite6.go

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package v1
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"fmt"
2223

24+
"github.com/in-toto/in-toto-golang/in_toto"
2325
"github.com/tektoncd/chains/pkg/chains/formats"
2426
"github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig"
2527
"github.com/tektoncd/chains/pkg/chains/formats/slsa/v1/pipelinerun"
@@ -34,21 +36,57 @@ const (
3436
)
3537

3638
func init() {
37-
formats.RegisterPayloader(PayloadTypeInTotoIte6, NewFormatter)
38-
formats.RegisterPayloader(PayloadTypeSlsav1, NewFormatter)
39+
formats.RegisterPayloader(PayloadTypeInTotoIte6, NewPayloader)
40+
formats.RegisterPayloader(PayloadTypeSlsav1, NewPayloader)
3941
}
4042

4143
type InTotoIte6 struct {
4244
slsaConfig *slsaconfig.SlsaConfig
4345
}
4446

45-
func NewFormatter(cfg config.Config) (formats.Payloader, error) {
47+
func NewPayloader(cfg config.Config) (formats.Payloader, error) {
48+
return NewPayloaderFromConfig(cfg), nil
49+
}
50+
51+
func NewPayloaderFromConfig(cfg config.Config) *InTotoIte6 {
52+
opts := []Option{
53+
WithBuilderID(cfg.Builder.ID),
54+
WithDeepInspection(cfg.Artifacts.PipelineRuns.DeepInspectionEnabled),
55+
}
56+
return NewFormatter(opts...)
57+
}
58+
59+
type options struct {
60+
builderID string
61+
deepInspection bool
62+
}
63+
64+
type Option func(*options)
65+
66+
func WithDeepInspection(enabled bool) Option {
67+
return func(o *options) {
68+
o.deepInspection = enabled
69+
}
70+
}
71+
72+
func WithBuilderID(id string) Option {
73+
return func(o *options) {
74+
o.builderID = id
75+
}
76+
}
77+
78+
func NewFormatter(opts ...Option) *InTotoIte6 {
79+
o := &options{}
80+
for _, f := range opts {
81+
f(o)
82+
}
83+
4684
return &InTotoIte6{
4785
slsaConfig: &slsaconfig.SlsaConfig{
48-
BuilderID: cfg.Builder.ID,
49-
DeepInspectionEnabled: cfg.Artifacts.PipelineRuns.DeepInspectionEnabled,
86+
BuilderID: o.builderID,
87+
DeepInspectionEnabled: o.deepInspection,
5088
},
51-
}, nil
89+
}
5290
}
5391

5492
func (i *InTotoIte6) Wrap() bool {
@@ -66,6 +104,36 @@ func (i *InTotoIte6) CreatePayload(ctx context.Context, obj interface{}) (interf
66104
}
67105
}
68106

107+
func (i *InTotoIte6) FormatPayload(ctx context.Context, obj objects.TektonObject) (*ProvenanceStatement, error) {
108+
var (
109+
s *in_toto.ProvenanceStatement
110+
err error
111+
)
112+
113+
switch v := obj.(type) {
114+
case *objects.TaskRunObject:
115+
s, err = taskrun.GenerateAttestation(ctx, v, i.slsaConfig)
116+
case *objects.PipelineRunObject:
117+
s, err = pipelinerun.GenerateAttestation(ctx, v, i.slsaConfig)
118+
default:
119+
return nil, fmt.Errorf("intoto does not support type: %s", v)
120+
}
121+
122+
if err != nil {
123+
return nil, err
124+
}
125+
// Wrap output in BinaryMarshaller so we know how to format this.
126+
out := ProvenanceStatement(*s)
127+
return &out, nil
128+
129+
}
130+
69131
func (i *InTotoIte6) Type() config.PayloadType {
70132
return formats.PayloadTypeSlsav1
71133
}
134+
135+
type ProvenanceStatement in_toto.ProvenanceStatement
136+
137+
func (s ProvenanceStatement) MarshalBinary() ([]byte, error) {
138+
return json.Marshal(s)
139+
}

0 commit comments

Comments
 (0)