NGINX Plus
2 TopicsConfigure NGINX microgateway for MTLS termination and client certificate hash verification
Problem this snippet solves: This configuration is used to support advanced Open Banking scenarios where NGINX performs MTLS termination and checks the validity of the JWT by comparing the computed client certificate hash with the hash found inside JWT. How to use this snippet: The code snipped shows the contents of nginx.conf and x5t.js, the njs function responsible for computing the client certificate hash. Code : ########## #nginx.conf: ########## user nginx; worker_processes auto; load_module modules/ngx_http_js_module.so; load_module modules/ngx_stream_js_module.so; error_log /var/log/nginx/error.log debug; events { worker_connections 10240; } http { include /etc/nginx/mime.types; default_type application/octet-stream; error_log /var/log/nginx/error.log; log_format jwt_log_format '$remote_addr - "$jwt_claim_sub" [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$host" sn="$server_name" rt=$request_time ua="$upstream_addr" us="$upstream_status" ut="$upstream_response_time" ul="$upstream_response_length" cs="$upstream_cache_status" http_authorization="$http_authorization" ssl_client_verify="$ssl_client_verify" jwt_cnf_fingerprint="$jwt_cnf_fingerprint" client_cert=$ssl_client_raw_cert thumbprint_match=$thumbprint_match'; sendfile on; keyval_zone zone=one:1m type=ip state=one.keyval timeout=1h; keyval $remote_addr $target zone=one; keepalive_timeout 65; server_tokens off; map $request_uri $request_uri_path { '~^(?P [^?]*)(\?.*)?$' $path; } limit_req_zone $binary_remote_addr zone=policy_1_99c0d81f-d277-4cd3-a97b-e94dde53453f_binary_remote_addr:10m rate=5000r/m; proxy_cache_path /etc/nginx/jwk_bank_idp levels=1 keys_zone=jwk_bank_idp:1m max_size=10m; map $jwt_claim_scope $passes_conditional_policy_policy_1_761 { default 0; ~.*payments.* 1; } auth_jwt_claim_set $jwt_cnf_fingerprint 'cnf' 'x5t#S256'; js_import /etc/nginx/x5t.js; js_set $thumbprint_match x5t.validate; server { server_name api.bank.f5lab; listen 443 ssl; ssl_certificate /etc/nginx/f5lab.crt; ssl_certificate_key /etc/nginx/f5lab.key; ssl_session_cache off; ssl_prefer_server_ciphers off; ssl_client_certificate /etc/nginx/updated_ca.crt; ssl_verify_client on; ssl_verify_depth 10; access_log /var/log/nginx/access.log jwt_log_format; location = /_jwks_uri_bank_idp { internal; subrequest_output_buffer_size 64k; proxy_cache jwk_bank_idp; proxy_cache_valid 200 1h; proxy_cache_use_stale error timeout updating; proxy_ignore_headers Cache-Control Expires Set-Cookie; proxy_method GET; proxy_pass https://idp.bank.f5lab/ext/open_banking; } location = /open-banking/v3.1/pisp/domestic-payments { if ($request_method !~ '^GET|POST$') { return 405; } error_log /dev/null; proxy_set_header Host ''; proxy_set_header Host bank.f5lab; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Connection ''; proxy_http_version 1.1; limit_req zone=policy_1_99c0d81f-d277-4cd3-a97b-e94dde53453f_binary_remote_addr nodelay; limit_req_status 429; auth_jwt bank_idp; auth_jwt_key_request /_jwks_uri_bank_idp; if ($passes_conditional_policy_policy_1_761 = 0) { return 403 'Access denied because payments scope is missing'; } if ($thumbprint_match != 1) { return 403 'Access denied because client SSL certificate thumbprint does not match jwt_cnf_fingerprint'; } if ($ssl_client_verify != "SUCCESS") { return 403 'Client SSL cert verification failed'; } proxy_pass http://wl_openbanking; } location ~* /open-banking/v3.1/pisp/domestic-payments/* { if ($request_method !~ '^GET|POST$') { return 405; } error_log /dev/null; proxy_set_header Host ''; proxy_set_header Host bank.f5lab; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Connection ''; proxy_http_version 1.1; limit_req zone=policy_1_99c0d81f-d277-4cd3-a97b-e94dde53453f_binary_remote_addr nodelay; limit_req_status 429; auth_jwt bank_idp; auth_jwt_key_request /_jwks_uri_bank_idp; if ($passes_conditional_policy_policy_1_761 = 0) { return 403 'Access denied because payments scope is missing'; } if ($thumbprint_match != 1) { return 403 'Access denied because client SSL certificate thumbprint does not match jwt_cnf_fingerprint'; } if ($ssl_client_verify != "SUCCESS") { return 403 'Client SSL cert verification failed'; } proxy_pass http://wl_openbanking; } location = /open-banking/v3.1/pisp/domestic-payment-consents { if ($request_method !~ '^GET|POST$') { return 405; } error_log /dev/null; proxy_set_header Host ''; proxy_set_header Host bank.f5lab; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Connection ''; proxy_http_version 1.1; limit_req zone=policy_1_99c0d81f-d277-4cd3-a97b-e94dde53453f_binary_remote_addr nodelay; limit_req_status 429; auth_jwt bank_idp; auth_jwt_key_request /_jwks_uri_bank_idp; if ($passes_conditional_policy_policy_1_761 = 0) { return 403 'Access denied because payments scope is missing'; } if ($thumbprint_match != 1) { return 403 'Access denied because client SSL certificate thumbprint does not match jwt_cnf_fingerprint'; } if ($ssl_client_verify != "SUCCESS") { return 403 'Client SSL cert verification failed'; } proxy_pass http://wl_openbanking; } location ~* /open-banking/v3.1/pisp/domestic-payment-consents/* { if ($request_method !~ '^GET|POST$') { return 405; } error_log /dev/null; proxy_set_header Host ''; proxy_set_header Host bank.f5lab; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Connection ''; proxy_http_version 1.1; limit_req zone=policy_1_99c0d81f-d277-4cd3-a97b-e94dde53453f_binary_remote_addr nodelay; limit_req_status 429; auth_jwt bank_idp; auth_jwt_key_request /_jwks_uri_bank_idp; if ($passes_conditional_policy_policy_1_761 = 0) { return 403 'Access denied because payments scope is missing'; } if ($thumbprint_match != 1) { return 403 'Access denied because client SSL certificate thumbprint does not match jwt_cnf_fingerprint'; } if ($ssl_client_verify != "SUCCESS") { return 403 'Client SSL cert verification failed'; } proxy_pass http://wl_openbanking; } location ~* /open-banking/v3.1/pisp/domestic-payments/*/payment-details { if ($request_method !~ '^GET|POST$') { return 405; } error_log /dev/null; proxy_set_header Host ''; proxy_set_header Host bank.f5lab; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Connection ''; proxy_http_version 1.1; limit_req zone=policy_1_99c0d81f-d277-4cd3-a97b-e94dde53453f_binary_remote_addr nodelay; limit_req_status 429; auth_jwt bank_idp; auth_jwt_key_request /_jwks_uri_bank_idp; if ($passes_conditional_policy_policy_1_761 = 0) { return 403 'Access denied because payments scope is missing'; } if ($thumbprint_match != 1) { return 403 'Access denied because client SSL certificate thumbprint does not match jwt_cnf_fingerprint'; } if ($ssl_client_verify != "SUCCESS") { return 403 'Client SSL cert verification failed'; } proxy_pass http://wl_openbanking; } location ~* /open-banking/v3.1/pisp/domestic-payment-consents/*/funds-confirmation { if ($request_method !~ '^GET|POST$') { return 405; } error_log /dev/null; proxy_set_header Host ''; proxy_set_header Host bank.f5lab; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Connection ''; proxy_http_version 1.1; limit_req zone=policy_1_99c0d81f-d277-4cd3-a97b-e94dde53453f_binary_remote_addr nodelay; limit_req_status 429; auth_jwt bank_idp; auth_jwt_key_request /_jwks_uri_bank_idp; if ($passes_conditional_policy_policy_1_761 = 0) { return 403 'Access denied because payments scope is missing'; } if ($thumbprint_match != 1) { return 403 'Access denied because client SSL certificate thumbprint does not match jwt_cnf_fingerprint'; } if ($ssl_client_verify != "SUCCESS") { return 403 'Client SSL cert verification failed'; } proxy_pass http://wl_openbanking; } if ($target) { return 403; } } upstream wl_openbanking { zone wl_openbanking 512k; server 10.0.0.1:30511; keepalive 64; keepalive_requests 100; keepalive_timeout 60s; } } ################################################################################### ########## #x5t.js ########## function validate(r) { var clientThumbprint = require("crypto") .createHash("sha256") .update(Buffer.from(r.variables.ssl_client_raw_cert.replace(/(\n|----|-BEGIN|-END| CERTIFICATE-)/gm, ''), 'base64')) .digest("base64url"); return clientThumbprint === r.variables.jwt_cnf_fingerprint ? '1' : '0'; } export default { validate } Tested this on version: 16.01.3KViews1like0CommentsExample NGINX App Protect deployed on Kubernetes Ingress Controller
Problem this snippet solves: This code offers a couple of examples of deploying NGINX App Protect on Kubernetes Ingress Controller, showing one instance protecting traditional Web applications and one protecting API applications. How to use this snippet: The code can be applied manually through kubectl commands or as a part of a CI/CD pipeline. Code : #### Deploy NGINX Plus Ingress for WebApp from Gitlab.com ##### --- apiVersion: apps/v1 kind: Deployment metadata: name: webapp-nginx-ingress namespace: nginx-ingress spec: replicas: 1 selector: matchLabels: app: webapp-nginx-ingress template: metadata: labels: app: webapp-nginx-ingress #annotations: #prometheus.io/scrape: "true" #prometheus.io/port: "9113" spec: serviceAccountName: nginx-ingress imagePullSecrets: - name: containers: - image: name: webapp-nginx-plus-ingress imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 - name: https containerPort: 443 #- name: prometheus #containerPort: 9113 securityContext: allowPrivilegeEscalation: true runAsUser: 101 #nginx capabilities: drop: - ALL add: - NET_BIND_SERVICE env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name args: - -nginx-plus - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret - -enable-app-protect - -ingress-class=webapp-arcadia-ingress-class #- -v=3 # Enables extensive logging. Useful for troubleshooting. #- -report-ingress-status #- -external-service=nginx-ingress #- -enable-leader-election #- -enable-prometheus-metrics #### WebApp Protect Policy ### --- apiVersion: appprotect.f5.com/v1beta1 kind: APPolicy metadata: name: webapp-dataguard-blocking spec: policy: name: webapp-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: [] ### App Protect Logs ### --- apiVersion: appprotect.f5.com/v1beta1 kind: APLogConf metadata: name: logconf spec: filter: request_type: all content: format: default max_request_size: any max_message_size: 5k ### Create Ingress Service #### --- apiVersion: v1 kind: Service metadata: name: webapp-nginx-ingress namespace: nginx-ingress spec: type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30274 protocol: TCP name: http - port: 443 targetPort: 443 nodePort: 30275 protocol: TCP name: https selector: app: webapp-nginx-ingress ### Deploy Arcadia Ingress Service ##### --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: webapp-arcadia-ingress annotations: kubernetes.io/ingress.class: "webapp-arcadia-ingress-class" appprotect.f5.com/app-protect-policy: "default/webapp-dataguard-blocking" appprotect.f5.com/app-protect-enable: "True" appprotect.f5.com/app-protect-security-log-enable: "True" appprotect.f5.com/app-protect-security-log: "default/logconf" appprotect.f5.com/app-protect-security-log-destination: "syslog:server=10.1.20.6:5144" spec: rules: - host: k8s.arcadia-finance.io http: paths: - path: / backend: serviceName: main servicePort: 80 - path: /files backend: serviceName: backend servicePort: 80 - path: /api backend: serviceName: app2 servicePort: 80 - path: /app3 backend: serviceName: app3 servicePort: 80 #### Deploy WebAPI NGINX Plus Ingress for WebAPI from Gitlab.com ##### --- apiVersion: apps/v1 kind: Deployment metadata: name: webapi-nginx-ingress namespace: nginx-ingress spec: replicas: 1 selector: matchLabels: app: webapi-nginx-ingress template: metadata: labels: app: webapi-nginx-ingress #annotations: #prometheus.io/scrape: "true" #prometheus.io/port: "9113" spec: serviceAccountName: nginx-ingress imagePullSecrets: - name: containers: - image: name: webapi-nginx-plus-ingress imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 - name: https containerPort: 443 #- name: prometheus #containerPort: 9113 securityContext: allowPrivilegeEscalation: true runAsUser: 101 #nginx capabilities: drop: - ALL add: - NET_BIND_SERVICE env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name args: - -nginx-plus - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret - -enable-app-protect - -ingress-class=webapi-arcadia-ingress-class #- -v=3 # Enables extensive logging. Useful for troubleshooting. #- -report-ingress-status #- -external-service=nginx-ingress #- -enable-leader-election #- -enable-prometheus-metrics #### App Protect Policy ### --- apiVersion: appprotect.f5.com/v1beta1 kind: APPolicy metadata: name: webapi-blocking spec: policy: name: webapi-blocking template: name: POLICY_TEMPLATE_NGINX_BASE open-api-files: - link: "http://10.1.20.4/root/nap_kic_openapi/-/raw/master/App/openapi3-arcadia-kic.json" applicationLanguage: utf-8 enforcementMode: blocking blocking-settings: violations: - name: VIOL_MANDATORY_REQUEST_BODY alarm: true block: true - name: VIOL_PARAMETER_LOCATION alarm: true block: true - name: VIOL_MANDATORY_PARAMETER alarm: true block: true - name: VIOL_JSON_SCHEMA alarm: true block: true - name: VIOL_PARAMETER_ARRAY_VALUE alarm: true block: true - name: VIOL_PARAMETER_VALUE_BASE64 alarm: true block: true - name: VIOL_FILE_UPLOAD alarm: true block: true - name: VIOL_URL_CONTENT_TYPE alarm: true block: true - name: VIOL_PARAMETER_STATIC_VALUE alarm: true block: true - name: VIOL_PARAMETER_VALUE_LENGTH alarm: true block: true - name: VIOL_PARAMETER_DATA_TYPE alarm: true block: true - name: VIOL_PARAMETER_NUMERIC_VALUE alarm: true block: true - name: VIOL_PARAMETER_VALUE_REGEXP alarm: true block: true - name: VIOL_URL alarm: true block: true - name: VIOL_PARAMETER alarm: true block: true - name: VIOL_PARAMETER_EMPTY_VALUE alarm: true block: true - name: VIOL_PARAMETER_REPEATED alarm: true block: true ### Create Ingress Service #### --- apiVersion: v1 kind: Service metadata: name: webapi-nginx-ingress namespace: nginx-ingress spec: type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30276 protocol: TCP name: http - port: 443 targetPort: 443 nodePort: 30277 protocol: TCP name: https selector: app: webapi-nginx-ingress ### Deploy Arcadia Ingress Service ##### --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: webapi-arcadia-ingress annotations: kubernetes.io/ingress.class: "webapi-arcadia-ingress-class" appprotect.f5.com/app-protect-policy: "default/webapi-blocking" appprotect.f5.com/app-protect-enable: "True" appprotect.f5.com/app-protect-security-log-enable: "True" appprotect.f5.com/app-protect-security-log: "default/logconf" appprotect.f5.com/app-protect-security-log-destination: "syslog:server=10.1.20.25:5144" spec: rules: - host: k8s.arcadia-finance.io http: paths: - path: /trading backend: serviceName: main servicePort: 80 - path: /api backend: serviceName: app2 servicePort: 80 Tested this on version: No Version Found917Views1like0Comments