91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
|
|
admissionv1 "k8s.io/api/admission/v1"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/errors"
|
|
)
|
|
|
|
func verifyDeployment(deploy *appsv1.Deployment) error {
|
|
var errs []error
|
|
for i, c := range deploy.Spec.Template.Spec.Containers {
|
|
if c.Name == "" {
|
|
return fmt.Errorf("container %d has no name", i)
|
|
}
|
|
if c.SecurityContext == nil {
|
|
errs = append(errs, fmt.Errorf("container %q does not have SecurityContext", c.Name))
|
|
}
|
|
if c.SecurityContext.RunAsNonRoot == nil || !*c.SecurityContext.RunAsNonRoot {
|
|
errs = append(errs, fmt.Errorf("container %q must set RunAsNonRoot to true in its SecurityContext", c.Name))
|
|
}
|
|
if c.SecurityContext.ReadOnlyRootFilesystem == nil || !*c.SecurityContext.ReadOnlyRootFilesystem {
|
|
errs = append(errs, fmt.Errorf("container %q must set ReadOnlyRootFilesystem to true in its SecurityContext", c.Name))
|
|
}
|
|
if c.SecurityContext.AllowPrivilegeEscalation != nil && *c.SecurityContext.AllowPrivilegeEscalation {
|
|
errs = append(errs, fmt.Errorf("container %q must NOT set AllowPrivilegeEscalation to true in its SecurityContext", c.Name))
|
|
}
|
|
if c.SecurityContext.Privileged != nil && *c.SecurityContext.Privileged {
|
|
errs = append(errs, fmt.Errorf("container %q must NOT set Privileged to true in its SecurityContext", c.Name))
|
|
}
|
|
}
|
|
return errors.NewAggregate(errs)
|
|
}
|
|
|
|
func WebhookEnforceSecurePodConfiguration(rw http.ResponseWriter, req *http.Request) {
|
|
result := &admissionv1.AdmissionReview{Response: &admissionv1.AdmissionResponse{}}
|
|
err := func() error {
|
|
ar := new(admissionv1.AdmissionReview)
|
|
err := json.NewDecoder(req.Body).Decode(ar)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ar.Request == nil {
|
|
return nil
|
|
}
|
|
result.TypeMeta = ar.TypeMeta
|
|
result.Response.UID = ar.Request.UID
|
|
if len(ar.Request.Object.Raw) == 0 {
|
|
return nil
|
|
}
|
|
deploy := new(appsv1.Deployment)
|
|
err = json.Unmarshal(ar.Request.Object.Raw, deploy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return verifyDeployment(deploy)
|
|
}()
|
|
if err == nil {
|
|
result.Response.Allowed = true
|
|
} else {
|
|
result.Response.Allowed = false
|
|
result.Response.Result = &metav1.Status{
|
|
Code: http.StatusForbidden,
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
err = json.NewEncoder(rw).Encode(result)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
var _ http.HandlerFunc = WebhookEnforceSecurePodConfiguration
|
|
|
|
func main() {
|
|
http.HandleFunc("/", WebhookEnforceSecurePodConfiguration)
|
|
|
|
addr := flag.String("addr", ":8443", "address to listen on")
|
|
certFile := flag.String("cert", "cert.pem", "path to TLS certificate")
|
|
keyFile := flag.String("key", "key.pem", "path to TLS key")
|
|
flag.Parse()
|
|
|
|
log.Fatalln(http.ListenAndServeTLS(*addr, *certFile, *keyFile, nil))
|
|
}
|