Skip to content

Commit

Permalink
E2E tests. Plumb rekor through for key-ful signing too.
Browse files Browse the repository at this point in the history
Signed-off-by: Ville Aikas <vaikas@chainguard.dev>
  • Loading branch information
vaikas committed Apr 18, 2022
1 parent d9604cf commit 04c1d8b
Show file tree
Hide file tree
Showing 7 changed files with 353 additions and 90 deletions.
10 changes: 6 additions & 4 deletions pkg/cosign/kubernetes/webhook/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (
"github.com/sigstore/sigstore/pkg/signature"
)

func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, opts ...ociremote.Option) ([]oci.Signature, error) {
func valid(ctx context.Context, ref name.Reference, rekorClient *client.Rekor, keys []*crypto.PublicKey, opts ...ociremote.Option) ([]oci.Signature, error) {
if len(keys) == 0 {
// If there are no keys, then verify against the fulcio root.
sps, err := validSignaturesWithFulcio(ctx, ref, fulcioroots.Get(), nil /* rekor */, nil /* no identities */, opts...)
Expand All @@ -58,7 +58,7 @@ func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, opt
continue
}

sps, err := validSignatures(ctx, ref, verifier, opts...)
sps, err := validSignatures(ctx, ref, verifier, rekorClient, opts...)
if err != nil {
logging.FromContext(ctx).Errorf("error validating signatures: %v", err)
lastErr = err
Expand All @@ -76,10 +76,11 @@ func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, opt
var cosignVerifySignatures = cosign.VerifyImageSignatures
var cosignVerifyAttestations = cosign.VerifyImageAttestations

func validSignatures(ctx context.Context, ref name.Reference, verifier signature.Verifier, opts ...ociremote.Option) ([]oci.Signature, error) {
func validSignatures(ctx context.Context, ref name.Reference, verifier signature.Verifier, rekorClient *client.Rekor, opts ...ociremote.Option) ([]oci.Signature, error) {
sigs, _, err := cosignVerifySignatures(ctx, ref, &cosign.CheckOpts{
RegistryClientOpts: opts,
SigVerifier: verifier,
RekorClient: rekorClient,
ClaimVerifier: cosign.SimpleClaimVerifier,
})
return sigs, err
Expand All @@ -98,10 +99,11 @@ func validSignaturesWithFulcio(ctx context.Context, ref name.Reference, fulcioRo
return sigs, err
}

func validAttestations(ctx context.Context, ref name.Reference, verifier signature.Verifier, opts ...ociremote.Option) ([]oci.Signature, error) {
func validAttestations(ctx context.Context, ref name.Reference, verifier signature.Verifier, rekorClient *client.Rekor, opts ...ociremote.Option) ([]oci.Signature, error) {
attestations, _, err := cosignVerifyAttestations(ctx, ref, &cosign.CheckOpts{
RegistryClientOpts: opts,
SigVerifier: verifier,
RekorClient: rekorClient,
ClaimVerifier: cosign.IntotoSubjectClaimVerifier,
})
return attestations, err
Expand Down
53 changes: 32 additions & 21 deletions pkg/cosign/kubernetes/webhook/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/x509"
"encoding/json"
"fmt"
"strings"

"cuelang.org/go/cue/cuecontext"
cuejson "cuelang.org/go/encoding/json"
Expand Down Expand Up @@ -214,7 +215,7 @@ func (v *Validator) validatePodSpec(ctx context.Context, ps *corev1.PodSpec, opt
continue
}

if _, err := valid(ctx, ref, containerKeys, ociremote.WithRemoteOptions(remote.WithAuthFromKeychain(kc))); err != nil {
if _, err := valid(ctx, ref, nil, containerKeys, ociremote.WithRemoteOptions(remote.WithAuthFromKeychain(kc))); err != nil {
errorField := apis.ErrGeneric(err.Error(), "image").ViaFieldIndex(field, i)
errorField.Details = c.Image
errs = errs.Also(errorField)
Expand Down Expand Up @@ -339,13 +340,25 @@ func ociSignatureToPolicySignature(ctx context.Context, sigs []oci.Signature) []
// verify a signature against it.
func ValidatePolicySignaturesForAuthority(ctx context.Context, ref name.Reference, kc authn.Keychain, authority webhookcip.Authority, remoteOpts ...ociremote.Option) ([]PolicySignature, error) {
name := authority.Name

var rekorClient *client.Rekor
var err error
if authority.CTLog != nil && authority.CTLog.URL != nil {
logging.FromContext(ctx).Debugf("Using CTLog %s for %s", authority.CTLog.URL, ref.Name())
rekorClient, err = rekor.GetRekorClient(authority.CTLog.URL.String())
if err != nil {
logging.FromContext(ctx).Errorf("failed creating rekor client: +v", err)
return nil, errors.Wrap(err, "creating Rekor client")
}
}

switch {
case authority.Key != nil && len(authority.Key.PublicKeys) > 0:
// TODO(vaikas): What should happen if there are multiple keys
// Is it even allowed? 'valid' returns success if any key
// matches.
// https://github.com/sigstore/cosign/issues/1652
sps, err := valid(ctx, ref, authority.Key.PublicKeys, remoteOpts...)
sps, err := valid(ctx, ref, rekorClient, authority.Key.PublicKeys, remoteOpts...)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to validate public keys with authority %s for %s", name, ref.Name()))
} else {
Expand All @@ -363,15 +376,6 @@ func ValidatePolicySignaturesForAuthority(ctx context.Context, ref name.Referenc
if err != nil {
return nil, errors.Wrap(err, "fetching FulcioRoot")
}
var rekorClient *client.Rekor
if authority.CTLog != nil && authority.CTLog.URL != nil {
logging.FromContext(ctx).Debugf("Using CTLog %s for %s", authority.CTLog.URL, ref.Name())
rekorClient, err = rekor.GetRekorClient(authority.CTLog.URL.String())
if err != nil {
logging.FromContext(ctx).Errorf("failed creating rekor client: +v", err)
return nil, errors.Wrap(err, "creating Rekor client")
}
}
sps, err := validSignaturesWithFulcio(ctx, ref, fulcioroot, rekorClient, authority.Keyless.Identities, remoteOpts...)
if err != nil {
logging.FromContext(ctx).Errorf("failed validSignatures for authority %s with fulcio for %s: %v", name, ref.Name(), err)
Expand All @@ -393,6 +397,17 @@ func ValidatePolicySignaturesForAuthority(ctx context.Context, ref name.Referenc
// verify attestations against it.
func ValidatePolicyAttestationsForAuthority(ctx context.Context, ref name.Reference, kc authn.Keychain, authority webhookcip.Authority, remoteOpts ...ociremote.Option) (map[string][]PolicySignature, error) {
name := authority.Name
var rekorClient *client.Rekor
var err error
if authority.CTLog != nil && authority.CTLog.URL != nil {
logging.FromContext(ctx).Debugf("Using CTLog %s for %s", authority.CTLog.URL, ref.Name())
rekorClient, err = rekor.GetRekorClient(authority.CTLog.URL.String())
if err != nil {
logging.FromContext(ctx).Errorf("failed creating rekor client: +v", err)
return nil, errors.Wrap(err, "creating Rekor client")
}
}

verifiedAttestations := []oci.Signature{}
switch {
case authority.Key != nil && len(authority.Key.PublicKeys) > 0:
Expand All @@ -402,7 +417,7 @@ func ValidatePolicyAttestationsForAuthority(ctx context.Context, ref name.Refere
logging.FromContext(ctx).Errorf("error creating verifier: %v", err)
return nil, errors.Wrap(err, "creating verifier")
}
va, err := validAttestations(ctx, ref, verifier, remoteOpts...)
va, err := validAttestations(ctx, ref, verifier, rekorClient, remoteOpts...)
if err != nil {
logging.FromContext(ctx).Errorf("error validating attestations: %v", err)
return nil, errors.Wrap(err, "validating attestations")
Expand All @@ -417,15 +432,6 @@ func ValidatePolicyAttestationsForAuthority(ctx context.Context, ref name.Refere
if err != nil {
return nil, errors.Wrap(err, "fetching FulcioRoot")
}
var rekorClient *client.Rekor
if authority.CTLog != nil && authority.CTLog.URL != nil {
logging.FromContext(ctx).Debugf("Using CTLog %s for %s", authority.CTLog.URL, ref.Name())
rekorClient, err = rekor.GetRekorClient(authority.CTLog.URL.String())
if err != nil {
logging.FromContext(ctx).Errorf("failed creating rekor client: +v", err)
return nil, errors.Wrap(err, "creating Rekor client")
}
}
va, err := validAttestationsWithFulcio(ctx, ref, fulcioroot, rekorClient, authority.Keyless.Identities, remoteOpts...)
if err != nil {
logging.FromContext(ctx).Errorf("failed validAttestationsWithFulcio for authority %s with fulcio for %s: %v", name, ref.Name(), err)
Expand Down Expand Up @@ -462,6 +468,10 @@ func ValidatePolicyAttestationsForAuthority(ctx context.Context, ref name.Refere
return nil, errors.Wrap(err, "Failed to convert attestation payload to json")
}
if err := EvaluatePolicyAgainstJSON(ctx, wantedAttestation.PredicateType, wantedAttestation.Type, wantedAttestation.Data, attBytes); err != nil {
if strings.Contains(err.Error(), "invalid JSON") {
logging.FromContext(ctx).Errorf("Invalid Json for predicatetype: %s : JSONBYTES: %s", wantedAttestation.PredicateType, string(attBytes))
continue
}
return nil, err
}
// Ok, so this passed aok, jot it down to our result set as
Expand All @@ -480,6 +490,7 @@ func ValidatePolicyAttestationsForAuthority(ctx context.Context, ref name.Refere
// policyBody - String representing either cue or rego language
// jsonBytes - Bytes to evaluate against the policyBody in the given language
func EvaluatePolicyAgainstJSON(ctx context.Context, predicateType, policyType string, policyBody string, jsonBytes []byte) error {
logging.FromContext(ctx).Infof("vaikas: Evaluating JSON:\n%s\n%s", string(jsonBytes), policyBody)
switch policyType {
case "cue":
cueValidationErr := evaluateCue(ctx, jsonBytes, policyBody)
Expand Down
14 changes: 7 additions & 7 deletions pkg/reconciler/clusterimagepolicy/clusterimagepolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ RCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==
-----END PUBLIC KEY-----`

// This is the patch for replacing a single entry in the ConfigMap
replaceCIPPatch = `[{"op":"replace","path":"/data/test-cip","value":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"key\":{\"data\":\"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\\n-----END PUBLIC KEY-----\"}}]}"}]`
replaceCIPPatch = `[{"op":"replace","path":"/data/test-cip","value":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"name\":\"authority-0\",\"key\":{\"data\":\"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\\n-----END PUBLIC KEY-----\"}}]}"}]`

// This is the patch for adding an entry for non-existing KMS for cipName2
addCIP2Patch = `[{"op":"add","path":"/data/test-cip-2","value":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"key\":{\"data\":\"azure-kms://foo/bar\"}}]}"}]`
addCIP2Patch = `[{"op":"add","path":"/data/test-cip-2","value":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"name\":\"authority-0\",\"key\":{\"data\":\"azure-kms://foo/bar\"}}]}"}]`

// This is the patch for removing the last entry, leaving just the
// configmap objectmeta, no data.
Expand All @@ -82,7 +82,7 @@ RCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==
removeSingleEntryKeylessPatch = `[{"op":"remove","path":"/data/test-cip-2"}]`

// This is the patch for inlined secret for keyless cakey ref data
inlinedSecretKeylessPatch = `[{"op":"replace","path":"/data/test-cip-2","value":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"keyless\":{\"ca-cert\":{\"data\":\"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\\n-----END PUBLIC KEY-----\"}}}]}"}]`
inlinedSecretKeylessPatch = `[{"op":"replace","path":"/data/test-cip-2","value":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"name\":\"authority-0\",\"keyless\":{\"ca-cert\":{\"data\":\"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\\n-----END PUBLIC KEY-----\"}}}]}"}]`
)

func TestReconcile(t *testing.T) {
Expand Down Expand Up @@ -576,7 +576,7 @@ func makeConfigMap() *corev1.ConfigMap {
Name: config.ImagePoliciesConfigName,
},
Data: map[string]string{
cipName: `{"images":[{"glob":"ghcr.io/example/*"}],"authorities":[{"key":{"data":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\n-----END PUBLIC KEY-----"}}]}`,
cipName: `{"images":[{"glob":"ghcr.io/example/*"}],"authorities":[{"name":"authority-0","key":{"data":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\n-----END PUBLIC KEY-----"}}]}`,
},
}
}
Expand All @@ -587,7 +587,7 @@ func patchKMS(ctx context.Context, t *testing.T, kmsKey string) clientgotesting.
t.Fatalf("Failed to read KMS key ID %q: %v", kmsKey, err)
}

patch := `[{"op":"add","path":"/data","value":{"test-kms-cip":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"key\":{\"data\":\"` + strings.ReplaceAll(pubKey, "\n", "\\\\n") + `\"}}]}"}}]`
patch := `[{"op":"add","path":"/data","value":{"test-kms-cip":"{\"images\":[{\"glob\":\"ghcr.io/example/*\"}],\"authorities\":[{\"name\":\"authority-0\",\"key\":{\"data\":\"` + strings.ReplaceAll(pubKey, "\n", "\\\\n") + `\"}}]}"}}]`

return clientgotesting.PatchActionImpl{
ActionImpl: clientgotesting.ActionImpl{
Expand All @@ -606,7 +606,7 @@ func makeDifferentConfigMap() *corev1.ConfigMap {
Name: config.ImagePoliciesConfigName,
},
Data: map[string]string{
cipName: `{"images":[{"glob":"ghcr.io/example/*"}],"authorities":[{"key":{"data":"-----BEGIN NOTPUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\n-----END NOTPUBLIC KEY-----"}}]}`,
cipName: `{"images":[{"glob":"ghcr.io/example/*"}],"authorities":[{"name":"authority-0","key":{"data":"-----BEGIN NOTPUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\n-----END NOTPUBLIC KEY-----"}}]}`,
},
}
}
Expand All @@ -619,7 +619,7 @@ func makeConfigMapWithTwoEntries() *corev1.ConfigMap {
Name: config.ImagePoliciesConfigName,
},
Data: map[string]string{
cipName: `{"images":[{"glob":"ghcr.io/example/*"}],"authorities":[{"key":{"data":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\n-----END PUBLIC KEY-----"}}]}`,
cipName: `{"images":[{"glob":"ghcr.io/example/*"}],"authorities":[{"name":"authority-0","key":{"data":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\n-----END PUBLIC KEY-----"}}]}`,
cipName2: "remove me please",
},
}
Expand Down

0 comments on commit 04c1d8b

Please sign in to comment.