diff --git a/build/appprotect/DockerfileWithAppProtectForPlus b/build/appprotect/DockerfileWithAppProtectForPlus new file mode 100644 index 0000000000..08382da277 --- /dev/null +++ b/build/appprotect/DockerfileWithAppProtectForPlus @@ -0,0 +1,128 @@ +ARG GOLANG_CONTAINER=golang:latest + +FROM debian:stretch-slim as base + +LABEL maintainer="NGINX Docker Maintainers " + +ENV APPPROTECT_VERSION 21+2.52.1-1~stretch +ENV APPPROTECT_SIG_VERSION 2020.06.18-1~stretch +ENV NGINX_PLUS_VERSION 21-1~stretch +ENV NGINX_PLUS_RELEASE R21 +ARG IC_VERSION + +# Download certificate and key from the customer portal (https://cs.nginx.com) +# and copy to the build context +COPY nginx-repo.crt nginx-repo.key /etc/ssl/nginx/ + +# Make sure the certificate and key have correct permissions +RUN chmod 644 /etc/ssl/nginx/* + +# Install NGINX Plus +RUN set -x \ + && apt-get update \ + && apt-get install --no-install-recommends --no-install-suggests -y apt-transport-https ca-certificates gnupg1 libcap2-bin wget \ + && \ + NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \ + found=''; \ + for server in \ + ha.pool.sks-keyservers.net \ + hkp://keyserver.ubuntu.com:80 \ + hkp://p80.pool.sks-keyservers.net:80 \ + pgp.mit.edu \ + ; do \ + echo "Fetching GPG key $NGINX_GPGKEY from $server"; \ + apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \ + done; \ + test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \ + echo "Acquire::https::plus-pkgs.nginx.com::Verify-Peer \"true\";" >> /etc/apt/apt.conf.d/90nginx \ + && echo "Acquire::https::plus-pkgs.nginx.com::Verify-Host \"true\";" >> /etc/apt/apt.conf.d/90nginx \ + && echo "Acquire::https::plus-pkgs.nginx.com::SslCert \"/etc/ssl/nginx/nginx-repo.crt\";" >> /etc/apt/apt.conf.d/90nginx \ + && echo "Acquire::https::plus-pkgs.nginx.com::SslKey \"/etc/ssl/nginx/nginx-repo.key\";" >> /etc/apt/apt.conf.d/90nginx \ + && echo "Acquire::https::plus-pkgs.nginx.com::User-Agent \"k8s-ic-$IC_VERSION-app-$APPPROTECT_VERSION-apt\";" >> /etc/apt/apt.conf.d/90nginx \ + && echo "deb https://plus-pkgs.nginx.com/${NGINX_PLUS_RELEASE}/debian stretch nginx-plus\n" > /etc/apt/sources.list.d/nginx-plus.list \ + && echo "deb https://app-protect-sigs.nginx.com/debian/ stretch nginx-plus\n" | tee /etc/apt/sources.list.d/app-protect-sigs.list \ + && wget https://nginx.org/keys/app-protect-sigs.key && apt-key add app-protect-sigs.key \ + && echo "Acquire::https::app-protect-sigs.nginx.com::Verify-Peer \"true\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \ + && echo "Acquire::https::app-protect-sigs.nginx.com::Verify-Host \"true\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \ + && echo "Acquire::https::app-protect-sigs.nginx.com::SslCert \"/etc/ssl/nginx/nginx-repo.crt\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \ + && echo "Acquire::https::app-protect-sigs.nginx.com::SslKey \"/etc/ssl/nginx/nginx-repo.key\";" >> /etc/apt/apt.conf.d/90app-protect-sigs \ + && apt-get update && apt-get install -y nginx-plus=$NGINX_PLUS_VERSION app-protect=$APPPROTECT_VERSION \ + app-protect-attack-signatures${APPPROTECT_SIG_VERSION:+=$APPPROTECT_SIG_VERSION} \ + && setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx \ + && setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx-debug \ + && apt-get remove --purge --auto-remove -y gnupg1 wget\ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /etc/ssl/nginx \ + && rm /etc/apt/apt.conf.d/90nginx /etc/apt/sources.list.d/nginx-plus.list \ + && rm /etc/apt/apt.conf.d/90app-protect-sigs /etc/apt/sources.list.d/app-protect-sigs.list + +RUN usermod -u 101 nginx \ + && groupmod -g 101 nginx + +# forward nginx access and error logs to stdout and stderr of the ingress +# controller process +RUN ln -sf /proc/1/fd/1 /var/log/nginx/access.log \ + && ln -sf /proc/1/fd/1 /var/log/nginx/stream-access.log \ + && ln -sf /proc/1/fd/2 /var/log/nginx/error.log + +RUN mkdir -p /var/lib/nginx \ + && mkdir -p /etc/nginx/secrets \ + && mkdir -p /etc/nginx/waf \ + && mkdir -p /etc/nginx/waf/nac-policies \ + && mkdir -p /etc/nginx/waf/nac-logconfs \ + && mkdir -p /var/log/app_protect \ + && mkdir -p /opt/app_protect \ + && chown -R nginx:0 /etc/app_protect \ + && chown -R nginx:0 /usr/share/ts \ + && chown -R nginx:0 /etc/nginx \ + && chown -R nginx:0 /var/cache/nginx \ + && chown -R nginx:0 /var/lib/nginx/ \ + && chown -R nginx:0 /var/log/app_protect/ \ + && chown -R nginx:0 /opt/app_protect/ \ + && chown -R nginx:0 /var/log/nginx/ \ + && apt-get remove --purge -y libcap2-bin \ + && rm /etc/nginx/conf.d/* + +RUN printf "MODULE = ALL;\nLOG_LEVEL = TS_CRIT;\nFILE = 2;\n" > /etc/app_protect/bd/logger.cfg \ + && printf "[config_set_compiler]\nlog_level=fatal\n" >> /etc/app_protect/tools/asm_logging.conf \ + && for v in \ + asm_config_server \ + lock_factory \ + bd_agent \ + import_export_policy \ + set_active \ + ; do sed -i "/\[$v/a log_level=fatal" "/etc/app_protect/tools/asm_logging.conf" \ + ; done + +COPY --chown=nginx:0 build/appprotect/log-default.json /etc/nginx + +EXPOSE 80 443 + +COPY internal/configs/version1/nginx-plus.ingress.tmpl \ + internal/configs/version1/nginx-plus.tmpl \ + internal/configs/version2/nginx-plus.virtualserver.tmpl \ + internal/configs/version2/nginx-plus.transportserver.tmpl / + +# Uncomment the line below if you would like to add the default.pem to the image +# and use it as a certificate and key for the default server +# ADD default.pem /etc/nginx/secrets/default + +USER nginx + +ENTRYPOINT ["/nginx-ingress"] + +FROM base AS local +COPY nginx-ingress / + + +FROM $GOLANG_CONTAINER AS builder +ARG VERSION +ARG GIT_COMMIT +WORKDIR /go/src/github.com/nginxinc/kubernetes-ingress/nginx-ingress/cmd/nginx-ingress +COPY . /go/src/github.com/nginxinc/kubernetes-ingress/nginx-ingress/ +RUN CGO_ENABLED=0 GOFLAGS='-mod=vendor' \ + go build -installsuffix cgo -ldflags "-w -X main.version=${VERSION} -X main.gitCommit=${GIT_COMMIT}" -o /nginx-ingress + + +FROM base AS container +COPY --from=builder /nginx-ingress / \ No newline at end of file diff --git a/build/appprotect/log-default.json b/build/appprotect/log-default.json new file mode 100644 index 0000000000..e6739af3d2 --- /dev/null +++ b/build/appprotect/log-default.json @@ -0,0 +1,10 @@ +{ + "filter": { + "request_type": "all" + }, + "content": { + "format": "default", + "max_request_size": "any", + "max_message_size": "5k" + } +} diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index 4c2ed7c032..aade059fad 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -29,6 +29,7 @@ import ( api_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -37,6 +38,7 @@ import ( ) var ( + // Set during build version string gitCommit string @@ -62,6 +64,8 @@ var ( nginxPlus = flag.Bool("nginx-plus", false, "Enable support for NGINX Plus") + appProtect = flag.Bool("enable-app-protect", false, "Enable support for NGINX App Protect. Requires -nginx-plus.") + ingressClass = flag.String("ingress-class", "nginx", `A class of the Ingress controller. The Ingress controller only processes Ingress resources that belong to its class - 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 ( nginxDebug = flag.Bool("nginx-debug", false, "Enable debugging for NGINX. Uses the nginx-debug binary. Requires 'error-log-level: debug' in the ConfigMap.") + nginxReloadTimeout = flag.Int("nginx-reload-timeout", 0, + `The timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. + The default is 4000 (or 20000 if -enable-app-protect is true). If set to 0, the default value will be used`) + wildcardTLSSecret = flag.String("wildcard-tls-secret", "", `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. Format: /. 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() { glog.Fatalf("enable-tls-passthrough flag requires -enable-custom-resources") } + if *appProtect && !*nginxPlus { + glog.Fatal("NGINX App Protect support is for NGINX Plus only") + } + glog.Infof("Starting NGINX Ingress controller Version=%v GitCommit=%v\n", version, gitCommit) var config *rest.Config @@ -215,6 +227,13 @@ func main() { glog.Fatalf("Failed to create client: %v.", err) } + var dynClient dynamic.Interface + if *appProtect { + dynClient, err = dynamic.NewForConfig(config) + if err != nil { + glog.Fatalf("Failed to create dynamic client: %v.", err) + } + } var confClient k8s_nginx.Interface if *enableCustomResources { confClient, err = k8s_nginx.NewForConfig(config) @@ -296,7 +315,18 @@ func main() { if useFakeNginxManager { nginxManager = nginx.NewFakeManager("/etc/nginx") } else { - nginxManager = nginx.NewLocalManager("/etc/nginx/", nginxBinaryPath, managerCollector) + nginxManager = nginx.NewLocalManager("/etc/nginx/", nginxBinaryPath, managerCollector, parseReloadTimeout(*appProtect, *nginxReloadTimeout)) + } + + var aPPluginDone chan error + var aPAgentDone chan error + + if *appProtect { + aPPluginDone = make(chan error, 1) + aPAgentDone = make(chan error, 1) + + nginxManager.AppProtectAgentStart(aPAgentDone, *nginxDebug) + nginxManager.AppProtectPluginStart(aPPluginDone) } if *defaultServerSecret != "" { @@ -355,6 +385,7 @@ func main() { } cfgParams := configs.NewDefaultConfigParams() + if *nginxConfigMaps != "" { ns, name, err := k8s.ParseNamespaceName(*nginxConfigMaps) if err != nil { @@ -364,7 +395,7 @@ func main() { if err != nil { glog.Fatalf("Error when getting %v: %v", *nginxConfigMaps, err) } - cfgParams = configs.ParseConfigMap(cfm, *nginxPlus) + cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect) if cfgParams.MainServerSSLDHParamFileContent != nil { fileName, err := nginxManager.CreateDHParam(*cfgParams.MainServerSSLDHParamFileContent) if err != nil { @@ -386,7 +417,6 @@ func main() { } } } - staticCfgParams := &configs.StaticConfigParams{ HealthStatus: *healthStatus, HealthStatusURI: *healthStatusURI, @@ -397,6 +427,7 @@ func main() { TLSPassthrough: *enableTLSPassthrough, EnableSnippets: *enableSnippets, SpiffeCerts: *spireAgentAddress != "", + MainAppProtectLoadModule: *appProtect, } ngxConfig := configs.GenerateNginxMainConfig(staticCfgParams, cfgParams) @@ -457,10 +488,12 @@ func main() { lbcInput := k8s.NewLoadBalancerControllerInput{ KubeClient: kubeClient, ConfClient: confClient, + DynClient: dynClient, ResyncPeriod: 30 * time.Second, Namespace: *watchNamespace, NginxConfigurator: cnf, DefaultServerSecret: *defaultServerSecret, + AppProtectEnabled: *appProtect, IsNginxPlus: *nginxPlus, IngressClass: *ingressClass, UseIngressClassOnly: *useIngressClassOnly, @@ -481,7 +514,11 @@ func main() { lbc := k8s.NewLoadBalancerController(lbcInput) - go handleTermination(lbc, nginxManager, nginxDone) + if *appProtect { + go handleTerminationWithAppProtect(lbc, nginxManager, nginxDone, aPAgentDone, aPPluginDone) + } else { + go handleTermination(lbc, nginxManager, nginxDone) + } lbc.Run() for { @@ -631,3 +668,43 @@ func validateLocation(location string) error { } return nil } + +func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, nginxDone, agentDone, pluginDone chan error) { + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGTERM) + + select { + case err := <-nginxDone: + glog.Fatalf("nginx command exited unexpectedly with status: %v", err) + case err := <-pluginDone: + glog.Fatalf("AppProtectPlugin command exited unexpectedly with status: %v", err) + case err := <-agentDone: + glog.Fatalf("AppProtectAgent 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 + } + glog.Info("Exiting successfully") + os.Exit(0) +} + +func parseReloadTimeout(appProtectEnabled bool, timeout int) int { + const defaultTimeout = 4000 + const defaultTimeoutAppProtect = 20000 + + if timeout != 0 { + return timeout + } + + if appProtectEnabled { + return defaultTimeoutAppProtect + } + + return defaultTimeout +} diff --git a/cmd/nginx-ingress/main_test.go b/cmd/nginx-ingress/main_test.go index 7bc9ddae40..b815311cdc 100644 --- a/cmd/nginx-ingress/main_test.go +++ b/cmd/nginx-ingress/main_test.go @@ -124,3 +124,39 @@ func TestValidateLocation(t *testing.T) { } } } + +func TestParseReloadTimeout(t *testing.T) { + tests := []struct { + timeout int + appProtectEnabled bool + expected int + }{ + { + timeout: 0, + appProtectEnabled: true, + expected: 20000, + }, + { + timeout: 0, + appProtectEnabled: false, + expected: 4000, + }, + { + timeout: 1000, + appProtectEnabled: true, + expected: 1000, + }, + { + timeout: 1000, + appProtectEnabled: false, + expected: 1000, + }, + } + + for _, test := range tests { + result := parseReloadTimeout(test.appProtectEnabled, test.timeout) + if result != test.expected { + t.Errorf("parseReloadTimeout(%v, %v) returned %v but expected %v", test.appProtectEnabled, test.timeout, result, test.expected) + } + } +} diff --git a/deployments/common/ap-logconf-definition.yaml b/deployments/common/ap-logconf-definition.yaml new file mode 100644 index 0000000000..52c1b4805f --- /dev/null +++ b/deployments/common/ap-logconf-definition.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + creationTimestamp: null + name: aplogconfs.appprotect.f5.com +spec: + preserveUnknownFields: false + group: appprotect.f5.com + names: + kind: APLogConf + listKind: APLogConfList + plural: aplogconfs + singular: aplogconf + scope: Namespaced + validation: + openAPIV3Schema: + description: APLogConf is the Schema for the APLogConfs 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: APLogConfSpec defines the desired state of APLogConf + properties: + content: + properties: + format: + enum: + - splunk + - arcsight + - default + - user-defined + type: string + format_string: + type: string + max_message_size: + pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ + type: string + max_request_size: + pattern: ^([1-9]|[1-9][0-9]|[1-9][0-9]{2}|1[0-9]{3}|20[1-3][0-9]|204[1-8]|any)$ + type: string + type: object + filter: + properties: + request_type: + enum: + - all + - illegal + - blocked + type: string + type: object + type: object + type: object + version: v1beta1 + versions: + - name: v1beta1 + served: true + storage: true diff --git a/deployments/common/ap-policy-definition.yaml b/deployments/common/ap-policy-definition.yaml new file mode 100644 index 0000000000..05ba53b222 --- /dev/null +++ b/deployments/common/ap-policy-definition.yaml @@ -0,0 +1,825 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + creationTimestamp: null + name: appolicies.appprotect.f5.com +spec: + preserveUnknownFields: false + group: appprotect.f5.com + names: + kind: APPolicy + listKind: APPolicyList + plural: appolicies + singular: appolicy + scope: Namespaced + validation: + openAPIV3Schema: + description: APPolicyConfig is the Schema for the APPolicyconfigs 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: APPolicySpec defines the desired state of APPolicy + properties: + modifications: + items: + properties: {} + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + policy: + description: Defines the App Protect policy + properties: + applicationLanguage: + enum: + - iso-8859-10 + - iso-8859-6 + - windows-1255 + - auto-detect + - koi8-r + - gb18030 + - iso-8859-8 + - windows-1250 + - iso-8859-9 + - windows-1252 + - iso-8859-16 + - gb2312 + - iso-8859-2 + - iso-8859-5 + - windows-1257 + - windows-1256 + - iso-8859-13 + - windows-874 + - windows-1253 + - iso-8859-3 + - euc-jp + - utf-8 + - gbk + - windows-1251 + - big5 + - iso-8859-1 + - shift_jis + - euc-kr + - iso-8859-4 + - iso-8859-7 + - iso-8859-15 + type: string + blocking-settings: + properties: + evasions: + items: + properties: + description: + enum: + - '%u decoding' + - Apache whitespace + - Bad unescape + - Bare byte decoding + - Directory traversals + - IIS backslashes + - IIS Unicode codepoints + - Multiple decoding + type: string + enabled: + type: boolean + maxDecodingPasses: + type: integer + type: object + type: array + http-protocols: + items: + properties: + description: + enum: + - Unparsable request content + - Several Content-Length headers + - 'POST request with Content-Length: 0' + - Null in request + - No Host header in HTTP/1.1 request + - Multiple host headers + - Host header contains IP address + - High ASCII characters in headers + - Header name with no header value + - CRLF characters before request start + - Content length should be a positive number + - Chunked request with Content-Length header + - Check maximum number of parameters + - Check maximum number of headers + - Body in GET or HEAD requests + - Bad multipart/form-data request parsing + - Bad multipart parameters parsing + - Bad HTTP version + - Bad host header value + type: string + enabled: + type: boolean + maxHeaders: + type: integer + maxParams: + type: integer + type: object + type: array + violations: + items: + properties: + alarm: + type: boolean + block: + type: boolean + description: + type: string + name: + enum: + - VIOL_XML_SOAP_ATTACHMENT + - VIOL_DATA_GUARD + - VIOL_LOGIN_URL_EXPIRED + - VIOL_LOGIN_URL_BYPASSED + - VIOL_REQUEST_MAX_LENGTH + - VIOL_VIRUS + - VIOL_EVASION + - VIOL_XML_WEB_SERVICES_SECURITY + - VIOL_XML_FORMAT + - VIOL_XML_SCHEMA + - VIOL_XML_MALFORMED + - VIOL_CSRF + - VIOL_ENCODING + - VIOL_HTTP_PROTOCOL + - VIOL_GEOLOCATION + - VIOL_QUERY_STRING_LENGTH + - VIOL_REQUEST_LENGTH + - VIOL_COOKIE_LENGTH + - VIOL_URL_LENGTH + - VIOL_CSRF_EXPIRED + - VIOL_BRUTE_FORCE + - VIOL_XML_SOAP_METHOD + - VIOL_PARAMETER_VALUE_METACHAR + - VIOL_PARAMETER_NAME_METACHAR + - VIOL_URL_METACHAR + - VIOL_PARAMETER_REPEATED + - VIOL_JSON_FORMAT + - VIOL_HEADER_LENGTH + - VIOL_PARAMETER_MULTIPART_NULL_VALUE + - VIOL_POST_DATA_LENGTH + - VIOL_PARAMETER_EMPTY_VALUE + - VIOL_PARAMETER + - VIOL_FLOW_DISALLOWED_INPUT + - VIOL_DYNAMIC_SESSION + - VIOL_METHOD + - VIOL_FLOW + - VIOL_URL + - VIOL_FILETYPE + - VIOL_PARAMETER_VALUE_REGEXP + - VIOL_FLOW_MANDATORY_PARAMS + - VIOL_ATTACK_SIGNATURE + - VIOL_PARAMETER_NUMERIC_VALUE + - VIOL_PARAMETER_DATA_TYPE + - VIOL_PARAMETER_VALUE_LENGTH + - VIOL_PARAMETER_DYNAMIC_VALUE + - VIOL_PARAMETER_STATIC_VALUE + - VIOL_COOKIE_EXPIRED + - VIOL_ASM_COOKIE_HIJACKING + - VIOL_SESSION_AWARENESS + - VIOL_FLOW_ENTRY_POINT + - VIOL_JSON_MALFORMED + - VIOL_COOKIE_MALFORMED + - VIOL_COOKIE_MODIFIED + - VIOL_ASM_COOKIE_MODIFIED + - VIOL_HTTP_RESPONSE_STATUS + - VIOL_URL_CONTENT_TYPE + - VIOL_HEADER_METACHAR + - VIOL_GWT_MALFORMED + - VIOL_FILE_UPLOAD + - VIOL_MALICIOUS_IP + - VIOL_PARAMETER_VALUE_BASE64 + - VIOL_GWT_FORMAT + - VIOL_MANDATORY_HEADER + - VIOL_REDIRECT + - VIOL_WEBSOCKET_BAD_REQUEST + - VIOL_WEBSOCKET_FRAMING_PROTOCOL + - VIOL_WEBSOCKET_FRAME_MASKING + - VIOL_WEBSOCKET_FRAME_LENGTH + - VIOL_WEBSOCKET_TEXT_NULL_VALUE + - VIOL_CROSS_ORIGIN_REQUEST + - VIOL_WEBSOCKET_TEXT_MESSAGE_NOT_ALLOWED + - VIOL_WEBSOCKET_BINARY_MESSAGE_NOT_ALLOWED + - VIOL_WEBSOCKET_EXTENSION + - VIOL_WEBSOCKET_FRAMES_PER_MESSAGE_COUNT + - VIOL_WEBSOCKET_BINARY_MESSAGE_LENGTH + - VIOL_PLAINTEXT_FORMAT + - VIOL_BLACKLISTED_IP + - VIOL_THREAT_CAMPAIGN + - VIOL_PARAMETER_ARRAY_VALUE + - VIOL_JSON_SCHEMA + - VIOL_MANDATORY_PARAMETER + - VIOL_PARAMETER_LOCATION + - VIOL_MALICIOUS_DEVICE + - VIOL_BLOCKING_CONDITION + - VIOL_THREAT_ANALYSIS + - VIOL_LEAKED_CREDENTIALS + - VIOL_HOSTNAME + - VIOL_HOSTNAME_MISMATCH + - VIOL_CONVICTION + - VIOL_MANDATORY_REQUEST_BODY + - VIOL_RATING_THREAT + - VIOL_RATING_NEED_EXAMINATION + type: string + type: object + type: array + type: object + caseInsensitive: + type: boolean + character-sets: + items: + properties: + characterSet: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + characterSetType: + enum: + - gwt-content + - header + - json-content + - parameter-name + - parameter-value + - plain-text-content + - url + - xml-content + type: string + type: object + type: array + cookie-settings: + properties: + maximumCookieHeaderLength: + pattern: any|\d+ + type: string + type: object + cookies: + items: + properties: + accessibleOnlyThroughTheHttpProtocol: + type: boolean + attackSignaturesCheck: + type: boolean + enforcementType: + type: string + insertSameSiteAttribute: + enum: + - lax + - none + - none-value + - strict + type: string + name: + type: string + securedOverHttpsConnection: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + signatureId: + type: integer + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + type: object + type: array + data-guard: + properties: + creditCardNumbers: + type: boolean + enabled: + type: boolean + enforcementMode: + enum: + - ignore-urls-in-list + - enforce-urls-in-list + type: string + enforcementUrls: + items: + type: string + type: array + lastCcnDigitsToExpose: + type: integer + lastSsnDigitsToExpose: + type: integer + maskData: + type: boolean + usSocialSecurityNumbers: + type: boolean + type: object + description: + type: string + enablePassiveMode: + type: boolean + enforcementMode: + enum: + - transparent + - blocking + type: string + filetypes: + items: + properties: + allowed: + type: boolean + checkPostDataLength: + type: boolean + checkQueryStringLength: + type: boolean + checkRequestLength: + type: boolean + checkUrlLength: + type: boolean + name: + type: string + postDataLength: + type: integer + queryStringLength: + type: integer + requestLength: + type: integer + responseCheck: + type: boolean + type: + enum: + - explicit + - wildcard + type: string + urlLength: + type: integer + type: object + type: array + fullPath: + type: string + general: + properties: + allowedResponseCodes: + items: + format: int32 + maximum: 999 + minimum: 100 + type: integer + type: array + customXffHeaders: + items: + type: string + type: array + maskCreditCardNumbersInRequest: + type: boolean + trustXff: + type: boolean + type: object + header-settings: + properties: + maximumHttpHeaderLength: + pattern: any|\d+ + type: string + type: object + headers: + items: + properties: + base64Decoding: + type: boolean + checkSignatures: + type: boolean + htmlNormalization: + type: boolean + mandatory: + type: boolean + maskValueInLogs: + type: boolean + name: + type: string + normalizationViolations: + type: boolean + percentDecoding: + type: boolean + type: + enum: + - explicit + - wildcard + type: string + urlNormalization: + type: boolean + type: object + type: array + json-profiles: + items: + properties: + defenseAttributes: + properties: + maximumArrayLength: + pattern: any|\d+ + type: string + maximumStructureDepth: + pattern: any|\d+ + type: string + maximumTotalLengthOfJSONData: + pattern: any|\d+ + type: string + maximumValueLength: + pattern: any|\d+ + type: string + tolerateJSONParsingWarnings: + type: boolean + type: object + description: + type: string + hasValidationFiles: + type: boolean + name: + enum: + - Default + type: string + type: object + type: array + json-validation-files: + items: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + methods: + items: + properties: + name: + type: string + type: object + type: array + name: + type: string + parameters: + items: + properties: + allowEmptyValue: + type: boolean + allowRepeatedParameterName: + type: boolean + attackSignaturesCheck: + type: boolean + checkMaxValueLength: + type: boolean + checkMetachars: + type: boolean + level: + enum: + - global + type: string + metacharsOnParameterValueCheck: + type: boolean + name: + enum: + - '*' + type: string + nameMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + parameterLocation: + type: string + sensitiveParameter: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + signatureId: + type: integer + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + valueMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + valueType: + type: string + type: object + type: array + response-pages: + items: + properties: + ajaxActionType: + enum: + - alert-popup + - custom + - redirect + type: string + ajaxCustomContent: + type: boolean + ajaxPopupMessage: + type: string + ajaxRedirectUrl: + type: string + responseActionType: + enum: + - custom + - default + - erase-cookies + - redirect + - soap-fault + type: string + responseContent: + type: string + responseHeader: + type: string + responsePageType: + enum: + - ajax + - ajax-login + - captcha + - captcha-fail + - default + - failed-login-honeypot + - failed-login-honeypot-ajax + - hijack + - leaked-credentials + - leaked-credentials-ajax + - mobile + - persistent-flow + - xml + type: string + responseRedirectUrl: + type: string + type: object + type: array + sensitive-parameters: + items: + properties: + name: + type: string + type: object + type: array + server-technologies: + items: + properties: + serverTechnologyName: + enum: + - Jenkins + - SharePoint + - Oracle Application Server + - Python + - Oracle Identity Manager + - Spring Boot + - CouchDB + - SQLite + - Handlebars + - Mustache + - Prototype + - Zend + - Redis + - Underscore.js + - Ember.js + - ZURB Foundation + - ef.js + - Vue.js + - UIKit + - TYPO3 CMS + - RequireJS + - React + - MooTools + - Laravel + - GraphQL + - Google Web Toolkit + - Express.js + - CodeIgniter + - Backbone.js + - AngularJS + - JavaScript + - Nginx + - Jetty + - Joomla + - JavaServer Faces (JSF) + - Ruby + - MongoDB + - Django + - Node.js + - Citrix + - JBoss + - Elasticsearch + - Apache Struts + - XML + - PostgreSQL + - IBM DB2 + - Sybase/ASE + - CGI + - Proxy Servers + - SSI (Server Side Includes) + - Cisco + - Novell + - Macromedia JRun + - BEA Systems WebLogic Server + - Lotus Domino + - MySQL + - Oracle + - Microsoft SQL Server + - PHP + - Outlook Web Access + - Apache/NCSA HTTP Server + - Apache Tomcat + - WordPress + - Macromedia ColdFusion + - Unix/Linux + - Microsoft Windows + - ASP.NET + - Front Page Server Extensions (FPSE) + - IIS + - WebDAV + - ASP + - Java Servlets/JSP + - jQuery + type: string + type: object + type: array + signature-sets: + items: + properties: {} + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + signature-settings: + properties: + attackSignatureFalsePositiveMode: + enum: + - detect + - detect-and-allow + - disabled + type: string + minimumAccuracyForAutoAddedSignatures: + enum: + - high + - low + - medium + type: string + type: object + signatures: + items: + properties: + enabled: + type: boolean + signatureId: + type: integer + type: object + type: array + softwareVersion: + type: string + template: + properties: + name: + type: string + type: object + urls: + items: + properties: + method: + enum: + - '*' + type: string + name: + enum: + - '*' + type: string + protocol: + enum: + - http + - https + type: string + type: object + type: array + whitelist-ips: + items: + properties: + blockRequests: + enum: + - always + - never + type: string + ipAddress: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + ipMask: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + type: object + type: array + xml-profiles: + items: + properties: + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + allowCDATA: + type: boolean + allowDTDs: + type: boolean + allowExternalReferences: + type: boolean + allowProcessingInstructions: + type: boolean + maximumAttributeValueLength: + pattern: any|\d+ + type: string + maximumAttributesPerElement: + pattern: any|\d+ + type: string + maximumChildrenPerElement: + pattern: any|\d+ + type: string + maximumDocumentDepth: + pattern: any|\d+ + type: string + maximumDocumentSize: + pattern: any|\d+ + type: string + maximumElements: + pattern: any|\d+ + type: string + maximumNSDeclarations: + pattern: any|\d+ + type: string + maximumNameLength: + pattern: any|\d+ + type: string + maximumNamespaceLength: + pattern: any|\d+ + type: string + tolerateCloseTagShorthand: + type: boolean + tolerateLeadingWhiteSpace: + type: boolean + tolerateNumericNames: + type: boolean + type: object + description: + type: string + enableWss: + type: boolean + followSchemaLinks: + type: boolean + name: + type: string + type: object + type: array + xml-validation-files: + items: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + type: object + type: object + type: object + version: v1beta1 + versions: + - name: v1beta1 + served: true + storage: true diff --git a/deployments/daemon-set/nginx-plus-ingress.yaml b/deployments/daemon-set/nginx-plus-ingress.yaml index 05d5055b30..487bfc7412 100644 --- a/deployments/daemon-set/nginx-plus-ingress.yaml +++ b/deployments/daemon-set/nginx-plus-ingress.yaml @@ -50,6 +50,7 @@ spec: - -nginx-plus - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret + #- -enable-app-protect #- -v=3 # Enables extensive logging. Useful for troubleshooting. #- -report-ingress-status #- -external-service=nginx-ingress diff --git a/deployments/deployment/nginx-plus-ingress.yaml b/deployments/deployment/nginx-plus-ingress.yaml index 8444a8fe31..be41f8b6fc 100644 --- a/deployments/deployment/nginx-plus-ingress.yaml +++ b/deployments/deployment/nginx-plus-ingress.yaml @@ -49,6 +49,7 @@ spec: - -nginx-plus - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret + #- -enable-app-protect #- -v=3 # Enables extensive logging. Useful for troubleshooting. #- -report-ingress-status #- -external-service=nginx-ingress diff --git a/deployments/helm-chart/README.md b/deployments/helm-chart/README.md index 0749a483d9..1a19c38e09 100644 --- a/deployments/helm-chart/README.md +++ b/deployments/helm-chart/README.md @@ -181,6 +181,7 @@ Parameter | Description | Default `controller.name` | The name of the Ingress controller daemonset or deployment. | Autogenerated `controller.kind` | The kind of the Ingress controller installation - deployment or daemonset. | deployment `controller.nginxplus` | Deploys the Ingress controller for NGINX Plus. | false +`controller.nginxReloadTimeout` | The timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. The default is 4000 (or 20000 if `controller.appprotect.enable` is true). If set to 0, the default value will be used. | 0 `controller.hostNetwork` | Enables the Ingress controller pods to use the host's network namespace. | false `controller.nginxDebug` | Enables debugging for NGINX. Uses the `nginx-debug` binary. Requires `error-log-level: debug` in the ConfigMap via `controller.config.entries`. | false `controller.logLevel` | The log level of the Ingress Controller. | 1 @@ -242,6 +243,7 @@ Parameter | Description | Default `controller.reportIngressStatus.leaderElectionLockName` | Specifies the name of the ConfigMap, within the same namespace as the controller, used as the lock for leader election. controller.reportIngressStatus.enableLeaderElection must be set to true. | Autogenerated `controller.reportIngressStatus.annotations` | The annotations of the leader election configmap. | {} `controller.pod.annotations` | The annotations of the Ingress Controller pod. | {} +`controller.appprotect.enable` | Enables the App Protect module in the Ingress Controller. | false `rbac.create` | Configures RBAC. | true `prometheus.create` | Expose NGINX or NGINX Plus metrics in the Prometheus format. | false `prometheus.port` | Configures the port to scrape the metrics. | 9113 diff --git a/deployments/helm-chart/crds/ap-logconf.yaml b/deployments/helm-chart/crds/ap-logconf.yaml new file mode 100644 index 0000000000..70c825c1e6 --- /dev/null +++ b/deployments/helm-chart/crds/ap-logconf.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + "helm.sh/hook": crd-install + creationTimestamp: null + name: aplogconfs.appprotect.f5.com + labels: + app.kubernetes.io/name: "nginx-ingress" +spec: + preserveUnknownFields: false + group: appprotect.f5.com + names: + kind: APLogConf + listKind: APLogConfList + plural: aplogconfs + singular: aplogconf + scope: Namespaced + validation: + openAPIV3Schema: + description: APLogConf is the Schema for the APLogConfs 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: APLogConfSpec defines the desired state of APLogConf + properties: + content: + properties: + format: + enum: + - splunk + - arcsight + - default + - user-defined + type: string + format_string: + type: string + max_message_size: + pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ + type: string + max_request_size: + pattern: ^([1-9]|[1-9][0-9]|[1-9][0-9]{2}|1[0-9]{3}|20[1-3][0-9]|204[1-8]|any)$ + type: string + type: object + filter: + properties: + request_type: + enum: + - all + - illegal + - blocked + type: string + type: object + type: object + type: object + version: v1beta1 + versions: + - name: v1beta1 + served: true + storage: true \ No newline at end of file diff --git a/deployments/helm-chart/crds/ap-policy.yaml b/deployments/helm-chart/crds/ap-policy.yaml new file mode 100644 index 0000000000..22360d0e3a --- /dev/null +++ b/deployments/helm-chart/crds/ap-policy.yaml @@ -0,0 +1,828 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + "helm.sh/hook": crd-install + creationTimestamp: null + name: appolicies.appprotect.f5.com + labels: + app.kubernetes.io/name: "nginx-ingress" +spec: + preserveUnknownFields: false + group: appprotect.f5.com + names: + kind: APPolicy + listKind: APPolicyList + plural: appolicies + singular: appolicy + scope: Namespaced + validation: + openAPIV3Schema: + description: APPolicyConfig is the Schema for the APPolicyconfigs 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: APPolicySpec defines the desired state of APPolicy + properties: + modifications: + items: + properties: {} + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + policy: + description: Defines the App Protect policy + properties: + applicationLanguage: + enum: + - iso-8859-10 + - iso-8859-6 + - windows-1255 + - auto-detect + - koi8-r + - gb18030 + - iso-8859-8 + - windows-1250 + - iso-8859-9 + - windows-1252 + - iso-8859-16 + - gb2312 + - iso-8859-2 + - iso-8859-5 + - windows-1257 + - windows-1256 + - iso-8859-13 + - windows-874 + - windows-1253 + - iso-8859-3 + - euc-jp + - utf-8 + - gbk + - windows-1251 + - big5 + - iso-8859-1 + - shift_jis + - euc-kr + - iso-8859-4 + - iso-8859-7 + - iso-8859-15 + type: string + blocking-settings: + properties: + evasions: + items: + properties: + description: + enum: + - '%u decoding' + - Apache whitespace + - Bad unescape + - Bare byte decoding + - Directory traversals + - IIS backslashes + - IIS Unicode codepoints + - Multiple decoding + type: string + enabled: + type: boolean + maxDecodingPasses: + type: integer + type: object + type: array + http-protocols: + items: + properties: + description: + enum: + - Unparsable request content + - Several Content-Length headers + - 'POST request with Content-Length: 0' + - Null in request + - No Host header in HTTP/1.1 request + - Multiple host headers + - Host header contains IP address + - High ASCII characters in headers + - Header name with no header value + - CRLF characters before request start + - Content length should be a positive number + - Chunked request with Content-Length header + - Check maximum number of parameters + - Check maximum number of headers + - Body in GET or HEAD requests + - Bad multipart/form-data request parsing + - Bad multipart parameters parsing + - Bad HTTP version + - Bad host header value + type: string + enabled: + type: boolean + maxHeaders: + type: integer + maxParams: + type: integer + type: object + type: array + violations: + items: + properties: + alarm: + type: boolean + block: + type: boolean + description: + type: string + name: + enum: + - VIOL_XML_SOAP_ATTACHMENT + - VIOL_DATA_GUARD + - VIOL_LOGIN_URL_EXPIRED + - VIOL_LOGIN_URL_BYPASSED + - VIOL_REQUEST_MAX_LENGTH + - VIOL_VIRUS + - VIOL_EVASION + - VIOL_XML_WEB_SERVICES_SECURITY + - VIOL_XML_FORMAT + - VIOL_XML_SCHEMA + - VIOL_XML_MALFORMED + - VIOL_CSRF + - VIOL_ENCODING + - VIOL_HTTP_PROTOCOL + - VIOL_GEOLOCATION + - VIOL_QUERY_STRING_LENGTH + - VIOL_REQUEST_LENGTH + - VIOL_COOKIE_LENGTH + - VIOL_URL_LENGTH + - VIOL_CSRF_EXPIRED + - VIOL_BRUTE_FORCE + - VIOL_XML_SOAP_METHOD + - VIOL_PARAMETER_VALUE_METACHAR + - VIOL_PARAMETER_NAME_METACHAR + - VIOL_URL_METACHAR + - VIOL_PARAMETER_REPEATED + - VIOL_JSON_FORMAT + - VIOL_HEADER_LENGTH + - VIOL_PARAMETER_MULTIPART_NULL_VALUE + - VIOL_POST_DATA_LENGTH + - VIOL_PARAMETER_EMPTY_VALUE + - VIOL_PARAMETER + - VIOL_FLOW_DISALLOWED_INPUT + - VIOL_DYNAMIC_SESSION + - VIOL_METHOD + - VIOL_FLOW + - VIOL_URL + - VIOL_FILETYPE + - VIOL_PARAMETER_VALUE_REGEXP + - VIOL_FLOW_MANDATORY_PARAMS + - VIOL_ATTACK_SIGNATURE + - VIOL_PARAMETER_NUMERIC_VALUE + - VIOL_PARAMETER_DATA_TYPE + - VIOL_PARAMETER_VALUE_LENGTH + - VIOL_PARAMETER_DYNAMIC_VALUE + - VIOL_PARAMETER_STATIC_VALUE + - VIOL_COOKIE_EXPIRED + - VIOL_ASM_COOKIE_HIJACKING + - VIOL_SESSION_AWARENESS + - VIOL_FLOW_ENTRY_POINT + - VIOL_JSON_MALFORMED + - VIOL_COOKIE_MALFORMED + - VIOL_COOKIE_MODIFIED + - VIOL_ASM_COOKIE_MODIFIED + - VIOL_HTTP_RESPONSE_STATUS + - VIOL_URL_CONTENT_TYPE + - VIOL_HEADER_METACHAR + - VIOL_GWT_MALFORMED + - VIOL_FILE_UPLOAD + - VIOL_MALICIOUS_IP + - VIOL_PARAMETER_VALUE_BASE64 + - VIOL_GWT_FORMAT + - VIOL_MANDATORY_HEADER + - VIOL_REDIRECT + - VIOL_WEBSOCKET_BAD_REQUEST + - VIOL_WEBSOCKET_FRAMING_PROTOCOL + - VIOL_WEBSOCKET_FRAME_MASKING + - VIOL_WEBSOCKET_FRAME_LENGTH + - VIOL_WEBSOCKET_TEXT_NULL_VALUE + - VIOL_CROSS_ORIGIN_REQUEST + - VIOL_WEBSOCKET_TEXT_MESSAGE_NOT_ALLOWED + - VIOL_WEBSOCKET_BINARY_MESSAGE_NOT_ALLOWED + - VIOL_WEBSOCKET_EXTENSION + - VIOL_WEBSOCKET_FRAMES_PER_MESSAGE_COUNT + - VIOL_WEBSOCKET_BINARY_MESSAGE_LENGTH + - VIOL_PLAINTEXT_FORMAT + - VIOL_BLACKLISTED_IP + - VIOL_THREAT_CAMPAIGN + - VIOL_PARAMETER_ARRAY_VALUE + - VIOL_JSON_SCHEMA + - VIOL_MANDATORY_PARAMETER + - VIOL_PARAMETER_LOCATION + - VIOL_MALICIOUS_DEVICE + - VIOL_BLOCKING_CONDITION + - VIOL_THREAT_ANALYSIS + - VIOL_LEAKED_CREDENTIALS + - VIOL_HOSTNAME + - VIOL_HOSTNAME_MISMATCH + - VIOL_CONVICTION + - VIOL_MANDATORY_REQUEST_BODY + - VIOL_RATING_THREAT + - VIOL_RATING_NEED_EXAMINATION + type: string + type: object + type: array + type: object + caseInsensitive: + type: boolean + character-sets: + items: + properties: + characterSet: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + characterSetType: + enum: + - gwt-content + - header + - json-content + - parameter-name + - parameter-value + - plain-text-content + - url + - xml-content + type: string + type: object + type: array + cookie-settings: + properties: + maximumCookieHeaderLength: + pattern: any|\d+ + type: string + type: object + cookies: + items: + properties: + accessibleOnlyThroughTheHttpProtocol: + type: boolean + attackSignaturesCheck: + type: boolean + enforcementType: + type: string + insertSameSiteAttribute: + enum: + - lax + - none + - none-value + - strict + type: string + name: + type: string + securedOverHttpsConnection: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + signatureId: + type: integer + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + type: object + type: array + data-guard: + properties: + creditCardNumbers: + type: boolean + enabled: + type: boolean + enforcementMode: + enum: + - ignore-urls-in-list + - enforce-urls-in-list + type: string + enforcementUrls: + items: + type: string + type: array + lastCcnDigitsToExpose: + type: integer + lastSsnDigitsToExpose: + type: integer + maskData: + type: boolean + usSocialSecurityNumbers: + type: boolean + type: object + description: + type: string + enablePassiveMode: + type: boolean + enforcementMode: + enum: + - transparent + - blocking + type: string + filetypes: + items: + properties: + allowed: + type: boolean + checkPostDataLength: + type: boolean + checkQueryStringLength: + type: boolean + checkRequestLength: + type: boolean + checkUrlLength: + type: boolean + name: + type: string + postDataLength: + type: integer + queryStringLength: + type: integer + requestLength: + type: integer + responseCheck: + type: boolean + type: + enum: + - explicit + - wildcard + type: string + urlLength: + type: integer + type: object + type: array + fullPath: + type: string + general: + properties: + allowedResponseCodes: + items: + format: int32 + maximum: 999 + minimum: 100 + type: integer + type: array + customXffHeaders: + items: + type: string + type: array + maskCreditCardNumbersInRequest: + type: boolean + trustXff: + type: boolean + type: object + header-settings: + properties: + maximumHttpHeaderLength: + pattern: any|\d+ + type: string + type: object + headers: + items: + properties: + base64Decoding: + type: boolean + checkSignatures: + type: boolean + htmlNormalization: + type: boolean + mandatory: + type: boolean + maskValueInLogs: + type: boolean + name: + type: string + normalizationViolations: + type: boolean + percentDecoding: + type: boolean + type: + enum: + - explicit + - wildcard + type: string + urlNormalization: + type: boolean + type: object + type: array + json-profiles: + items: + properties: + defenseAttributes: + properties: + maximumArrayLength: + pattern: any|\d+ + type: string + maximumStructureDepth: + pattern: any|\d+ + type: string + maximumTotalLengthOfJSONData: + pattern: any|\d+ + type: string + maximumValueLength: + pattern: any|\d+ + type: string + tolerateJSONParsingWarnings: + type: boolean + type: object + description: + type: string + hasValidationFiles: + type: boolean + name: + enum: + - Default + type: string + type: object + type: array + json-validation-files: + items: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + methods: + items: + properties: + name: + type: string + type: object + type: array + name: + type: string + parameters: + items: + properties: + allowEmptyValue: + type: boolean + allowRepeatedParameterName: + type: boolean + attackSignaturesCheck: + type: boolean + checkMaxValueLength: + type: boolean + checkMetachars: + type: boolean + level: + enum: + - global + type: string + metacharsOnParameterValueCheck: + type: boolean + name: + enum: + - '*' + type: string + nameMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + parameterLocation: + type: string + sensitiveParameter: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + signatureId: + type: integer + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + valueMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + valueType: + type: string + type: object + type: array + response-pages: + items: + properties: + ajaxActionType: + enum: + - alert-popup + - custom + - redirect + type: string + ajaxCustomContent: + type: boolean + ajaxPopupMessage: + type: string + ajaxRedirectUrl: + type: string + responseActionType: + enum: + - custom + - default + - erase-cookies + - redirect + - soap-fault + type: string + responseContent: + type: string + responseHeader: + type: string + responsePageType: + enum: + - ajax + - ajax-login + - captcha + - captcha-fail + - default + - failed-login-honeypot + - failed-login-honeypot-ajax + - hijack + - leaked-credentials + - leaked-credentials-ajax + - mobile + - persistent-flow + - xml + type: string + responseRedirectUrl: + type: string + type: object + type: array + sensitive-parameters: + items: + properties: + name: + type: string + type: object + type: array + server-technologies: + items: + properties: + serverTechnologyName: + enum: + - Jenkins + - SharePoint + - Oracle Application Server + - Python + - Oracle Identity Manager + - Spring Boot + - CouchDB + - SQLite + - Handlebars + - Mustache + - Prototype + - Zend + - Redis + - Underscore.js + - Ember.js + - ZURB Foundation + - ef.js + - Vue.js + - UIKit + - TYPO3 CMS + - RequireJS + - React + - MooTools + - Laravel + - GraphQL + - Google Web Toolkit + - Express.js + - CodeIgniter + - Backbone.js + - AngularJS + - JavaScript + - Nginx + - Jetty + - Joomla + - JavaServer Faces (JSF) + - Ruby + - MongoDB + - Django + - Node.js + - Citrix + - JBoss + - Elasticsearch + - Apache Struts + - XML + - PostgreSQL + - IBM DB2 + - Sybase/ASE + - CGI + - Proxy Servers + - SSI (Server Side Includes) + - Cisco + - Novell + - Macromedia JRun + - BEA Systems WebLogic Server + - Lotus Domino + - MySQL + - Oracle + - Microsoft SQL Server + - PHP + - Outlook Web Access + - Apache/NCSA HTTP Server + - Apache Tomcat + - WordPress + - Macromedia ColdFusion + - Unix/Linux + - Microsoft Windows + - ASP.NET + - Front Page Server Extensions (FPSE) + - IIS + - WebDAV + - ASP + - Java Servlets/JSP + - jQuery + type: string + type: object + type: array + signature-sets: + items: + properties: {} + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + signature-settings: + properties: + attackSignatureFalsePositiveMode: + enum: + - detect + - detect-and-allow + - disabled + type: string + minimumAccuracyForAutoAddedSignatures: + enum: + - high + - low + - medium + type: string + type: object + signatures: + items: + properties: + enabled: + type: boolean + signatureId: + type: integer + type: object + type: array + softwareVersion: + type: string + template: + properties: + name: + type: string + type: object + urls: + items: + properties: + method: + enum: + - '*' + type: string + name: + enum: + - '*' + type: string + protocol: + enum: + - http + - https + type: string + type: object + type: array + whitelist-ips: + items: + properties: + blockRequests: + enum: + - always + - never + type: string + ipAddress: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + ipMask: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + type: object + type: array + xml-profiles: + items: + properties: + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + allowCDATA: + type: boolean + allowDTDs: + type: boolean + allowExternalReferences: + type: boolean + allowProcessingInstructions: + type: boolean + maximumAttributeValueLength: + pattern: any|\d+ + type: string + maximumAttributesPerElement: + pattern: any|\d+ + type: string + maximumChildrenPerElement: + pattern: any|\d+ + type: string + maximumDocumentDepth: + pattern: any|\d+ + type: string + maximumDocumentSize: + pattern: any|\d+ + type: string + maximumElements: + pattern: any|\d+ + type: string + maximumNSDeclarations: + pattern: any|\d+ + type: string + maximumNameLength: + pattern: any|\d+ + type: string + maximumNamespaceLength: + pattern: any|\d+ + type: string + tolerateCloseTagShorthand: + type: boolean + tolerateLeadingWhiteSpace: + type: boolean + tolerateNumericNames: + type: boolean + type: object + description: + type: string + enableWss: + type: boolean + followSchemaLinks: + type: boolean + name: + type: string + type: object + type: array + xml-validation-files: + items: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + type: object + type: object + type: object + version: v1beta1 + versions: + - name: v1beta1 + served: true + storage: true diff --git a/deployments/helm-chart/templates/controller-daemonset.yaml b/deployments/helm-chart/templates/controller-daemonset.yaml index bbd11bf7e9..189dabea7e 100644 --- a/deployments/helm-chart/templates/controller-daemonset.yaml +++ b/deployments/helm-chart/templates/controller-daemonset.yaml @@ -90,6 +90,8 @@ spec: {{ toYaml .Values.controller.resources | indent 10 }} args: - -nginx-plus={{ .Values.controller.nginxplus }} + - -nginx-reload-timeout={{ .Values.controller.nginxReloadTimeout }} + - -enable-app-protect={{ .Values.controller.appprotect.enable }} - -nginx-configmaps=$(POD_NAMESPACE)/{{ include "nginx-ingress.configName" . }} {{- if .Values.controller.defaultTLS.secret }} - -default-server-tls-secret={{ .Values.controller.defaultTLS.secret }} diff --git a/deployments/helm-chart/templates/controller-deployment.yaml b/deployments/helm-chart/templates/controller-deployment.yaml index 753d830564..dc5f884061 100644 --- a/deployments/helm-chart/templates/controller-deployment.yaml +++ b/deployments/helm-chart/templates/controller-deployment.yaml @@ -88,6 +88,8 @@ spec: fieldPath: metadata.name args: - -nginx-plus={{ .Values.controller.nginxplus }} + - -nginx-reload-timeout={{ .Values.controller.nginxReloadTimeout }} + - -enable-app-protect={{ .Values.controller.appprotect.enable }} - -nginx-configmaps=$(POD_NAMESPACE)/{{ include "nginx-ingress.configName" . }} {{- if .Values.controller.defaultTLS.secret }} - -default-server-tls-secret={{ .Values.controller.defaultTLS.secret }} diff --git a/deployments/helm-chart/templates/rbac.yaml b/deployments/helm-chart/templates/rbac.yaml index 1c1d337171..7091f5935c 100644 --- a/deployments/helm-chart/templates/rbac.yaml +++ b/deployments/helm-chart/templates/rbac.yaml @@ -6,6 +6,17 @@ metadata: labels: {{- include "nginx-ingress.labels" . | nindent 4 }} rules: +{{- if .Values.controller.appprotect.enable }} +- apiGroups: + - appprotect.f5.com + resources: + - appolicies + - aplogconfs + verbs: + - get + - watch + - list +{{- end }} - apiGroups: - "" resources: diff --git a/deployments/helm-chart/values.yaml b/deployments/helm-chart/values.yaml index f83b53066d..a99f0abc5b 100644 --- a/deployments/helm-chart/values.yaml +++ b/deployments/helm-chart/values.yaml @@ -9,6 +9,16 @@ controller: ## Deploys the Ingress controller for NGINX Plus. nginxplus: false + # Timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. + # Default is 4000 (default is 20000 instead if enable-app-protect is true) + # If set to 0, default values will be used. + nginxReloadTimeout: 0 + + ## Support for App Protect + appprotect: + ## Enable the App Protect module in the Ingress Controller. + enable: false + ## Enables the Ingress controller pods to use the host's network namespace. hostNetwork: false @@ -38,7 +48,7 @@ controller: ## The annotations of the Ingress Controller configmap. annotations: {} - + ## The entries of the ConfigMap for customizing NGINX configuration. entries: {} diff --git a/deployments/rbac/ap-rbac.yaml b/deployments/rbac/ap-rbac.yaml new file mode 100644 index 0000000000..7b2e1eb20f --- /dev/null +++ b/deployments/rbac/ap-rbac.yaml @@ -0,0 +1,27 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: nginx-ingress-app-protect +rules: +- apiGroups: + - appprotect.f5.com + resources: + - appolicies + - aplogconfs + verbs: + - "get" + - "watch" + - "list" +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: nginx-ingress-app-protect +subjects: +- kind: ServiceAccount + name: nginx-ingress + namespace: nginx-ingress +roleRef: + kind: ClusterRole + name: nginx-ingress-app-protect + apiGroup: rbac.authorization.k8s.io diff --git a/docs-web/app-protect/configuration.md b/docs-web/app-protect/configuration.md new file mode 100644 index 0000000000..a473c4d6a7 --- /dev/null +++ b/docs-web/app-protect/configuration.md @@ -0,0 +1,126 @@ +# Configuration +This document describes how to configure the NGINX App Protect module +> Check out the complete [NGINX Ingress Controller with App Protect example resources on GitHub](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/appprotect). + +## Global Configuration + +The NGINX Ingress Controller has a set of global configuration parameters that align with those available in the NGINX App Protect module. See [ConfigMap keys](/nginx-ingress-controller/configuration/global-configuration/configmap-resource/#modules) for the complete list. The App Protect parameters use the `app-protect*` prefix. + +> Check out the complete [NGINX Ingress Controller with App Protect example resources on GitHub](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/appprotect). + +## Enable App Protect for an Ingress Resource + +You can enable and configure NGINX App Protect on a per-Ingress-resource basis. To do so, you can apply the [App Protect annotations](/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/#app-protect) to each desired resource. + +## App Protect Policies + +You can define App Protect policies for your Ingress resources by creating an `APPolicy` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). + +To add any [App Protect policy](/nginx-app-protect/policy/#policy) to an Ingress resource: + +1. Create an `APPolicy` Custom resource manifest. +2. Add the desired policy to the `spec` field in the `APPolicy` resource. + + > **Note**: The relationship between the Policy JSON and the resource spec is 1:1. If you're defining your resources in YAML, as we do in our examples, you'll need to represent the policy as YAML. The fields must match those in the source JSON exactly in name and level. + + For example, say you want to use the [DataGuard policy](/nginx-app-protect/policy/#data-guard) shown below: + + ```json + { + "policy": { + "name": "dataguard_blocking", + "template": { "name": "POLICY_TEMPLATE_NGINX_BASE" }, + "applicationLanguage": "utf-8", + "enforcementMode": "blocking", + "blocking-settings": { + "violations": [ + { + "name": "VIOL_DATA_GUARD", + "alarm": true, + "block": true + } + ] + }, + "data-guard": { + "enabled": true, + "maskData": true, + "creditCardNumbers": true, + "usSocialSecurityNumbers": true, + "enforcementMode": "ignore-urls-in-list", + "enforcementUrls": [] + } + } + } + ``` + + You would create an `APPolicy` resource with the policy defined in the `spec`, as shown below: + + ```yaml + apiVersion: appprotect.f5.com/v1beta1 + kind: APPolicy + metadata: + name: dataguard-blocking + spec: + policy: + name: dataguard_blocking + template: + name: POLICY_TEMPLATE_NGINX_BASE + applicationLanguage: utf-8 + enforcementMode: blocking + blocking-settings: + violations: + - name: VIOL_DATA_GUARD + alarm: true + block: true + data-guard: + enabled: true + maskData: true + creditCardNumbers: true + usSocialSecurityNumbers: true + enforcementMode: ignore-urls-in-list + enforcementUrls: [] + ``` + + > Notice how the fields match exactly in name and level. The Ingress Controller will transform the YAML into a valid JSON App Protect policy config. + +## App Protect Logs + +You can set the [App Protect Log configurations](/nginx-app-protect/nginx-app-protect/troubleshooting/#app-protect-logging-overview) by creating an `APLogConf` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). + +To add the [App Protect log configurations](/nginx-app-protect/policy/#policy) to an Ingress resource: + +1. Create an `APLogConf` Custom resource manifest. +2. Add the desired log configuration to the `spec` field in the `APLogConf` resource. + + > **Note**: The fields from the JSON must be presented in the YAML *exactly* the same, in name and level. The Ingress Controller will transform the YAML into a valid JSON App Protect log config. + +For example, say you want to [log state changing requests](nginx-app-protect/troubleshooting/#log-state-changing-requests) for your Ingress resources using App Protect. The App Protect log configuration looks like this: + +```json +{ + "filter": { + "request_type": "all" + }, + "content": { + "format": "default", + "max_request_size": "any", + "max_message_size": "5k" + } +} +``` + +You would add define that config in the `spec` of your `APLogConf` resource as follows: + +```yaml +apiVersion: appprotect.f5.com/v1beta1 +kind: APLogConf +metadata: + name: logconf +spec: + filter: + request_types: all + content: + format: default + max_request_size: any + max_message_size: 5k +``` diff --git a/docs-web/app-protect/index.rst b/docs-web/app-protect/index.rst new file mode 100644 index 0000000000..b8c89b28f9 --- /dev/null +++ b/docs-web/app-protect/index.rst @@ -0,0 +1,11 @@ +.. meta:: + :description: Learn how to use NGINX Ingress Controller for Kubernetes with NGINX App Protect. + +Ingress Controller with App Protect +=================================== + +.. toctree:: + :maxdepth: 2 + + installation + configuration diff --git a/docs-web/app-protect/installation.md b/docs-web/app-protect/installation.md new file mode 100644 index 0000000000..e6ee989301 --- /dev/null +++ b/docs-web/app-protect/installation.md @@ -0,0 +1,36 @@ +# Install NGINX Ingress Controller with App Protect + +> **Note**: The NGINX Kubernetes Ingress Controller integration with NGINX App Protect requires the use of NGINX Plus. + +This document provides an overview of the steps required to use NGINX App Protect with your NGINX Ingress Controller deployment. You can visit the linked documents to find additional information and instructions. + +You can also [install the Ingress Controller with App Protect by using Helm](/nginx-ingress-controller/installation/installation-with-helm/). Use the `controller.appprotect.*` parameters of the chart. + +## Build the Docker Image + +Take the steps below to create the Docker image that you'll use to deploy NGINX Ingress Controller with App Protect in Kubernetes. + +- [Build the NGINX Ingress Controller image](/nginx-ingress-controller/installation/building-ingress-controller-image). + + When running the `make` command to build the image, be sure to use the `DOCKERFILE=appprotect/DockerfileWithAppProtectForPlus` build parameter. For example: + + ```bash + make DOCKERFILE=appprotect/DockerfileWithAppProtectForPlus PREFIX=/nginx-plus-ingress + ``` + +- [Push the image to your local Docker registry](/nginx-ingress-controller/installation/building-ingress-controller-image/#building-the-image-and-pushing-it-to-the-private-registry). + +## Install the Ingress Controller + +Take the steps below to set up and deploy the NGINX Ingress Controller and App Protect module in your Kubernetes cluster. + +1. [Configure role-based access control (RBAC)](/nginx-ingress-controller/installation/installation-with-manifests/#configure-rbac). + + > **Important**: You must have an admin role to configure RBAC in your Kubernetes cluster. + +2. [Create the common Kubernetes resources](/nginx-ingress-controller/installation/installation-with-manifests/#create-common-resources). +3. Enable the App Protect module by adding the `enable-app-protect` [cli argument](/nginx-ingress-controller/configuration/global-configuration/command-line-arguments/#cmdoption-enable-app-protect) to your Deployment or DaemonSet file. +4. [Deploy the Ingress Controller](/nginx-ingress-controller/installation/installation-with-manifests/#deploy-the-ingress-controller). + +For more information, see the [Configuration guide](/nginx-ingress-controller/app-protect/configuration) and the [NGINX Ingress Controller with App Protect examples on GitHub](https://github.com/nginxinc/kubernetes-ingress/tree/master/examples/appprotect). + diff --git a/docs-web/configuration/global-configuration/command-line-arguments.md b/docs-web/configuration/global-configuration/command-line-arguments.md index 6014627762..4f206125c8 100644 --- a/docs-web/configuration/global-configuration/command-line-arguments.md +++ b/docs-web/configuration/global-configuration/command-line-arguments.md @@ -102,6 +102,10 @@ Below we describe the available command-line arguments: Enable support for NGINX Plus +.. option:: -nginx-reload-timeout + + Timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. (default is 4000. Default is 20000 instead if `enable-app-protect` is true) + .. option:: -nginx-status Enable the NGINX stub_status, or the NGINX Plus API. (default true) @@ -176,4 +180,13 @@ Below we describe the available command-line arguments: Specifies the address of a running Spire agent. **For use with NGINX Service Mesh only**. - If the argument is set, but the Ingress Controller is unable to connect to the Spire Agent, the Ingress Controller will fail to start. + +.. option:: -enable-app-protect + + Enables support for App Protect. + + Requires :option:`-nginx-plus` + + - If the argument is set, but `nginx-plus` is set to false, the Ingress Controller will fail to start. + ``` diff --git a/docs-web/configuration/global-configuration/configmap-resource.md b/docs-web/configuration/global-configuration/configmap-resource.md index 1bf4021b9d..7ddb73806c 100644 --- a/docs-web/configuration/global-configuration/configmap-resource.md +++ b/docs-web/configuration/global-configuration/configmap-resource.md @@ -406,4 +406,20 @@ See the doc about [VirtualServer and VirtualServerRoute resources](/nginx-ingres - Sets the tracer configuration in JSON format. - N/A - `Support for OpenTracing `_. + * - ``app-protect-cookie-seed`` + - Sets the ``app_protect_cookie_seed`` `global directive `_. + - Random automatically generated string + - + * - ``app-protect-failure-mode-action`` + - Sets the ``app_protect_failure_mode_action`` `global directive `_. + - ``pass`` + - + * - ``app-protect-cpu-thresholds`` + - Sets the ``app_protect_cpu_thresholds`` `global directive `_. + - ``high=100 low=100`` + - + * - ``app-protect-physical-memory-util-thresholds`` + - Sets the ``app_protect_physical_memory_util_thresholds`` `global directive `_. + - ``high=100 low=100`` + - ``` diff --git a/docs-web/configuration/ingress-resources/advanced-configuration-with-annotations.md b/docs-web/configuration/ingress-resources/advanced-configuration-with-annotations.md index 836a7b50f7..81f8f0053d 100644 --- a/docs-web/configuration/ingress-resources/advanced-configuration-with-annotations.md +++ b/docs-web/configuration/ingress-resources/advanced-configuration-with-annotations.md @@ -340,3 +340,43 @@ The table below summarizes the available annotations. - N/A - ``` + +### App Protect + +**Note**: The App Protect annotations only work if App Protect module is [installed](/nginx-ingress-controller/app-protect/installation/). + +```eval_rst +.. list-table:: + :header-rows: 1 + + * - Annotation + - ConfigMap Key + - Description + - Default + - Example + * - ``appprotect.f5.com/app-protect-policy`` + - N/A + - The name of the App Protect Policy for the Ingress Resource. Format is ``namespace/name``. If no namespace is specified, the same namespace of the Ingress Resource is used. If not specified but ``appprotect.f5.com/app-protect-enable`` is true, a default policy id applied. If the referenced policy resource does not exist, or policy is invalid, this annotation will be ignored, and the default policy will be applied. + - N/A + - `Example for App Protect `_. + * - ``appprotect.f5.com/app-protect-enable`` + - N/A + - Enable App Protect for the Ingress Resource. + - ``False`` + - `Example for App Protect `_. + * - ``appprotect.f5.com/app-protect-security-log-enable`` + - N/A + - Enable the `security log `_ for App Protect. + - ``False`` + - `Example for App Protect `_. + * - ``appprotect.f5.com/app-protect-security-log`` + - N/A + - The App Protect log configuration for the Ingress Resource. Format is ``namespace/name``. If no namespace is specified, the same namespace as the Ingress Resource is used. If not specified the default is used which is: filter: ``illegal``, format: ``default`` + - N/A + - `Example for App Protect `_. + * - ``appprotect.f5.com/app-protect-security-log-destination`` + - N/A + - The destination of the security log. For more information check the `DESTINATION argument `_. + - ``syslog:server=localhost:514`` + - `Example for App Protect `_. +``` diff --git a/docs-web/index.rst b/docs-web/index.rst index 0e58038059..68f40febdb 100644 --- a/docs-web/index.rst +++ b/docs-web/index.rst @@ -12,6 +12,7 @@ NGINX Ingress Controller configuration/index logging-and-monitoring/index troubleshooting + app-protect/index third-party-modules/index releases Technical Specifications \ No newline at end of file diff --git a/docs-web/installation/building-ingress-controller-image.md b/docs-web/installation/building-ingress-controller-image.md index 57e2df973b..2d2cff9a86 100644 --- a/docs-web/installation/building-ingress-controller-image.md +++ b/docs-web/installation/building-ingress-controller-image.md @@ -73,6 +73,7 @@ The **Makefile** contains the following main variables for you to customize (eit 1. `DockerfileWithOpentracingForPlus`, for building a debian-based image with NGINX Plus, [opentracing](https://github.com/opentracing-contrib/nginx-opentracing) module and the [Jaeger](https://www.jaegertracing.io/) tracer. 1. `openshift/Dockerfile`, for building an ubi-based image with NGINX for [Openshift](https://www.openshift.com/) clusters. 1. `openshift/DockerfileForPlus`, for building an ubi-based image with NGINX Plus for [Openshift](https://www.openshift.com/) clusters. + 1. `appprotect/DockerfileWithAppProtectForPlus `, for building a debian-based image with NGINX Plus and the [appprotect](/nginx-app-protect/) module. * **GENERATE_DEFAULT_CERT_AND_KEY** - The Ingress controller requires a certificate and a key for the default HTTP/HTTPS server. You can reference them in a TLS Secret in a command-line argument to the Ingress controller. As an alternative, you can add a file in the PEM format with your certificate and key to the image as `/etc/nginx/secrets/default`. Optionally, you can generate a self-signed certificate and a key during the build process. Set `GENERATE_DEFAULT_CERT_AND_KEY` to `1` to generate a certificate and a key in the `default.pem` file. Note that you must add the `ADD` instruction in the Dockerfile to copy the cert and the key to the image. The default value of `GENERATE_DEFAULT_CERT_AND_KEY` is `0`. * **DOCKER_BUILD_OPTIONS** -- the [options](https://docs.docker.com/engine/reference/commandline/build/#options) for the `docker build` command. For example, `--pull`. * **BUILD_IN_CONTAINER** -- By default, to compile the controller we use the [golang](https://hub.docker.com/_/golang/) container that we run as part of the building process. If you want to compile the controller using your local golang environment: diff --git a/docs-web/installation/installation-with-helm.md b/docs-web/installation/installation-with-helm.md index 89337aaa57..631e31277a 100644 --- a/docs-web/installation/installation-with-helm.md +++ b/docs-web/installation/installation-with-helm.md @@ -189,6 +189,12 @@ The following tables lists the configurable parameters of the NGINX Ingress cont * - ``controller.nginxplus`` - Deploys the Ingress controller for NGINX Plus. - false + * - ``controller.nginxReloadTimeout`` + - The timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. The default is 4000 (or 20000 if `controller.appprotect.enable` is true). If set to 0, the default value will be used. + - 0 + * - ``controller.appprotect.enable`` + - Enables the App Protect module in the Ingress Controller. + - false * - ``controller.hostNetwork`` - Enables the Ingress controller pods to use the host's network namespace. - false diff --git a/docs-web/installation/installation-with-manifests.md b/docs-web/installation/installation-with-manifests.md index 3736884579..dfc77c955a 100644 --- a/docs-web/installation/installation-with-manifests.md +++ b/docs-web/installation/installation-with-manifests.md @@ -23,6 +23,13 @@ This document describes how to install the NGINX Ingress Controller in your Kube ``` $ kubectl apply -f rbac/rbac.yaml ``` + +3. (App Protect only) Create the App Protect role and role binding: + + ``` + $ kubectl apply -f rbac/ap-rbac.yaml + ``` + **Note**: To perform this step you must be a cluster admin. Follow the documentation of your Kubernetes platform to configure the admin access. For GKE, see the [Role-Based Access Control](https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control) doc. ## 2. Create Common Resources @@ -61,6 +68,17 @@ If you would like to use the TCP and UDP load balancing features of the Ingress > **Feature Status**: The TransportServer, GlobalConfiguration and Policy resources are available as a preview feature: it is suitable for experimenting and testing; however, it must be used with caution in production environments. Additionally, while the feature is in preview, we might introduce some backward-incompatible changes to the resources specification in the next releases. +### Resources for NGINX App Protect + +If you would like to use the App Protect module, create the following additional resources: + +1. Create a custom resource definition for `APPolicy` and `APLogConf`: + + ``` + $ kubectl apply -f common/ap-logconf-definition.yaml + $ kubectl apply -f common/ap-policy-definition.yaml + ``` + ## 3. Deploy the Ingress Controller We include two options for deploying the Ingress controller: diff --git a/docs-web/technical-specifications.md b/docs-web/technical-specifications.md index fb8804dcc3..777a0299d8 100644 --- a/docs-web/technical-specifications.md +++ b/docs-web/technical-specifications.md @@ -73,12 +73,18 @@ NGINX Plus images are not available through DockerHub. - ``openshift/DockerfileForPlus`` - ``registry.access.redhat.com/ubi8/ubi:8.1`` - + * - Debian-based image with App Protect + - ``appprotect/DockerfileWithAppProtectForPlus`` + - ``debian:stretch-slim`` + - NGINX Plus App Protect module ``` + \* -- Dockerfile paths are relative to the ``build`` folder of the Ingress Controller git repo. ### Custom Images You can customize an existing Dockerfile or use it as a reference to create a new one, which is necessary for the following cases: + * Choosing a different base image. * Installing additional NGINX modules. diff --git a/examples/appprotect/README.md b/examples/appprotect/README.md new file mode 100644 index 0000000000..d2bb7e70b1 --- /dev/null +++ b/examples/appprotect/README.md @@ -0,0 +1,70 @@ +# NGINX App Protect Support + +In this example we deploy the NGINX Plus Ingress controller with [NGINX App Protect](https://www.nginx.com/products/nginx-app-protect/), a simple web application and then configure load balancing and WAF protection for that application using the Ingress resource. + +## Running the Example + +## 1. Deploy the Ingress Controller + +1. Follow the installation [instructions](../../docs/installation.md) to deploy the Ingress controller with NGINX App Protect. + +2. Save the public IP address of the Ingress controller into a shell variable: + ``` + $ IC_IP=XXX.YYY.ZZZ.III + ``` +3. Save the HTTPS port of the Ingress controller into a shell variable: + ``` + $ IC_HTTPS_PORT= + ``` + +## 2. Deploy the Cafe Application + +Create the coffee and the tea deployments and services: +``` +$ kubectl create -f cafe.yaml +``` + +## 3. Configure Load Balancing + +1. Create a secret with an SSL certificate and a key: + ``` + $ kubectl create -f cafe-secret.yaml + ``` +2. Create the App Protect policy and log configuration: + ``` + kubectl create -f dataguard-alarm.yaml + kubectl create -f logconf.yaml + ``` +3. Create an Ingress resource: + ``` + $ kubectl create -f cafe-ingress.yaml + ``` + Note the App Protect annotations in the Ingress resource. They enable WAF protection by configuring App Protect with the policy and log configuration created in the previous step. + +## 4. Test the Application + +1. To access the application, curl the coffee and the tea services. We'll use ```curl```'s --insecure option to turn off certificate verification of our self-signed +certificate and the --resolve option to set the Host header of a request with ```cafe.example.com``` + + To get coffee: + ``` + $ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecure + Server address: 10.12.0.18:80 + Server name: coffee-7586895968-r26zn + ... + ``` + If your prefer tea: + ``` + $ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure + Server address: 10.12.0.19:80 + Server name: tea-7cd44fcb4d-xfw2x + ... + ``` + Now, let's try to send a suspicious request: + ``` + $ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP "https://cafe.example.com:$IC_HTTPS_PORT/tea/