Securing backend traffic
Learn how to encrypt HTTP traffic between NGINX Gateway Fabric and your backend pods.
In this guide, we will show how to specify the TLS configuration of the connection from the Gateway to a backend pod with the Service API object using a BackendTLSPolicy.
The intended use-case is when a service or backend owner is managing their own TLS and NGINX Gateway Fabric needs to know how to connect to this backend pod that has its own certificate over HTTPS.
BackendTLSPolicy is a Gateway API resource from the experimental release channel.
To use Gateway API experimental resources, the Gateway API resources from the experimental channel must be installed before deploying NGINX Gateway Fabric. Additionally, NGINX Gateway Fabric must have experimental features enabled.
As noted in the Gateway API documentation, future releases of the Gateway API can include breaking changes to experimental resources and fields.
To install the Gateway API resources from the experimental channel, run the following:
kubectl kustomize "https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/experimental?ref=v2.1.4" | kubectl apply -f -
If you plan to use theedge
version of NGINX Gateway Fabric, you can replace the version inref
withmain
, for exampleref=main
.
To enable experimental features on NGINX Gateway Fabric:
Using Helm: Set nginxGateway.gwAPIExperimentalFeatures.enable
to true. An example can be found in the Installation with Helm guide.
Using Kubernetes manifests: Add the --gateway-api-experimental-features
command-line flag to the deployment manifest args. An example can be found in the Installation with Kubernetes manifests guide.
- Install NGINX Gateway Fabric with experimental features enabled.
Create the secure-app application in Kubernetes by copying and pasting the following block into your terminal:
kubectl apply -f - <<EOF apiVersion: apps/v1 kind: Deployment metadata: name: secure-app spec: replicas: 1 selector: matchLabels: app: secure-app template: metadata: labels: app: secure-app spec: containers: - name: secure-app image: nginxinc/nginx-unprivileged:latest ports: - containerPort: 8443 volumeMounts: - name: secret mountPath: /etc/nginx/ssl readOnly: true - name: config-volume mountPath: /etc/nginx/conf.d volumes: - name: secret secret: secretName: app-tls-secret - name: config-volume configMap: name: secure-config --- apiVersion: v1 kind: Service metadata: name: secure-app spec: ports: - port: 8443 targetPort: 8443 protocol: TCP name: https selector: app: secure-app --- apiVersion: v1 kind: ConfigMap metadata: name: secure-config data: app.conf: |- server { listen 8443 ssl; listen [::]:8443 ssl; server_name secure-app.example.com; ssl_certificate /etc/nginx/ssl/tls.crt; ssl_certificate_key /etc/nginx/ssl/tls.key; default_type text/plain; location / { return 200 "hello from pod secure-app\n"; } } --- apiVersion: v1 kind: Secret metadata: name: app-tls-secret type: Opaque data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVpekNDQW5PZ0F3SUJBZ0lVQ0g2NWhwVDNkSlI0SVdPTXdaL2lCb3M5Q0Rrd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1d6RUxNQWtHQTFVRUJoTUNWVk14Q3pBSkJnTlZCQWdNQWxkQk1SQXdEZ1lEVlFRSERBZFRaV0YwZEd4bApNUk13RVFZRFZRUUtEQXBGZUdGdGNHeGxJRU5CTVJnd0ZnWURWUVFEREE5RmVHRnRjR3hsSUZKdmIzUWdRMEV3CkhoY05NalV3TmpFMk1UVXpOelUzV2hjTk16VXdOakUwTVRVek56VTNXakJnTVFzd0NRWURWUVFHRXdKVlV6RUwKTUFrR0ExVUVDQXdDVjBFeEVEQU9CZ05WQkFjTUIxTmxZWFIwYkdVeEVUQVBCZ05WQkFvTUNFWTFJRTVIU1U1WQpNUjh3SFFZRFZRUUREQlp6WldOMWNtVXRZWEJ3TG1WNFlXMXdiR1V1WTI5dE1JSUJJakFOQmdrcWhraUc5dzBCCkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQTM3M0JyRG5kU1Z1UG0xSm5VNCtFVFZqSTFVSTJydVhIY0srUUJ5am4KNk5jMklEZ0NPNzdMOVdTajQzcmRicnlKNm1LMEFrVTBjekhlM1ZVUndaaG95TlM2QnEwaHJIOTFOWHlVcGNaMQpzaTJrbEdFbnVRYSs4dHgrVUwrUGYzck4yTlZ2TytTdnZSL2NxUWpYNnEzeURVMXJLWTZEQUlWaWxBNytDdHhVClE0KzI2MXluSlNTaWZ6YnB0R1ExTmZuV1Y5eHNoOWNyVklqbk9MNlhiek90eHNoYnBxU04xVENoQTR0KzVSb0kKOFo0aG0wWmpMNll3bVYycDB0R2ZFbVV0WGszelRjVVRYTitSODE4MVE0c0JPbEt5YjJpMG1seE9GYUpqc3hQVAprYjBsQmU2WS95TVBrallaT0o5c3YwR0k1YmszaHUwb0lGRDBOb0N5bFNmUmx3SURBUUFCbzBJd1FEQWRCZ05WCkhRNEVGZ1FVS29FbkhWU1dqSFRIeEVoK2xFazFQT2hUYWU4d0h3WURWUjBqQkJnd0ZvQVU0NS9URHQwUVE5dTQKTDN5OVJUOW50Z0VhQnE0d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFBN1ZNKzZ6bEtKZGlZVElWTjN4ZzJrOApkUG1zSm8vR1UwRW1WTFRzaFJtQWppdnI0bXBkYXRpL0p2UEtIcGtSTTNLcDNqQURmVFRqRnhuZ2ZJajNSN1J5CkpJamZVMGdrdHBKOG9UWVJlMlNsdXdVQ2VrYWlLa1BmWDRLdHcwUWVRSU4vSVN6YTdOS2krWklXOVJhRE1kbzIKNHZYUzIySDRIcjN6dmtFYVhvdHB6YXBlTzJBU1BDS3hoOWRkZWlyVzNydGZpZkRhSFF2UUpZSGpGYWpYcGZhaQpMazRHSWNrdHY2WGNqeFA5V1ArQ1RUWWFxZTVIMkg1dXlheDNyckRWRm43Y3Yzb29mcDJrTVhGUmRjTG9ERGdOCllUT3czTjhRLzl1bjNPRHBZR1l6b3V5RmJieXJ1MUlhcTZKeTRQNURnLzFSaEl3MnpuaWlCdzR0aXBzV2tqSTgKSEtRcWxKZE1KYlZUMHkraUlPMThxdWVsL1hDSC9hS1FEclpMd2Z0WVEvczVTWUtXUG5UZzhIbjJpeUlhUmZ0dQorbkgyMCszZkJuQnorK0hOd1duY2g0WmJDM0k3UklpcXFpdm1ML09YMWkvbng2UG5BVTlQMFlsOGhsMFFINndGCmtOd0NuTmE5TEp6eXBCandjY0IzU1ZaNUIxanhRTTJTcTYrSnB2cFZJSDQzL29paWJ0anhLMDhQenVVZ2luTUgKVC9aMXQ0NDBhRmk2V2VIZHRHM2RkVkM0SGxhS0JqQXVkZm44MHA1M3RPbXN6dkFRUUFidlhpd3pzZVZxYW5mdgpnS1BqWTd5aE9oZzZTdUJ2OFRTVTkvSVBDUklBTnRiMXkvOGxnVVlkVDJ0dGVmTVlnMnhFempndGIxQWEzMzBWCk1ONXV5ZFpCTU92aTRReHJuVzVICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRGZ2Y0dzT2QxSlc0K2IKVW1kVGo0Uk5XTWpWUWphdTVjZHdyNUFIS09mbzF6WWdPQUk3dnN2MVpLUGpldDF1dklucVlyUUNSVFJ6TWQ3ZApWUkhCbUdqSTFMb0dyU0dzZjNVMWZKU2x4bld5TGFTVVlTZTVCcjd5M0g1UXY0OS9lczNZMVc4NzVLKzlIOXlwCkNOZnFyZklOVFdzcGpvTUFoV0tVRHY0SzNGUkRqN2JyWEtjbEpLSi9OdW0wWkRVMStkWlgzR3lIMXl0VWlPYzQKdnBkdk02M0d5RnVtcEkzVk1LRURpMzdsR2dqeG5pR2JSbU12cGpDWlhhblMwWjhTWlMxZVRmTk54Uk5jMzVIegpYelZEaXdFNlVySnZhTFNhWEU0Vm9tT3pFOU9SdlNVRjdwai9JdytTTmhrNG4yeS9RWWpsdVRlRzdTZ2dVUFEyCmdMS1ZKOUdYQWdNQkFBRUNnZ0VBYXhSQXpYRkFFNnlyVlBXaUY5NjJ2ZUhBOURkbFBsMGdEekVtcUJhT3J1UFkKdHFDM2lPcHVhSG9LNllMUzJQMklyOUVmUDNycGVEd2s0aDZsaWRhc1IzbHZzbVJIbW12QnA2Q0E3N25FZUVyWgoybDJKQ2tkTk9hUUhIQlFoMUN2c3VscWppckdPM2QrUzFwOHgzdEh5NXlUbkpaTmI1UEx4VTlTOUJtdWVORnA5ClBiSWwwV0Ewb1h6VWlPa0VESFpJaE9LeEQ1SExNQkpEUGIzYy9ZRE9HSlFtTGRtRldYS1VsL3NHNHJJdzFGNWYKZzhvYzRyekJpRlhVY0EvRlhoWllNcW9lMlp6MFhXWWVCYWhINFB2Sm4zdFVhd0xFQ0NTbW1nYkVzT1ltNHdvZQptUHZmT2k3TDR3VmhGNjNBNkxnL3hpR3VFUkd0cmJlMS8xcVFlTXFGU1FLQmdRRDRmT2ZPaUVpUkViODlJeDdKCjJra3ZuS1ZERXl1YjExc3NzZTNjU054R1haVFBkK0JjTGc5YVBTdXFrWC9NbmJsQ2JBOXZhSzZOL3U2YkxsSkEKQXNnVFM0Rkc0K2J2d2hBTzQybUVKQTJIRG0zNDVGRmRFQ2JYblhjV1VxVzJWblBqNkpDdDB2d1R1ZDNMVGdxbgpFM3RmZVZuM0J4bkpsVU4vOHdXUlB1M2NoUUtCZ1FEbWdWVDJHUDZvanBudC92SjRPOTBrYnEzVVJHYklGK0tOCjYrUnJmb0kxWkN2dElXaGV4OVhtZi8wWVlBaFQvaXJTSDd3RTk3QmpIbWJTMlZId051cDNmc1dDNFNYMDFYTkwKYnFjQmxOeW1JUzFiNGN0U01jMFFyeWM5YWlMUVM1d3Fvc1UwNGZubGdkM2o2cHRjdW8vd0Z6Q3JoV0xUZDk1bgpHeUc5NTZYdWF3S0JnUURySDJWSUsvUmVNR2pBTk1jaFFJY1hvaVZOL29tNUFHR3BQUU5RK1RCVTlKK21ZRXZQCmJWWGhrUmdNWVhpSDZJWXZyNGc3WnRZa1RpRUFmU2dlb1lNbm5yNUlrY1VuQUgycFdNMnkxMXBsZk9YYUtGQkUKdXMvR0haMWRaZjZmTmRhYXhLaUJrYTRzRENjdUJENVlNVHIvOEJlTWd3K0hpdEUvOUhoRUkwTjI4UUtCZ1FDagoxazJEVnFTN1BoQ2ZIMVZNckpBMHN3NlBEOGRXZGRPc09IejFBc2llRm9NNlcwS0tDOVEzcjhVL3JCSi9VT3N5Cnl5ZWpDRUt4VVF5WTFhcnQ2THFqRU5KbWdvMnVCb0dhbmgzS2UvcVJnb2R4Qlg2MC8zellYUWF4R2wyQVhCMjIKR0ZlL2pOZElrQlFkU2NZQUZRTDJEaVdqNUgwbi9jMXd6OUlkM3ljTDNRS0JnQ3NNV1NxU1lobUN3ajdON1U2awpyWndjTGxJdS94UEZsMWFTL2Q2MGV4UXpzWmxzd0xkMzJMcStKdkxVYXNPdWVQaGlCajZZNFNiNlk3bDBLMmNvCkZndXFFbmxBL3JNY2Qxa1h6L0dEbVZvYVVoSk8rZlFNSmk3ZEQzS0tidm80SGxhSG1kSkUvNGN5ek1jQmZVeUUKUUtXUElyVS9WZFU0ckZHV0xqRjJKc1ZPCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= EOF
This will create the secure-app service and a deployment, as well as a Secret containing the certificate and key that will be used by the backend application to decrypt the HTTPS traffic. Note that the application is configured to accept HTTPS traffic only. Run the following command to verify the resources were created:
kubectl get pods,svc
Your output should include the secure-app pod and the secure-app service:
NAME READY STATUS RESTARTS AGE pod/secure-app-868cfd5b5-v7gwk 1/1 Running 0 9s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/secure-app ClusterIP 10.96.213.57 <none> 8443/TCP 9s
First, create the Gateway resource with an HTTP listener:
kubectl apply -f - <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: gateway spec: gatewayClassName: nginx listeners: - name: http port: 80 protocol: HTTP EOF
Next, create a HTTPRoute to route traffic to the secure-app backend:
kubectl apply -f - <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: secure-app spec: parentRefs: - name: gateway sectionName: http hostnames: - "secure-app.example.com" rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: secure-app port: 8443 EOF
After creating the Gateway resource, NGINX Gateway Fabric will provision an NGINX Pod and Service fronting it to route traffic.
Save the public IP address and port of the NGINX Service into shell variables:
GW_IP=XXX.YYY.ZZZ.III GW_PORT=<port number>
In a production environment, you should have a DNS record for the external IP address that is exposed, and it should refer to the hostname that the gateway will forward for.
Using the external IP address and port for the NGINX Service, we can send traffic to our secure-app application. To show what happens if we send plain HTTP traffic from NGINX to our secure-app
, let’s try sending a request before we create the backend TLS configuration.
If you have a DNS record allocated forsecure-app.example.com
, you can send the request directly to that hostname, without needing to resolve.
curl --resolve secure-app.example.com:$GW_PORT:$GW_IP http://secure-app.example.com:$GW_PORT/
<html> <head><title>400 The plain HTTP request was sent to HTTPS port</title></head> <body> <center><h1>400 Bad Request</h1></center> <center>The plain HTTP request was sent to HTTPS port</center> <hr><center>nginx/1.25.3</center> </body> </html>
We can see we a status 400 Bad Request message from NGINX.
This example uses aConfigMap
to store the CA certificate, but you can also use aSecret
. This could be a better option if integrating with cert-manager. TheSecret
should have aca.crt
key that holds the contents of the CA certificate.
To configure the backend TLS termination, first we will create the ConfigMap that holds the ca.crt
entry for verifying our self-signed certificates:
kubectl apply -f - <<EOF kind: ConfigMap apiVersion: v1 metadata: name: backend-cert data: ca.crt: | -----BEGIN CERTIFICATE----- MIIFlzCCA3+gAwIBAgIULQcgBeB9ApX+Wf+FYLMLAVOLwYIwDQYJKoZIhvcNAQEL BQAwWzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl MRMwEQYDVQQKDApFeGFtcGxlIENBMRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0Ew HhcNMjUwNjE2MTUzNjQzWhcNMzUwNjE0MTUzNjQzWjBbMQswCQYDVQQGEwJVUzEL MAkGA1UECAwCV0ExEDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAoMCkV4YW1wbGUg Q0ExGDAWBgNVBAMMD0V4YW1wbGUgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAI4fKNzZU/9IEifa6k9L38Ei4lFwFt6wjCZt70Rb5IpJfOkr PsZf+XaCgIQZh3ZTBIPZoa2uNxVF1BoKEhEqzGdWdH82lVDmbkSaLeW4YnYhM9fe IuYHqyVrNFsp7M+xJD3XWwwCXDN//H+vx8JLQIOX4tQ9RM4rB7avcu0rmaXJuqJi N0AU8AsEFpoIiC/vFBucqpG2KzC+FsVPe/Ri+PQTWJ6aLVUvGJ3hXRAQdOMzIGys +WTugzqk+Sv9pKDB7/EMJg+5IagNP5QWrDmdBwROdRhd8wq+zYJMxVaaSqetNrnY aqWPdGj+RSB5YuiL8kwDiJOPc5G4t2I/hbol5hpBleL84qijclzJQeFOKdEbRzMa w2QfyZxZ24TCZGwDzs480x5bKmUoRufdk8X4DSjV4tnKMh9sfHX3w4cokp2IWBqt B59HMN4nAKELftjfKjI9L0jnEbJR/Xae0qPLxjAuN0HARQNx1/EvvsWGhgu4Sr5S Ua604wHU9p85a5zWOgOJ471f/sA3q2yEiiHPYqbZ2YXmVMrHvMROO5EVCN7HOjoL QW4z85fX7QT3OwF6Xckk8jA45tcy0cIlOHsl19XbZUJ5Gc7jAiYbmy2IRYgNiMmb iSBL0eoC2jan1Y6Of0UcOiAfsMvt5f21dehYkP7Mwe7sGmMk8Lkva1PRSUFfAgMB AAGjUzBRMB0GA1UdDgQWBBTjn9MO3RBD27gvfL1FP2e2ARoGrjAfBgNVHSMEGDAW gBTjn9MO3RBD27gvfL1FP2e2ARoGrjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 DQEBCwUAA4ICAQBD/b0Vu1vbRYQIeBbaPLCHS5IO8R2G18w+RyUBYpcr50FLI81X YPTCnEXw5sVd7Y7PNZ6q3crr4u/tGWwDWr9vHL5YWApEnUJzdp9NGv9Z29pgRrQS Dzr+ggv8/NRMy4xFZ/U5JKy1lHgNFhSp68PrZI6T4xN3zyTCj29qC9hxoxhYpbXS 1m3PB84T1VqZu2dwNZll+kOIMv+Qon8MWZbuGLkFrcxuDqHB6RRAWyRB7WKKZZXu r+Uw+9+0htNcASNuEx1yXUaE9Bf9V22fs4ARilP2QpFtMN8BCVQiKp8tj+em3ZX0 5KfUavNIZekrHKn/3pc9M+PhX+CEPLNncgywBVYZN89ZujyNVCKVwWD3LmH5NZ8w JMJ9f3SC8ZqTu7xqX6FVLvmNAm5sS7lE8M0wGzp9cZHHEmstC1//jc+JzhRu8XyU wGJyANVFlPn7+nnSq7dWbv4OTFsurbWrHQlzwjHaht84DGR4E0QgLUtA+ICGU0Ig 3/Ma5N7Jugw2mbx8NupgOE3AL6aScbuSt2E1/4QZ2uNPh8soxbANu9WbEXOXzxd5 LItmVd4ltG/4i2qwLu4NLSNA56iaCDIRD/cfr6WmNLfcMTGRMIJ+AlxnETAIy+pK s6cpSfBLcDUhStgvr5oNDFeCbmsXRgyJZE9I4mKijG8v+LkS9RgKsS7tRw== -----END CERTIFICATE----- EOF
Next, we create the Backend TLS Policy which targets our secure-app
Service and refers to the ConfigMap created in the previous step:
kubectl apply -f - <<EOF apiVersion: gateway.networking.k8s.io/v1alpha3 kind: BackendTLSPolicy metadata: name: backend-tls spec: targetRefs: - group: '' kind: Service name: secure-app validation: caCertificateRefs: - name: backend-cert group: '' kind: ConfigMap hostname: secure-app.example.com EOF
To confirm the Policy was created and attached successfully, we can run a describe on the BackendTLSPolicy object:
kubectl describe backendtlspolicies.gateway.networking.k8s.io
Name: backend-tls Namespace: default Labels: <none> Annotations: <none> API Version: gateway.networking.k8s.io/v1alpha3 Kind: BackendTLSPolicy Metadata: Creation Timestamp: 2024-05-15T12:02:38Z Generation: 1 Resource Version: 19380 UID: b3983a6e-92f1-4a98-b2af-64b317d74528 Spec: Target Refs: Group: Kind: Service Name: secure-app Validation: Ca Certificate Refs: Group: Kind: ConfigMap Name: backend-cert Hostname: secure-app.example.com Status: Ancestors: Ancestor Ref: Group: gateway.networking.k8s.io Kind: Gateway Name: gateway Namespace: default Conditions: Last Transition Time: 2024-05-15T12:02:38Z Message: BackendTLSPolicy is accepted by the Gateway Reason: Accepted Status: True Type: Accepted Controller Name: gateway.nginx.org/nginx-gateway-controller Events: <none>
Now let’s try sending traffic again:
curl --resolve secure-app.example.com:$GW_PORT:$GW_IP http://secure-app.example.com:$GW_PORT/
hello from pod secure-app
To learn more about configuring backend TLS termination using the Gateway API, see the following resources: