Skip to content

Commit

Permalink
Chains Spire Verification (#24)
Browse files Browse the repository at this point in the history
* Implement Chains API server with GRPC, and add in an integration test

* Add SPIRE verification to chains

* fix merge conflict

* pull main and tidy

Signed-off-by: pxp928 <parth.psu@gmail.com>

* added spire annotation verification

Signed-off-by: pxp928 <parth.psu@gmail.com>

* error if spire verification fails

Signed-off-by: pxp928 <parth.psu@gmail.com>

* changed to check status annotations

Signed-off-by: pxp928 <parth.psu@gmail.com>

* removed local spire and moved condition check

Signed-off-by: pxp928 <parth.psu@gmail.com>

* updated condition check and spire check to format

Signed-off-by: pxp928 <parth.psu@gmail.com>

* fixed typo

Signed-off-by: pxp928 <parth.psu@gmail.com>

* fixed vendor for pipelines

Signed-off-by: pxp928 <parth.psu@gmail.com>

Co-authored-by: Priya Wadhwa <priyawadhwa@google.com>
  • Loading branch information
pxp928 and Priya Wadhwa authored Mar 15, 2022
1 parent 122fe23 commit 1fb014c
Show file tree
Hide file tree
Showing 77 changed files with 8,503 additions and 270 deletions.
6 changes: 6 additions & 0 deletions config/100-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ spec:
mountPath: /etc/signing-secrets
- name: oidc-info
mountPath: /var/run/sigstore/cosign
- name: spiffe-workload-api
mountPath: /spiffe-workload-api
readOnly: true
env:
- name: SYSTEM_NAMESPACE
valueFrom:
Expand Down Expand Up @@ -119,3 +122,6 @@ spec:
path: oidc-token
expirationSeconds: 600 # Use as short-lived as possible.
audience: sigstore
- name: spiffe-workload-api
csi:
driver: "csi.spiffe.io"
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.16

replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c

replace github.com/tektoncd/pipeline v0.31.1-0.20220105002759-3e137645be61 => ../pipeline

require (
cloud.google.com/go/compute v1.4.0
cloud.google.com/go/storage v1.21.0
Expand All @@ -14,7 +16,7 @@ require (
github.com/golangci/golangci-lint v1.44.0
github.com/google/addlicense v1.0.0
github.com/google/go-cmp v0.5.7
github.com/google/go-containerregistry v0.8.1-0.20220202214207-9c35968ef47e
github.com/google/go-containerregistry v0.8.1-0.20220211173031-41f8d92709b7
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20220125170349-50dfc2733d10
github.com/google/go-licenses v0.0.0-20210816172045-3099c18c36e1
github.com/hashicorp/errwrap v1.1.0
Expand Down Expand Up @@ -51,5 +53,5 @@ require (
k8s.io/apimachinery v0.22.5
k8s.io/client-go v0.22.5
k8s.io/code-generator v0.22.5
knative.dev/pkg v0.0.0-20220121092305-3ba5d72e310a
knative.dev/pkg v0.0.0-20220131144930-f4b57aef0006
)
63 changes: 10 additions & 53 deletions go.sum

Large diffs are not rendered by default.

43 changes: 42 additions & 1 deletion pkg/chains/formats/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,20 @@ limitations under the License.

package formats

import (
"context"
"fmt"

"github.com/pkg/errors"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/spire"
"go.uber.org/zap"
"knative.dev/pkg/apis"
)

// Payloader is an interface to generate a chains Payload from a TaskRun
type Payloader interface {
CreatePayload(obj interface{}) (interface{}, error)
CreatePayload(ctx context.Context, obj interface{}) (interface{}, error)
Type() PayloadType
Wrap() bool
}
Expand All @@ -31,3 +42,33 @@ const (
)

var AllFormatters = []PayloadType{PayloadTypeTekton, PayloadTypeSimpleSigning, PayloadTypeInTotoIte6, PayloadTypeProvenance}

func VerifySpire(ctx context.Context, tr *v1beta1.TaskRun, spireControllerAPI *spire.SpireControllerApiClient, logger *zap.SugaredLogger) error {
if err := verifySignedTaskrrunResults(tr); err != nil {
return err
} else {
if len(tr.Status.TaskRunResults) > 0 {
logger.Info("spire taskrun status condition verified")
}
}
if err := spireControllerAPI.VerifyStatusInternalAnnotation(ctx, tr, logger); err != nil {
return errors.Wrap(err, "verifying SPIRE")
} else {
logger.Info("internal status annotation verified by spire")
}
return nil
}

func verifySignedTaskrrunResults(tr *v1beta1.TaskRun) error {
if len(tr.Status.TaskRunResults) > 0 {
taskRunCondition := tr.Status.GetCondition(apis.ConditionType(v1beta1.TaskRunConditionResultsVerified.String()))
if taskRunCondition != nil {
if taskRunCondition.IsFalse() {
return errors.New("taskrun status condition not verified. Spire taskrun results verification failure")
}
} else {
return fmt.Errorf("could not find condition Type %s in taskrun status", v1beta1.TaskRunConditionResultsVerified.String())
}
}
return nil
}
24 changes: 19 additions & 5 deletions pkg/chains/formats/intotoite6/intotoite6.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package intotoite6

import (
"context"
"fmt"
"sort"
"strings"
Expand All @@ -30,6 +31,8 @@ import (
"github.com/tektoncd/chains/pkg/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/spire"
spireconfig "github.com/tektoncd/pipeline/pkg/spire/config"
"go.uber.org/zap"

"github.com/google/go-containerregistry/pkg/name"
Expand All @@ -45,26 +48,37 @@ const (
)

type InTotoIte6 struct {
builderID string
logger *zap.SugaredLogger
builderID string
logger *zap.SugaredLogger
spireEnabled bool
spireControllerAPI *spire.SpireControllerApiClient
}

func NewFormatter(cfg config.Config, logger *zap.SugaredLogger) (formats.Payloader, error) {
return &InTotoIte6{
builderID: cfg.Builder.ID,
logger: logger,
builderID: cfg.Builder.ID,
logger: logger,
spireEnabled: cfg.SPIRE.Enabled,
spireControllerAPI: spire.NewSpireControllerApiClient(spireconfig.SpireConfig{
SocketPath: cfg.SPIRE.SocketPath,
}),
}, nil
}

func (i *InTotoIte6) Wrap() bool {
return true
}

func (i *InTotoIte6) CreatePayload(obj interface{}) (interface{}, error) {
func (i *InTotoIte6) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) {
var tr *v1beta1.TaskRun
switch v := obj.(type) {
case *v1beta1.TaskRun:
tr = v
if i.spireEnabled {
if err := formats.VerifySpire(ctx, tr, i.spireControllerAPI, i.logger); err != nil {
return nil, err
}
}
default:
return nil, fmt.Errorf("intoto does not support type: %s", v)
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/chains/formats/intotoite6/intotoite6_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package intotoite6

import (
"context"
"encoding/json"
"io/ioutil"
"testing"
Expand Down Expand Up @@ -105,7 +106,7 @@ func TestCreatePayload1(t *testing.T) {
}
i, _ := NewFormatter(cfg, logtesting.TestLogger(t))

got, err := i.CreatePayload(tr)
got, err := i.CreatePayload(context.Background(), tr)

if err != nil {
t.Errorf("unexpected error: %s", err.Error())
Expand Down Expand Up @@ -151,7 +152,7 @@ func TestCreatePayload2(t *testing.T) {
},
}
i, _ := NewFormatter(cfg, logtesting.TestLogger(t))
got, err := i.CreatePayload(tr)
got, err := i.CreatePayload(context.Background(), tr)

if err != nil {
t.Errorf("unexpected error: %s", err.Error())
Expand All @@ -171,7 +172,7 @@ func TestCreatePayloadNilTaskRef(t *testing.T) {
}
f, _ := NewFormatter(cfg, logtesting.TestLogger(t))

p, err := f.CreatePayload(tr)
p, err := f.CreatePayload(context.Background(), tr)
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}
Expand Down Expand Up @@ -231,7 +232,7 @@ func TestMultipleSubjects(t *testing.T) {
}

i, _ := NewFormatter(cfg, logtesting.TestLogger(t))
got, err := i.CreatePayload(tr)
got, err := i.CreatePayload(context.Background(), tr)
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}
Expand Down Expand Up @@ -266,7 +267,7 @@ func TestCreatePayloadError(t *testing.T) {
f, _ := NewFormatter(cfg, logtesting.TestLogger(t))

t.Run("Invalid type", func(t *testing.T) {
p, err := f.CreatePayload("not a task ref")
p, err := f.CreatePayload(context.Background(), "not a task ref")

if p != nil {
t.Errorf("Unexpected payload")
Expand Down
24 changes: 19 additions & 5 deletions pkg/chains/formats/provenance/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package provenance

import (
"context"
"fmt"
"sort"
"strings"
Expand All @@ -30,6 +31,8 @@ import (
"github.com/tektoncd/chains/pkg/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/spire"
spireconfig "github.com/tektoncd/pipeline/pkg/spire/config"
"go.uber.org/zap"

"github.com/google/go-containerregistry/pkg/name"
Expand All @@ -44,8 +47,10 @@ const (
)

type Provenance struct {
builderID string
logger *zap.SugaredLogger
builderID string
logger *zap.SugaredLogger
spireEnabled bool
spireControllerAPI *spire.SpireControllerApiClient
}

func NewFormatter(cfg config.Config, logger *zap.SugaredLogger) (formats.Payloader, error) {
Expand All @@ -56,20 +61,29 @@ func NewFormatter(cfg config.Config, logger *zap.SugaredLogger) (formats.Payload
kubectl patch configmap chains-config -n tekton-chains -p='{"data":{"artifacts.taskrun.format": "in-toto"}}'
`
return &Provenance{
builderID: cfg.Builder.ID,
logger: logger,
builderID: cfg.Builder.ID,
logger: logger,
spireEnabled: cfg.SPIRE.Enabled,
spireControllerAPI: spire.NewSpireControllerApiClient(spireconfig.SpireConfig{
SocketPath: cfg.SPIRE.SocketPath,
}),
}, errors.New(errorMsg)
}

func (i *Provenance) Wrap() bool {
return true
}

func (i *Provenance) CreatePayload(obj interface{}) (interface{}, error) {
func (i *Provenance) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) {
var tr *v1beta1.TaskRun
switch v := obj.(type) {
case *v1beta1.TaskRun:
tr = v
if i.spireEnabled {
if err := formats.VerifySpire(ctx, tr, i.spireControllerAPI, i.logger); err != nil {
return nil, err
}
}
default:
return nil, fmt.Errorf("intoto does not support type: %s", v)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/chains/formats/simple/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ limitations under the License.
package simple

import (
"context"
"fmt"

"github.com/sigstore/sigstore/pkg/signature/payload"
Expand All @@ -30,7 +31,7 @@ type SimpleSigning struct {
type SimpleContainerImage payload.SimpleContainerImage

// CreatePayload implements the Payloader interface.
func (i *SimpleSigning) CreatePayload(obj interface{}) (interface{}, error) {
func (i *SimpleSigning) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) {
switch v := obj.(type) {
case name.Digest:
format := NewSimpleStruct(v)
Expand Down
5 changes: 3 additions & 2 deletions pkg/chains/formats/simple/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ limitations under the License.
package simple

import (
"context"
"reflect"
"testing"

Expand Down Expand Up @@ -61,7 +62,7 @@ func TestSimpleSigning_CreatePayload(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := &SimpleSigning{}
got, err := i.CreatePayload(tt.obj)
got, err := i.CreatePayload(context.Background(), tt.obj)
if (err != nil) != tt.wantErr {
t.Errorf("SimpleSigning.CreatePayload() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -81,7 +82,7 @@ func TestImageName(t *testing.T) {
obj := makeDigest(t, img)

i := &SimpleSigning{}
format, err := i.CreatePayload(obj)
format, err := i.CreatePayload(context.Background(), obj)
if err != nil {
t.Fatal(err)
}
Expand Down
29 changes: 24 additions & 5 deletions pkg/chains/formats/tekton/tekton.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,50 @@ limitations under the License.
package tekton

import (
"context"
"fmt"

"github.com/tektoncd/chains/pkg/chains/formats"
"github.com/tektoncd/chains/pkg/config"
"github.com/tektoncd/pipeline/pkg/spire"
spireconfig "github.com/tektoncd/pipeline/pkg/spire/config"
"go.uber.org/zap"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
)

// Tekton is a formatter that just captures the TaskRun Status with no modifications.
type Tekton struct {
logger *zap.SugaredLogger
spireEnabled bool
spireControllerAPI *spire.SpireControllerApiClient
}

func NewFormatter() (formats.Payloader, error) {
return &Tekton{}, nil
func NewFormatter(cfg config.Config, l *zap.SugaredLogger) (formats.Payloader, error) {
return &Tekton{
logger: l,
spireEnabled: cfg.SPIRE.Enabled,
spireControllerAPI: spire.NewSpireControllerApiClient(spireconfig.SpireConfig{
SocketPath: cfg.SPIRE.SocketPath,
}),
}, nil
}

// CreatePayload implements the Payloader interface.
func (i *Tekton) CreatePayload(obj interface{}) (interface{}, error) {

func (i *Tekton) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) {
var tr *v1beta1.TaskRun
switch v := obj.(type) {
case *v1beta1.TaskRun:
tr = v
if i.spireEnabled {
if err := formats.VerifySpire(ctx, tr, i.spireControllerAPI, i.logger); err != nil {
return nil, err
}
}
return v.Status, nil
default:
return nil, fmt.Errorf("unsupported type %s", v)
}

}

func (i *Tekton) Type() formats.PayloadType {
Expand Down
3 changes: 2 additions & 1 deletion pkg/chains/formats/tekton/tekton_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ limitations under the License.
package tekton

import (
"context"
"reflect"
"testing"

Expand All @@ -35,7 +36,7 @@ func TestTekton_CreatePayload(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := &Tekton{}
got, err := i.CreatePayload(tt.tr)
got, err := i.CreatePayload(context.Background(), tt.tr)
if err != nil {
t.Errorf("Tekton.CreatePayload() error = %v", err)
return
Expand Down
Loading

0 comments on commit 1fb014c

Please sign in to comment.