Skip to content

Commit

Permalink
cosigned: Unify cue data and policy before evaluating it (#1793)
Browse files Browse the repository at this point in the history
* chore: update cue policy evaluation

Signed-off-by: hectorj2f <hectorf@vmware.com>

* chore: change cue policy for the cip

Signed-off-by: hectorj2f <hectorf@vmware.com>

* chore: avoid using names with hyphens

Signed-off-by: hectorj2f <hectorf@vmware.com>

* test: add unit tests for the eval policy func

Signed-off-by: hectorj2f <hectorf@vmware.com>

* test: delete job before creating it

Signed-off-by: hectorj2f <hectorf@vmware.com>

* test: add statement to check the length of a struct

Signed-off-by: hectorj2f <hectorf@vmware.com>

* test: add more unit tests for eval policy

Signed-off-by: hectorj2f <hectorf@vmware.com>

* fix: wrong redirected file directory

Signed-off-by: hectorj2f <hectorf@vmware.com>
  • Loading branch information
Hector Fernandez committed Apr 26, 2022
1 parent cba2cfb commit db323cd
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 63 deletions.
4 changes: 2 additions & 2 deletions pkg/cosign/kubernetes/webhook/validator_test.go
Expand Up @@ -381,10 +381,10 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw==
want: func() *apis.FieldError {
var errs *apis.FieldError
fe := apis.ErrGeneric("failed policy: cluster-image-policy-keyless", "image").ViaFieldIndex("initContainers", 0)
fe.Details = fmt.Sprintf("%s failed evaluating cue policy for ClusterImagePolicy : string literal not terminated", digest.String())
fe.Details = fmt.Sprintf("%s failed evaluating cue policy for ClusterImagePolicy : failed to compile the cue policy with error: string literal not terminated", digest.String())
errs = errs.Also(fe)
fe2 := apis.ErrGeneric("failed policy: cluster-image-policy-keyless", "image").ViaFieldIndex("containers", 0)
fe2.Details = fmt.Sprintf("%s failed evaluating cue policy for ClusterImagePolicy : string literal not terminated", digest.String())
fe2.Details = fmt.Sprintf("%s failed evaluating cue policy for ClusterImagePolicy : failed to compile the cue policy with error: string literal not terminated", digest.String())
errs = errs.Also(fe2)
return errs
}(),
Expand Down
18 changes: 15 additions & 3 deletions pkg/policy/eval.go
Expand Up @@ -20,7 +20,6 @@ import (
"fmt"

"cuelang.org/go/cue/cuecontext"
cuejson "cuelang.org/go/encoding/json"

"knative.dev/pkg/logging"
)
Expand Down Expand Up @@ -54,9 +53,22 @@ func EvaluatePolicyAgainstJSON(ctx context.Context, name, policyType string, pol
// evaluateCue evaluates a cue policy `evaluator` against `attestation`
func evaluateCue(ctx context.Context, attestation []byte, evaluator string) error {
logging.FromContext(ctx).Infof("Evaluating attestation: %s", string(attestation))
logging.FromContext(ctx).Infof("Evaluator: %s", evaluator)

cueCtx := cuecontext.New()
v := cueCtx.CompileString(evaluator)
return cuejson.Validate(attestation, v)
cueEvaluator := cueCtx.CompileString(evaluator)
if cueEvaluator.Err() != nil {
return fmt.Errorf("failed to compile the cue policy with error: %w", cueEvaluator.Err())
}
cueAtt := cueCtx.CompileBytes(attestation)
if cueAtt.Err() != nil {
return fmt.Errorf("failed to compile the attestation data with error: %w", cueAtt.Err())
}
result := cueEvaluator.Unify(cueAtt)
if err := result.Validate(); err != nil {
return fmt.Errorf("failed to evaluate the policy with error: %w", err)
}
return nil
}

// evaluateRego evaluates a rego policy `evaluator` against `attestation`
Expand Down
48 changes: 46 additions & 2 deletions pkg/policy/eval_test.go
Expand Up @@ -75,8 +75,7 @@ const (
}
}`

// TODO(vaikas): Enable tests once we sort this out.
// cipAttestation = "authorityMatches:{\"key-att\":{\"signatures\":null,\"attestations\":{\"custom-match-predicate\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}},\"key-signature\":{\"signatures\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}],\"attestations\":null},\"keyless-att\":{\"signatures\":null,\"attestations\":{\"custom-keyless\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}},\"keyless-signature\":{\"signatures\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}],\"attestations\":null}}"
cipAttestation = "{\"authorityMatches\":{\"keyatt\":{\"signatures\":null,\"attestations\":{\"vuln-key\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}},\"keysignature\":{\"signatures\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}],\"attestations\":null},\"keylessatt\":{\"signatures\":null,\"attestations\":{\"custom-keyless\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}}}}"
)

func TestEvalPolicy(t *testing.T) {
Expand Down Expand Up @@ -124,6 +123,51 @@ func TestEvalPolicy(t *testing.T) {
policyType: "cue",
policyFile: `predicateType: "cosign.sigstore.dev/attestation/vuln/v1"
predicate: invocation: uri: "invocation.example.com/cosign-testing"`,
}, {
name: "cluster image policy main policy, checks out",
json: cipAttestation,
policyType: "cue",
policyFile: `package sigstore
import "struct"
import "list"
authorityMatches: {
keyatt: {
attestations: struct.MaxFields(1) & struct.MinFields(1)
},
keysignature: {
signatures: list.MaxItems(1) & list.MinItems(1)
},
keylessatt: {
attestations: struct.MaxFields(1) & struct.MinFields(1)
},
keylesssignature: {
signatures: list.MaxItems(1) & list.MinItems(1)
}
}`,
}, {
name: "cluster image policy main policy, fails",
json: cipAttestation,
policyType: "cue",
wantErr: true,
wantErrSub: `failed evaluating cue policy for cluster image policy main policy, fails : failed to evaluate the policy with error: authorityMatches.keylessattMinAttestations: conflicting values 2 and "Error" (mismatched types int and string)`,
policyFile: `package sigstore
import "struct"
import "list"
authorityMatches: {
keyatt: {
attestations: struct.MaxFields(1) & struct.MinFields(1)
},
keysignature: {
signatures: list.MaxItems(1) & list.MinItems(1)
},
if( len(authorityMatches.keylessatt.attestations) < 2) {
keylessattMinAttestations: 2
keylessattMinAttestations: "Error"
},
keylesssignature: {
signatures: list.MaxItems(1) & list.MinItems(1)
}
}`,
}}
for _, tc := range tests {
ctx := context.Background()
Expand Down
14 changes: 6 additions & 8 deletions test/e2e_test_cluster_image_policy_with_attestations.sh
Expand Up @@ -62,6 +62,7 @@ assert_error() {
local KUBECTL_OUT_FILE="/tmp/kubectl.failure.out"
match="$@"
echo looking for ${match}
kubectl delete job demo -n ${NS} --ignore-not-found=true
if kubectl create -n ${NS} job demo --image=${demoimage} 2> ${KUBECTL_OUT_FILE} ; then
echo Failed to block unsigned Job creation!
exit 1
Expand Down Expand Up @@ -206,17 +207,14 @@ echo '::endgroup::'
# Note we have to bake in the inline data from the keys above
echo '::group:: Add cip for two signatures and two attestations'
yq '. | .spec.authorities[1].key.data |= load_str("cosign.pub") | .spec.authorities[3].key.data |= load_str("cosign.pub")' ./test/testdata/cosigned/e2e/cip-requires-two-signatures-and-two-attestations.yaml | kubectl apply -f -
# allow things to propagate
sleep 5
echo '::endgroup::'

# TODO(vaikas): Enable the remaining tests once we sort out how to write
# a valid CUE policy, or once #1787 goes in try implementing a Rego one.
echo 'Not testing the CIP policy evaluation yet'
exit 0

# The CIP policy is the one that should fail now because it doesn't have enough
# attestations
echo '::group:: test job rejection'
expected_error='no matching attestations'
expected_error='failed to evaluate the policy with error: authorityMatches.keylessattMinAttestations'
assert_error ${expected_error}
echo '::endgroup::'

Expand All @@ -229,9 +227,9 @@ echo '::endgroup::'
echo '::group:: test job success'
# We signed this with key and keyless and it has two keyless attestations and
# it has one key attestation, so it should succeed.
if ! kubectl create -n ${NS} job demo3 --image=${demoimage} 2> ./${KUBECTL_OUT_FILE} ; then
if ! kubectl create -n ${NS} job demo3 --image=${demoimage} 2> ${KUBECTL_SUCCESS_FILE} ; then
echo Failed to create job that has two signatures and 3 attestations
cat ${KUBECTL_OUT_FILE}
cat ${KUBECTL_SUCCESS_FILE}
exit 1
fi
echo '::endgroup::'
Expand Down
Expand Up @@ -20,14 +20,14 @@ spec:
images:
- glob: registry.local:5000/cosigned/demo*
authorities:
- name: keyless-att
- name: keylessatt
keyless:
url: http://fulcio.fulcio-system.svc
ctlog:
url: http://rekor.rekor-system.svc
attestations:
- predicateType: custom
name: custom-keyless
name: customkeyless
policy:
type: cue
data: |
Expand All @@ -39,7 +39,7 @@ spec:
Timestamp: <before
}
- predicateType: vuln
name: vuln-keyless
name: vulnkeyless
policy:
type: cue
data: |
Expand All @@ -61,7 +61,7 @@ spec:
scanFinishedOn: >after
}
}
- name: key-att
- name: keyatt
key:
data: |
-----BEGIN PUBLIC KEY-----
Expand All @@ -78,12 +78,12 @@ spec:
data: |
predicateType: "cosign.sigstore.dev/attestation/v1"
predicate: Data: "foobar key e2e test"
- name: keyless-signature
- name: keylesssignature
keyless:
url: http://fulcio.fulcio-system.svc
ctlog:
url: http://rekor.rekor-system.svc
- name: key-signature
- name: keysignature
key:
data: |
-----BEGIN PUBLIC KEY-----
Expand All @@ -95,48 +95,21 @@ spec:
policy:
type: cue
data: |
if len(authorityMatches."keyless-att".attestations) < 2 {
keylessAttestationsErr: "error"
keylessAttestationsErr: "Did not get both keyless attestations"
}
if len(authorityMatches."key-att".attestations) < 1 {
keyAttestationsErr: 1
keyAttestationsErr: "Did not get key attestation"
}
if len(authorityMatches."keyless-signature".signatures) < 1 {
keylessSignatureErr: 1
keylessSignatureErr: "Did not get keyless signature"
}
if len(authorityMatches."key-signature".signatures) < 1 {
keySignatureErr: 1
keySignatureErr: "Did not get key signature"
}
package sigstore
import "struct"
import "list"
authorityMatches: {
key-att: {
attestations: {
"vuln-key": [
{subject: "PLACEHOLDER", issuer: "PLACEHOLDER"},
]
}
}
keyless-att: {
attestations: {
"vuln-keyless": [
{subject: "PLACEHOLDER", issuer: "PLACEHOLDER"},
],
"custom-keyless": [
{subject: "PLACEHOLDER", issuer: "PLACEHOLDER"},
],
}
}
keyless-signature: {
signatures: [
{subject: "PLACEHOLDER", issuer: "PLACEHOLDER"},
]
}
key-signature: {
signatures: [
{subject: "PLACEHOLDER", issuer: "PLACEHOLDER"},
]
keyatt: {
attestations: struct.MaxFields(1) & struct.MinFields(1)
},
keysignature: {
signatures: list.MaxItems(1) & list.MinItems(1)
},
if (len(authorityMatches.keylessatt.attestations) < 2) {
keylessattMinAttestations: 2
keylessattMinAttestations: "Error"
},
keylesssignature: {
signatures: list.MaxItems(1) & list.MinItems(1)
}
}

0 comments on commit db323cd

Please sign in to comment.