pre-sync/oci-image-verification/main.go (124 lines of code) (raw):
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/google"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sigstore/cosign/v2/pkg/cosign"
ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
"github.com/sigstore/cosign/v2/pkg/signature"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
)
const (
imageToSync = "configsync.gke.io/image-to-sync"
publicKeyPath = "/cosign-key/cosign.pub"
)
// getAnnotationByKey extracts a specific annotation by key from the raw JSON data.
func getAnnotationByKey(raw []byte, key string) (string, error) {
var metadata map[string]interface{}
if err := json.Unmarshal(raw, &metadata); err != nil {
return "", err
}
annotations, ok := metadata["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})
if !ok {
klog.Infof("No annotations found in the object")
return "", nil
}
if value, found := annotations[key]; found {
return fmt.Sprintf("%v", value), nil
}
klog.Infof("Annotation %s not found in the object", key)
return "", nil
}
func verifyImageSignature(ctx context.Context, image string) error {
if image == "" {
return nil
}
pubKey, err := signature.LoadPublicKey(ctx, publicKeyPath)
if err != nil {
return fmt.Errorf("error loading public key: %v", err)
}
googleAuth, err := google.NewEnvAuthenticator(ctx)
if err != nil {
return err
}
opts := &cosign.CheckOpts{
RegistryClientOpts: []ociremote.Option{ociremote.WithRemoteOptions(remote.WithAuth(googleAuth))},
SigVerifier: pubKey,
IgnoreTlog: true,
}
ref, err := name.ParseReference(image)
if err != nil {
return fmt.Errorf("failed to parse image reference: %v", err)
}
_, _, err = cosign.VerifyImageSignatures(ctx, ref, opts)
if err != nil {
return fmt.Errorf("image verification failed for %s: %v", image, err)
}
klog.Infof("Image %s verified successfully", image)
return nil
}
func handleWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
klog.Errorf("Failed to read request body: %v", err)
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
var admissionReview admissionv1.AdmissionReview
if err := json.Unmarshal(body, &admissionReview); err != nil {
klog.Errorf("Failed to unmarshal admission review: %v", err)
http.Error(w, "Failed to unmarshal admission review", http.StatusBadRequest)
return
}
response := &admissionv1.AdmissionResponse{
UID: admissionReview.Request.UID,
}
oldImage, err := getAnnotationByKey(admissionReview.Request.OldObject.Raw, imageToSync)
if err != nil {
klog.Errorf("Failed to extract old annotations: %v", err)
response.Result = &metav1.Status{
Message: fmt.Sprintf("Failed to extract old annotations: %v", err),
}
response.Allowed = false
return
}
newImage, err := getAnnotationByKey(admissionReview.Request.Object.Raw, imageToSync)
if err != nil {
klog.Errorf("Failed to extract new annotations: %v", err)
response.Result = &metav1.Status{
Message: fmt.Sprintf("Failed to extract new annotations: %v", err),
}
response.Allowed = false
return
}
if newImage != oldImage {
klog.Infof("Annotation %s changed from %s to %s", imageToSync, oldImage, newImage)
if err := verifyImageSignature(r.Context(), newImage); err != nil {
klog.Errorf("Image verification failed: %v", err)
response.Allowed = false
response.Result = &metav1.Status{
Message: fmt.Sprintf("Image verification failed: %v", err),
}
} else {
klog.Infof("Image verification successful for %s", newImage)
response.Allowed = true
}
} else {
response.Allowed = true
}
admissionReview.Response = response
if err := json.NewEncoder(w).Encode(admissionReview); err != nil {
klog.Errorf("Failed to encode admission response: %v", err)
}
}
func main() {
http.HandleFunc("/validate", handleWebhook)
klog.Info("Starting webhook server on port 10250...")
klog.Error(http.ListenAndServeTLS(":10250", "/tls/tls.crt", "/tls/tls.key", nil))
}