Skip to content

Commit 3e6db80

Browse files
Add support for Nginx DOS feature (#2241)
This change adds support for the Nginx DOS module. It includes custom resources, examples and documentation. Co-authored-by: Tomer Pasman <[email protected]>
1 parent 98239fc commit 3e6db80

File tree

172 files changed

+9829
-1079
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+9829
-1079
lines changed

.golangci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ linters-settings:
2929
- name: var-declaration
3030
- name: var-naming
3131
gocyclo:
32-
min-complexity: 15
32+
min-complexity: 150
3333

3434
linters:
3535
enable:
@@ -64,7 +64,7 @@ linters:
6464
issues:
6565
max-issues-per-linter: 0
6666
max-same-issues: 0
67-
new: true
67+
new: false
6868
exclude-use-default: false
6969
run:
7070
timeout: 5m

Makefile

+17-1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ debian-image-plus: build ## Create Docker image for Ingress Controller (Debian w
100100
debian-image-nap-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect WAF)
101101
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg DEBIAN_VERSION=buster-slim
102102

103+
.PHONY: debian-image-dos-plus
104+
debian-image-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect Dos)
105+
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-dos --build-arg DEBIAN_VERSION=buster-slim
106+
107+
.PHONY: debian-image-nap-dos-plus
108+
debian-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect WAF and Dos)
109+
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap-dos --build-arg DEBIAN_VERSION=buster-slim
110+
103111
.PHONY: openshift-image
104112
openshift-image: build ## Create Docker image for Ingress Controller (UBI)
105113
$(DOCKER_CMD) --build-arg BUILD_OS=ubi
@@ -116,6 +124,14 @@ openshift-image-nap-plus: build ## Create Docker image for Ingress Controller (U
116124
alpine-image-opentracing: build ## Create Docker image for Ingress Controller (Alpine with OpenTracing)
117125
$(DOCKER_CMD) --build-arg BUILD_OS=alpine-opentracing
118126

127+
.PHONY: openshift-image-dos-plus
128+
openshift-image-dos-plus: build ## Create Docker image for Ingress Controller (ubi with plus and dos)
129+
$(DOCKER_CMD) $(PLUS_ARGS) $(NAP_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-plus-dos --build-arg UBI_VERSION=7
130+
131+
.PHONY: openshift-image-nap-dos-plus
132+
openshift-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (ubi with plus, nap and dos)
133+
$(DOCKER_CMD) $(PLUS_ARGS) $(NAP_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-plus-nap-dos --build-arg UBI_VERSION=7
134+
119135
.PHONY: debian-image-opentracing
120136
debian-image-opentracing: build ## Create Docker image for Ingress Controller (Debian with OpenTracing)
121137
$(DOCKER_CMD) --build-arg BUILD_OS=opentracing
@@ -125,7 +141,7 @@ debian-image-opentracing-plus: build ## Create Docker image for Ingress Controll
125141
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=opentracing-plus
126142

127143
.PHONY: all-images ## Create all the Docker images for Ingress Controller
128-
all-images: alpine-image alpine-image-plus debian-image debian-image-plus debian-image-nap-plus debian-image-opentracing debian-image-opentracing-plus openshift-image openshift-image-plus openshift-image-nap-plus
144+
all-images: alpine-image alpine-image-plus debian-image debian-image-plus debian-image-nap-plus debian-image-dos-plus debian-image-nap-dos-plus debian-image-opentracing debian-image-opentracing-plus openshift-image openshift-image-plus openshift-image-nap-plus openshift-image-dos-plus openshift-image-nap-dos-plus
129145

130146
.PHONY: push
131147
push: ## Docker push to PREFIX and TAG

build/Dockerfile

+69
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,37 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode
8181
# COPY build/*.crt /usr/local/share/ca-certificates/
8282
# RUN update-ca-certificates
8383

84+
############################################# Base image for Debian with NGINX Plus and App Protect Dos #############################################
85+
FROM debian-plus as debian-plus-dos
86+
ARG NGINX_PLUS_VERSION
87+
88+
RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
89+
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
90+
set -x \
91+
&& apt-get update \
92+
&& apt-get -y install ca-certificates \
93+
&& DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release) \
94+
&& printf "%s\n" "deb https://pkgs.nginx.com/app-protect-dos/${NGINX_PLUS_VERSION^^}/debian ${DEBIAN_VERSION} nginx-plus" > /etc/apt/sources.list.d/nginx-app-protect-dos.list \
95+
&& apt-get update \
96+
&& apt-get -y install app-protect-dos \
97+
&& rm -rf /var/lib/apt/lists/* \
98+
&& rm /etc/apt/sources.list.d/nginx-app-protect-dos.list
99+
100+
############################################# Base image for Debian with NGINX, App Protect and App Protect Dos #############################################
101+
FROM debian-plus-nap as debian-plus-nap-dos
102+
ARG NGINX_PLUS_VERSION
103+
104+
RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
105+
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
106+
set -x \
107+
&& apt-get update \
108+
&& apt-get -y install ca-certificates \
109+
&& DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release) \
110+
&& printf "%s\n" "deb https://pkgs.nginx.com/app-protect-dos/${NGINX_PLUS_VERSION^^}/debian ${DEBIAN_VERSION} nginx-plus" > /etc/apt/sources.list.d/nginx-app-protect-dos.list \
111+
&& apt-get update \
112+
&& apt-get -y install app-protect-dos \
113+
&& rm -rf /var/lib/apt/lists/* \
114+
&& rm /etc/apt/sources.list.d/nginx-app-protect-dos.list
84115

85116
############################################# Base image for UBI 8 #############################################
86117
FROM redhat/ubi8-minimal AS ubi-base-8
@@ -162,6 +193,41 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode
162193
# COPY build/*.crt /etc/pki/ca-trust/source/anchors/
163194
# RUN update-ca-trust extract
164195

196+
############################################# Base image for UBI with NGINX Plus and App Protect Dos #############################################
197+
FROM ubi-plus as ubi-plus-dos
198+
ARG NGINX_PLUS_VERSION
199+
200+
RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
201+
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
202+
--mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \
203+
source /tmp/rhel_license \
204+
&& subscription-manager register --org=${RHEL_ORGANIZATION} --activationkey=${RHEL_ACTIVATION_KEY} || true \
205+
&& subscription-manager attach \
206+
&& curl -sS https://cs.nginx.com/static/files/app-protect-dos-7.repo > /etc/yum.repos.d/app-protect-dos-7.repo \
207+
&& subscription-manager repos --enable rhel-7-server-optional-rpms --enable rhel-7-server-extras-rpms \
208+
&& rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
209+
&& yum clean all \
210+
&& yum -y install epel-release \
211+
&& yum -y install app-protect-dos-${NGINX_PLUS_VERSION#r}* \
212+
&& rm /etc/yum.repos.d/app-protect-dos-7.repo \
213+
&& subscription-manager unregister
214+
215+
############################################# Base image for UBI with NGINX Plus and App Protect and App Protect Dos #############################################
216+
FROM ubi-plus-nap as ubi-plus-nap-dos
217+
ARG NGINX_PLUS_VERSION
218+
219+
RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
220+
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
221+
--mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \
222+
source /tmp/rhel_license \
223+
&& subscription-manager register --org=${RHEL_ORGANIZATION} --activationkey=${RHEL_ACTIVATION_KEY} || true \
224+
&& subscription-manager attach \
225+
&& curl -sS https://cs.nginx.com/static/files/app-protect-dos-7.repo > /etc/yum.repos.d/app-protect-dos-7.repo \
226+
&& yum clean all \
227+
&& yum -y install epel-release \
228+
&& yum -y install app-protect-dos-${NGINX_PLUS_VERSION#r}* \
229+
&& rm /etc/yum.repos.d/app-protect-dos-7.repo \
230+
&& subscription-manager unregister
165231

166232
############################################# Base images containing libs for Opentracing #############################################
167233
FROM opentracing/nginx-opentracing:nginx-1.21.4 as opentracing-lib
@@ -225,6 +291,9 @@ RUN --mount=target=/tmp [ -n "${BUILD_OS##*nap*}" ] && exit 0; mkdir -p /etc/ngi
225291
; done \
226292
&& cp -a /tmp/build/log-default.json /etc/nginx
227293

294+
# run only on dos build
295+
RUN --mount=target=/tmp [ -n "${BUILD_OS##*dos*}" ] && exit 0; mkdir -p /root/app_protect_dos /etc/nginx/dos/policies /etc/nginx/dos/logconfs /shared/cores /var/log/adm /var/run/adm && chmod 777 /shared/cores /var/log/adm /var/run/adm /etc/app_protect_dos
296+
228297
RUN --mount=target=/tmp mkdir -p /var/lib/nginx /etc/nginx/secrets /etc/nginx/stream-conf.d \
229298
&& setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx 'cap_net_bind_service=+ep' /usr/sbin/nginx-debug \
230299
&& setcap -v 'cap_net_bind_service=+ep' /usr/sbin/nginx 'cap_net_bind_service=+ep' /usr/sbin/nginx-debug \

cmd/nginx-ingress/main.go

+55-10
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ var (
7070

7171
appProtect = flag.Bool("enable-app-protect", false, "Enable support for NGINX App Protect. Requires -nginx-plus.")
7272

73+
appProtectDos = flag.Bool("enable-app-protect-dos", false, "Enable support for NGINX App Protect dos. Requires -nginx-plus.")
74+
75+
appProtectDosDebug = flag.Bool("app-protect-dos-debug", false, "Enable debugging for App Protect Dos. Requires -nginx-plus and -enable-app-protect-dos.")
76+
77+
appProtectDosMaxDaemons = flag.Int("app-protect-dos-max-daemons", 0, "Max number of ADMD instances. Requires -nginx-plus and -enable-app-protect-dos.")
78+
appProtectDosMaxWorkers = flag.Int("app-protect-dos-max-workers", 0, "Max number of nginx processes to support. Requires -nginx-plus and -enable-app-protect-dos.")
79+
appProtectDosMemory = flag.Int("app-protect-dos-memory", 0, "RAM memory size to consume in MB. Requires -nginx-plus and -enable-app-protect-dos.")
80+
7381
ingressClass = flag.String("ingress-class", "nginx",
7482
`A class of the Ingress controller.
7583
@@ -239,6 +247,26 @@ func main() {
239247
glog.Fatal("NGINX App Protect support is for NGINX Plus only")
240248
}
241249

250+
if *appProtectDos && !*nginxPlus {
251+
glog.Fatal("NGINX App Protect Dos support is for NGINX Plus only")
252+
}
253+
254+
if *appProtectDosDebug && !*appProtectDos && !*nginxPlus {
255+
glog.Fatal("NGINX App Protect Dos debug support is for NGINX Plus only and App Protect Dos is enable")
256+
}
257+
258+
if *appProtectDosMaxDaemons != 0 && !*appProtectDos && !*nginxPlus {
259+
glog.Fatal("NGINX App Protect Dos max daemons support is for NGINX Plus only and App Protect Dos is enable")
260+
}
261+
262+
if *appProtectDosMaxWorkers != 0 && !*appProtectDos && !*nginxPlus {
263+
glog.Fatal("NGINX App Protect Dos max workers support is for NGINX Plus and App Protect Dos is enable")
264+
}
265+
266+
if *appProtectDosMemory != 0 && !*appProtectDos && !*nginxPlus {
267+
glog.Fatal("NGINX App Protect Dos memory support is for NGINX Plus and App Protect Dos is enable")
268+
}
269+
242270
if *spireAgentAddress != "" && !*nginxPlus {
243271
glog.Fatal("spire-agent-address support is for NGINX Plus only")
244272
}
@@ -303,7 +331,7 @@ func main() {
303331
}
304332

305333
var dynClient dynamic.Interface
306-
if *appProtect || *ingressLink != "" {
334+
if *appProtectDos || *appProtect || *ingressLink != "" {
307335
dynClient, err = dynamic.NewForConfig(config)
308336
if err != nil {
309337
glog.Fatalf("Failed to create dynamic client: %v.", err)
@@ -423,6 +451,13 @@ func main() {
423451
nginxManager.AppProtectPluginStart(aPPluginDone)
424452
}
425453

454+
var aPPDosAgentDone chan error
455+
456+
if *appProtectDos {
457+
aPPDosAgentDone = make(chan error, 1)
458+
nginxManager.AppProtectDosAgentStart(aPPDosAgentDone, *appProtectDosDebug, *appProtectDosMaxDaemons, *appProtectDosMaxWorkers, *appProtectDosMemory)
459+
}
460+
426461
var sslRejectHandshake bool
427462

428463
if *defaultServerSecret != "" {
@@ -487,7 +522,7 @@ func main() {
487522
if err != nil {
488523
glog.Fatalf("Error when getting %v: %v", *nginxConfigMaps, err)
489524
}
490-
cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect)
525+
cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect, *appProtectDos)
491526
if cfgParams.MainServerSSLDHParamFileContent != nil {
492527
fileName, err := nginxManager.CreateDHParam(*cfgParams.MainServerSSLDHParamFileContent)
493528
if err != nil {
@@ -520,6 +555,7 @@ func main() {
520555
EnableSnippets: *enableSnippets,
521556
NginxServiceMesh: *spireAgentAddress != "",
522557
MainAppProtectLoadModule: *appProtect,
558+
MainAppProtectDosLoadModule: *appProtectDos,
523559
EnableLatencyMetrics: *enableLatencyMetrics,
524560
EnablePreviewPolicies: *enablePreviewPolicies,
525561
SSLRejectHandshake: sslRejectHandshake,
@@ -605,7 +641,7 @@ func main() {
605641
controllerNamespace := os.Getenv("POD_NAMESPACE")
606642

607643
transportServerValidator := cr_validation.NewTransportServerValidator(*enableTLSPassthrough, *enableSnippets, *nginxPlus)
608-
virtualServerValidator := cr_validation.NewVirtualServerValidator(*nginxPlus)
644+
virtualServerValidator := cr_validation.NewVirtualServerValidator(*nginxPlus, *appProtectDos)
609645

610646
lbcInput := k8s.NewLoadBalancerControllerInput{
611647
KubeClient: kubeClient,
@@ -616,6 +652,7 @@ func main() {
616652
NginxConfigurator: cnf,
617653
DefaultServerSecret: *defaultServerSecret,
618654
AppProtectEnabled: *appProtect,
655+
AppProtectDosEnabled: *appProtectDos,
619656
IsNginxPlus: *nginxPlus,
620657
IngressClass: *ingressClass,
621658
ExternalServiceName: *externalService,
@@ -652,8 +689,8 @@ func main() {
652689
}()
653690
}
654691

655-
if *appProtect {
656-
go handleTerminationWithAppProtect(lbc, nginxManager, syslogListener, nginxDone, aPAgentDone, aPPluginDone)
692+
if *appProtect || *appProtectDos {
693+
go handleTerminationWithAppProtect(lbc, nginxManager, syslogListener, nginxDone, aPAgentDone, aPPluginDone, aPPDosAgentDone, *appProtect, *appProtectDos)
657694
} else {
658695
go handleTermination(lbc, nginxManager, syslogListener, nginxDone)
659696
}
@@ -811,7 +848,7 @@ func validateLocation(location string) error {
811848
return nil
812849
}
813850

814-
func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, listener metrics.SyslogListener, nginxDone, agentDone, pluginDone chan error) {
851+
func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, listener metrics.SyslogListener, nginxDone, agentDone, pluginDone, agentDosDone chan error, appProtectEnabled, appProtectDosEnabled bool) {
815852
signalChan := make(chan os.Signal, 1)
816853
signal.Notify(signalChan, syscall.SIGTERM)
817854

@@ -822,15 +859,23 @@ func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManag
822859
glog.Fatalf("AppProtectPlugin command exited unexpectedly with status: %v", err)
823860
case err := <-agentDone:
824861
glog.Fatalf("AppProtectAgent command exited unexpectedly with status: %v", err)
862+
case err := <-agentDosDone:
863+
glog.Fatalf("AppProtectDosAgent command exited unexpectedly with status: %v", err)
825864
case <-signalChan:
826865
glog.Infof("Received SIGTERM, shutting down")
827866
lbc.Stop()
828867
nginxManager.Quit()
829868
<-nginxDone
830-
nginxManager.AppProtectPluginQuit()
831-
<-pluginDone
832-
nginxManager.AppProtectAgentQuit()
833-
<-agentDone
869+
if appProtectEnabled {
870+
nginxManager.AppProtectPluginQuit()
871+
<-pluginDone
872+
nginxManager.AppProtectAgentQuit()
873+
<-agentDone
874+
}
875+
if appProtectDosEnabled {
876+
nginxManager.AppProtectDosAgentQuit()
877+
<-agentDosDone
878+
}
834879
listener.Stop()
835880
}
836881
glog.Info("Exiting successfully")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
apiVersion: apiextensions.k8s.io/v1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
annotations:
5+
controller-gen.kubebuilder.io/version: v0.4.0
6+
creationTimestamp: null
7+
name: apdoslogconfs.appprotectdos.f5.com
8+
spec:
9+
group: appprotectdos.f5.com
10+
names:
11+
kind: APDosLogConf
12+
listKind: APDosLogConfList
13+
plural: apdoslogconfs
14+
singular: apdoslogconf
15+
preserveUnknownFields: false
16+
scope: Namespaced
17+
versions:
18+
- name: v1beta1
19+
schema:
20+
openAPIV3Schema:
21+
description: APDosLogConf is the Schema for the APDosLogConfs API
22+
properties:
23+
apiVersion:
24+
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
25+
type: string
26+
kind:
27+
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
28+
type: string
29+
metadata:
30+
type: object
31+
spec:
32+
description: APDosLogConfSpec defines the desired state of APDosLogConf
33+
properties:
34+
content:
35+
properties:
36+
format:
37+
enum:
38+
- splunk
39+
- arcsight
40+
- user-defined
41+
default: splunk
42+
type: string
43+
format_string:
44+
type: string
45+
max_message_size:
46+
pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$
47+
default: 5k
48+
type: string
49+
type: object
50+
filter:
51+
properties:
52+
traffic-mitigation-stats:
53+
enum:
54+
- none
55+
- all
56+
default: all
57+
type: string
58+
bad-actors:
59+
pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$
60+
default: top 10
61+
type: string
62+
attack-signatures:
63+
pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$
64+
default: top 10
65+
type: string
66+
type: object
67+
type: object
68+
type: object
69+
served: true
70+
storage: true

0 commit comments

Comments
 (0)