-
Notifications
You must be signed in to change notification settings - Fork 14
/
handler.go
141 lines (118 loc) · 3.93 KB
/
handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package admissioncontrol
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
admission "k8s.io/api/admission/v1beta1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
log "github.com/go-kit/kit/log"
)
// AdmitFunc validates whether an admission request is valid, and should return
// an admission response that sets Allowed to true or false as needed.
//
// Note: this mirrors the type in k8s source:
// https://github.com/kubernetes/kubernetes/blob/v1.13.0/test/images/webhook/main.go#L43-L44
type AdmitFunc func(reviewRequest *admission.AdmissionReview) (*admission.AdmissionResponse, error)
// AdmissionHandler represents the configuration & associated endpoint for an
// k8s ValidatingAdmissionController webhook. Multiple instances can be created
// with distinct CheckFuncs to handle different admission requirements.
type AdmissionHandler struct {
// The AdmitFunc to invoke for this handler.
AdmitFunc AdmitFunc
// A kitlog.Logger compatible interface
Logger log.Logger
// LimitBytes limits the size of objects the webhook will handle.
LimitBytes int64
// deserializer supports deserializing k8s objects. It can be left null; the
// ServeHTTP function will lazily instantiate a decoder instance.
deserializer runtime.Decoder
}
func (ah *AdmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if ah.deserializer == nil {
runtimeScheme := runtime.NewScheme()
ah.deserializer = serializer.NewCodecFactory(runtimeScheme).UniversalDeserializer()
}
if ah.LimitBytes <= 0 {
ah.LimitBytes = 1024 * 1024 * 1024 // 1MB
}
outgoingReview := &admission.AdmissionReview{
Response: &admission.AdmissionResponse{},
}
w.Header().Set("Content-Type", "application/json")
if err := ah.handleAdmissionRequest(w, r); err != nil {
outgoingReview.Response.Allowed = false
outgoingReview.Response.Result = &meta.Status{
Message: err.Error(),
}
admissionErr, ok := err.(AdmissionError)
if ok {
ah.Logger.Log(
"msg", admissionErr.Message,
"debug", admissionErr.Debug,
)
outgoingReview.Response.Allowed = admissionErr.Allowed
}
res, err := json.Marshal(outgoingReview)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
ah.Logger.Log(
"err", err.Error(),
"msg", "failed to marshal review response",
)
return
}
w.WriteHeader(http.StatusOK)
w.Write(res)
}
}
// AdmissionError represents an error (rejection, serialization error, etc) from
// an AdmissionHandler endpoint/handler.
type AdmissionError struct {
Allowed bool
Message string
Debug string
}
func (e AdmissionError) Error() string {
return fmt.Sprintf("admission error: %s (allowed: %t)", e.Message, e.Allowed)
}
func (ah *AdmissionHandler) handleAdmissionRequest(w http.ResponseWriter, r *http.Request) error {
limitReader := io.LimitReader(r.Body, ah.LimitBytes)
body, err := ioutil.ReadAll(limitReader)
if err != nil {
return AdmissionError{false, "could not read the request body", err.Error()}
}
if body == nil || len(body) == 0 {
return AdmissionError{
false,
"no request body was received",
"the request body was nil/len == 0",
}
}
incomingReview := admission.AdmissionReview{}
if _, _, err := ah.deserializer.Decode(body, nil, &incomingReview); err != nil {
return AdmissionError{false, "decoding the review request failed", err.Error()}
}
if incomingReview.Request == nil {
return errors.New("received invalid AdmissionReview")
}
reviewResponse, err := ah.AdmitFunc(&incomingReview)
if err != nil {
return AdmissionError{false, err.Error(), "the AdmitFunc returned an error"}
}
reviewResponse.UID = incomingReview.Request.UID
review := admission.AdmissionReview{
Response: reviewResponse,
}
res, err := json.Marshal(&review)
if err != nil {
return AdmissionError{false, "marshalling the review response failed", err.Error()}
}
w.WriteHeader(http.StatusOK)
w.Write(res)
return nil
}