Skip to content

Commit

Permalink
fix secretref
Browse files Browse the repository at this point in the history
  • Loading branch information
Yongxuanzhang committed Oct 31, 2022
1 parent 75d8cfe commit a3e76a8
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 8 deletions.
32 changes: 24 additions & 8 deletions pkg/trustedresources/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ import (
const (
// SignatureAnnotation is the key of signature in annotation map
SignatureAnnotation = "tekton.dev/signature"
// KeyReference is the prefix of secret reference
KeyReference = "k8s://"
// keyReference is the prefix of secret reference
keyReference = "k8s://"
)

// VerifyInterface get the checksum of json marshalled object and verify it.
Expand Down Expand Up @@ -165,7 +165,7 @@ func getVerifiers(ctx context.Context, k8s kubernetes.Interface) ([]signature.Ve
for key := range cfg.TrustedResources.Keys {
v, err := verifierForKeyRef(ctx, key, crypto.SHA256, k8s)
if err == nil {
verifiers = append(verifiers, v)
verifiers = append(verifiers, v...)
}
}
if len(verifiers) == 0 {
Expand All @@ -178,15 +178,29 @@ func getVerifiers(ctx context.Context, k8s kubernetes.Interface) ([]signature.Ve
// verifierForKeyRef parses the given keyRef, loads the key and returns an appropriate
// verifier using the provided hash algorithm
// TODO(#5527): consider wrap verifiers to resolver so the same verifiers are used for the same reconcile event
func verifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash, k8s kubernetes.Interface) (verifier signature.Verifier, err error) {
func verifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash, k8s kubernetes.Interface) (verifiers []signature.Verifier, err error) {
var raw []byte
if strings.HasPrefix(keyRef, KeyReference) {
verifiers = []signature.Verifier{}
// if the ref is secret then we fetch the keys from the secrets
if strings.HasPrefix(keyRef, keyReference) {
s, err := getKeyPairSecret(ctx, keyRef, k8s)
if err != nil {
return nil, err
}
raw = s.Data["cosign.pub"]
for _, raw := range s.Data {
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(raw)
if err != nil {
return nil, fmt.Errorf("pem to public key: %w", err)
}
v, _ := signature.LoadVerifier(pubKey, hashAlgorithm)
verifiers = append(verifiers, v)
}
if len(verifiers) == 0 {
return verifiers, fmt.Errorf("no public keys are founded for verification")
}
return verifiers, nil
} else {
// read the key from mounted file
raw, err = os.ReadFile(filepath.Clean(keyRef))
if err != nil {
return nil, err
Expand All @@ -198,8 +212,10 @@ func verifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.
if err != nil {
return nil, fmt.Errorf("pem to public key: %w", err)
}
v, _ := signature.LoadVerifier(pubKey, hashAlgorithm)
verifiers = append(verifiers, v)

return signature.LoadVerifier(pubKey, hashAlgorithm)
return verifiers, nil
}

func getKeyPairSecret(ctx context.Context, k8sRef string, k8s kubernetes.Interface) (*v1.Secret, error) {
Expand All @@ -218,7 +234,7 @@ func getKeyPairSecret(ctx context.Context, k8sRef string, k8s kubernetes.Interfa

// the reference should be formatted as <namespace>/<secret name>
func parseRef(k8sRef string) (string, string, error) {
s := strings.Split(strings.TrimPrefix(k8sRef, KeyReference), "/")
s := strings.Split(strings.TrimPrefix(k8sRef, keyReference), "/")
if len(s) != 2 {
return "", "", errors.New("kubernetes specification should be in the format k8s://<namespace>/<secret>")
}
Expand Down
67 changes: 67 additions & 0 deletions pkg/trustedresources/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"
Expand All @@ -29,7 +31,9 @@ import (
test "github.com/tektoncd/pipeline/test"
"github.com/tektoncd/pipeline/test/diff"
"go.uber.org/zap/zaptest"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fakek8s "k8s.io/client-go/kubernetes/fake"
"knative.dev/pkg/logging"
)

Expand Down Expand Up @@ -200,6 +204,69 @@ func TestVerifyPipeline(t *testing.T) {

}

func TestVerifyTask_SecretRef(t *testing.T) {
ctx := logging.WithLogger(context.Background(), zaptest.NewLogger(t).Sugar())

signer, keypath, err := test.GetSignerFromFile(ctx, t)
if err != nil {
t.Fatal(err)
}
fileBytes, err := ioutil.ReadFile(filepath.Clean(keypath))
if err != nil {
t.Fatal(err)
}

secret := &v1.Secret{
Data: map[string][]byte{"cosign.pub": fileBytes},
ObjectMeta: metav1.ObjectMeta{
Name: "verification-secrets",
Namespace: "default"}}
kubeclient := fakek8s.NewSimpleClientset(secret)

secretref := fmt.Sprintf("%sdefault/verification-secrets", keyReference)

ctx = test.SetupTrustedResourceConfig(ctx, secretref, config.EnforceResourceVerificationMode)

unsignedTask := test.GetUnsignedTask("test-task")

signedTask, err := test.GetSignedTask(unsignedTask, signer, "signed")
if err != nil {
t.Fatal("fail to sign task", err)
}

tamperedTask := signedTask.DeepCopy()
tamperedTask.Annotations["random"] = "attack"

tcs := []struct {
name string
task v1beta1.TaskObject
wantErr bool
}{{
name: "Signed Task Passes Verification",
task: signedTask,
wantErr: false,
}, {
name: "Tampered Task Fails Verification with tampered content",
task: tamperedTask,
wantErr: true,
}, {
name: "Unsigned Task Fails Verification without signature",
task: unsignedTask,
wantErr: true,
},
}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
err := VerifyTask(ctx, tc.task, kubeclient)
if (err != nil) != tc.wantErr {
t.Fatalf("verifyTaskRun() get err %v, wantErr %t", err, tc.wantErr)
}
})
}

}

func TestPrepareObjectMeta(t *testing.T) {
unsigned := test.GetUnsignedTask("test-task").ObjectMeta

Expand Down
17 changes: 17 additions & 0 deletions test/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,23 @@ func WaitForServiceExternalIPState(ctx context.Context, c *clients, namespace, n
})
}

// WaitForSecretState polls the status of the Secret called name from client every
// interval until inState returns `true` indicating it is done, returns an
// error or timeout. desc will be used to name the metric that is emitted to
// track how long it took for name to get into the state checked by inState.
func WaitForSecretState(ctx context.Context, c *clients, name string, inState ConditionAccessorFn, desc string) error {
metricName := fmt.Sprintf("WaitForTaskRunState/%s/%s", name, desc)
_, span := trace.StartSpan(context.Background(), metricName)
defer span.End()

return pollImmediateWithContext(ctx, func() (bool, error) {
r, err := c.TaskRunClient.Get(ctx, name, metav1.GetOptions{})
if err != nil {
return true, err
}
return inState(&r.Status)
})
}
// Succeed provides a poll condition function that checks if the ConditionAccessor
// resource has successfully completed or not.
func Succeed(name string) ConditionAccessorFn {
Expand Down

0 comments on commit a3e76a8

Please sign in to comment.