-
Notifications
You must be signed in to change notification settings - Fork 1
/
mutatingwebhook.go
124 lines (101 loc) · 3.56 KB
/
mutatingwebhook.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
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"strings"
networkingv1 "k8s.io/api/networking/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
const (
AnnotationEnabled = "ingress-waf/enabled"
AnnotationThreshold = "ingress-waf/threshold"
AnnotationRequestBodyLimit = "ingress-waf/request-body-limit"
AnnotationResponseBodyLimit = "ingress-waf/response-body-limit"
AnnotationAdditionalCRS = "ingress-waf/additional-crs"
)
type networkHealthSidecarInjector struct {
Client client.Client
decoder *admission.Decoder
}
func (a *networkHealthSidecarInjector) InjectDecoder(d *admission.Decoder) error {
a.decoder = d
return nil
}
func (a *networkHealthSidecarInjector) Handle(ctx context.Context, req admission.Request) admission.Response {
// unmarshal
ingress := &networkingv1.Ingress{}
err := a.decoder.Decode(req, ingress)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
// process
prepare(ingress)
err = enableWAF(ingress)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
// marshal
marshaledIngress, err := json.Marshal(ingress)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledIngress)
}
func prepare(ingress *networkingv1.Ingress) {
if ingress.Annotations == nil {
ingress.Annotations = map[string]string{}
}
_, found := ingress.Annotations[AnnotationEnabled]
if !found {
ingress.Annotations[AnnotationEnabled] = "true"
}
_, found = ingress.Annotations[AnnotationThreshold]
if !found {
ingress.Annotations[AnnotationThreshold] = "5"
}
}
func enableWAF(ingress *networkingv1.Ingress) error {
if strings.ToLower(ingress.Annotations[AnnotationEnabled]) != "true" {
return nil
}
threshold := ingress.Annotations[AnnotationThreshold]
additionalCRS := ingress.Annotations[AnnotationAdditionalCRS]
secAuditLog := os.Getenv("SEC_AUDIT_LOG")
ingress.Annotations["nginx.ingress.kubernetes.io/enable-modsecurity"] = "true"
ingress.Annotations["nginx.ingress.kubernetes.io/enable-owasp-core-rules"] = "true"
ingress.Annotations["nginx.ingress.kubernetes.io/modsecurity-snippet"] = fmt.Sprintf(`
SecRuleEngine On
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus 403
SecAuditLog %s
SecAuditLogParts ABFHZ
SecAction "id:900110,phase:1,log,pass,t:none,setvar:tx.inbound_anomaly_score_threshold=%s"
SecRule TX:ANOMALY_SCORE "@gt 0" "id:10001,phase:5,auditlog,log,pass,msg:\'Anomaly Score %%{TX.anomaly_score} Threshold %%{TX.inbound_anomaly_score_threshold}\'"
%s
`, secAuditLog, threshold, additionalCRS)
requestBodyLimit, found := ingress.Annotations[AnnotationRequestBodyLimit]
if found {
requestBodyLimit, err := strconv.Atoi(requestBodyLimit)
if err != nil {
return fmt.Errorf("%s is not a number (bytes): %v", AnnotationRequestBodyLimit, err)
}
ingress.Annotations["nginx.ingress.kubernetes.io/modsecurity-snippet"] +=
fmt.Sprintf("SecRequestBodyLimit %d\n", requestBodyLimit)
}
responseBodyLimit, found := ingress.Annotations[AnnotationResponseBodyLimit]
if found {
responseBodyLimit, err := strconv.Atoi(responseBodyLimit)
if err != nil {
return fmt.Errorf("%s is not a number (bytes): %v", AnnotationResponseBodyLimit, err)
}
ingress.Annotations["nginx.ingress.kubernetes.io/modsecurity-snippet"] +=
fmt.Sprintf("SecResponseBodyLimit %d\n", responseBodyLimit)
}
ingress.Annotations["nginx.ingress.kubernetes.io/modsecurity-transaction-id"] = "$request_id"
return nil
}