Skip to content

Commit

Permalink
Cosigned validate against remote sig src
Browse files Browse the repository at this point in the history
Signed-off-by: Denny Hoang <dhoang@vmware.com>

Add github workflow steps for remote signatures

Signed-off-by: Denny Hoang <dhoang@vmware.com>

Move source addition one level up in workflow

Rename job create in workflow

Fix lint error

Signed-off-by: Denny Hoang <dhoang@vmware.com>

Update github workflow cosigned key file name

Signed-off-by: Denny Hoang <dhoang@vmware.com>

fix indentation

Signed-off-by: Denny Hoang <dhoang@vmware.com>
  • Loading branch information
DennyHoang committed Apr 14, 2022
1 parent 0c232da commit c443e6b
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 11 deletions.
67 changes: 63 additions & 4 deletions .github/workflows/kind-cluster-image-policy.yaml
Expand Up @@ -197,13 +197,15 @@ jobs:
kubectl delete cip image-policy-keyless-with-identities-mismatch
sleep 5
- name: Generate New Signing Key
- name: Generate New Signing Key For Colocated Signature
run: |
COSIGN_PASSWORD="" ./cosign generate-key-pair
mv cosign.key cosign-colocated-signing.key
mv cosign.pub cosign-colocated-signing.pub
- name: Deploy ClusterImagePolicy With Key Signing
run: |
yq '. | .spec.authorities[0].key.data |= load_str("cosign.pub")' ./test/testdata/cosigned/e2e/cip-key.yaml | \
yq '. | .spec.authorities[0].key.data |= load_str("cosign-colocated-signing.pub")' ./test/testdata/cosigned/e2e/cip-key.yaml | \
kubectl apply -f -
- name: Verify with two CIP, one not signed with public key
Expand All @@ -215,11 +217,11 @@ jobs:
- name: Sign demoimage with cosign key
run: |
./cosign sign --key cosign.key --force --allow-insecure-registry ${{ env.demoimage }}
./cosign sign --key cosign-colocated-signing.key --force --allow-insecure-registry ${{ env.demoimage }}
- name: Verify with cosign
run: |
./cosign verify --key cosign.pub --allow-insecure-registry ${{ env.demoimage }}
./cosign verify --key cosign-colocated-signing.pub --allow-insecure-registry ${{ env.demoimage }}
- name: Deploy jobs and verify signed works, unsigned fails
run: |
Expand All @@ -246,6 +248,63 @@ jobs:
fi
echo '::endgroup::'
- name: Generate New Signing Key For Remote Signature
run: |
COSIGN_PASSWORD="" ./cosign generate-key-pair
mv cosign.key cosign-remote-signing.key
mv cosign.pub cosign-remote-signing.pub
- name: Deploy ClusterImagePolicy With Remote Public Key But Missing Source
run: |
yq '. | .metadata.name = "image-policy-remote-source"
| .spec.authorities[0].key.data |= load_str("cosign-remote-signing.pub")' ./test/testdata/cosigned/e2e/cip-key.yaml | \
kubectl apply -f -
- name: Sign demoimage with cosign key
run: |
COSIGN_REPOSITORY="${{ env.KO_DOCKER_REPO }}/remote-signature" ./cosign sign --key cosign-remote-signing.key --force --allow-insecure-registry ${{ env.demoimage }}
- name: Verify with cosign
run: |
if ./cosign verify --key cosign-remote-signing.pub --allow-insecure-registry ${{ env.demoimage }}; then
echo "Signature should not have been verified unless COSIGN_REPOSITORY was defined"
exit 1
fi
if ! COSIGN_REPOSITORY="${{ env.KO_DOCKER_REPO }}/remote-signature" ./cosign verify --key cosign-remote-signing.pub --allow-insecure-registry ${{ env.demoimage }}; then
echo "Signature should have been verified when COSIGN_REPOSITORY was defined"
exit 1
fi
- name: Verify with three CIP, one without correct Source set
run: |
kubectl create namespace demo-key-remote
kubectl label namespace demo-key-remote cosigned.sigstore.dev/include=true
if kubectl create -n demo-key-remote job demo --image=${{ env.demoimage }}; then
echo Failed to block unsigned Job creation!
exit 1
fi
- name: Deploy ClusterImagePolicy With Remote Public Key With Source
run: |
yq '. | .metadata.name = "image-policy-remote-source"
| .spec.authorities[0].key.data |= load_str("cosign-remote-signing.pub")
| .spec.authorities[0] += {"source": [{"oci": "${{ env.KO_DOCKER_REPO }}/remote-signature"}]}' ./test/testdata/cosigned/e2e/cip-key.yaml | tee image-policy-remote-source.yaml
kubectl apply -f image-policy-remote-source.yaml
- name: Verify with three CIP, one with correct Source set
run: |
echo '::group:: test job success'
# We signed this above, this should work
if ! kubectl create -n demo-key-remote job demo --image=${{ env.demoimage }} ; then
echo Failed to create Job in namespace without label!
exit 1
else
echo Succcessfully created Job with signed image
fi
echo '::endgroup:: test job success'
- name: Collect diagnostics
if: ${{ failure() }}
uses: chainguard-dev/actions/kind-diag@84c993eaf02da1c325854fb272a4df9184bd80fc # main
14 changes: 14 additions & 0 deletions pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation.go
Expand Up @@ -83,6 +83,12 @@ func (authority *Authority) Validate(ctx context.Context) *apis.FieldError {
errs = errs.Also(authority.Keyless.Validate(ctx).ViaField("keyless"))
}

if len(authority.Sources) > 0 {
for _, source := range authority.Sources {
errs = errs.Also(source.Validate(ctx).ViaField("source"))
}
}

return errs
}

Expand Down Expand Up @@ -130,6 +136,14 @@ func (keyless *KeylessRef) Validate(ctx context.Context) *apis.FieldError {
return errs
}

func (source *Source) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
if source.OCI == "" {
errs = errs.Also(apis.ErrMissingField("oci"))
}
return errs
}

func (identity *Identity) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
if identity.Issuer == "" && identity.Subject == "" {
Expand Down
50 changes: 50 additions & 0 deletions pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation_test.go
Expand Up @@ -397,7 +397,57 @@ func TestAuthoritiesValidation(t *testing.T) {
},
},
},
{
name: "Should pass when source oci is present",
expectErr: false,
policy: ClusterImagePolicy{
Spec: ClusterImagePolicySpec{
Images: []ImagePattern{{Regex: ".*"}},
Authorities: []Authority{
{
Key: &KeyRef{KMS: "kms://key/path"},
Sources: []Source{{OCI: "registry.example.com"}},
},
},
},
},
},
{
name: "Should fail when source oci is empty",
expectErr: true,
errorString: "missing field(s): spec.authorities[0].source.oci",
policy: ClusterImagePolicy{
Spec: ClusterImagePolicySpec{
Images: []ImagePattern{{Regex: ".*"}},
Authorities: []Authority{
{
Key: &KeyRef{KMS: "kms://key/path"},
Sources: []Source{{OCI: ""}},
},
},
},
},
},
{
name: "Should pass with multiple source oci is present",
expectErr: false,
policy: ClusterImagePolicy{
Spec: ClusterImagePolicySpec{
Images: []ImagePattern{{Regex: ".*"}},
Authorities: []Authority{
{
Key: &KeyRef{KMS: "kms://key/path"},
Sources: []Source{
{OCI: "registry1"},
{OCI: "registry2"},
},
},
},
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.policy.Validate(context.TODO())
Expand Down
Expand Up @@ -20,7 +20,10 @@ import (
"encoding/json"
"encoding/pem"

"github.com/google/go-containerregistry/pkg/name"
"github.com/pkg/errors"
"github.com/sigstore/cosign/pkg/apis/cosigned/v1alpha1"
"github.com/sigstore/cosign/pkg/oci/remote"

"knative.dev/pkg/apis"
)
Expand All @@ -44,6 +47,9 @@ type Authority struct {
Sources []v1alpha1.Source `json:"source,omitempty"`
// +optional
CTLog *v1alpha1.TLog `json:"ctlog,omitempty"`
// RemoteOpts are not marshalled because they are an unsupported type
// +optional
RemoteOpts []remote.Option `json:"-"`
}

// This references a public verification key stored in
Expand Down Expand Up @@ -92,6 +98,34 @@ func (k *KeyRef) UnmarshalJSON(data []byte) error {
return nil
}

// UnmarshalJSON populates the authority with the remoteOpts
// from authority sources
func (a *Authority) UnmarshalJSON(data []byte) error {
// Create a new type to avoid recursion
type RawAuthority Authority

var rawAuthority RawAuthority
err := json.Unmarshal(data, &rawAuthority)
if err != nil {
return err
}

// Determine additional RemoteOpts
if len(rawAuthority.Sources) > 0 {
for _, source := range rawAuthority.Sources {
if targetRepoOverride, err := name.NewRepository(source.OCI); err != nil {
return errors.Wrap(err, "failed to determine source")
} else if (targetRepoOverride != name.Repository{}) {
rawAuthority.RemoteOpts = append(rawAuthority.RemoteOpts, remote.WithTargetRepository(targetRepoOverride))
}
}
}

// Set the new type instance to casted original
*a = Authority(rawAuthority)
return nil
}

func ConvertClusterImagePolicyV1alpha1ToWebhook(in *v1alpha1.ClusterImagePolicy) *ClusterImagePolicy {
copyIn := in.DeepCopy()

Expand Down
14 changes: 7 additions & 7 deletions pkg/cosign/kubernetes/webhook/validator.go
Expand Up @@ -257,23 +257,23 @@ func validatePolicies(ctx context.Context, ref name.Reference, kc authn.Keychain
// return a success if at least one of the Authorities validated the signatures.
// Returns the validated signatures, or the errors encountered.
func ValidatePolicy(ctx context.Context, ref name.Reference, kc authn.Keychain, authorities []webhookcip.Authority, remoteOpts ...ociremote.Option) ([]oci.Signature, []error) {
remoteOpts = append(remoteOpts, ociremote.WithRemoteOptions(remote.WithAuthFromKeychain(kc)))

// If none of the Authorities for a given policy pass the checks, gather
// the errors here. If one passes, do not return the errors.
authorityErrors := []error{}
for _, authority := range authorities {
logging.FromContext(ctx).Debugf("Checking Authority: %+v", authority)
// TODO(vaikas): We currently only use the kc, we have to look
// at authority.Sources to determine additional information for the
// WithRemoteOptions below, at least the 'TargetRepository'
// https://github.com/sigstore/cosign/issues/1651
// Assignment for appendAssign lint error
authorityRemoteOpts := remoteOpts
authorityRemoteOpts = append(authorityRemoteOpts, authority.RemoteOpts...)

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, authority.Key.PublicKeys, authorityRemoteOpts...)
if err != nil {
authorityErrors = append(authorityErrors, errors.Wrap(err, "failed to validate keys"))
continue
Expand Down Expand Up @@ -303,7 +303,7 @@ func ValidatePolicy(ctx context.Context, ref name.Reference, kc authn.Keychain,
continue
}
}
sps, err := validSignaturesWithFulcio(ctx, ref, fulcioroot, rekorClient, authority.Keyless.Identities, remoteOpts...)
sps, err := validSignaturesWithFulcio(ctx, ref, fulcioroot, rekorClient, authority.Keyless.Identities, authorityRemoteOpts...)
if err != nil {
logging.FromContext(ctx).Errorf("failed validSignatures with fulcio for %s: %v", ref.Name(), err)
authorityErrors = append(authorityErrors, errors.Wrap(err, "validate signatures with fulcio"))
Expand Down

0 comments on commit c443e6b

Please sign in to comment.