Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ import (
log "github.com/sirupsen/logrus"
)

// maxRequestBodyBytes caps the size of an incoming AdmissionReview body to
// guard against memory exhaustion from oversized or malicious requests. An
// AdmissionReview carries both the old and new object, and Grafana dashboards
// can be large, so the default is generous; it is configurable via the
// --max-request-body-bytes flag.
var maxRequestBodyBytes int64 = 16 << 20 // 16 MiB

var (
// Create a histogram metric to track the duration of requests in milliseconds
requestDuration = prometheus.NewHistogramVec(
Expand Down Expand Up @@ -56,9 +63,10 @@ func handleAdmissionReview(w http.ResponseWriter, r *http.Request) {
start := time.Now()

var admissionReviewReq admissionv1.AdmissionReview
r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodyBytes)
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read request body", http.StatusInternalServerError)
http.Error(w, "failed to read request body", http.StatusRequestEntityTooLarge)
return
}

Expand Down Expand Up @@ -227,6 +235,7 @@ func printDifferences(owner string, oldMap, newMap map[string]interface{}) {
func main() {
port := flag.String("port", "8443", "Webhook server port")
logLevel := flag.String("log-level", "info", "Log level (debug, info, warn, error, fatal, panic)")
flag.Int64Var(&maxRequestBodyBytes, "max-request-body-bytes", maxRequestBodyBytes, "Maximum accepted request body size in bytes")
flag.Parse()

addr := fmt.Sprintf(":%s", *port)
Expand Down
17 changes: 17 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ func TestHandleAdmissionReview_NilRequest(t *testing.T) {
}
}

func TestHandleAdmissionReview_BodyTooLarge(t *testing.T) {
// A body exceeding maxRequestBodyBytes must be rejected rather than
// read fully into memory.
oversized := bytes.Repeat([]byte("a"), int(maxRequestBodyBytes)+1)
req := httptest.NewRequest(http.MethodPost, "/validate", bytes.NewReader(oversized))
w := httptest.NewRecorder()

handleAdmissionReview(w, req)

resp := w.Result()
defer resp.Body.Close()

if resp.StatusCode != http.StatusRequestEntityTooLarge {
t.Errorf("Expected status code 413, got %d", resp.StatusCode)
}
}

func TestHandleAdmissionReview_StatusSyncRevisionChange(t *testing.T) {
reqBody := admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{
Expand Down
Loading