Skip to content

Commit

Permalink
use verification mode in trusted resources
Browse files Browse the repository at this point in the history
This commit uses verification policy's mode field in trusted resources.
The mode can be set to "warn" or "enforce". The policy will use "enforce" mode if the `mode` is not set.
If the mode is set `warn`, then fails to verify this policy will only
log a warning and not fail the taskruns or pipelineruns. "enforce" mode
policy will fail the taskruns or pipelineruns if fails to verify.

Signed-off-by: Yongxuan Zhang yongxuanzhang@google.com
  • Loading branch information
Yongxuanzhang committed Mar 21, 2023
1 parent d3f10fd commit 0d1ecca
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 39 deletions.
7 changes: 6 additions & 1 deletion docs/trusted-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ How does VerificationPolicy work?
You can create multiple `VerificationPolicy` and apply them to the cluster.
1. Trusted resources will look up policies from the resource namespace (usually this is the same as taskrun/pipelinerun namespace).
2. If multiple policies are found. For each policy we will check if the resource url is matching any of the `patterns` in the `resources` list. If matched then this policy will be used for verification.
3. If multiple policies are matched, the resource needs to pass all of them to pass verification.
3. If multiple policies are matched, the resource needs to pass all of the "enforce" mode policies to pass verification.
4. To pass one policy, the resource can pass any public keys in the policy.

Take the following `VerificationPolicies` for example, a resource from "https://github.com/tektoncd/catalog.git", needs to pass both `verification-policy-a` and `verification-policy-b`, to pass `verification-policy-a` the resource needs to pass either `key1` or `key2`.
Expand Down Expand Up @@ -107,6 +107,8 @@ spec:
key:
# data stores the inline public key data
data: "STRING_ENCODED_PUBLIC_KEY"
# mode can be set to "enforce" or "warn".
mode: enforce
```

```yaml
Expand Down Expand Up @@ -139,6 +141,9 @@ To learn more about `ConfigSource` please refer to resolvers doc for more contex

`hashAlgorithm` is the algorithm for the public key, by default is `sha256`. It also supports `SHA224`, `SHA384`, `SHA512`.

`mode` controls whether a failing policy will fail the taskrun/pipelinerun, or only log the a warning
* enforce - fail the taskrun/pipelinerun if verification fails (default)
* warn - don't fail the taskrun/pipelinerun if verification fails but log a warning

#### Migrate Config key at configmap to VerificationPolicy
**Note:** key configuration in configmap is deprecated,
Expand Down
15 changes: 11 additions & 4 deletions pkg/trustedresources/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/tektoncd/pipeline/pkg/trustedresources/verifier"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"knative.dev/pkg/logging"
)

const (
Expand Down Expand Up @@ -108,7 +109,7 @@ func matchedPolicies(resourceName string, source string, policies []*v1alpha1.Ve

// verifyResource verifies resource which implements metav1.Object by provided signature and public keys from verification policies.
// For matched policies, `verifyResource“ will adopt the following rules to do verification:
// 1. If multiple policies are matched, the resource needs to pass all of them to pass verification. We use AND logic on matched policies.
// 1. If multiple policies are matched, the resource needs to pass all of "enforced" mode policies to pass verification. We use AND logic on matched policies.
// 2. To pass one policy, the resource can pass any public keys in the policy. We use OR logic on public keys of one policy.
func verifyResource(ctx context.Context, resource metav1.Object, k8s kubernetes.Interface, signature []byte, source string, matchedPolicies []*v1alpha1.VerificationPolicy) error {
for _, p := range matchedPolicies {
Expand All @@ -124,9 +125,15 @@ func verifyResource(ctx context.Context, resource metav1.Object, k8s kubernetes.
break
}
}
// if this policy fails the verification, should return error directly. No need to check other policies
// if this policy fails the verification and the mode is not warn, should return error directly. No need to check other policies
if !passVerification {
return fmt.Errorf("%w: resource %s in namespace %s fails verification", ErrResourceVerificationFailed, resource.GetName(), resource.GetNamespace())
if p.Spec.Mode == v1alpha1.ModeWarn {
logger := logging.FromContext(ctx)
logger.Warnf("%w: resource %s in namespace %s fails verification", ErrResourceVerificationFailed, resource.GetName(), resource.GetNamespace())
} else {
// if the mode is "Enforce" or not set, return error.
return fmt.Errorf("%w: resource %s in namespace %s fails verification", ErrResourceVerificationFailed, resource.GetName(), resource.GetNamespace())
}
}
}
return nil
Expand Down Expand Up @@ -180,7 +187,7 @@ func prepareObjectMeta(in metav1.ObjectMeta) (metav1.ObjectMeta, []byte, error)
// signature should be contained in annotation
sig, ok := in.Annotations[SignatureAnnotation]
if !ok {
return out, nil, ErrSignatureMissing
return out, []byte{}, nil
}
// extract signature
signature, err := base64.StdEncoding.DecodeString(sig)
Expand Down
100 changes: 66 additions & 34 deletions pkg/trustedresources/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ func TestVerifyTask_VerificationPolicy_Success(t *testing.T) {
t.Fatal("fail to sign task", err)
}

modifiedTask := signedTask.DeepCopy()
modifiedTask.Name = "modified"

signer384, _, pub, err := test.GenerateKeys(elliptic.P384(), crypto.SHA384)
if err != nil {
t.Fatalf("failed to generate keys %v", err)
Expand Down Expand Up @@ -165,35 +168,74 @@ func TestVerifyTask_VerificationPolicy_Success(t *testing.T) {
},
},
}
vps = append(vps, sha384Vp)

warnPolicy := &v1alpha1.VerificationPolicy{
TypeMeta: metav1.TypeMeta{
Kind: "VerificationPolicy",
APIVersion: "v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "warnPolicy",
Namespace: namespace,
},
Spec: v1alpha1.VerificationPolicySpec{
Resources: []v1alpha1.ResourcePattern{
{Pattern: "https://github.com/tektoncd/catalog.git"},
},
Authorities: []v1alpha1.Authority{
{
Name: "key",
Key: &v1alpha1.KeyRef{
Data: string(pub),
HashAlgorithm: "sha384",
},
},
},
Mode: v1alpha1.ModeWarn,
},
}

signedTask384, err := test.GetSignedTask(unsignedTask, signer384, "signed384")
if err != nil {
t.Fatal("fail to sign task", err)
}

tcs := []struct {
name string
task v1beta1.TaskObject
source string
signer signature.SignerVerifier
name string
task v1beta1.TaskObject
source string
signer signature.SignerVerifier
verificationPolicies []*v1alpha1.VerificationPolicy
}{{
name: "signed git source task passes verification",
task: signedTask,
source: "git+https://github.com/tektoncd/catalog.git",
name: "signed git source task passes verification",
task: signedTask,
source: "git+https://github.com/tektoncd/catalog.git",
verificationPolicies: vps,
}, {
name: "signed bundle source task passes verification",
task: signedTask,
source: "gcr.io/tekton-releases/catalog/upstream/git-clone",
name: "signed bundle source task passes verification",
task: signedTask,
source: "gcr.io/tekton-releases/catalog/upstream/git-clone",
verificationPolicies: vps,
}, {
name: "signed task with sha384 key",
task: signedTask384,
source: "gcr.io/tekton-releases/catalog/upstream/sha384",
verificationPolicies: []*v1alpha1.VerificationPolicy{sha384Vp},
}, {
name: "unsigned task matches warn policy doesn't fail verification",
task: unsignedTask,
source: "git+https://github.com/tektoncd/catalog.git",
verificationPolicies: []*v1alpha1.VerificationPolicy{warnPolicy},
}, {
name: "signed task with sha384 key",
task: signedTask384,
source: "gcr.io/tekton-releases/catalog/upstream/sha384",
name: "modified task matches warn policy doesn't fail verification",
task: modifiedTask,
source: "git+https://github.com/tektoncd/catalog.git",
verificationPolicies: []*v1alpha1.VerificationPolicy{warnPolicy},
}}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
err := VerifyTask(ctx, tc.task, k8sclient, tc.source, vps)
err := VerifyTask(ctx, tc.task, k8sclient, tc.source, tc.verificationPolicies)
if err != nil {
t.Fatalf("VerifyTask() get err %v", err)
}
Expand Down Expand Up @@ -223,6 +265,12 @@ func TestVerifyTask_VerificationPolicy_Error(t *testing.T) {
verificationPolicy []*v1alpha1.VerificationPolicy
expectedError error
}{{
name: "unsigned Task fails verification",
task: unsignedTask,
source: "git+https://github.com/tektoncd/catalog.git",
verificationPolicy: vps,
expectedError: ErrResourceVerificationFailed,
}, {
name: "modified Task fails verification",
task: tamperedTask,
source: "git+https://github.com/tektoncd/catalog.git",
Expand Down Expand Up @@ -382,7 +430,6 @@ func TestPrepareObjectMeta(t *testing.T) {
name string
objectmeta *metav1.ObjectMeta
expected metav1.ObjectMeta
wantErr bool
}{{
name: "Prepare signed objectmeta without labels",
objectmeta: signed,
Expand All @@ -391,7 +438,6 @@ func TestPrepareObjectMeta(t *testing.T) {
Namespace: namespace,
Annotations: map[string]string{},
},
wantErr: false,
}, {
name: "Prepare signed objectmeta with labels",
objectmeta: signedWithLabels,
Expand All @@ -401,7 +447,6 @@ func TestPrepareObjectMeta(t *testing.T) {
Labels: map[string]string{"label": "foo"},
Annotations: map[string]string{},
},
wantErr: false,
}, {
name: "Prepare signed objectmeta with extra annotations",
objectmeta: signedWithExtraAnnotations,
Expand All @@ -410,33 +455,20 @@ func TestPrepareObjectMeta(t *testing.T) {
Namespace: namespace,
Annotations: map[string]string{},
},
wantErr: false,
}, {
name: "Fail preparation without signature",
objectmeta: &unsigned,
expected: metav1.ObjectMeta{
Name: "test-task",
Namespace: namespace,
Annotations: map[string]string{"foo": "bar"},
},
wantErr: true,
}}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
task, signature, err := prepareObjectMeta(*tc.objectmeta)
if (err != nil) != tc.wantErr {
t.Fatalf("prepareObjectMeta() get err %v, wantErr %t", err, tc.wantErr)
if err != nil {
t.Errorf("got unexpected err: %v", err)
}
if d := cmp.Diff(task, tc.expected); d != "" {
t.Error(diff.PrintWantGot(d))
}

if tc.wantErr {
return
}
if signature == nil {
t.Fatal("signature is not extracted")
t.Error("signature is not extracted")
}
})
}
Expand Down

0 comments on commit 0d1ecca

Please sign in to comment.