From 14099e3b98b397e177667579efbe4eec7684b6e2 Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Tue, 11 Jun 2024 10:58:45 +0100 Subject: [PATCH 1/9] Fix getting started instructions in README and add Makefile task for rebuild and reload --- Makefile | 9 ++++++++- README.md | 25 ++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index d64abb8..12d7113 100644 --- a/Makefile +++ b/Makefile @@ -34,4 +34,11 @@ docker-buildx: docker-buildx-builder .PHONY: kind-load-image kind-load-image: - kind load docker-image ${IMG} --name gatekeeper + kind load docker-image ${IMG} + +.PHONY: rollout-restart +rollout-restart: + kubectl -n security rollout restart deployment/attest-provider + +.PHONY: reload +reload: docker-buildx kind-load-image rollout-restart diff --git a/README.md b/README.md index 5b81900..b5a98de 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ OPA Gatekeeper external data provider implementation for Docker attest library i 1. Create a [kind cluster](https://kind.sigs.k8s.io/docs/user/quick-start/). +```bash +kind create cluster +``` + 2. Install the latest version of Gatekeeper and enable the external data feature. ```bash @@ -23,6 +27,9 @@ helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts helm install gatekeeper/gatekeeper \ --set enableExternalData=true \ --set validatingWebhookFailurePolicy=Fail \ + --set validatingWebhookTimeoutSeconds=30 \ + --set postInstall.probeWebhook.enabled=false \ + --set postInstall.labelNamespace.enabled=false \ --name-template=gatekeeper \ --namespace security \ --create-namespace @@ -61,31 +68,33 @@ helm install attest-provider charts/external-data-provider \ --set provider.tls.caBundle="$(cat certs/ca.crt | base64 | tr -d '\n\r')" \ --namespace "${NAMESPACE:-gatekeeper-system}" \ --create-namespace +``` -4a. Install constraint template and constraint. +4. Install constraint template and constraint. ```bash kubectl apply -f validation/attest-constraint-template.yaml kubectl apply -f validation/attest-constraint.yaml ``` -4b. Test the external data provider by dry-running the following command: +5. Test the external data provider by dry-running the following command: ```bash kubectl create ns test -kubectl run nginx -n test --dry-run=server -ojson +kubectl run nginx --image nginx -n test --dry-run=server -ojson ``` Gatekeeper should deny the pod admission above because the image `nginx` is missing signed annotations but has an image policy in tuf-staging. TODO: implement mutating policy (tag -> digest) - +1. To reload the attest-provider image after making changes, run the following command: + +```bash +make reload +``` + 1. Uninstall the external data provider and Gatekeeper. ```bash From db036df9d59ea4ff84e50705f85db4853e8b6049 Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Tue, 11 Jun 2024 11:22:57 +0100 Subject: [PATCH 2/9] Cache go deps and build cache --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 10cc9f8..797d2b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY . . # --- This block can be replaced by `RUN go mod download` when github.com/docker/attest is public ENV GOPRIVATE="github.com/docker/attest" -RUN --mount=type=secret,id=GITHUB_TOKEN < Date: Tue, 11 Jun 2024 11:16:11 +0100 Subject: [PATCH 3/9] Use http method in route pattern --- main.go | 2 +- pkg/handler/handler.go | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/main.go b/main.go index c51664d..8c82df6 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,7 @@ func init() { func main() { mux := http.NewServeMux() - mux.HandleFunc("/", handler.Handler) + mux.HandleFunc("POST /", handler.Handler) server := &http.Server{ Addr: fmt.Sprintf(":%d", port), diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index fa8b03a..9011a98 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -19,11 +19,6 @@ import ( ) func Handler(w http.ResponseWriter, req *http.Request) { - // only accept POST requests - if req.Method != http.MethodPost { - utils.SendResponse(nil, "only POST is allowed", w) - return - } // read request body requestBody, err := io.ReadAll(req.Body) From f9195a21339523a8df3cb6aa5ee018e22572c7c2 Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Tue, 11 Jun 2024 11:29:09 +0100 Subject: [PATCH 4/9] Don't exit on failure to write OK to panic on marshal error as this would be a developer error --- pkg/utils/utils.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f97a42b..b87218d 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,7 +3,6 @@ package utils import ( "encoding/json" "net/http" - "os" "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" "k8s.io/klog/v2" @@ -35,13 +34,13 @@ func SendResponse(results *[]externaldata.Item, systemErr string, w http.Respons body, err := json.Marshal(response) if err != nil { klog.ErrorS(err, "unable to marshal response") - os.Exit(1) + panic(err) } w.Header().Set("Content-Type", "application/json") _, err = w.Write(body) if err != nil { klog.ErrorS(err, "unable to write response") - os.Exit(1) + return } } From 11a0d75e80d3326dd697858d0a8c20e915f10c04 Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Tue, 11 Jun 2024 11:19:52 +0100 Subject: [PATCH 5/9] Swap buggy timeout code for http.TimeoutHandler --- main.go | 30 ++++++------------------------ pkg/handler/handler.go | 13 ++++++++++++- pkg/utils/utils.go | 18 +++++++++++++----- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/main.go b/main.go index 8c82df6..fb281e5 100644 --- a/main.go +++ b/main.go @@ -11,11 +11,13 @@ import ( "time" "github.com/open-policy-agent/gatekeeper-external-data-provider/pkg/handler" + "github.com/open-policy-agent/gatekeeper-external-data-provider/pkg/utils" "k8s.io/klog/v2" ) const ( + timeout = 15 * time.Second defaultPort = 8090 certName = "tls.crt" @@ -28,6 +30,8 @@ var ( port int ) +var timeoutError = string(utils.GatekeeperError("operation timed out")) + func init() { klog.InitFlags(nil) flag.StringVar(&certDir, "cert-dir", "", "path to directory containing TLS certificates") @@ -38,12 +42,12 @@ func init() { func main() { mux := http.NewServeMux() - mux.HandleFunc("POST /", handler.Handler) + mux.Handle("POST /", http.TimeoutHandler(handler.Handler(), timeout, timeoutError)) server := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: mux, - ReadHeaderTimeout: time.Duration(15) * time.Second, + ReadHeaderTimeout: 15 * time.Second, } config := &tls.Config{ @@ -79,25 +83,3 @@ func main() { os.Exit(1) } } - -// TODO: root cause Content-Length header error -// func processTimeout(h http.HandlerFunc, duration time.Duration) http.HandlerFunc { -// return func(w http.ResponseWriter, r *http.Request) { -// ctx, cancel := context.WithTimeout(r.Context(), duration) -// defer cancel() - -// r = r.WithContext(ctx) - -// processDone := make(chan bool) -// go func() { -// h(w, r) -// processDone <- true -// }() - -// select { -// case <-ctx.Done(): -// utils.SendResponse(nil, "operation timed out", w) -// case <-processDone: -// } -// } -// } diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 9011a98..8ae84bc 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "path/filepath" + "runtime/debug" "github.com/docker/attest/pkg/attest" "github.com/docker/attest/pkg/oci" @@ -18,7 +19,17 @@ import ( "k8s.io/klog/v2" ) -func Handler(w http.ResponseWriter, req *http.Request) { +func Handler() http.Handler { + return http.HandlerFunc(handler) +} + +func handler(w http.ResponseWriter, req *http.Request) { + defer func() { + if r := recover(); r != nil { + klog.Error(string(debug.Stack())) + klog.ErrorS(fmt.Errorf("%v", r), "panic occurred") + } + }() // read request body requestBody, err := io.ReadAll(req.Body) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index b87218d..87fcfa2 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -13,8 +13,7 @@ const ( kind = "ProviderResponse" ) -// sendResponse sends back the response to Gatekeeper. -func SendResponse(results *[]externaldata.Item, systemErr string, w http.ResponseWriter) { +func GatekeeperResponse(results *[]externaldata.Item, systemErr string) []byte { response := externaldata.ProviderResponse{ APIVersion: apiVersion, Kind: kind, @@ -29,16 +28,25 @@ func SendResponse(results *[]externaldata.Item, systemErr string, w http.Respons response.Response.SystemError = systemErr } - klog.InfoS("sending response", "response", response) - body, err := json.Marshal(response) if err != nil { klog.ErrorS(err, "unable to marshal response") panic(err) } + return body +} + +func GatekeeperError(systemErr string) []byte { + return GatekeeperResponse(nil, systemErr) +} + +// sendResponse sends back the response to Gatekeeper. +func SendResponse(results *[]externaldata.Item, systemErr string, w http.ResponseWriter) { + body := GatekeeperResponse(results, systemErr) + klog.InfoS("sending response", "response", string(body)) w.Header().Set("Content-Type", "application/json") - _, err = w.Write(body) + _, err := w.Write(body) if err != nil { klog.ErrorS(err, "unable to write response") return From 0e128d608357087591ae60e997a2848e8910ddef Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Tue, 11 Jun 2024 11:20:42 +0100 Subject: [PATCH 6/9] Pass context from http request to attest lib --- pkg/handler/handler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 8ae84bc..32e719d 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -1,7 +1,6 @@ package handler import ( - "context" "encoding/json" "fmt" "io" @@ -74,7 +73,7 @@ func handler(w http.ResponseWriter, req *http.Request) { } // verify attestations - ctx := context.TODO() + ctx := req.Context() debug := true ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(debug)) policy, err := attest.Verify(ctx, opts, resolver) From b6a388ed943f8828c3cb2a3426fd3349f9197b68 Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Wed, 12 Jun 2024 11:51:36 +0100 Subject: [PATCH 7/9] Use lower timeout for reading headers --- main.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index fb281e5..d25a703 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,11 @@ import ( ) const ( - timeout = 15 * time.Second + handlerTimeout = 15 * time.Second + readHeaderTimeout = 1 * time.Second +) + +const ( defaultPort = 8090 certName = "tls.crt" @@ -42,12 +46,12 @@ func init() { func main() { mux := http.NewServeMux() - mux.Handle("POST /", http.TimeoutHandler(handler.Handler(), timeout, timeoutError)) + mux.Handle("POST /", http.TimeoutHandler(handler.Handler(), handlerTimeout, timeoutError)) server := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: mux, - ReadHeaderTimeout: 15 * time.Second, + ReadHeaderTimeout: readHeaderTimeout, } config := &tls.Config{ From f276ebc37b08217b83387458659994f153a60423 Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Wed, 12 Jun 2024 11:51:57 +0100 Subject: [PATCH 8/9] Have test certs last for 10 years --- scripts/generate-tls-cert.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate-tls-cert.sh b/scripts/generate-tls-cert.sh index 16243c3..a8586ab 100755 --- a/scripts/generate-tls-cert.sh +++ b/scripts/generate-tls-cert.sh @@ -12,13 +12,13 @@ generate() { # generate CA key and certificate echo "Generating CA key and certificate for attest-provider..." openssl genrsa -out ca.key 2048 - openssl req -new -x509 -days 1 -key ca.key -subj "/O=Gatekeeper/CN=Gatekeeper Root CA" -out ca.crt + openssl req -new -x509 -days 3650 -key ca.key -subj "/O=Gatekeeper/CN=Gatekeeper Root CA" -out ca.crt # generate server key and certificate echo "Generating server key and certificate for attest-provider..." openssl genrsa -out tls.key 2048 openssl req -newkey rsa:2048 -nodes -keyout tls.key -subj "/CN=attest-provider.${NAMESPACE}" -out server.csr - openssl x509 -req -extfile <(printf "subjectAltName=DNS:attest-provider.%s" "${NAMESPACE}") -days 1 -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt + openssl x509 -req -extfile <(printf "subjectAltName=DNS:attest-provider.%s" "${NAMESPACE}") -days 3650 -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt } mkdir -p "${REPO_ROOT}/certs" From 60c8fac0ae2421140822792b5b16d62b02b61724 Mon Sep 17 00:00:00 2001 From: Jonny Stoten Date: Wed, 12 Jun 2024 12:33:49 +0100 Subject: [PATCH 9/9] Add --name gatekeeper back to make task --- Makefile | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 12d7113..672e78e 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ docker-buildx: docker-buildx-builder .PHONY: kind-load-image kind-load-image: - kind load docker-image ${IMG} + kind load docker-image ${IMG} --name gatekeeper .PHONY: rollout-restart rollout-restart: diff --git a/README.md b/README.md index b5a98de..2a6cba6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ OPA Gatekeeper external data provider implementation for Docker attest library i 1. Create a [kind cluster](https://kind.sigs.k8s.io/docs/user/quick-start/). ```bash -kind create cluster +kind create cluster --name gatekeeper ``` 2. Install the latest version of Gatekeeper and enable the external data feature.