NGINX Plus 기반 K8s 인증형 Forward Proxy 구축 가이드
Kubernetes 환경에서 내부 워크로드의 외부 통신을 제어하기 위해서는, 클라이언트가 명시적으로 설정한 Forward Proxy를 통해서만 외부 접근이 가능하도록 구성하고, 해당 프록시에서 인증 및 접근 제어를 진행해야합니다. 그러나 NGINX Plus의 기본 Forward Proxy 기능만으로는 HTTPS CONNECT 터널링 구간에서의 사용자 인증을 완전하게 강제하는 데 한계가 있습니다.
본 가이드에서는 Lua 스크립트를 NGINX Plus에 결합하여 이러한 기술적 제약을 해결하는 방법을 제시합니다. 이를 통해 사용자 식별이 가능한 안전한 Forward Proxy 환경을 구성하는 핵심 과정을 단계별로 소개합니다
목차
1. 환경 구성
2. NGINX Plus Forward Proxy Basic 인증 구성
2.1. Custom NGINX Image
2.2. NGINX 구성 Configmap 배포
2.3. NGINX 배포
3. Forward Proxy 테스트
4. 결론
1. 환경 구성
- Kubernetes : v1.32.2
- NGINX Plus : R36 p1
- nginx-plus-module-lua : 36+0.10.29-1~bookworm
- nginx-plus-module-lua-dbg :36+0.10.29-1~bookworm
- nginx-plus-module-ndk : 36+0.3.3-1~bookworm
2. NGINX Plus Forward Proxy Basic 인증 구성
2.1. Custom NGINX Image
NGINX Plus R36에서 Forward Proxy를 구성할 수 있지만 해당 Forward Proxy에 대한 Basic 인증은 지원하지 않고 있습니다.
Basic 인증을 구성하고 Lua Script를 통해 NGINX Plus Forward Proxy에서 Basic 인증을 사용할 수 있도록 구성합니다.
Lua Script 모듈을 사용할 수 있도록 Dockerfile을 구성합니다.
Dockerfile
ARG RELEASE=bookwormFROM debian:${RELEASE}-slimLABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"# Define NGINX versions for NGINX Plus and NGINX Plus modules# Uncomment this block and the versioned nginxPackages block in the main RUN# instruction to install a specific release# ARG RELEASE# ENV NGINX_VERSION=34# ENV NGINX_PKG_RELEASE=2~${RELEASE}# ENV NJS_VERSION=0.8.9# ENV NJS_PKG_RELEASE=1~${RELEASE}# ENV OTEL_VERSION=0.1.1# ENV OTEL_PKG_RELEASE=1~${RELEASE}# ENV PKG_RELEASE=1~${RELEASE}# Download your NGINX license certificate and key from the F5 customer portal (https://account.f5.com) and copy to the build contextRUN --mount=type=secret,id=nginx-crt,dst=nginx-repo.crt \ --mount=type=secret,id=nginx-key,dst=nginx-repo.key \ --mount=type=secret,id=nginx-jwt,dst=license.jwt \ set -x \# Create nginx user/group first, to be consistent throughout Docker variants && groupadd --system --gid 101 nginx \ && useradd --system --gid nginx --no-create-home --home /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx \ && apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg1 lsb-release \ && \ NGINX_GPGKEYS="573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 8540A6F18833A80E9C1653A42FD21310B49F6B46 9E9BE90EACBCDE69FE9B204CBCDCD8A38D88A2B3"; \ NGINX_GPGKEY_PATH=/usr/share/keyrings/nginx-archive-keyring.gpg; \ export GNUPGHOME="$(mktemp -d)"; \ found=''; \ for NGINX_GPGKEY in $NGINX_GPGKEYS; do \ for server in \ hkp://keyserver.ubuntu.com:80 \ pgp.mit.edu \ ; do \ echo "Fetching GPG key $NGINX_GPGKEY from $server"; \ gpg1 --batch --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; \ done; \ gpg1 --batch --export $NGINX_GPGKEYS > "$NGINX_GPGKEY_PATH" ; \ rm -rf "$GNUPGHOME"; \ apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \# Install the latest release of NGINX Plus and/or NGINX Plus modules (written and maintained by F5)# Uncomment any desired module packages to install the latest release or use the versioned package format to specify a release# For an exhaustive list of supported modules and how to install them, see https://docs.nginx.com/nginx/admin-guide/dynamic-modules/dynamic-modules/ && nginxPackages=" \ nginx-plus \ nginx-plus-module-lua-dbg \ # lua script 모듈을 추가합니다. nginx-plus-module-lua \ # lua script 모듈을 추가합니다. nginx-plus-module-ndk \ # lua script 모듈을 추가합니다. # nginx-plus=${NGINX_VERSION}-${NGINX_PKG_RELEASE} \ # nginx-plus-module-geoip \ # nginx-plus-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} \ # nginx-plus-module-image-filter \ # nginx-plus-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} \ # nginx-plus-module-njs \ # nginx-plus-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${NJS_PKG_RELEASE} \ # nginx-plus-module-otel \ # nginx-plus-module-otel=${NGINX_VERSION}+${OTEL_VERSION}-${OTEL_PKG_RELEASE} \ # nginx-plus-module-perl \ # nginx-plus-module-perl=${NGINX_VERSION}-${PKG_RELEASE} \ # nginx-plus-module-xslt \ # nginx-plus-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} \ " \ && echo "Acquire::https::pkgs.nginx.com::Verify-Peer \"true\";" > /etc/apt/apt.conf.d/90nginx \ && echo "Acquire::https::pkgs.nginx.com::Verify-Host \"true\";" >> /etc/apt/apt.conf.d/90nginx \ && echo "Acquire::https::pkgs.nginx.com::SslCert \"/etc/ssl/nginx/nginx-repo.crt\";" >> /etc/apt/apt.conf.d/90nginx \ && echo "Acquire::https::pkgs.nginx.com::SslKey \"/etc/ssl/nginx/nginx-repo.key\";" >> /etc/apt/apt.conf.d/90nginx \ && echo "deb [signed-by=$NGINX_GPGKEY_PATH] https://pkgs.nginx.com/plus/debian `lsb_release -cs` nginx-plus\n" > /etc/apt/sources.list.d/nginx-plus.list \ && mkdir -p /etc/ssl/nginx /etc/nginx \ && cat nginx-repo.crt > /etc/ssl/nginx/nginx-repo.crt \ && cat nginx-repo.key > /etc/ssl/nginx/nginx-repo.key \ && cat license.jwt > /etc/nginx/license.jwt \ && apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages curl gettext-base \ && apt-get remove --purge -y lsb-release \ && apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx-plus.list \ && rm -rf /etc/apt/apt.conf.d/90nginx /etc/ssl/nginx \# Forward request logs to Docker log collector && ln -sf /dev/stdout /var/log/nginx/access.log \ && ln -sf /dev/stderr /var/log/nginx/error.logEXPOSE 80STOPSIGNAL SIGQUITCMD ["nginx", "-g", "daemon off;"]
해당 이미지를 작성하고 빌드합니다.
docker build --no-cache --secret id=nginx-key,src=[nginx-repo.key 경로] --secret id=nginx-crt,src=[nginx-repo.crt 경로] --secret id=nginx-jwt,src=[license.jwt 경로] -t nginxplus .
해당 이미지를 private registry로 이미지를 푸시합니다.
2.2. NGINX 구성 Configmap 배포
Deployment를 구성하기 전 먼저 NGINX 구성을 configmap으로 변경합니다.
nginx-conf.yaml
apiVersion: v1kind: ConfigMapmetadata: name: nginx-conf namespace: devopschan data: nginx.conf: | load_module /etc/nginx/modules/ndk_http_module.so; # lua script 모듈을 불러오기 전 NGINX Developer kit 모듈을 먼저 불러옵니다. load_module /etc/nginx/modules/ngx_http_lua_module.so; # lua script 모듈을 불러옵니다. user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" $upstream_addr $http_Host $host body=$request_body' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; }
NGINX Forward Proxy의 인증을 구성하기 전 NGINX는 아래와 같은 문제가 있습니다.
- NGINX를 기존 reverse proxy 구성과 같이 auth_basic 만 구성하게 되면 HTTP 401 Status 만 리턴하게 됩니다.
- 브라우저에서는 Proxy를 구성했기 때문에 인증을 위해 407 (Proxy Authentication Required) 응답을 대기합니다.
해당 에러를 위해 error_page 401 = @proxy_auth_fail; 를 이용하여 401에러가 나왔을 경우 proxy_auth_fail location block으로 요청을 전달합니다.
- 브라우저는 Proxy가 설정되어 있을 경우 Proxy-Authorization에 유저의 정보를 기입합니다.
하지만 NGINX는 auth_basic를 구성했기 때문에 Authorization 헤더를 확인하게 되고 무한 루프가 발생하게 됩니다.
이를 해결하기 위해 lua script를 통해 Proxy-Authorization 헤더의 값을 Authorization 헤더로 복사하여 NGINX 인증을 진행합니다.
nginx-sites-config.yaml
apiVersion: v1kind: ConfigMapmetadata: name: nginx-sites-config namespace: devopschan data: proxy.conf: | server { listen 3128; resolver 8.8.8.8 ipv6=off valid=60s; error_log /var/log/nginx/error.log info; # 401이 떴을 경우 인증 로직으로 이동합니다. error_page 401 = @proxy_auth_fail; location / { # 요청을 조작하여 Header를 변경합니다. rewrite_by_lua_block { -- request header를 req_headers로 저장합니다. local req_headers = ngx.req.get_headers() -- proxy-authorization 헤더 값을 proxy_auth로 저장합니다. local proxy_auth = req_headers["proxy-authorization"] -- proxy_auth 값이 있을 경우 Authorization에 proxy-authorization 값을 넣습니다. if proxy_auth then ngx.log(ngx.INFO, "## DEBUG: Found Proxy-Auth header, copying") ngx.req.set_header("Authorization", proxy_auth) -- proxy_auth 값이 없을 경우 @proxy_auth_fail로 리다이렉트 됩니다. else ngx.log(ngx.INFO, "## DEBUG: No Proxy-Auth header found.") end } auth_basic "Restricted Proxy"; auth_basic_user_file /etc/nginx/auth/auth; tunnel_pass; } location @proxy_auth_fail { return 407; header_filter_by_lua_block { -- 웹 인증이 아닌 프록시 인증 로그인 창을 띄웁니다. ngx.header["WWW-Authenticate"] = nil ngx.header["Proxy-Authenticate"] = "Basic realm=\"Restricted Proxy\"" } } }
해당 구성을 추가합니다.

Basic 인증을 위해 사용자 파일을 생성합니다.

해당 사용자 파일을 Secret으로 추가합니다.
kubectl create secret generic basic-auth --from-file=auth -n devopschan
2.3. NGINX 배포
NGINX Plus 라이선스를 Secret으로 추가합니다.
kubectl create secret generic license-token --from-file=license.jwt=license.jwt --type=nginx.com/license -n devopschan
deployment를 추가합니다.
apiVersion: apps/v1kind: Deploymentmetadata: name: nginx namespace: devopschan labels: app: nginx-proxyspec: replicas: 1 selector: matchLabels: app: nginx-proxy template: metadata: labels: app: nginx-proxy spec: containers: - name: nginx image: [Custom NGINX Plus Image] ports: - containerPort: 3128 volumeMounts: - name: nginx-conf # NGINX 모듈 구성을 마운트합니다. mountPath: /etc/nginx/nginx.conf subPath: nginx.conf - name: nginx-license # NGINX 라이선스를 마운트합니다. mountPath: /etc/nginx/license.jwt subPath: license.jwt - name: nginx-sites-config # Forward Proxy Configmap을 마운트합니다. mountPath: /etc/nginx/conf.d readOnly: true - name: auth-volume # Basic 유저 파일을 마운트합니다 mountPath: /etc/nginx/auth readOnly: true imagePullSecrets: - name: regcred # Custom NGINX Image를 Private Registry에서 가져오기 위한 자격증명입니다. volumes: - name: nginx-conf configMap: name: nginx-conf - name: nginx-sites-config configMap: name: nginx-sites-config - name: nginx-license secret: defaultMode: 420 secretName: license-token - name: auth-volume secret: secretName: basic-auth
Service를 추가합니다.
apiVersion: v1kind: Servicemetadata: name: nginx-proxy-svc namespace: devopschan labels: app: nginx-proxy spec: type: NodePort ports: - protocol: TCP name: http port: 3128 selector: app: nginx-proxy type: LoadBalancer
3. Forward Proxy 테스트

Windows의 프록시 서버 편집에 접근하여 EXTERNAL-IP로 구성하고 접속합니다.(Service Type LoadBalancer 기준)

브라우저에서 모든 사이트 접근시 아래와 같이 Proxy의 ID:Password를 입력하게 됩니다.


로그인되어 응답이 오는 것을 확인할 수 있습니다.

NGINX Log를 확인합니다.
k logs [NGINX Pods] -n devopschan | grep [접속 사이트]
NGINX를 거쳐 요청이 전송된 것을 확인할 수 있습니다.
10.0.27.0 - user [19/Dec/2025:04:34:27 +0000] "CONNECT www.google.com:443 HTTP/1.1" 200 739125 "-" 142.250.194.68:443 www.google.com:443 www.google.com body=-"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" "-"
curl도 동일한 방식으로 작동하는 것을 확인할 수 있습니다.
사용자 계정을 넣지 않았을 경우 407 Response Code를 응답하는 것을 확인할 수 있습니다.

사용자 계정을 넣고 요청을 보냈을 경우 성공적으로 응답이 오는 것을 확인할 수 있습니다.

4. 결론
본 가이드에서는 NGINX Plus와 Lua 스크립트를 결합하여, NGINX 네이티브 설정만으로는 구현이 어려운 Forward Proxy 환경에서의 Basic 인증 체계를 완성했습니다. 프록시 헤더를 동적으로 재가공하는 방식을 통해 Kubernetes 내에서도 클라이언트와 게이트웨이 간의 인증 핸드셰이크를 완벽하게 처리할 수 있음을 검증하였습니다.
이러한 구성은 검증된 사용자에게만 인터넷 접근 권한을 부여하는 세밀한 트래픽 관리를 가능하게 합니다. 결과적으로 관리자는 Egress 트래픽에 대한 정보를 NGINX 로그를 통해 확인할 수 있으며 Basic 인증을 통해 보안정책을 구성할 수 있습니다.
NGINX Plus의 Forward Proxy 기능을 사용해 보고 싶으시거나 NGINX Plus Trial License 체험이 필요하시다면 언제든지 NGINX STORE로 문의해 주세요.
댓글을 달려면 로그인해야 합니다.