Skip to content

Commit

Permalink
Feat/logger (#15)
Browse files Browse the repository at this point in the history
* switch to slog

* fix flags

* reformat slog out

* reformat slog out

* add doc and format messages

* reformat

* rename slog to log

* warn instead error on get secret

* test debug

* fix full copy of src dir

* introduce loglevel

* fix loglevel

* new chart value logLevel

* fix deploy arg
  • Loading branch information
eumel8 authored Aug 16, 2023
1 parent 31befa4 commit 69cd155
Show file tree
Hide file tree
Showing 303 changed files with 50,968 additions and 3,373 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o cos

#FROM scratch
FROM alpine:latest
COPY --from=build-env /go/src/github.com/eumel8/cosignwebhook .
COPY --from=build-env /go/src/github.com/eumel8/cosignwebhook/cosignwebhook .
COPY --from=build-env /etc/passwd /etc/passwd
USER webhook
ENTRYPOINT ["/cosignwebhook"]
6 changes: 2 additions & 4 deletions chart/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ spec:
containers:
- name: {{ .Chart.Name }}
args:
- -alsologtostderr
- 2>&1
# - --log_dir=/
# - -v=10
- -logLevel
- {{ .Values.logLevel | default "info" }}
env:
- name: COSIGNPUBKEY
value: {{- toYaml .Values.cosign.key | indent 12 }}
Expand Down
2 changes: 2 additions & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ image:
tag: 2.2.0

imagePullSecrets: []
logLevel: info

nameOverride: ""
fullnameOverride: ""

Expand Down
86 changes: 47 additions & 39 deletions cosignwebhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"io"
"net/http"

"github.com/golang/glog"
log "github.com/gookit/slog"
v1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -45,12 +45,12 @@ type CosignServerHandler struct{}
func restClient() (*kubernetes.Clientset, error) {
restConfig, err := rest.InClusterConfig()
if err != nil {
glog.Errorf("error init in-cluster config: %v", err)
log.Errorf("error init in-cluster config: %v", err)
return nil, err
}
k8sclientset, err := kubernetes.NewForConfig(restConfig)
if err != nil {
glog.Errorf("error creating k8sclientset: %v", err)
log.Errorf("error creating k8sclientset: %v", err)
return nil, err
}
return k8sclientset, err
Expand All @@ -68,17 +68,17 @@ func recordEvent(pod *corev1.Pod, k8sclientset *kubernetes.Clientset) {
func getPod(byte []byte) (*corev1.Pod, *v1.AdmissionReview, error) {
arRequest := v1.AdmissionReview{}
if err := json.Unmarshal(byte, &arRequest); err != nil {
glog.Error("incorrect body")
log.Error("Incorrect body")
return nil, nil, err
}
if arRequest.Request == nil {
glog.Error("AdmissionReview request not found")
log.Error("AdmissionReview request not found")
return nil, nil, fmt.Errorf("admissionreview request not found")
}
raw := arRequest.Request.Object.Raw
pod := corev1.Pod{}
if err := json.Unmarshal(raw, &pod); err != nil {
glog.Error("Error deserializing pod")
log.Error("Error deserializing pod")
return nil, nil, err
}
return &pod, &arRequest, nil
Expand All @@ -99,23 +99,23 @@ func getEnv(pod *corev1.Pod) (string, error) {
func getSecret(namespace string, name string) (string, error) {
clientset, err := restClient()
if err != nil {
glog.Errorf("Can't init rest client for secret: %v", err)
log.Errorf("Can't init rest client for secret: %v", err)
return "", err
}
secret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
glog.Errorf("Can't get secret %s/%s from kubernetes: %v", namespace, name, err)
log.Debugf("Can't get secret %s/%s : %v", namespace, name, err)
return "", err
}
value := secret.Data[cosignEnvVar]
if value == nil {
glog.Errorf("Secret value empty for %s/%s", namespace, name)
log.Debugf("Secret value empty for %s/%s", namespace, name)
return "", nil
}
/*
decodedValue, err := base64.StdEncoding.DecodeString(string(value))
if err != nil {
glog.Error("Can't decode value ", err)
log.Errorf("Can't decode value ", err)
return "", err
}
*/
Expand Down Expand Up @@ -143,13 +143,13 @@ func (cs *CosignServerHandler) serve(w http.ResponseWriter, r *http.Request) {

// Url path of admission
if r.URL.Path != "/validate" {
glog.Error("no validate")
log.Error("No validate URI")
http.Error(w, "no validate", http.StatusBadRequest)
return
}

if len(body) == 0 {
glog.Error("empty body")
log.Error("Empty body")
http.Error(w, "empty body", http.StatusBadRequest)
return
}
Expand All @@ -159,52 +159,55 @@ func (cs *CosignServerHandler) serve(w http.ResponseWriter, r *http.Request) {

pod, arRequest, err := getPod(body)
if err != nil {
glog.Errorf("Error getPod in %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Error getPod in %s/%s: %v", pod.Namespace, pod.Name, err)
http.Error(w, "incorrect body", http.StatusBadRequest)
return
}

// Get public key from environment var
pubKey, err := getEnv(pod)
if err != nil {
glog.Warningf("Could not get public key from environment variable in %s/%s: %v. Trying to get public key from secret", pod.Namespace, pod.Name, err)
log.Debugf("Could not get public key from environment variable in %s/%s: %v. Trying to get public key from secret", pod.Namespace, pod.Name, err)
}

// If no public key get here, try to load from secret
if len(pubKey) == 0 {
pubKey, err = getSecret(pod.Namespace, "cosignwebhook")
if err != nil {
glog.Warningf("Could not get public key from secret in %s/%s: %v", pod.Namespace, pod.Name, err)
log.Debugf("Could not get public key from secret in %s/%s: %v", pod.Namespace, pod.Name, err)
}
}

// Still no public key, we don't care. Otherwise POD won't start, if we return with 403
if len(pubKey) == 0 {
glog.Errorf("No public key set in %s/%s", pod.Namespace, pod.Name)
// log.Errorf("No public key set in %s/%s", pod.Namespace, pod.Name)
// return OK if no key is set, so user don't want a verification
// otherwise set failurePolicy: Skip in ValidatingWebhookConfiguration
resp, err := json.Marshal(admissionResponse(200, true, "Success", "Cosign image skipped", arRequest))
if err != nil {
glog.Errorf("Can't encode response: %v", err)
log.Errorf("Can't encode response: %v", err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response: %v", err)
log.Errorf("Can't write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
return
}
glog.Info("Successfully got public key")
// log.Info("Successfully got public key")

// Lookup image name of first container
image := pod.Spec.Containers[0].Image
refImage, err := name.ParseReference(image)
if err != nil {
glog.Errorf("Error ParseRef image: %v", err)
log.Errorf("Error ParseRef image: %v", err)
resp, err := json.Marshal(admissionResponse(403, false, "Failure", "Cosign ParseRef image failed", arRequest))
if err != nil {
glog.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response: %v", err)
log.Errorf("Can't write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
return
Expand All @@ -221,51 +224,55 @@ func (cs *CosignServerHandler) serve(w http.ResponseWriter, r *http.Request) {
ImagePullSecrets: imagePullSecrets,
}

// Encrypt public key
publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(pubKey))
if err != nil {
glog.Errorf("Error UnmarshalPEMToPublicKey %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Error UnmarshalPEMToPublicKey %s/%s: %v", pod.Namespace, pod.Name, err)
resp, err := json.Marshal(admissionResponse(403, false, "Failure", "Cosign UnmarshalPEMToPublicKey failed", arRequest))
if err != nil {
glog.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response: %v", err)
log.Errorf("Can't write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
return
}

// Load public key to verify
cosignLoadKey, err := signature.LoadECDSAVerifier(publicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
glog.Errorf("Error LoadECDSAVerifier %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Error LoadECDSAVerifier %s/%s: %v", pod.Namespace, pod.Name, err)
resp, err := json.Marshal(admissionResponse(403, false, "Failure", "Cosign key encoding failed", arRequest))
if err != nil {
glog.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Can't write response %s/%s: %v", pod.Namespace, pod.Name, err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
return
}

// Kubernetes client to operate in cluster
kc, err := k8schain.NewInCluster(context.Background(), opt)
if err != nil {
glog.Errorf("Error k8schain %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Error k8schain %s/%s: %v", pod.Namespace, pod.Name, err)
resp, err := json.Marshal(admissionResponse(403, false, "Failure", "Cosign UnmarshalPEMToPublicKey failed", arRequest))
if err != nil {
glog.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Can't encode response %s/%s: %v", pod.Namespace, pod.Name, err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response: %v", err)
log.Errorf("Can't write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
return
}

// Verify signature on remote image with the presented public key
remoteOpts := []ociremote.Option{ociremote.WithRemoteOptions(remote.WithAuthFromKeychain(kc))}
_, _, err = cosign.VerifyImageSignatures(
context.Background(),
Expand All @@ -279,38 +286,39 @@ func (cs *CosignServerHandler) serve(w http.ResponseWriter, r *http.Request) {
})

// this is always false,
// glog.Info("Resp bundleVerified: ", bundleVerified)
// log.Info("Resp bundleVerified: ", bundleVerified)

// Verify Image failed, needs to reject pod start
if err != nil {
glog.Errorf("Error VerifyImageSignatures %s/%s: %v", pod.Namespace, pod.Name, err)
log.Errorf("Error VerifyImageSignatures %s/%s: %v", pod.Namespace, pod.Name, err)
resp, err := json.Marshal(admissionResponse(403, false, "Failure", "Cosign image verification failed", arRequest))
if err != nil {
glog.Errorf("Can't encode response: %v", err)
log.Errorf("Can't encode response: %v", err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response: %v", err)
log.Errorf("Can't write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
} else {
// count successful verifies for prometheus metric
verifiedProcessed.Inc()
glog.Info("Image successful verified: ", pod.Namespace, "/", pod.Name)
log.Infof("Image successful verified: %s/%s", pod.Namespace, pod.Name)
resp, err := json.Marshal(admissionResponse(200, true, "Success", "Cosign image verified", arRequest))
// Verify Image successful, needs to allow pod start
if err != nil {
glog.Errorf("Can't encode response: %v", err)
log.Errorf("Can't encode response: %v", err)
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
glog.Errorf("Can't write response: %v", err)
log.Errorf("Can't write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}

// Just another K8S client to record events
clientset, err := restClient()
if err != nil {
glog.Errorf("Can't init rest client for event recorder: %v", err)
log.Errorf("Can't init rest client for event recorder: %v", err)
} else {
recordEvent(pod, clientset)
}
Expand Down
16 changes: 11 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/golang/glog v1.0.0
github.com/google/go-containerregistry v0.12.1
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230104193340-e797859b62b6
github.com/gookit/slog v0.5.4
github.com/prometheus/client_golang v1.14.0
github.com/sigstore/cosign v1.13.1
github.com/sigstore/cosign/v2 v2.0.0-rc.0
Expand Down Expand Up @@ -84,6 +85,9 @@ require (
github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20221017135236-9b4fdd506cdd // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gookit/goutil v0.6.12 // indirect
github.com/gookit/gsr v0.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/in-toto/in-toto-golang v0.5.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
Expand Down Expand Up @@ -129,21 +133,23 @@ require (
github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/transparency-dev/merkle v0.0.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.mongodb.org/mongo-driver v1.10.1 // indirect
go.opentelemetry.io/otel v1.11.1 // indirect
go.opentelemetry.io/otel/trace v1.11.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.4.0 // indirect
golang.org/x/exp v0.0.0-20220823124025-807a23277127 // indirect
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.3.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221206210731-b1a01be3a5f6 // indirect
Expand Down
Loading

0 comments on commit 69cd155

Please sign in to comment.