This commit is contained in:
Hyatt 2023-03-19 17:19:09 -05:00
parent d5963a693d
commit 80ef925ff4
Signed by: nhyatt
GPG Key ID: C50D0BBB5BC40BEA
9 changed files with 130 additions and 42 deletions

View File

@ -5,7 +5,6 @@ import (
"io" "io"
"log" "log"
"strconv" "strconv"
"strings"
"time" "time"
"crypto/tls" "crypto/tls"
@ -77,49 +76,48 @@ func httpServer(cfg *config.Config) {
config: cfg, config: cfg,
} }
// healthcheck wh := &webHandler{
path.HandleFunc("/healthcheck", webHealthCheck) config: cfg,
}
// pod admission // pod admission
path.HandleFunc("/api/v1/admit/pod", ah.Serve(operations.PodsValidation())) path.HandleFunc("/api/v1/admit/pod", ah.ahServe(operations.PodsValidation()))
// deployment admission // deployment admission
path.HandleFunc("/api/v1/admit/deployment", ah.Serve(operations.DeploymentsValidation())) path.HandleFunc("/api/v1/admit/deployment", ah.ahServe(operations.DeploymentsValidation()))
// pod mutation // pod mutation
path.HandleFunc("/api/v1/mutate/pod", ah.Serve(operations.PodsMutation())) path.HandleFunc("/api/v1/mutate/pod", ah.ahServe(operations.PodsMutation()))
// web root // web root
path.HandleFunc("/", webRoot) path.HandleFunc("/", wh.webServe())
if err := connection.ListenAndServeTLS("", ""); err != nil { if err := connection.ListenAndServeTLS("", ""); err != nil {
log.Fatalf("[ERROR] %s\n", err) log.Fatalf("[ERROR] %s\n", err)
} }
} }
func webRoot(w http.ResponseWriter, r *http.Request) { type webHandler struct {
httpAccessLog(r) config *config.Config
crossSiteOrigin(w)
strictTransport(w)
switch {
case strings.ToLower(r.Method) != "get":
log.Printf("[DEBUG] Request to '/' was made using the wrong method: expected %s, got %s", "GET", strings.ToUpper(r.Method))
tmpltError(w, http.StatusBadRequest, InvalidMethod)
case r.URL.Path != "/":
log.Printf("[DEBUG] Unable to locate requested path: '%s'", r.URL.Path)
tmpltError(w, http.StatusNotFound, "Requested path not found.")
default:
tmpltWebRoot(w)
}
} }
func webHealthCheck(w http.ResponseWriter, r *http.Request) { func (h *webHandler) webServe() http.HandlerFunc {
httpAccessLog(r) return func(w http.ResponseWriter, r *http.Request) {
crossSiteOrigin(w) httpAccessLog(r)
strictTransport(w) crossSiteOrigin(w)
strictTransport(w)
if strings.ToLower(r.Method) == "get" { switch {
tmpltHealthCheck(w) case r.Method != http.MethodGet:
} else { msg := fmt.Sprintf("incorrect method: got request type %s, expected request type %s", r.Method, http.MethodPost)
log.Printf("[DEBUG] Request to '/healthcheck' was made using the wrong method: expected %s, got %s", "GET", strings.ToUpper(r.Method)) log.Printf("[DEBUG] %s", msg)
tmpltError(w, http.StatusBadRequest, InvalidMethod) tmpltError(w, http.StatusMethodNotAllowed, msg)
case r.URL.Path == "/healthcheck":
tmpltHealthCheck(w)
case r.URL.Path == "/":
tmpltWebRoot(w, r.URL.Query(), *h.config)
default:
msg := fmt.Sprintf("Unable to locate requested path: '%s'", r.URL.Path)
log.Printf("[DEBUG] %s", msg)
tmpltError(w, http.StatusNotFound, msg)
}
} }
} }
@ -128,8 +126,7 @@ type admissionHandler struct {
config *config.Config config *config.Config
} }
// Serve returns a http.HandlerFunc for an admission webhook func (h *admissionHandler) ahServe(hook operations.Hook) http.HandlerFunc {
func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
httpAccessLog(r) httpAccessLog(r)
crossSiteOrigin(w) crossSiteOrigin(w)
@ -138,14 +135,14 @@ func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
msg := fmt.Sprintf("incorrect method: got request type %s, expected request type %s", r.Method, http.MethodPost) msg := fmt.Sprintf("incorrect method: got request type %s, expected request type %s", r.Method, http.MethodPost)
log.Printf("[TRACE] %s", msg) log.Printf("[DEBUG] %s", msg)
tmpltError(w, http.StatusMethodNotAllowed, msg) tmpltError(w, http.StatusMethodNotAllowed, msg)
return return
} }
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" { if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
msg := "only content type 'application/json' is supported" msg := "only content type 'application/json' is supported"
log.Printf("[TRACE] %s", msg) log.Printf("[DEBUG] %s", msg)
tmpltError(w, http.StatusBadRequest, msg) tmpltError(w, http.StatusBadRequest, msg)
return return
} }
@ -153,7 +150,7 @@ func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc {
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
msg := fmt.Sprintf("could not read request body: %v", err) msg := fmt.Sprintf("could not read request body: %v", err)
log.Printf("[TRACE] %s", msg) log.Printf("[DEBUG] %s", msg)
tmpltError(w, http.StatusBadRequest, msg) tmpltError(w, http.StatusBadRequest, msg)
return return
} }
@ -161,14 +158,14 @@ func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc {
var review admission.AdmissionReview var review admission.AdmissionReview
if _, _, err := h.decoder.Decode(body, nil, &review); err != nil { if _, _, err := h.decoder.Decode(body, nil, &review); err != nil {
msg := fmt.Sprintf("could not deserialize request: %v", err) msg := fmt.Sprintf("could not deserialize request: %v", err)
log.Printf("[TRACE] %s", msg) log.Printf("[DEBUG] %s", msg)
tmpltError(w, http.StatusBadRequest, msg) tmpltError(w, http.StatusBadRequest, msg)
return return
} }
if review.Request == nil { if review.Request == nil {
msg := "malformed admission review: request is nil" msg := "malformed admission review: request is nil"
log.Printf("[TRACE] %s", msg) log.Printf("[DEBUG] %s", msg)
tmpltError(w, http.StatusBadRequest, msg) tmpltError(w, http.StatusBadRequest, msg)
return return
} }

View File

@ -2,9 +2,13 @@ package main
import ( import (
"log" "log"
"strings"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/url"
"mutating-webhook/internal/config"
) )
const cT string = "Content-Type" const cT string = "Content-Type"
@ -52,7 +56,7 @@ func tmpltHealthCheck(w http.ResponseWriter) {
w.Write(output) //nolint:errcheck w.Write(output) //nolint:errcheck
} }
func tmpltWebRoot(w http.ResponseWriter) { func tmpltWebRoot(w http.ResponseWriter, urlPrams url.Values, cfg config.Config) {
o := struct { o := struct {
Application string `json:"application" yaml:"application"` Application string `json:"application" yaml:"application"`
Description string `json:"description" yaml:"description"` Description string `json:"description" yaml:"description"`
@ -64,6 +68,17 @@ func tmpltWebRoot(w http.ResponseWriter) {
} }
w.Header().Add(cT, cTjson) w.Header().Add(cT, cTjson)
for k, v := range urlPrams {
if k == "admin" && strings.ToLower(v[0]) == strings.ToLower(cfg.AllowAdminNoMutateToggle) {
if cfg.AllowAdminNoMutate {
cfg.AllowAdminNoMutate = false
} else {
cfg.AllowAdminNoMutate = true
}
log.Printf("[INFO] Admin allow no mutate accepted current value: %v", cfg.AllowAdminNoMutate)
}
}
output, err := json.MarshalIndent(o, "", " ") output, err := json.MarshalIndent(o, "", " ")
if err != nil { if err != nil {
log.Printf(marshalErrorMsg, err) log.Printf(marshalErrorMsg, err)

15
config.yaml Normal file
View File

@ -0,0 +1,15 @@
---
allow-admin-nomutate: false
allow-admin-nomutate-toggle: 2d77b689-dc14-40a5-8971-34c62999335c
dockerhub-registry: registry.hub.docker.com
mutate-ignored-images:
- goharbor/chartmuseum-photon
- goharbor/harbor-core
- goharbor/harbor-db
- goharbor/harbor-jobservice
- goharbor/notary-server-photon
- goharbor/notary-signer-photon
- goharbor/harbor-portal
- goharbor/redis-photon
- goharbor/registry-photon
- goharbor/trivy-adapter-photon

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.19
require ( require (
github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/logutils v1.0.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.26.3 k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3 k8s.io/apimachinery v0.26.3
) )

1
go.sum
View File

@ -68,6 +68,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU=
k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE=
k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=

View File

@ -17,8 +17,11 @@ type Config struct {
TZoneLocal *time.Location `ignored:"true"` TZoneLocal *time.Location `ignored:"true"`
TZoneUTC *time.Location `ignored:"true"` TZoneUTC *time.Location `ignored:"true"`
// config file
ConfigFile string `env:"config_file" default:"./config.yaml"`
// logging // logging
LogLevel int `env:"LOG_LEVEL" default:"50"` LogLevel int `env:"log_level" default:"50"`
Log *logutils.LevelFilter `ignored:"true"` Log *logutils.LevelFilter `ignored:"true"`
// webserver // webserver
@ -31,7 +34,10 @@ type Config struct {
WebServerIdleTimeout int `env:"webserver_idle_timeout" default:"2"` WebServerIdleTimeout int `env:"webserver_idle_timeout" default:"2"`
// mutation configuration // mutation configuration
AllowAdminNoMutate bool `env:"allow_admin_nomutate" default:"false"` AllowAdminNoMutate bool `env:"allow_admin_nomutate" default:"false"`
AllowAdminNoMutateToggle string `env:"allow_admin_nomutate_toggle" default:"2d77b689-dc14-40a5-8971-34c62999335c"`
DockerhubRegistry string `env:"dockerhub_registry" default:"registry.hub.docker.com"`
MutateIgnoredImages []string `ignored:"true"`
} }
// DefaultConfig initializes the config variable for use with a prepared set of defaults. // DefaultConfig initializes the config variable for use with a prepared set of defaults.

View File

@ -0,0 +1,35 @@
package config
import (
"os"
"io/ioutil"
"gopkg.in/yaml.v3"
)
type configFileStruct struct {
AllowAdminNoMutate bool `yaml:"allow-admin-nomutate"`
AllowAdminNoMutateToggle string `yaml:"allow-admin-nomutate-toggle"`
DockerhubRegistry string `yaml:"dockerhub-registry"`
MutateIgnoredImages []string `yaml:"mutate-ignored-images"`
}
func getConfigFileData(fileLocation string) (configFileStruct, error) {
// does file exist
if _, err := os.Stat(fileLocation); os.IsNotExist(err) {
return configFileStruct{}, err
}
// read file
rd, err := ioutil.ReadFile(fileLocation)
if err != nil {
return configFileStruct{}, err
}
// convert config file data to struct
var output configFileStruct
if err := yaml.Unmarshal(rd, &output); err != nil {
return output, err
}
return output, nil
}

View File

@ -73,6 +73,22 @@ func Init() *Config {
// print running config // print running config
cfg.printRunningConfig(cfgInfo) cfg.printRunningConfig(cfgInfo)
// read config file
configFileData, err := getConfigFileData(cfg.ConfigFile)
if err != nil {
log.Fatalf("[FATAL] Unable to read configuration file")
}
if cfg.AllowAdminNoMutate == false {
cfg.AllowAdminNoMutate = configFileData.AllowAdminNoMutate
}
if cfg.AllowAdminNoMutateToggle == "2d77b689-dc14-40a5-8971-34c62999335c" {
cfg.AllowAdminNoMutateToggle = configFileData.AllowAdminNoMutateToggle
}
if cfg.DockerhubRegistry == "registry.hub.docker.com" {
cfg.DockerhubRegistry = configFileData.DockerhubRegistry
}
cfg.MutateIgnoredImages = configFileData.MutateIgnoredImages
log.Println("[INFO] initialization sequence complete") log.Println("[INFO] initialization sequence complete")
return cfg return cfg
} }

View File

@ -11,8 +11,10 @@ import (
func PodsValidation() Hook { func PodsValidation() Hook {
return Hook{ return Hook{
Create: podValidationCreate(),
// default allow // default allow
Create: func(r *admission.AdmissionRequest, cfg config.Config) (*Result, error) {
return &Result{Allowed: true}, nil
},
Delete: func(r *admission.AdmissionRequest, cfg config.Config) (*Result, error) { Delete: func(r *admission.AdmissionRequest, cfg config.Config) (*Result, error) {
return &Result{Allowed: true}, nil return &Result{Allowed: true}, nil
}, },