Merge pull request #17 from docker/fix-http-timeout

Fix HTTP timeout handler
This commit is contained in:
Jonny Stoten
2024-06-12 20:30:59 +01:00
committed by GitHub
7 changed files with 70 additions and 49 deletions

View File

@@ -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 <<EOT
RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=secret,id=GITHUB_TOKEN <<EOT
set -e
GITHUB_TOKEN=${GITHUB_TOKEN:-$(cat /run/secrets/GITHUB_TOKEN)}
if [ -n "$GITHUB_TOKEN" ]; then
@@ -24,7 +24,8 @@ RUN --mount=type=secret,id=GITHUB_TOKEN <<EOT
go mod download
EOT
# ---
RUN make build
RUN --mount=type=cache,target=$GOPATH/pkg/mod --mount=type=cache,target=/root/.cache/go-build make build
FROM ${BASEIMAGE}

View File

@@ -35,3 +35,10 @@ docker-buildx: docker-buildx-builder
.PHONY: kind-load-image
kind-load-image:
kind load docker-image ${IMG} --name gatekeeper
.PHONY: rollout-restart
rollout-restart:
kubectl -n security rollout restart deployment/attest-provider
.PHONY: reload
reload: docker-buildx kind-load-image rollout-restart

View File

@@ -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 --name gatekeeper
```
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)
<!-- 5a. Install Assign mutation.
<!-- 6. Install Assign mutation.
```bash
kubectl apply -f mutation/external-data-provider-mutation.yaml
```
5b. Test the external data provider by dry-running the following command:
7. Test the external data provider by dry-running the following command:
```bash
kubectl run nginx --image=nginx --dry-run=server -ojson
@@ -103,6 +112,12 @@ The expected JSON output should have the following image field with `_valid` app
]
``` -->
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

34
main.go
View File

@@ -11,10 +11,16 @@ 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 (
handlerTimeout = 15 * time.Second
readHeaderTimeout = 1 * time.Second
)
const (
defaultPort = 8090
@@ -28,6 +34,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 +46,12 @@ func init() {
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler.Handler)
mux.Handle("POST /", http.TimeoutHandler(handler.Handler(), handlerTimeout, timeoutError))
server := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: mux,
ReadHeaderTimeout: time.Duration(15) * time.Second,
ReadHeaderTimeout: readHeaderTimeout,
}
config := &tls.Config{
@@ -79,25 +87,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:
// }
// }
// }

View File

@@ -1,12 +1,12 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"path/filepath"
"runtime/debug"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/oci"
@@ -18,12 +18,17 @@ import (
"k8s.io/klog/v2"
)
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
}
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)
@@ -68,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)

View File

@@ -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"
@@ -14,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,
@@ -30,18 +28,27 @@ 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")
os.Exit(1)
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")
os.Exit(1)
return
}
}

View File

@@ -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"