Skip to content

Commit

Permalink
Add support for Nginx DOS feature (#2241)
Browse files Browse the repository at this point in the history
This change adds support for the Nginx DOS module. It includes custom resources, examples and documentation.

Co-authored-by: Tomer Pasman <[email protected]>
  • Loading branch information
soneillf5 and tomerpasman authored Dec 16, 2021
1 parent 98239fc commit 3e6db80
Show file tree
Hide file tree
Showing 172 changed files with 9,829 additions and 1,079 deletions.
4 changes: 2 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ linters-settings:
- name: var-declaration
- name: var-naming
gocyclo:
min-complexity: 15
min-complexity: 150

linters:
enable:
Expand Down Expand Up @@ -64,7 +64,7 @@ linters:
issues:
max-issues-per-linter: 0
max-same-issues: 0
new: true
new: false
exclude-use-default: false
run:
timeout: 5m
18 changes: 17 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ debian-image-plus: build ## Create Docker image for Ingress Controller (Debian w
debian-image-nap-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect WAF)
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg DEBIAN_VERSION=buster-slim

.PHONY: debian-image-dos-plus
debian-image-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect Dos)
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-dos --build-arg DEBIAN_VERSION=buster-slim

.PHONY: debian-image-nap-dos-plus
debian-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect WAF and Dos)
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap-dos --build-arg DEBIAN_VERSION=buster-slim

.PHONY: openshift-image
openshift-image: build ## Create Docker image for Ingress Controller (UBI)
$(DOCKER_CMD) --build-arg BUILD_OS=ubi
Expand All @@ -116,6 +124,14 @@ openshift-image-nap-plus: build ## Create Docker image for Ingress Controller (U
alpine-image-opentracing: build ## Create Docker image for Ingress Controller (Alpine with OpenTracing)
$(DOCKER_CMD) --build-arg BUILD_OS=alpine-opentracing

.PHONY: openshift-image-dos-plus
openshift-image-dos-plus: build ## Create Docker image for Ingress Controller (ubi with plus and dos)
$(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

.PHONY: openshift-image-nap-dos-plus
openshift-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (ubi with plus, nap and dos)
$(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

.PHONY: debian-image-opentracing
debian-image-opentracing: build ## Create Docker image for Ingress Controller (Debian with OpenTracing)
$(DOCKER_CMD) --build-arg BUILD_OS=opentracing
Expand All @@ -125,7 +141,7 @@ debian-image-opentracing-plus: build ## Create Docker image for Ingress Controll
$(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=opentracing-plus

.PHONY: all-images ## Create all the Docker images for Ingress Controller
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
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

.PHONY: push
push: ## Docker push to PREFIX and TAG
Expand Down
69 changes: 69 additions & 0 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,37 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode
# COPY build/*.crt /usr/local/share/ca-certificates/
# RUN update-ca-certificates

############################################# Base image for Debian with NGINX Plus and App Protect Dos #############################################
FROM debian-plus as debian-plus-dos
ARG NGINX_PLUS_VERSION

RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
set -x \
&& apt-get update \
&& apt-get -y install ca-certificates \
&& DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release) \
&& 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 \
&& apt-get update \
&& apt-get -y install app-protect-dos \
&& rm -rf /var/lib/apt/lists/* \
&& rm /etc/apt/sources.list.d/nginx-app-protect-dos.list

############################################# Base image for Debian with NGINX, App Protect and App Protect Dos #############################################
FROM debian-plus-nap as debian-plus-nap-dos
ARG NGINX_PLUS_VERSION

RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
set -x \
&& apt-get update \
&& apt-get -y install ca-certificates \
&& DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release) \
&& 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 \
&& apt-get update \
&& apt-get -y install app-protect-dos \
&& rm -rf /var/lib/apt/lists/* \
&& rm /etc/apt/sources.list.d/nginx-app-protect-dos.list

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

############################################# Base image for UBI with NGINX Plus and App Protect Dos #############################################
FROM ubi-plus as ubi-plus-dos
ARG NGINX_PLUS_VERSION

RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
--mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \
source /tmp/rhel_license \
&& subscription-manager register --org=${RHEL_ORGANIZATION} --activationkey=${RHEL_ACTIVATION_KEY} || true \
&& subscription-manager attach \
&& curl -sS https://cs.nginx.com/static/files/app-protect-dos-7.repo > /etc/yum.repos.d/app-protect-dos-7.repo \
&& subscription-manager repos --enable rhel-7-server-optional-rpms --enable rhel-7-server-extras-rpms \
&& rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
&& yum clean all \
&& yum -y install epel-release \
&& yum -y install app-protect-dos-${NGINX_PLUS_VERSION#r}* \
&& rm /etc/yum.repos.d/app-protect-dos-7.repo \
&& subscription-manager unregister

############################################# Base image for UBI with NGINX Plus and App Protect and App Protect Dos #############################################
FROM ubi-plus-nap as ubi-plus-nap-dos
ARG NGINX_PLUS_VERSION

RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \
--mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \
--mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \
source /tmp/rhel_license \
&& subscription-manager register --org=${RHEL_ORGANIZATION} --activationkey=${RHEL_ACTIVATION_KEY} || true \
&& subscription-manager attach \
&& curl -sS https://cs.nginx.com/static/files/app-protect-dos-7.repo > /etc/yum.repos.d/app-protect-dos-7.repo \
&& yum clean all \
&& yum -y install epel-release \
&& yum -y install app-protect-dos-${NGINX_PLUS_VERSION#r}* \
&& rm /etc/yum.repos.d/app-protect-dos-7.repo \
&& subscription-manager unregister

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

# run only on dos build
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

RUN --mount=target=/tmp mkdir -p /var/lib/nginx /etc/nginx/secrets /etc/nginx/stream-conf.d \
&& setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx 'cap_net_bind_service=+ep' /usr/sbin/nginx-debug \
&& setcap -v 'cap_net_bind_service=+ep' /usr/sbin/nginx 'cap_net_bind_service=+ep' /usr/sbin/nginx-debug \
Expand Down
65 changes: 55 additions & 10 deletions cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ var (

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

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

appProtectDosDebug = flag.Bool("app-protect-dos-debug", false, "Enable debugging for App Protect Dos. Requires -nginx-plus and -enable-app-protect-dos.")

appProtectDosMaxDaemons = flag.Int("app-protect-dos-max-daemons", 0, "Max number of ADMD instances. Requires -nginx-plus and -enable-app-protect-dos.")
appProtectDosMaxWorkers = flag.Int("app-protect-dos-max-workers", 0, "Max number of nginx processes to support. Requires -nginx-plus and -enable-app-protect-dos.")
appProtectDosMemory = flag.Int("app-protect-dos-memory", 0, "RAM memory size to consume in MB. Requires -nginx-plus and -enable-app-protect-dos.")

ingressClass = flag.String("ingress-class", "nginx",
`A class of the Ingress controller.
Expand Down Expand Up @@ -239,6 +247,26 @@ func main() {
glog.Fatal("NGINX App Protect support is for NGINX Plus only")
}

if *appProtectDos && !*nginxPlus {
glog.Fatal("NGINX App Protect Dos support is for NGINX Plus only")
}

if *appProtectDosDebug && !*appProtectDos && !*nginxPlus {
glog.Fatal("NGINX App Protect Dos debug support is for NGINX Plus only and App Protect Dos is enable")
}

if *appProtectDosMaxDaemons != 0 && !*appProtectDos && !*nginxPlus {
glog.Fatal("NGINX App Protect Dos max daemons support is for NGINX Plus only and App Protect Dos is enable")
}

if *appProtectDosMaxWorkers != 0 && !*appProtectDos && !*nginxPlus {
glog.Fatal("NGINX App Protect Dos max workers support is for NGINX Plus and App Protect Dos is enable")
}

if *appProtectDosMemory != 0 && !*appProtectDos && !*nginxPlus {
glog.Fatal("NGINX App Protect Dos memory support is for NGINX Plus and App Protect Dos is enable")
}

if *spireAgentAddress != "" && !*nginxPlus {
glog.Fatal("spire-agent-address support is for NGINX Plus only")
}
Expand Down Expand Up @@ -303,7 +331,7 @@ func main() {
}

var dynClient dynamic.Interface
if *appProtect || *ingressLink != "" {
if *appProtectDos || *appProtect || *ingressLink != "" {
dynClient, err = dynamic.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create dynamic client: %v.", err)
Expand Down Expand Up @@ -423,6 +451,13 @@ func main() {
nginxManager.AppProtectPluginStart(aPPluginDone)
}

var aPPDosAgentDone chan error

if *appProtectDos {
aPPDosAgentDone = make(chan error, 1)
nginxManager.AppProtectDosAgentStart(aPPDosAgentDone, *appProtectDosDebug, *appProtectDosMaxDaemons, *appProtectDosMaxWorkers, *appProtectDosMemory)
}

var sslRejectHandshake bool

if *defaultServerSecret != "" {
Expand Down Expand Up @@ -487,7 +522,7 @@ func main() {
if err != nil {
glog.Fatalf("Error when getting %v: %v", *nginxConfigMaps, err)
}
cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect)
cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect, *appProtectDos)
if cfgParams.MainServerSSLDHParamFileContent != nil {
fileName, err := nginxManager.CreateDHParam(*cfgParams.MainServerSSLDHParamFileContent)
if err != nil {
Expand Down Expand Up @@ -520,6 +555,7 @@ func main() {
EnableSnippets: *enableSnippets,
NginxServiceMesh: *spireAgentAddress != "",
MainAppProtectLoadModule: *appProtect,
MainAppProtectDosLoadModule: *appProtectDos,
EnableLatencyMetrics: *enableLatencyMetrics,
EnablePreviewPolicies: *enablePreviewPolicies,
SSLRejectHandshake: sslRejectHandshake,
Expand Down Expand Up @@ -605,7 +641,7 @@ func main() {
controllerNamespace := os.Getenv("POD_NAMESPACE")

transportServerValidator := cr_validation.NewTransportServerValidator(*enableTLSPassthrough, *enableSnippets, *nginxPlus)
virtualServerValidator := cr_validation.NewVirtualServerValidator(*nginxPlus)
virtualServerValidator := cr_validation.NewVirtualServerValidator(*nginxPlus, *appProtectDos)

lbcInput := k8s.NewLoadBalancerControllerInput{
KubeClient: kubeClient,
Expand All @@ -616,6 +652,7 @@ func main() {
NginxConfigurator: cnf,
DefaultServerSecret: *defaultServerSecret,
AppProtectEnabled: *appProtect,
AppProtectDosEnabled: *appProtectDos,
IsNginxPlus: *nginxPlus,
IngressClass: *ingressClass,
ExternalServiceName: *externalService,
Expand Down Expand Up @@ -652,8 +689,8 @@ func main() {
}()
}

if *appProtect {
go handleTerminationWithAppProtect(lbc, nginxManager, syslogListener, nginxDone, aPAgentDone, aPPluginDone)
if *appProtect || *appProtectDos {
go handleTerminationWithAppProtect(lbc, nginxManager, syslogListener, nginxDone, aPAgentDone, aPPluginDone, aPPDosAgentDone, *appProtect, *appProtectDos)
} else {
go handleTermination(lbc, nginxManager, syslogListener, nginxDone)
}
Expand Down Expand Up @@ -811,7 +848,7 @@ func validateLocation(location string) error {
return nil
}

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

Expand All @@ -822,15 +859,23 @@ func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManag
glog.Fatalf("AppProtectPlugin command exited unexpectedly with status: %v", err)
case err := <-agentDone:
glog.Fatalf("AppProtectAgent command exited unexpectedly with status: %v", err)
case err := <-agentDosDone:
glog.Fatalf("AppProtectDosAgent command exited unexpectedly with status: %v", err)
case <-signalChan:
glog.Infof("Received SIGTERM, shutting down")
lbc.Stop()
nginxManager.Quit()
<-nginxDone
nginxManager.AppProtectPluginQuit()
<-pluginDone
nginxManager.AppProtectAgentQuit()
<-agentDone
if appProtectEnabled {
nginxManager.AppProtectPluginQuit()
<-pluginDone
nginxManager.AppProtectAgentQuit()
<-agentDone
}
if appProtectDosEnabled {
nginxManager.AppProtectDosAgentQuit()
<-agentDosDone
}
listener.Stop()
}
glog.Info("Exiting successfully")
Expand Down
70 changes: 70 additions & 0 deletions deployments/common/crds/appprotectdos.f5.com_apdoslogconfs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: apdoslogconfs.appprotectdos.f5.com
spec:
group: appprotectdos.f5.com
names:
kind: APDosLogConf
listKind: APDosLogConfList
plural: apdoslogconfs
singular: apdoslogconf
preserveUnknownFields: false
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: APDosLogConf is the Schema for the APDosLogConfs API
properties:
apiVersion:
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'
type: string
kind:
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'
type: string
metadata:
type: object
spec:
description: APDosLogConfSpec defines the desired state of APDosLogConf
properties:
content:
properties:
format:
enum:
- splunk
- arcsight
- user-defined
default: splunk
type: string
format_string:
type: string
max_message_size:
pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$
default: 5k
type: string
type: object
filter:
properties:
traffic-mitigation-stats:
enum:
- none
- all
default: all
type: string
bad-actors:
pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$
default: top 10
type: string
attack-signatures:
pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$
default: top 10
type: string
type: object
type: object
type: object
served: true
storage: true
Loading

0 comments on commit 3e6db80

Please sign in to comment.