Skip to content

Commit

Permalink
Create convert functions for internal CIP
Browse files Browse the repository at this point in the history
Signed-off-by: Denny Hoang <[email protected]>
  • Loading branch information
DennyHoang committed Apr 11, 2022
1 parent de85b7e commit 237919b
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 194 deletions.
1 change: 0 additions & 1 deletion pkg/apis/config/image_policies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ func checkPublicKey(t *testing.T, gotKey *ecdsa.PublicKey) {

// pem.EncodeToMemory has an extra newline at the end
got := strings.TrimSuffix(string(pemBytes), "\n")

if got != inlineKeyData {
t.Errorf("Did not get what I wanted %s, got %s", inlineKeyData, string(pemBytes))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,25 @@
package clusterimagepolicy

import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"strings"

"github.com/sigstore/cosign/pkg/apis/cosigned/v1alpha1"
"github.com/sigstore/cosign/pkg/apis/utils"
sigs "github.com/sigstore/cosign/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/kms"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
corev1listers "k8s.io/client-go/listers/core/v1"
"knative.dev/pkg/apis"
"knative.dev/pkg/logging"
"knative.dev/pkg/system"
"knative.dev/pkg/tracker"
)

// ClusterImagePolicy defines the images that go through verification
Expand All @@ -37,7 +50,7 @@ type Authority struct {
// +optional
Key *KeyRef `json:"key,omitempty"`
// +optional
Keyless *v1alpha1.KeylessRef `json:"keyless,omitempty"`
Keyless *KeylessRef `json:"keyless,omitempty"`
// +optional
Sources []v1alpha1.Source `json:"source,omitempty"`
// +optional
Expand All @@ -50,15 +63,21 @@ type KeyRef struct {
// Data contains the inline public key
// +optional
Data string `json:"data,omitempty"`
// KMS contains the KMS url of the public key
// +optional
KMS string `json:"kms,omitempty"`
// PublicKeys are not marshalled because JSON unmarshalling
// errors for *big.Int
// +optional
PublicKeys []*ecdsa.PublicKey `json:"-"`
}

type KeylessRef struct {
// +optional
URL *apis.URL `json:"url,omitempty"`
// +optional
Identities []v1alpha1.Identity `json:"identities,omitempty"`
// +optional
CACert *KeyRef `json:"ca-cert,omitempty"`
}

// UnmarshalJSON populates the PublicKeys using Data because
// JSON unmashalling errors for *big.Int
func (k *KeyRef) UnmarshalJSON(data []byte) error {
Expand All @@ -73,7 +92,7 @@ func (k *KeyRef) UnmarshalJSON(data []byte) error {
k.Data = ret["data"]

if ret["data"] != "" {
publicKeys, err = convertKeyDataToPublicKeys(ret["data"])
publicKeys, err = ConvertKeyDataToPublicKeys(context.Background(), ret["data"])
if err != nil {
return err
}
Expand All @@ -84,9 +103,103 @@ func (k *KeyRef) UnmarshalJSON(data []byte) error {
return nil
}

func convertKeyDataToPublicKeys(pubKey string) ([]*ecdsa.PublicKey, error) {
func ConvertClusterImagePolicyV1alpha1ToWebhook(ctx context.Context, in *v1alpha1.ClusterImagePolicy, rtracker tracker.Interface, secretLister corev1listers.SecretLister) (*ClusterImagePolicy, error) {
copyIn := in.DeepCopy()
outAuthorities := make([]Authority, 0)
for _, authority := range copyIn.Spec.Authorities {
outAuthority, err := convertAuthorityV1Alpha1ToWebhook(ctx, copyIn, authority, rtracker, secretLister)
if err != nil {
return nil, err
}
outAuthorities = append(outAuthorities, *outAuthority)
}

return &ClusterImagePolicy{
Images: copyIn.Spec.Images,
Authorities: outAuthorities,
}, nil
}

// convertAuthorityV1Alpha1ToWebhook will evaluate Key and Keyless
// references and return the converted Authority
func convertAuthorityV1Alpha1ToWebhook(ctx context.Context, cipIn *v1alpha1.ClusterImagePolicy, in v1alpha1.Authority, rtracker tracker.Interface, secretLister corev1listers.SecretLister) (*Authority, error) {
var err error
var keyRef *KeyRef
var keylessRef *KeylessRef

if in.Key != nil {
if keyRef, err = convertKeyRefV1Alpha1ToWebhook(ctx, in.Key, cipIn, rtracker, secretLister); err != nil {
return nil, err
}
}

if in.Keyless != nil {
if keylessRef, err = convertKeylessRefV1Alpha1ToWebhook(ctx, in.Keyless, cipIn, rtracker, secretLister); err != nil {
return nil, err
}
}

return &Authority{
Key: keyRef,
Keyless: keylessRef,
Sources: in.Sources,
CTLog: in.CTLog,
}, nil
}

// convertKeyRefV1Alpha1ToWebhook will evaluate secretRef or KMS
// and return KeyRef with inlined data
func convertKeyRefV1Alpha1ToWebhook(ctx context.Context, in *v1alpha1.KeyRef, cipIn *v1alpha1.ClusterImagePolicy, rtracker tracker.Interface, secretLister corev1listers.SecretLister) (*KeyRef, error) {
var data string
var err error

if in != nil {
data = in.Data
}

if in != nil && in.SecretRef != nil {
if data, err = dataAndTrackSecret(ctx, cipIn, in, rtracker, secretLister); err != nil {
logging.FromContext(ctx).Errorf("Failed to read secret %q: %v", in.SecretRef.Name, err)
return nil, err
}
} else if in != nil && in.KMS != "" {
if strings.Contains(in.KMS, "://") {
if data, err = GetKMSPublicKey(ctx, in.KMS); err != nil {
return nil, err
}
}
}

return &KeyRef{
Data: data,
}, nil
}

// convertKeylessRefV1Alpha1ToWebhook will evaluate CACert KeyRef
// Returns new KeylessRef with new KeyRef with inlined data
func convertKeylessRefV1Alpha1ToWebhook(ctx context.Context, in *v1alpha1.KeylessRef, cipIn *v1alpha1.ClusterImagePolicy, rtracker tracker.Interface, secretLister corev1listers.SecretLister) (*KeylessRef, error) {
var CACertRef *KeyRef
var err error

CACertRef, err = convertKeyRefV1Alpha1ToWebhook(ctx, in.CACert, cipIn, rtracker, secretLister)
if err != nil {
return nil, err
}

return &KeylessRef{
URL: in.URL,
Identities: in.Identities,
CACert: CACertRef,
}, nil
}

func ConvertKeyDataToPublicKeys(ctx context.Context, pubKey string) ([]*ecdsa.PublicKey, error) {
keys := []*ecdsa.PublicKey{}

if ctx != nil {
logging.FromContext(ctx).Debugf("Got public key: %v", pubKey)
}

pems := parsePems([]byte(pubKey))
for _, p := range pems {
key, err := x509.ParsePKIXPublicKey(p.Bytes)
Expand All @@ -110,3 +223,54 @@ func parsePems(b []byte) []*pem.Block {
}
return pems
}

// dataAndTrackSecret will take in a KeyRef and tries to read the Secret, finding the
// first key from it and return the data.
// Additionally, we set up a tracker so we will be notified if the secret
// is modified.
// There's still some discussion about how to handle multiple keys in a secret
// for now, just grab one from it. For reference, the discussion is here:
// TODO(vaikas): https://github.com/sigstore/cosign/issues/1573
func dataAndTrackSecret(ctx context.Context, cip *v1alpha1.ClusterImagePolicy, keyref *v1alpha1.KeyRef, rtracker tracker.Interface, secretLister corev1listers.SecretLister) (string, error) {
if err := rtracker.TrackReference(tracker.Reference{
APIVersion: "v1",
Kind: "Secret",
Namespace: system.Namespace(),
Name: keyref.SecretRef.Name,
}, cip); err != nil {
return "", fmt.Errorf("failed to track changes to secret %q : %w", keyref.SecretRef.Name, err)
}
secret, err := secretLister.Secrets(system.Namespace()).Get(keyref.SecretRef.Name)
if err != nil {
return "", err
}
if len(secret.Data) == 0 {
return "", fmt.Errorf("secret %q contains no data", keyref.SecretRef.Name)
}
if len(secret.Data) > 1 {
return "", fmt.Errorf("secret %q contains multiple data entries, only one is supported", keyref.SecretRef.Name)
}
for k, v := range secret.Data {
logging.FromContext(ctx).Infof("inlining secret %q key %q", keyref.SecretRef.Name, k)
if !utils.IsValidKey(v) {
return "", fmt.Errorf("secret %q contains an invalid public key", keyref.SecretRef.Name)
}

return string(v), nil
}
return "", nil
}

// GetKMSPublicKey returns the public key as a string from the configured KMS service using the key ID
func GetKMSPublicKey(ctx context.Context, keyID string) (string, error) {
kmsSigner, err := kms.Get(ctx, keyID, crypto.SHA256)
if err != nil {
logging.FromContext(ctx).Errorf("Failed to read KMS key ID %q: %v", keyID, err)
return "", err
}
pemBytes, err := sigs.PublicKeyPem(kmsSigner, signatureoptions.WithContext(ctx))
if err != nil {
return "", err
}
return string(pemBytes), nil
}
6 changes: 3 additions & 3 deletions pkg/cosign/kubernetes/webhook/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw==
}},
Authorities: []webhookcip.Authority{
{
Keyless: &v1alpha1.KeylessRef{
Keyless: &webhookcip.KeylessRef{
URL: badURL,
},
},
Expand Down Expand Up @@ -315,7 +315,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw==
}},
Authorities: []webhookcip.Authority{
{
Keyless: &v1alpha1.KeylessRef{
Keyless: &webhookcip.KeylessRef{
URL: fulcioURL,
},
},
Expand Down Expand Up @@ -358,7 +358,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw==
}},
Authorities: []webhookcip.Authority{
{
Keyless: &v1alpha1.KeylessRef{
Keyless: &webhookcip.KeylessRef{
URL: fulcioURL,
},
CTLog: &v1alpha1.TLog{
Expand Down
Loading

0 comments on commit 237919b

Please sign in to comment.