Skip to content

Commit 6c9347b

Browse files
Rafal Wegrzyckirafwegv
Rafal Wegrzycki
authored andcommitted
Support for App Protect module
1 parent 5047caf commit 6c9347b

File tree

340 files changed

+25395
-205
lines changed

Some content is hidden

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

340 files changed

+25395
-205
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
ARG GOLANG_CONTAINER=golang:latest
2+
3+
FROM debian:stretch-slim as base
4+
5+
LABEL maintainer="NGINX Docker Maintainers <[email protected]>"
6+
7+
ENV APPPROTECT_VERSION 21+2.52.1-1~stretch
8+
ENV APPPROTECT_SIG_VERSION 2020.06.18-1~stretch
9+
ENV NGINX_PLUS_VERSION 21-1~stretch
10+
ENV NGINX_PLUS_RELEASE R21
11+
ARG IC_VERSION
12+
13+
# Download certificate and key from the customer portal (https://cs.nginx.com)
14+
# and copy to the build context
15+
COPY nginx-repo.crt nginx-repo.key /etc/ssl/nginx/
16+
17+
# Make sure the certificate and key have correct permissions
18+
RUN chmod 644 /etc/ssl/nginx/*
19+
20+
# Install NGINX Plus
21+
RUN set -x \
22+
&& apt-get update \
23+
&& apt-get install --no-install-recommends --no-install-suggests -y apt-transport-https ca-certificates gnupg1 libcap2-bin wget \
24+
&& \
25+
NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
26+
found=''; \
27+
for server in \
28+
ha.pool.sks-keyservers.net \
29+
hkp://keyserver.ubuntu.com:80 \
30+
hkp://p80.pool.sks-keyservers.net:80 \
31+
pgp.mit.edu \
32+
; do \
33+
echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
34+
apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
35+
done; \
36+
test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
37+
echo "Acquire::https::plus-pkgs.nginx.com::Verify-Peer \"true\";" >> /etc/apt/apt.conf.d/90nginx \
38+
&& echo "Acquire::https::plus-pkgs.nginx.com::Verify-Host \"true\";" >> /etc/apt/apt.conf.d/90nginx \
39+
&& echo "Acquire::https::plus-pkgs.nginx.com::SslCert \"/etc/ssl/nginx/nginx-repo.crt\";" >> /etc/apt/apt.conf.d/90nginx \
40+
&& echo "Acquire::https::plus-pkgs.nginx.com::SslKey \"/etc/ssl/nginx/nginx-repo.key\";" >> /etc/apt/apt.conf.d/90nginx \
41+
&& echo "Acquire::https::plus-pkgs.nginx.com::User-Agent \"k8s-ic-$IC_VERSION-app-$APPPROTECT_VERSION-apt\";" >> /etc/apt/apt.conf.d/90nginx \
42+
&& echo "deb https://plus-pkgs.nginx.com/${NGINX_PLUS_RELEASE}/debian stretch nginx-plus\n" > /etc/apt/sources.list.d/nginx-plus.list \
43+
&& echo "deb https://app-protect-sigs.nginx.com/debian/ stretch nginx-plus\n" | tee /etc/apt/sources.list.d/app-protect-sigs.list \
44+
&& wget https://nginx.org/keys/app-protect-sigs.key && apt-key add app-protect-sigs.key \
45+
&& echo "Acquire::https::app-protect-sigs.nginx.com::Verify-Peer \"true\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \
46+
&& echo "Acquire::https::app-protect-sigs.nginx.com::Verify-Host \"true\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \
47+
&& echo "Acquire::https::app-protect-sigs.nginx.com::SslCert \"/etc/ssl/nginx/nginx-repo.crt\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \
48+
&& echo "Acquire::https::app-protect-sigs.nginx.com::SslKey \"/etc/ssl/nginx/nginx-repo.key\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \
49+
&& apt-get update && apt-get install -y nginx-plus=$NGINX_PLUS_VERSION app-protect=$APPPROTECT_VERSION \
50+
app-protect-attack-signatures${APPPROTECT_SIG_VERSION:+=$APPPROTECT_SIG_VERSION} \
51+
&& setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx \
52+
&& setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx-debug \
53+
&& apt-get remove --purge --auto-remove -y gnupg1 wget\
54+
&& rm -rf /var/lib/apt/lists/* \
55+
&& rm -rf /etc/ssl/nginx \
56+
&& rm /etc/apt/apt.conf.d/90nginx /etc/apt/sources.list.d/nginx-plus.list \
57+
&& rm /etc/apt/apt.conf.d/90app-protect-sigs /etc/apt/sources.list.d/app-protect-sigs.list
58+
59+
RUN usermod -u 101 nginx \
60+
&& groupmod -g 101 nginx
61+
62+
# forward nginx access and error logs to stdout and stderr of the ingress
63+
# controller process
64+
RUN ln -sf /proc/1/fd/1 /var/log/nginx/access.log \
65+
&& ln -sf /proc/1/fd/1 /var/log/nginx/stream-access.log \
66+
&& ln -sf /proc/1/fd/2 /var/log/nginx/error.log
67+
68+
RUN mkdir -p /var/lib/nginx \
69+
&& mkdir -p /etc/nginx/secrets \
70+
&& mkdir -p /etc/nginx/waf \
71+
&& mkdir -p /etc/nginx/waf/nac-policies \
72+
&& mkdir -p /etc/nginx/waf/nac-logconfs \
73+
&& mkdir -p /var/log/app_protect \
74+
&& mkdir -p /opt/app_protect \
75+
&& chown -R nginx:0 /etc/app_protect \
76+
&& chown -R nginx:0 /usr/share/ts \
77+
&& chown -R nginx:0 /etc/nginx \
78+
&& chown -R nginx:0 /var/cache/nginx \
79+
&& chown -R nginx:0 /var/lib/nginx/ \
80+
&& chown -R nginx:0 /var/log/app_protect/ \
81+
&& chown -R nginx:0 /opt/app_protect/ \
82+
&& chown -R nginx:0 /var/log/nginx/ \
83+
&& apt-get remove --purge -y libcap2-bin \
84+
&& rm /etc/nginx/conf.d/*
85+
86+
RUN printf "MODULE = ALL;\nLOG_LEVEL = TS_CRIT;\nFILE = 2;\n" > /etc/app_protect/bd/logger.cfg \
87+
&& printf "[config_set_compiler]\nlog_level=fatal\n" >> /etc/app_protect/tools/asm_logging.conf \
88+
&& for v in \
89+
asm_config_server \
90+
lock_factory \
91+
bd_agent \
92+
import_export_policy \
93+
set_active \
94+
; do sed -i "/\[$v/a log_level=fatal" "/etc/app_protect/tools/asm_logging.conf" \
95+
; done
96+
97+
COPY --chown=nginx:0 build/appprotect/log-default.json /etc/nginx
98+
99+
EXPOSE 80 443
100+
101+
COPY internal/configs/version1/nginx-plus.ingress.tmpl \
102+
internal/configs/version1/nginx-plus.tmpl \
103+
internal/configs/version2/nginx-plus.virtualserver.tmpl \
104+
internal/configs/version2/nginx-plus.transportserver.tmpl /
105+
106+
# Uncomment the line below if you would like to add the default.pem to the image
107+
# and use it as a certificate and key for the default server
108+
# ADD default.pem /etc/nginx/secrets/default
109+
110+
USER nginx
111+
112+
ENTRYPOINT ["/nginx-ingress"]
113+
114+
FROM base AS local
115+
COPY nginx-ingress /
116+
117+
118+
FROM $GOLANG_CONTAINER AS builder
119+
ARG VERSION
120+
ARG GIT_COMMIT
121+
WORKDIR /go/src/github.com/nginxinc/kubernetes-ingress/nginx-ingress/cmd/nginx-ingress
122+
COPY . /go/src/github.com/nginxinc/kubernetes-ingress/nginx-ingress/
123+
RUN CGO_ENABLED=0 GOFLAGS='-mod=vendor' \
124+
go build -installsuffix cgo -ldflags "-w -X main.version=${VERSION} -X main.gitCommit=${GIT_COMMIT}" -o /nginx-ingress
125+
126+
127+
FROM base AS container
128+
COPY --from=builder /nginx-ingress /

build/appprotect/log-default.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"filter": {
3+
"request_type": "all"
4+
},
5+
"content": {
6+
"format": "default",
7+
"max_request_size": "any",
8+
"max_message_size": "5k"
9+
}
10+
}

cmd/nginx-ingress/main.go

+81-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
api_v1 "k8s.io/api/core/v1"
3030
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3131
"k8s.io/apimachinery/pkg/util/validation"
32+
"k8s.io/client-go/dynamic"
3233
"k8s.io/client-go/kubernetes"
3334
"k8s.io/client-go/kubernetes/scheme"
3435
"k8s.io/client-go/rest"
@@ -37,6 +38,7 @@ import (
3738
)
3839

3940
var (
41+
4042
// Set during build
4143
version string
4244
gitCommit string
@@ -62,6 +64,8 @@ var (
6264

6365
nginxPlus = flag.Bool("nginx-plus", false, "Enable support for NGINX Plus")
6466

67+
appProtect = flag.Bool("enable-app-protect", false, "Enable support for NGINX App Protect. Requires -nginx-plus.")
68+
6569
ingressClass = flag.String("ingress-class", "nginx",
6670
`A class of the Ingress controller. The Ingress controller only processes Ingress resources that belong to its class
6771
- i.e. have the annotation "kubernetes.io/ingress.class" or the "ingressClassName" field in VirtualServer/VirtualServerRoute equal to the class. Additionally,
@@ -118,6 +122,10 @@ var (
118122
nginxDebug = flag.Bool("nginx-debug", false,
119123
"Enable debugging for NGINX. Uses the nginx-debug binary. Requires 'error-log-level: debug' in the ConfigMap.")
120124

125+
nginxReloadTimeout = flag.Int("nginx-reload-timeout", 0,
126+
`The timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start.
127+
The default is 4000 (or 20000 if -enable-app-protect is true). If set to 0, the default value will be used`)
128+
121129
wildcardTLSSecret = flag.String("wildcard-tls-secret", "",
122130
`A Secret with a TLS certificate and key for TLS termination of every Ingress host for which TLS termination is enabled but the Secret is not specified.
123131
Format: <namespace>/<name>. If the argument is not set, for such Ingress hosts NGINX will break any attempt to establish a TLS connection.
@@ -190,6 +198,10 @@ func main() {
190198
glog.Fatalf("enable-tls-passthrough flag requires -enable-custom-resources")
191199
}
192200

201+
if *appProtect && !*nginxPlus {
202+
glog.Fatal("NGINX App Protect support is for NGINX Plus only")
203+
}
204+
193205
glog.Infof("Starting NGINX Ingress controller Version=%v GitCommit=%v\n", version, gitCommit)
194206

195207
var config *rest.Config
@@ -215,6 +227,13 @@ func main() {
215227
glog.Fatalf("Failed to create client: %v.", err)
216228
}
217229

230+
var dynClient dynamic.Interface
231+
if *appProtect {
232+
dynClient, err = dynamic.NewForConfig(config)
233+
if err != nil {
234+
glog.Fatalf("Failed to create dynamic client: %v.", err)
235+
}
236+
}
218237
var confClient k8s_nginx.Interface
219238
if *enableCustomResources {
220239
confClient, err = k8s_nginx.NewForConfig(config)
@@ -296,7 +315,18 @@ func main() {
296315
if useFakeNginxManager {
297316
nginxManager = nginx.NewFakeManager("/etc/nginx")
298317
} else {
299-
nginxManager = nginx.NewLocalManager("/etc/nginx/", nginxBinaryPath, managerCollector)
318+
nginxManager = nginx.NewLocalManager("/etc/nginx/", nginxBinaryPath, managerCollector, parseReloadTimeout(*appProtect, *nginxReloadTimeout))
319+
}
320+
321+
var aPPluginDone chan error
322+
var aPAgentDone chan error
323+
324+
if *appProtect {
325+
aPPluginDone = make(chan error, 1)
326+
aPAgentDone = make(chan error, 1)
327+
328+
nginxManager.AppProtectAgentStart(aPAgentDone, *nginxDebug)
329+
nginxManager.AppProtectPluginStart(aPPluginDone)
300330
}
301331

302332
if *defaultServerSecret != "" {
@@ -355,6 +385,7 @@ func main() {
355385
}
356386

357387
cfgParams := configs.NewDefaultConfigParams()
388+
358389
if *nginxConfigMaps != "" {
359390
ns, name, err := k8s.ParseNamespaceName(*nginxConfigMaps)
360391
if err != nil {
@@ -364,7 +395,7 @@ func main() {
364395
if err != nil {
365396
glog.Fatalf("Error when getting %v: %v", *nginxConfigMaps, err)
366397
}
367-
cfgParams = configs.ParseConfigMap(cfm, *nginxPlus)
398+
cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect)
368399
if cfgParams.MainServerSSLDHParamFileContent != nil {
369400
fileName, err := nginxManager.CreateDHParam(*cfgParams.MainServerSSLDHParamFileContent)
370401
if err != nil {
@@ -386,7 +417,6 @@ func main() {
386417
}
387418
}
388419
}
389-
390420
staticCfgParams := &configs.StaticConfigParams{
391421
HealthStatus: *healthStatus,
392422
HealthStatusURI: *healthStatusURI,
@@ -397,6 +427,7 @@ func main() {
397427
TLSPassthrough: *enableTLSPassthrough,
398428
EnableSnippets: *enableSnippets,
399429
SpiffeCerts: *spireAgentAddress != "",
430+
MainAppProtectLoadModule: *appProtect,
400431
}
401432

402433
ngxConfig := configs.GenerateNginxMainConfig(staticCfgParams, cfgParams)
@@ -457,10 +488,12 @@ func main() {
457488
lbcInput := k8s.NewLoadBalancerControllerInput{
458489
KubeClient: kubeClient,
459490
ConfClient: confClient,
491+
DynClient: dynClient,
460492
ResyncPeriod: 30 * time.Second,
461493
Namespace: *watchNamespace,
462494
NginxConfigurator: cnf,
463495
DefaultServerSecret: *defaultServerSecret,
496+
AppProtectEnabled: *appProtect,
464497
IsNginxPlus: *nginxPlus,
465498
IngressClass: *ingressClass,
466499
UseIngressClassOnly: *useIngressClassOnly,
@@ -481,7 +514,11 @@ func main() {
481514

482515
lbc := k8s.NewLoadBalancerController(lbcInput)
483516

484-
go handleTermination(lbc, nginxManager, nginxDone)
517+
if *appProtect {
518+
go handleTerminationWithAppProtect(lbc, nginxManager, nginxDone, aPAgentDone, aPPluginDone)
519+
} else {
520+
go handleTermination(lbc, nginxManager, nginxDone)
521+
}
485522
lbc.Run()
486523

487524
for {
@@ -631,3 +668,43 @@ func validateLocation(location string) error {
631668
}
632669
return nil
633670
}
671+
672+
func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, nginxDone, agentDone, pluginDone chan error) {
673+
signalChan := make(chan os.Signal, 1)
674+
signal.Notify(signalChan, syscall.SIGTERM)
675+
676+
select {
677+
case err := <-nginxDone:
678+
glog.Fatalf("nginx command exited unexpectedly with status: %v", err)
679+
case err := <-pluginDone:
680+
glog.Fatalf("AppProtectPlugin command exited unexpectedly with status: %v", err)
681+
case err := <-agentDone:
682+
glog.Fatalf("AppProtectAgent command exited unexpectedly with status: %v", err)
683+
case <-signalChan:
684+
glog.Infof("Received SIGTERM, shutting down")
685+
lbc.Stop()
686+
nginxManager.Quit()
687+
<-nginxDone
688+
nginxManager.AppProtectPluginQuit()
689+
<-pluginDone
690+
nginxManager.AppProtectAgentQuit()
691+
<-agentDone
692+
}
693+
glog.Info("Exiting successfully")
694+
os.Exit(0)
695+
}
696+
697+
func parseReloadTimeout(appProtectEnabled bool, timeout int) int {
698+
const defaultTimeout = 4000
699+
const defaultTimeoutAppProtect = 20000
700+
701+
if timeout != 0 {
702+
return timeout
703+
}
704+
705+
if appProtectEnabled {
706+
return defaultTimeoutAppProtect
707+
}
708+
709+
return defaultTimeout
710+
}

cmd/nginx-ingress/main_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,39 @@ func TestValidateLocation(t *testing.T) {
124124
}
125125
}
126126
}
127+
128+
func TestParseReloadTimeout(t *testing.T) {
129+
tests := []struct {
130+
timeout int
131+
appProtectEnabled bool
132+
expected int
133+
}{
134+
{
135+
timeout: 0,
136+
appProtectEnabled: true,
137+
expected: 20000,
138+
},
139+
{
140+
timeout: 0,
141+
appProtectEnabled: false,
142+
expected: 4000,
143+
},
144+
{
145+
timeout: 1000,
146+
appProtectEnabled: true,
147+
expected: 1000,
148+
},
149+
{
150+
timeout: 1000,
151+
appProtectEnabled: false,
152+
expected: 1000,
153+
},
154+
}
155+
156+
for _, test := range tests {
157+
result := parseReloadTimeout(test.appProtectEnabled, test.timeout)
158+
if result != test.expected {
159+
t.Errorf("parseReloadTimeout(%v, %v) returned %v but expected %v", test.appProtectEnabled, test.timeout, result, test.expected)
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)