1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/cosign/cosign.go
Jim Bugwadia 2bd5bca721 merge foreach and add attestation checks
Signed-off-by: Jim Bugwadia <jim@nirmata.com>
2021-10-02 14:24:06 -07:00

232 lines
6.8 KiB
Go

package cosign
import (
"context"
"crypto"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"strings"
"github.com/gardener/controller-manager-library/pkg/logger"
"github.com/go-logr/logr"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/k8schain"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/pkg/errors"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/sigstore/pkg/signature"
"k8s.io/client-go/kubernetes"
)
var (
// ImageSignatureRepository is an alternate signature repository
ImageSignatureRepository string
)
// Initialize loads the image pull secrets and initializes the default auth method for container registry API calls
func Initialize(client kubernetes.Interface, namespace, serviceAccount string, imagePullSecrets []string) error {
var kc authn.Keychain
kcOpts := &k8schain.Options{
Namespace: namespace,
ServiceAccountName: serviceAccount,
ImagePullSecrets: imagePullSecrets,
}
kc, err := k8schain.New(context.Background(), client, *kcOpts)
if err != nil {
return errors.Wrap(err, "failed to initialize registry keychain")
}
authn.DefaultKeychain = kc
return nil
}
func VerifySignature(imageRef string, key []byte, repository string, log logr.Logger) (digest string, err error) {
pubKey, err := decodePEM(key)
if err != nil {
return "", errors.Wrapf(err, "failed to decode PEM %v", string(key))
}
cosignOpts := &cosign.CheckOpts{
RootCerts: fulcio.GetRoots(),
Annotations: map[string]interface{}{},
SigVerifier: pubKey,
RegistryClientOpts: []remote.Option{
remote.WithAuthFromKeychain(authn.DefaultKeychain),
},
}
ref, err := name.ParseReference(imageRef)
if err != nil {
return "", errors.Wrap(err, "failed to parse image")
}
cosignOpts.SignatureRepo = ref.Context()
if repository != "" {
signatureRepo, err := name.NewRepository(repository)
if err != nil {
return "", errors.Wrapf(err, "failed to parse signature repository %s", repository)
}
cosignOpts.SignatureRepo = signatureRepo
}
verified, err := cosign.Verify(context.Background(), ref, cosignOpts)
if err != nil {
msg := err.Error()
logger.Info("image verification failed", "error", msg)
if strings.Contains(msg, "NAME_UNKNOWN: repository name not known to registry") {
return "", fmt.Errorf("signature not found")
} else if strings.Contains(msg, "no matching signatures") {
return "", fmt.Errorf("invalid signature")
}
return "", errors.Wrap(err, "failed to verify image")
}
digest, err = extractDigest(imageRef, verified, log)
if err != nil {
return "", errors.Wrap(err, "failed to get digest")
}
return digest, nil
}
func FetchAttestations(imageRef string, key []byte, repository string) (map[string]interface{}, error) {
pubKey, err := decodePEM(key)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode PEM %v", string(key))
}
ref, err := name.ParseReference(imageRef)
if err != nil {
return nil, errors.Wrap(err, "failed to parse image")
}
cosignOpts := &cosign.CheckOpts{
RootCerts: fulcio.GetRoots(),
ClaimVerifier: cosign.IntotoSubjectClaimVerifier,
SigTagSuffixOverride: cosign.AttestationTagSuffix,
SigVerifier: dsse.WrapVerifier(pubKey),
VerifyBundle: false,
}
if err := setSignatureRepo(cosignOpts, ref, repository); err != nil {
return nil, errors.Wrap(err, "failed to set signature repository")
}
verified, err := cosign.Verify(context.Background(), ref, cosignOpts)
if err != nil {
return nil, errors.Wrap(err, "failed to verify image attestations")
}
inTotoAttestations, err := decodeAttestations(verified)
if err != nil {
return nil, err
}
return inTotoAttestations, nil
}
func decodeAttestations(attestations []cosign.SignedPayload) (map[string]interface{}, error) {
if len(attestations) == 0 {
return map[string]interface{}{}, nil
}
decodedAttestations := make([]map[string]interface{}, len(attestations))
for _, a := range attestations {
payload := a.Payload
data, err := base64.StdEncoding.DecodeString(string(payload))
if err != nil {
return nil, errors.Wrapf(err, "failed to base64 decode payload for attestation: %v", a)
}
inTotoAttestation := make(map[string]interface{})
if err := json.Unmarshal(data, &inTotoAttestation); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal JSON payload for attestation: %v", a)
}
attestationPayloadBase64 := inTotoAttestation["payload"].(string)
statement, err := base64.StdEncoding.DecodeString(attestationPayloadBase64)
if err != nil {
return nil, errors.Wrapf(err, "failed to base64 decode payload for ")
}
inTotoAttestation["payload"] = statement
decodedAttestations = append(decodedAttestations, inTotoAttestation)
}
results := map[string]interface{}{
"attestations": decodedAttestations,
}
return results, nil
}
func setSignatureRepo(cosignOpts *cosign.CheckOpts, ref name.Reference, repository string) error {
cosignOpts.SignatureRepo = ref.Context()
if repository != "" {
signatureRepo, err := name.NewRepository(repository)
if err != nil {
return errors.Wrapf(err, "failed to parse signature repository %s", repository)
}
cosignOpts.SignatureRepo = signatureRepo
}
return nil
}
func decodePEM(raw []byte) (signature.Verifier, error) {
// PEM encoded file.
ed, err := cosign.PemToECDSAKey(raw)
if err != nil {
return nil, errors.Wrap(err, "pem to ecdsa")
}
return signature.LoadECDSAVerifier(ed, crypto.SHA256)
}
func extractDigest(imgRef string, verified []cosign.SignedPayload, log logr.Logger) (string, error) {
var jsonMap map[string]interface{}
for _, vp := range verified {
if err := json.Unmarshal(vp.Payload, &jsonMap); err != nil {
return "", err
}
log.V(4).Info("image verification response", "image", imgRef, "payload", jsonMap)
// The cosign response is in the JSON format:
// {
// "critical": {
// "identity": {
// "docker-reference": "registry-v2.nirmata.io/pause"
// },
// "image": {
// "docker-manifest-digest": "sha256:4a1c4b21597c1b4415bdbecb28a3296c6b5e23ca4f9feeb599860a1dac6a0108"
// },
// "type": "cosign container image signature"
// },
// "optional": null
// }
critical := jsonMap["critical"].(map[string]interface{})
if critical != nil {
typeStr := critical["type"].(string)
if typeStr == "cosign container image signature" {
identity := critical["identity"].(map[string]interface{})
if identity != nil {
image := critical["image"].(map[string]interface{})
if image != nil {
return image["docker-manifest-digest"].(string), nil
}
}
}
}
}
return "", fmt.Errorf("digest not found for " + imgRef)
}