NGINX Gateway Fabric 백엔드 트래픽 보호
이 문서는 NGINX Gateway Fabric, 백엔드 pod 사이의 트래픽 보호 방법에 관해 설명합니다.
특히 BackendTLSPolicy를 사용하여 Gateway와 백엔드 pod간 연결의 TLS 설정 방법을 설명합니다. 서비스 혹은 백엔드가 자체 TLS를 수행하고, NGINX Gateway Fabric이 자체 HTTPS 인증서를 소유한 해당 백엔드 pod에 어떻게 연결해야 하는지 알아야 하는 경우의 사용 사례를 포함합니다.
목차
1. 참고 사항
2. NGINX Gateway Fabric 사전 구성
3. NGINX Gateway Fabric 라우팅 규칙 설정
4. 백엔드 애플리케이션으로 TLS 설정 없이 트래픽 전송
5. NGINX Gateway Fabric 트래픽 보호 백엔드 TLS 설정
1. 참고 사항
중요:
BackendTLSPolicy는 실험적 릴리즈 채널의 Gateway API 리소스입니다.
Gateway API 실험적 리소스 사용을 위해서, 실험적 채널의 Gateway API가 NGINX Gateway Fabric를 배포하기 전에 설치되어야 합니다. 추가로, NGINX Gateway Fabric의 실험적 기능이 활성화 되어야 합니다.
주의:
Gateway API 문서에 명시되어있듯이, Gateway API의 향후 릴리즈에서 실험적 리소스와 필드에 대해 큰 변화가 있을 수 있습니다.
실험적 채널에서 Gateway API 리소스를 설치하려면 아래 명령어를 사용합니다.
$ kubectl kustomize "https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/experimental?ref=v1.6.2" | kubectl apply -f -
NGINX Gateway Fabric의edge버전을 사용하려면ref의 버전을main으로 변경합니다. 예시:ref=main.
NGINX Gateway Fabric의 실험적 기능 활성화:
- Helm 사용:
nginxGateway.gwAPIExperimentalFeatures.enable을 true로 설정합니다. 예시는 NGINX Gateway Fabric Helm 설치 가이드에서 확인하실 수 있습니다. - Kubernetes manifest 사용:
--gateway-api-experimental-features명령줄 플래그를 deployment manifest의 args에 추가합니다. 예시는 NGINX Gateway Fabric 설치에서 확인하실 수 있습니다.
2. NGINX Gateway Fabric 사전 구성
실험적 기능이 활성화된 NGINX Gateway Fabric을 설치합니다.
NGINX Gateway Fabric의 공인 IP 주소와 포트를 쉘 변수에 저장합니다.
GW_IP=XXX.YYY.ZZZ.III
GW_PORT=<port number>
프로덕션 환경에선 외부 IP 주소에 대한 DNS 레코드가 있어야하며, 게이트웨이가 전달할 호스트 이름을 참조해야 합니다.
다음 블록을 터미널에 입력하여 Kubernetes에 secure-app 애플리케이션을 생성합니다.
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: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJpZ0F3SUJBZ0lVVDQwYTFYd3doUHVBdDJNMkdZZUovYXluZlFBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1JqRWZNQjBHQTFVRUF3d1djMlZqZFhKbExXRndjQzVsZUdGdGNHeGxMbU52YlRFTE1Ba0dBMVVFQmhNQwpWVk14RmpBVUJnTlZCQWNNRFZOaGJpQkdjbUZ1YzJselkyOHdIaGNOTWpRd01URTRNVGd3TVRBeFdoY05NalV3Ck1URTNNVGd3TVRBeFdqQi9NUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVcKTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzV6YVhOamJ6RU9NQXdHQTFVRUNnd0ZUa2RKVGxneEVqQVFCZ05WQkFzTQpDVTVIU1U1WUlFUmxkakVmTUIwR0ExVUVBd3dXYzJWamRYSmxMV0Z3Y0M1bGVHRnRjR3hsTG1OdmJUQ0NBU0l3CkRRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMeUx0eURNbTZ4M0ZEUFJsOGZ0azNweCtrRWQKYTVpTGZOQ3lDbUVjYktBQVBDNEhZckl5b1B5QXpSTlJCMWErekE0UTlrbzJZRG5vR0dkeFJaMEdydldKZUV2Mgo3MWlHNGxhbHRVTS9WOWNvSktQY0UyTEI0R3R6cFA3ckdIWXNvRDlOUXFpV3YwZ0lOdE42MjdrWGg4UW41V1hYCk92Y2FkS2h0bjJER3RvU0VzT3dpNzR5NEt3SmFkWnlwLzJaM0hPakRTNjVIVmxydmUxUXpBMVRzTEp6S3cva3gKbHBSR0lWK0lhUjZXbXZsaVFVdDJxWFg0L3hGeVVEM2Vic05TeXpHUk5mQ0NOTWxlWlV3MTR3ZUdhOEVnc2tDcQprOGdYSmpFZXQxMlR4OGxkY3BpVWlxYVpkOStYZjJmUS8yL2Y5c1IzM3Q4K0VVUWpoZ2ZIbHlsLzV1RUNBd0VBCkFhTjlNSHN3SHdZRFZSMGpCQmd3Rm9BVTRUT096c1d0Q3ZWdGJlWXFSU0FqN2tXajFkb3dDUVlEVlIwVEJBSXcKQURBTEJnTlZIUThFQkFNQ0JQQXdJUVlEVlIwUkJCb3dHSUlXYzJWamRYSmxMV0Z3Y0M1bGVHRnRjR3hsTG1OdgpiVEFkQmdOVkhRNEVGZ1FVZmtWREFFWmIwcjRTZ2swck10a0FvQ2c2RjRnd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBQWFiQit6RzVSODl6WitBT2RsRy9wWE9nYjF6VkJsQ0dMSkhyYTl1cTMvcXRPR1VacDlnd2dZSWJ4VnkKUkVLbWVRa05pV0haSDNCSlNTZ3czbE9abGNxcW5xbUJ2OFAxTUxDZ3JqbDJSN1d2NVhkb2RlQkJxc0lvZkNxVgp3ZG51THJUU3RTbmd2MGhDcldBNlBmTnlQeXMzSGJva1k3RExNREhuNmhBQWcwMUNDT0pWWGpNZjFqLzNIMFNCClBQSWxtek5aRUpEd0JMR2hyb1V3aUY3NkNUV1Fudi8yc1pvWHMwUlFiRTY3TmNraXc2Z0svaWRwVTVzMmlkOEQKVExjVjNxenVFaE1ZeUlua0ZWNEJLZlFkTWxDQnE1QWdyU1Jqb2FoaCszbFRwYVpUalJGUGFVd3VZYXVsQXRzNgpra1ROaGltWWQ3Ym1aVk5MK2I0MzhmN1RMaGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQzhpN2Nnekp1c2R4UXoKMFpmSDdaTjZjZnBCSFd1WWkzelFzZ3BoSEd5Z0FEd3VCMkt5TXFEOGdNMFRVUWRXdnN3T0VQWktObUE1NkJobgpjVVdkQnE3MWlYaEw5dTlZaHVKV3BiVkRQMWZYS0NTajNCTml3ZUJyYzZUKzZ4aDJMS0EvVFVLb2xyOUlDRGJUCmV0dTVGNGZFSitWbDF6cjNHblNvYlo5Z3hyYUVoTERzSXUrTXVDc0NXbldjcWY5bWR4em93MHV1UjFaYTczdFUKTXdOVTdDeWN5c1A1TVphVVJpRmZpR2tlbHByNVlrRkxkcWwxK1A4UmNsQTkzbTdEVXNzeGtUWHdnalRKWG1WTQpOZU1IaG12QklMSkFxcFBJRnlZeEhyZGRrOGZKWFhLWWxJcW1tWGZmbDM5bjBQOXYzL2JFZDk3ZlBoRkVJNFlICng1Y3BmK2JoQWdNQkFBRUNnZ0VBUXJucGJleXJmVTVKTW91Ty9UenBrQkJ0UWdVZzhvUVBBS2E1d0tONEYrbnQKWWxiUHlZUGNjSEErNDRLdUo3ZHZiTno0NU11NG8xV3Q2Vkh2a29KdWdjd01iRW53YTdLVXdKaDFmVjZaL2pXaApQZkpoVS9hTUwwcm1qaWJ5YWNRaVZEVEtEZk1Ici96a05sVEpGUWlzVGpIV1lBUGJSTjh5Z1BjR3pBK1hRVzg5CmxsOFdoeWwrZndjRVAzNTM4TUdKYUpHVmV4Q2d5cVRyKzZwQ29yRUpFL1pSNytiMTNyejRsbmxpZXVYa2pCVkcKSnYvUVI0RVhTSDhuRit3K2FvWTFQaGd2QnFJTnFQZjJMT1V0MzNiN3FDTkFmSVBRdFZFejJsN3NqbmJlcElTTwpvTkhNUFY0N21XTzY2dzhFMzJVMjNjVEtUbytJcWovM0d1eGhweXlYaHdLQmdRRDVNRDhmY3ZyM0xVZHkvZ3I0Ci82MVBqUXNSaWRYdjN0Mk1MVEk4UkduNzJWcGxvVVExNDZHV2xGTGVVVDY1L1ZVMjNsZFUrUWt2eFNMK3U1bW4KRUJIdXUyVmtBUWVYcUJXWkpPTmFSZG9Ia1YzK2Fyc0U4Qld0SWVtZHJ0MWN6bHFjc1VzMWdGdG1COGI3RHB3UwpHKzRoZDlzZG0weDBNS2hoOFVGOUQrZytUd0tCZ1FEQnN4dUpoZ1hnck14S0dVMHJ5MW9WWTJtMGpDUEpJcTkzCmNZRUZGY3lYZ0U4OWlDVmlob3dVVE8yMXpTZ3o0SVVKNUxoc2M5N3VIVER1VXdwcVI5NFBsdjlyaXJvakowM1UKT3FyWHgwbWdNN2xibVM1L1RwS0czZG1QblZ0WEZMektISFgyWDVnUW56emYvdXVKK3NtbDVLQW5WN0VZc1oxcgpkVXJvRm8zcnp3S0JnUUNZdjM5aUdzeEdLaVpMRWZqTjY0UmthRFBwdTFFOTZhSnE0K1dRVmV1VnF3V2ptTGhFClJGWHdCTm5MVjRnWTRIYVUzTFF4N1RvNVl5RnhmclBRV2FSMGI4RFdEVitIRWt5ekJJNnM3bmFZL3YzY0Q3YTIKYnlrS2FPaFlkVEZTUzFmMkJ5UHdGczl2K3NKNWNOb3dxNWhNUWJrNks5RXd4QWJqaXN5M0NjSTJOd0tCZ1FDbwo2c2pZNVVlNjV2WkFxRS9rSVRJdDlNUDU3enhGNnptWnNDSVRqUzhkNzRjcTRjKzRYQjFNbHNtMkFYTk55ajQ2Cm9uc3lHTm9RVE9TZThVdmo0MGlEeitwdW5rdzAyOUhEZ21YNlJwQ3VaRzBBdEZVWU1DMFg3K0FLbmU5SndZdmgKdFhBcHFyT3h5eXdMS3dPOUVEZEp0RmIxK0VNNGhhd0NTZ2RJM21KbGdRS0JnRzIxeEJNRXRzMFBVN3lDYTZ0YwpadDc1NUV4aEdkR3F5MmtHYmtmdzBEaHBQQVVUZmdncVF3NVBYdGVIS1ZBSDlKaG5kVnBBZFFxNmZ1MER1MDNKCkl0cGpxNWluZXVoR0x0alpMR1Nhd0dwY0FUU3h4Z3dCM0l0Z29LKzBCRFhteWxId0lEcUc5Z2crRU5KK0VhL0MKeTFOMmV0ZG1sQ01hNjM4cVJlNFlTWk55Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
EOF
위 내용을 입력하면 secure-app service와 deployment, 그리고 백엔드 애플리케이션이 HTTPS 트래픽을 복호화할 인증서와 키가 포함된 secret을 생성합니다. 애플리케이션은 HTTPS 트래픽만 허용하도록 설정되어 있습니다. 다음 명령어를 사용하여 리소스의 생성을 확인합니다.
$ kubectl get pods,svc
출력은 secure-app pod와 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
3. NGINX Gateway Fabric 라우팅 규칙 설정
먼저 HTTP listener가 구성된 Gateway 리소스를 생성합니다.
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
그리고 트래픽을 secure-app 백엔드로 라우팅할 HTTPRoute를 생성합니다.
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
4. 백엔드 애플리케이션으로 TLS 설정 없이 트래픽 전송
Gateway Fabric의 외부 IP 주소와 포트를 사용하면 트래픽을 secure-app 애플리케이션으로 전송할 수 있습니다. NGINX Gateway Fabric에서 secure-app으로 평문 HTTP 트래픽을 전송하면 발생하는 일을 확인하기 위해, 백엔드 TLS 설정 전에 요청을 전송합니다.
secure-app.example.com에 할당된 DNS 레코드가 있다면, 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>
NGINX의 400 상태 코드와 Bad Request 메시지를 확인할 수 있습니다.
5. NGINX Gateway Fabric 트래픽 보호 백엔드 TLS 설정
백엔드 TLS termination 설정을 위해, 자체 서명 인증서를 확인하기 위한 ca.crt를 포함하는 ConfigMap을 생성합니다.
kubectl apply -f - <<EOF
kind: ConfigMap
apiVersion: v1
metadata:
name: backend-cert
data:
ca.crt: |
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIUPA3fFnkLl63GZ7noUjb5NoLhSYkwDQYJKoZIhvcNAQEL
BQAwRjEfMB0GA1UEAwwWc2VjdXJlLWFwcC5leGFtcGxlLmNvbTELMAkGA1UEBhMC
VVMxFjAUBgNVBAcMDVNhbiBGcmFuc2lzY28wHhcNMjQwMTE4MTgwMTAxWhcNMjUw
MTA4MTgwMTAxWjBGMR8wHQYDVQQDDBZzZWN1cmUtYXBwLmV4YW1wbGUuY29tMQsw
CQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5zaXNjbzCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAJGgn81BrqzmI4aQmGrg7RgkO5oYwlThQ9X/xVHB
YVFptjRPAZz9g92g5birI/NZ43C6nEbZrJrSCqN3wgvV84jJmBAgpAvW+LhF4caa
nhAnecJCcTbwrd542vCDoDRsNV5ffbpESgC4FxPGkRVbSa0KHQz8qCLqS2+uaB7X
t76iw6y4pQ3klobVp1XtUpzZMGMBqZFnsAdl+PWMmSTvqjixkSlfcUY6Crnk9W6d
Sns5cpzKdUs+2ZkBe6VkBgSs8xbaz8Y2YC1GhRqGlxYLT3WBaIlSCKPuRrGjwE3r
AsW6gSL919H1O1a+MjQuLuQ4lnCbCpNzM9OV1JISMWfwifMCAwEAAaNTMFEwHQYD
VR0OBBYEFOEzjs7FrQr1bW3mKkUgI+5Fo9XaMB8GA1UdIwQYMBaAFOEzjs7FrQr1
bW3mKkUgI+5Fo9XaMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
AG/eX4pctINIrHvRyHOusdac5iXSJbQRZgWvP1F2p95qoIDESciAU1Sh1oJv+As5
IlJOZPJNuZFpDLjc8kzSoEbc1Q5+QyTBlyNNsagWYYwK0CEJ6KJt80vytffmdOIg
z8/a+2Ax829vcn1w1SUi5V6ea/l8K74f2SL/zSSHgtEiz8V0TlvT7J6wurgmnk4t
yQRmsXlDGefuijMNCVf7jWwLx2BODfKoEA1pJkthnNvdizlikmz+9elxhV9bRf3Y
NnubytWPfO1oeHjVGvxVjCouIYine+VlskvwHmMi/dYod6yd7aFYu4CU3g/hjwKo
LY2WNv5j3JhDnEYK9Zj3z7A=
-----END CERTIFICATE-----
EOF
그리고 secure-app 서비스를 타깃으로 설정하고 이전 단계에서 생성한 ConfigMap을 참조하는 BackendTLSPolicy 리소스를 생성합니다.
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
Policy의 성공적인 생성과 적용을 확인하기 위해 BackendTLSPolicy의 describe 명령어를 사용합니다.
$ 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>
다시 트래픽을 전송합니다.
$ curl --resolve secure-app.example.com:$GW_PORT:$GW_IP http://secure-app.example.com:$GW_PORT/
hello from pod secure-app