Skip to content
Published on

Ingress 레벨 인증 — oauth2-proxy와 forward auth로 SSO 적용

Authors

들어가며

쿠버네티스 클러스터에 내부 도구를 하나둘 올리다 보면 어느 순간 깨닫게 되는 사실이 있습니다. Grafana, Kibana, ArgoCD, 사내 대시보드, 빌드 리포트 페이지, 그리고 인증 기능이 아예 없는 작은 사이드 프로젝트까지. 이 모든 것을 "로그인한 직원만" 볼 수 있게 만들어야 하는데, 각 애플리케이션마다 인증을 따로 붙이는 것은 끔찍한 일입니다.

어떤 앱은 OIDC를 지원하고, 어떤 앱은 basic auth만 지원하며, 어떤 앱은 인증 기능 자체가 없습니다. 이 제각각인 앱들에 일관된 SSO(Single Sign-On)를 입히려면 어디에 인증 레이어를 둬야 할까요? 답은 의외로 단순합니다. 트래픽이 가장 먼저 통과하는 지점, 바로 Ingress입니다.

이 글은 이미 이 블로그에 여러 편 다뤄온 OIDC, Keycloak, SSO 개념을 다시 설명하지 않습니다. 대신 철저히 Ingress 관점에서, external auth 패턴을 통해 oauth2-proxy를 forward auth로 연결하고, 애플리케이션 코드를 단 한 줄도 건드리지 않으면서 여러 앱에 SSO를 입히는 방법에 집중합니다. ingress-nginx, Traefik, Contour 각각의 방식 차이도 비교합니다.

한 가지 전제부터 짚고 넘어가겠습니다. 2026년 현재 쿠버네티스 Ingress API는 사실상 동결(frozen) 상태입니다. 새로운 기능은 더 이상 추가되지 않으며, 후속 표준은 Gateway API입니다. ingress-nginx는 유지보수 모드에 가깝고 보안 이슈가 종종 보고됩니다. 그럼에도 현장에는 여전히 Ingress 기반 인증이 압도적으로 많이 깔려 있으므로, 이 글은 현실의 Ingress를 다루되 각 절에서 Gateway API와의 관계를 함께 짚겠습니다.

external auth 패턴이란

external auth(외부 인증, forward auth라고도 부릅니다)는 리버스 프록시가 요청을 백엔드로 보내기 전에, 별도의 인증 서비스에 "이 요청 통과시켜도 될까?"라고 먼저 물어보는 패턴입니다. 인증 서비스가 200을 응답하면 프록시는 요청을 백엔드로 전달하고, 401이나 302를 응답하면 프록시는 사용자를 로그인 페이지로 보냅니다.

이 패턴의 핵심 가치는 인증 로직을 애플리케이션 밖으로 빼낸다는 점입니다. 백엔드 앱은 자기가 누구에게 보여지는지 신경 쓸 필요가 없습니다. 오직 Ingress와 인증 서비스가 그 책임을 집니다.

                         (1) GET /dashboard
   ┌──────────┐  ─────────────────────────────►  ┌─────────────┐
   │ 브라우저  │                                   │   Ingress    │
   │          │  ◄─────────────────────────────  │  Controller  │
   └──────────┘   (6) 200 OK + 백엔드 응답         └──────┬──────┘
        ▲                                                 │
        │                                    (2) 서브리퀘스트│
        │                                     auth-url 호출 │
        │                                                 ▼
        │                                          ┌─────────────┐
        │  (4) 302 로그인 리다이렉트                 │ oauth2-proxy │
        └──────────────────────────────────────── │ (auth svc)   │
                                                   └──────┬──────┘
                                  (3) 쿠키 없음 → 401     │
                                  (5) 쿠키 있음 → 202     │
                                                   ┌─────────────┐
                                                   │   백엔드 앱   │
                                                   └─────────────┘

흐름을 단계별로 풀어보면 이렇습니다.

  1. 브라우저가 보호된 경로(/dashboard)에 접근합니다.
  2. Ingress 컨트롤러는 백엔드로 바로 보내지 않고, 먼저 인증 서비스(auth-url)에 서브리퀘스트를 보냅니다. 이때 원본 요청의 쿠키와 헤더를 함께 전달합니다.
  3. oauth2-proxy는 요청에 유효한 세션 쿠키가 있는지 확인합니다. 없으면 401을 응답합니다.
  4. Ingress는 401을 받으면 auth-signin에 지정된 로그인 URL로 사용자를 302 리다이렉트시킵니다.
  5. 사용자가 OIDC 로그인을 마치고 다시 접근하면, 이번엔 유효한 쿠키가 있으므로 oauth2-proxy는 202(또는 200)를 응답하며, 동시에 사용자 정보를 헤더에 담아 돌려줍니다.
  6. Ingress는 그 헤더를 백엔드로 전달하면서 원본 요청을 백엔드로 보냅니다.

여기서 중요한 차이를 하나 짚겠습니다. forward auth에서 인증 서비스가 받는 것은 "서브리퀘스트"이지 실제 요청 본문이 아닙니다. 인증 서비스는 메서드와 경로, 헤더(특히 쿠키)만 보고 통과 여부를 결정합니다. 그래서 인증 서비스는 가볍고 빠르게 응답할 수 있어야 합니다.

왜 oauth2-proxy인가

forward auth의 인증 서비스 자리에는 무엇이든 올 수 있습니다. 직접 작성한 작은 HTTP 서버여도 되고, Authelia나 Authentik 같은 통합 솔루션이어도 됩니다. 하지만 가장 널리 쓰이는 것은 oauth2-proxy입니다. 이유는 단순합니다.

  • OIDC와 OAuth2를 표준적으로 지원하므로 Keycloak, Google, GitHub, Azure AD 등 거의 모든 IdP와 연동됩니다.
  • 세션을 쿠키에 저장하거나 Redis에 저장할 수 있어 확장성 선택지가 넓습니다.
  • forward auth 모드(--upstream을 생략하고 /oauth2/auth 엔드포인트만 사용)를 기본 지원합니다.
  • 인증 후 사용자 정보(이메일, 그룹 등)를 응답 헤더로 백엔드에 넘겨줄 수 있습니다.

oauth2-proxy는 두 가지 모드로 동작할 수 있습니다. 하나는 자신이 직접 백엔드 앞단의 프록시가 되어 모든 트래픽을 받는 모드이고, 다른 하나는 Ingress 뒤에서 인증 판정만 담당하는 forward auth 모드입니다. Ingress 레벨 SSO에서는 후자를 씁니다. Ingress가 트래픽 라우팅을 맡고, oauth2-proxy는 /oauth2/auth 한 엔드포인트로 통과 여부만 응답합니다.

oauth2-proxy 배포하기

먼저 oauth2-proxy를 클러스터에 배포합니다. Keycloak을 IdP로 사용한다고 가정하겠습니다. Helm 차트를 쓰는 것이 가장 간편하지만, 동작을 명확히 보기 위해 핵심 설정만 직접 살펴보겠습니다.

# oauth2-proxy-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: auth
spec:
  replicas: 2
  selector:
    matchLabels:
      app: oauth2-proxy
  template:
    metadata:
      labels:
        app: oauth2-proxy
    spec:
      containers:
        - name: oauth2-proxy
          image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
          args:
            - --provider=oidc
            - --oidc-issuer-url=https://keycloak.example.com/realms/internal
            - --email-domain=*
            - --upstream=static://200
            - --http-address=0.0.0.0:4180
            - --reverse-proxy=true
            - --cookie-secure=true
            - --cookie-domain=.example.com
            - --whitelist-domain=.example.com
            - --set-xauthrequest=true
            - --pass-access-token=true
          env:
            - name: OAUTH2_PROXY_CLIENT_ID
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secret
                  key: client-id
            - name: OAUTH2_PROXY_CLIENT_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secret
                  key: client-secret
            - name: OAUTH2_PROXY_COOKIE_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secret
                  key: cookie-secret
          ports:
            - containerPort: 4180

여기서 주목할 인자들을 짚어보겠습니다.

  • --upstream=static://200: forward auth 모드에서는 oauth2-proxy가 실제 백엔드로 프록시하지 않습니다. 인증만 판정하므로 static 응답을 upstream으로 둡니다.
  • --reverse-proxy=true: Ingress 뒤에 있으므로 X-Forwarded 헤더를 신뢰하도록 켭니다.
  • --cookie-domain=.example.com: 쿠키 도메인을 상위 도메인으로 설정하면 여러 서브도메인 앱이 같은 세션 쿠키를 공유합니다. 이것이 멀티 앱 SSO의 핵심입니다.
  • --set-xauthrequest=true: 인증 응답에 X-Auth-Request-User, X-Auth-Request-Email 같은 헤더를 실어 보냅니다. 백엔드가 사용자 정보를 받을 수 있게 합니다.

쿠키 시크릿은 반드시 32바이트로 생성해야 합니다. 다음과 같이 만들 수 있습니다.

# cookie secret 생성 (16, 24, 32바이트 중 32바이트 권장)
openssl rand -base64 32 | head -c 32 | base64

서비스도 함께 만들어 줍니다.

# oauth2-proxy-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: oauth2-proxy
  namespace: auth
spec:
  selector:
    app: oauth2-proxy
  ports:
    - name: http
      port: 4180
      targetPort: 4180

OIDC IdP 연동 (Keycloak / Google)

oauth2-proxy가 IdP와 정상 연동되려면 IdP 쪽에 클라이언트(애플리케이션)를 등록하고, 리다이렉트 URL을 정확히 지정해야 합니다. oauth2-proxy의 콜백 경로는 기본적으로 /oauth2/callback입니다.

Keycloak에서는 다음과 같이 설정합니다.

  • realm을 하나 선택하거나 새로 만듭니다 (예: internal).
  • 클라이언트를 생성하고 클라이언트 타입을 OpenID Connect로 둡니다.
  • Valid redirect URIs에 oauth2-proxy의 콜백 주소를 등록합니다. 예: https://auth.example.com/oauth2/callback
  • Client authentication을 켜서 confidential 클라이언트로 만들고, 발급된 시크릿을 oauth2-proxy 시크릿에 넣습니다.

그룹 기반 접근 제어를 하고 싶다면 Keycloak에서 groups 클레임을 토큰에 포함하도록 매퍼를 추가하고, oauth2-proxy에 --allowed-group 인자를 줍니다.

# 특정 그룹만 허용
- --allowed-group=/platform-team
- --oidc-groups-claim=groups

Google을 IdP로 쓰는 경우에는 issuer URL이 고정되어 있습니다.

- --provider=google
- --oidc-issuer-url=https://accounts.google.com
- --email-domain=mycompany.com

Google은 그룹 정보를 OIDC 토큰에 기본 포함하지 않으므로, 특정 도메인의 이메일만 허용하는 --email-domain 방식이나 별도의 서비스 계정을 통한 Google Groups 조회 방식을 써야 합니다. 이 차이는 IdP 선택 시 반드시 고려해야 합니다.

ingress-nginx에서 forward auth 연결하기

이제 핵심입니다. ingress-nginx에서는 어노테이션 두 개로 forward auth를 활성화합니다.

# protected-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grafana
  namespace: monitoring
  annotations:
    nginx.ingress.kubernetes.io/auth-url: 'https://auth.example.com/oauth2/auth'
    nginx.ingress.kubernetes.io/auth-signin: 'https://auth.example.com/oauth2/start?rd=$escaped_request_uri'
    nginx.ingress.kubernetes.io/auth-response-headers: 'X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Groups'
spec:
  ingressClassName: nginx
  rules:
    - host: grafana.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: grafana
                port:
                  number: 3000

어노테이션의 의미를 풀어보겠습니다.

  • auth-url: 인증 서브리퀘스트를 보낼 주소입니다. oauth2-proxy의 /oauth2/auth 엔드포인트입니다. 이 엔드포인트는 쿠키가 유효하면 202, 아니면 401을 응답합니다.
  • auth-signin: 401을 받았을 때 사용자를 보낼 로그인 URL입니다. rd 파라미터에 원래 가려던 주소를 담아, 로그인 후 그 자리로 돌아오게 합니다.
  • auth-response-headers: 인증 서비스가 돌려준 헤더 중 백엔드로 전달할 것들을 지정합니다. 백엔드 앱이 이 헤더로 사용자를 식별할 수 있습니다.

여기서 oauth2-proxy 자체도 외부에서 접근 가능해야 합니다. 콜백과 로그인 시작 엔드포인트가 브라우저에서 접근되어야 하기 때문입니다. 그래서 oauth2-proxy용 Ingress를 따로 둡니다.

# oauth2-proxy-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: oauth2-proxy
  namespace: auth
spec:
  ingressClassName: nginx
  rules:
    - host: auth.example.com
      http:
        paths:
          - path: /oauth2
            pathType: Prefix
            backend:
              service:
                name: oauth2-proxy
                port:
                  number: 4180

이 구조에서 모든 보호 대상 앱의 Ingress는 같은 auth.example.com/oauth2/auth를 가리킵니다. 쿠키 도메인이 .example.com이므로 한 번 로그인하면 모든 서브도메인 앱에서 세션이 유지됩니다. 이것이 멀티 앱 SSO가 완성되는 지점입니다.

Traefik의 forwardAuth 미들웨어

Traefik은 어노테이션이 아니라 미들웨어(Middleware) CRD로 forward auth를 표현합니다. 철학이 조금 다릅니다. Traefik에서는 인증을 "미들웨어"라는 재사용 가능한 부품으로 정의하고, 라우터에 갖다 붙입니다.

# traefik-forwardauth-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: oauth2-forwardauth
  namespace: auth
spec:
  forwardAuth:
    address: 'http://oauth2-proxy.auth.svc.cluster.local:4180/oauth2/auth'
    trustForwardHeader: true
    authResponseHeaders:
      - X-Auth-Request-User
      - X-Auth-Request-Email
      - X-Auth-Request-Groups

이 미들웨어를 IngressRoute에 연결합니다.

# traefik-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: grafana
  namespace: monitoring
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`grafana.example.com`)
      kind: Rule
      services:
        - name: grafana
          port: 3000
      middlewares:
        - name: oauth2-forwardauth
          namespace: auth

Traefik의 forwardAuth와 ingress-nginx의 auth-url은 개념적으로 동일하지만 몇 가지 차이가 있습니다.

  • ingress-nginx는 인증 실패 시 auth-signin 어노테이션을 통해 직접 302 리다이렉트를 처리합니다. Traefik의 forwardAuth는 인증 서비스가 돌려준 응답(예: 401 또는 인증 서비스가 직접 보내는 302)을 그대로 클라이언트에 전달합니다. 그래서 oauth2-proxy 쪽에서 적절히 리다이렉트를 만들어 주도록 설정해야 합니다.
  • Traefik 미들웨어는 한 번 정의하면 여러 IngressRoute에서 재사용하기가 매우 깔끔합니다. 멀티 앱 환경에서는 이 재사용성이 큰 장점입니다.

Contour의 경우

Contour는 Envoy 기반 컨트롤러로, external authorization을 Envoy의 ext_authz 필터를 통해 구현합니다. Contour에서는 ExtensionService와 HTTPProxy를 조합합니다. gRPC 기반 ext_authz를 쓰는 것이 정석이지만, oauth2-proxy를 붙일 때는 HTTP 기반으로 연동하는 패턴을 쓰기도 합니다.

# contour-httpproxy.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: grafana
  namespace: monitoring
spec:
  virtualhost:
    fqdn: grafana.example.com
    authorization:
      extensionRef:
        name: oauth2-proxy-authz
        namespace: auth
  routes:
    - conditions:
        - prefix: /
      services:
        - name: grafana
          port: 3000

Contour는 ext_authz를 Envoy 레벨에서 처리하므로 성능 특성이 다르고, gRPC 인증 서버를 쓰면 더 효율적입니다. 다만 oauth2-proxy는 기본적으로 HTTP 인증을 제공하므로, Contour 환경에서는 어댑터 역할을 하는 별도 컴포넌트가 필요할 수 있습니다. 세 컨트롤러를 한 표로 비교하면 다음과 같습니다.

항목ingress-nginxTraefikContour
인증 연결 방식auth-url 어노테이션forwardAuth 미들웨어 CRDHTTPProxy authorization + ext_authz
리다이렉트 처리auth-signin이 직접 302인증 서비스 응답 전달Envoy ext_authz 응답 처리
재사용성Ingress마다 어노테이션 반복미들웨어 1회 정의 후 재사용ExtensionService 재사용
데이터 평면nginxTraefik (Go)Envoy
권장 인증 프로토콜HTTP 서브리퀘스트HTTP forward authgRPC ext_authz 선호

basic auth 및 mTLS와의 비교

Ingress 레벨에서 접근을 제한하는 방법은 forward auth만 있는 것이 아닙니다. 가장 단순한 것은 basic auth이고, 가장 강력한 것은 mTLS입니다. 각각의 자리를 이해하면 forward auth를 언제 써야 하는지가 명확해집니다.

basic auth는 어노테이션 한두 개와 시크릿 하나로 끝납니다.

# basic-auth-ingress.yaml
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'

매우 간단하지만 한계가 분명합니다. 사용자별 식별이 어렵고, 비밀번호 회전이 번거로우며, SSO가 불가능하고, 그룹 기반 권한 제어가 없습니다. 잠깐 막아두는 임시 보호 용도로는 괜찮지만 프로덕션 SSO 대체재는 못 됩니다.

mTLS는 정반대로 매우 강력합니다. 클라이언트가 유효한 인증서를 제시해야만 통과됩니다. 사람이 아니라 머신 간(서비스 간) 통신이나, 보안 등급이 매우 높은 내부 시스템에 적합합니다. 다만 사용자 단말마다 인증서를 배포하고 관리하는 비용이 크고, 브라우저 UX가 친화적이지 않습니다.

방식사용자 식별SSO그룹 권한도입 난이도적합한 상황
basic auth약함불가없음매우 낮음임시 보호, 내부 데모
forward auth (oauth2-proxy)강함가능가능중간직원용 웹 앱 다수
mTLS인증서 기반해당 없음인증서 정책높음머신 간 통신, 고보안 영역

요약하면, 사람이 브라우저로 접근하는 사내 웹 애플리케이션 다수에 일관된 로그인을 입히고 싶다면 forward auth가 최적입니다. basic auth는 너무 약하고, mTLS는 사람 사용자에게 과합니다.

세션과 쿠키 다루기

forward auth SSO의 안정성은 사실상 쿠키 관리에 달려 있습니다. 몇 가지 핵심 포인트를 정리하겠습니다.

쿠키 도메인. 멀티 앱 SSO의 핵심입니다. --cookie-domain=.example.com처럼 상위 도메인으로 설정해야 grafana.example.com과 kibana.example.com이 같은 세션 쿠키를 공유합니다. 도메인이 다르면(예: example.com과 example.net) 같은 oauth2-proxy로도 SSO가 되지 않습니다.

쿠키 저장소. oauth2-proxy는 세션을 쿠키 자체에 담거나(쿠키 세션), Redis에 저장할 수 있습니다. 토큰이 커서 쿠키 크기 한도(보통 4KB)를 넘으면 쿠키가 잘려 인증이 깨집니다. 그룹 클레임이 많거나 액세스 토큰을 담으면 쉽게 한도를 넘으므로, 이 경우 Redis 세션 저장소로 전환하는 것이 안전합니다.

# Redis 세션 저장소 사용
- --session-store-type=redis
- --redis-connection-url=redis://redis.auth.svc.cluster.local:6379

쿠키 보안 속성. 프로덕션에서는 --cookie-secure=true(HTTPS 전용), 그리고 가능하면 --cookie-samesite=lax를 권장합니다. OIDC 리다이렉트 흐름은 외부 IdP로 갔다 돌아오므로 SameSite를 strict로 두면 콜백 시 쿠키가 안 실려 로그인 루프에 빠질 수 있습니다.

세션 만료. --cookie-expire--cookie-refresh로 세션 수명과 갱신 주기를 제어합니다. refresh를 적절히 설정하면 액세스 토큰이 만료돼도 리프레시 토큰으로 조용히 갱신되어 사용자 경험이 끊기지 않습니다.

멀티 앱 SSO 완성하기

지금까지의 조각을 모으면 멀티 앱 SSO 그림이 완성됩니다. 핵심 원칙은 세 가지입니다.

  1. 하나의 oauth2-proxy를 공유한다. 앱마다 oauth2-proxy를 따로 두지 않습니다. 하나만 두고, 모든 앱의 Ingress가 같은 auth-url을 가리킵니다.
  2. 쿠키 도메인을 상위 도메인으로 둔다. 모든 앱이 같은 상위 도메인 아래 서브도메인이어야 쿠키를 공유합니다.
  3. 앱별로 인가(authorization)는 따로 처리한다. SSO로 "로그인했는가"는 공유되지만, "이 앱을 볼 권한이 있는가"는 앱마다 다를 수 있습니다. oauth2-proxy의 그룹 검사를 앱별로 다르게 두거나, 백엔드가 전달받은 그룹 헤더로 판단하게 합니다.
                       ┌──────────────────────┐
                       │   oauth2-proxy (1개)   │
                       │  auth.example.com      │
                       │  쿠키 도메인=.example.com │
                       └──────────┬───────────┘
                                  │ /oauth2/auth
        ┌─────────────┬───────────┼───────────┬─────────────┐
        ▼             ▼           ▼           ▼             ▼
  grafana.ex..   kibana.ex..  argo.ex..   wiki.ex..   reports.ex..
   (Ingress)      (Ingress)   (Ingress)   (Ingress)    (Ingress)
   auth-url ─────┴──── 모두 같은 auth-url 가리킴 ────┴──────┘

한 번 로그인하면 다섯 개 앱 모두에서 세션이 유지됩니다. 사용자는 grafana에 로그인한 뒤 kibana로 이동할 때 다시 로그인할 필요가 없습니다. 이것이 SSO 사용자 경험의 본질입니다.

트러블슈팅

forward auth SSO를 처음 붙이면 거의 반드시 한두 가지 문제를 만납니다. 자주 겪는 것들을 정리하겠습니다.

무한 리다이렉트 루프. 가장 흔한 문제입니다. 로그인했는데 계속 로그인 페이지로 돌아갑니다. 원인은 대개 쿠키 도메인 불일치이거나 SameSite 설정 문제입니다. auth-signin의 도메인과 쿠키 도메인이 맞는지, SameSite가 strict로 너무 빡빡하지 않은지 확인합니다.

401이 백엔드까지 전달됨. auth-url 서브리퀘스트가 401을 응답했는데 auth-signin이 동작하지 않으면 사용자에게 401이 그대로 보입니다. auth-signin 어노테이션 철자와 URL을 다시 확인합니다.

헤더가 백엔드에 전달되지 않음. 백엔드가 X-Auth-Request-Email을 못 받는다면 두 가지를 확인합니다. oauth2-proxy에 --set-xauthrequest=true가 켜져 있는지, 그리고 Ingress의 auth-response-headers에 그 헤더 이름이 명시되어 있는지입니다. 둘 다 있어야 헤더가 흘러갑니다.

쿠키가 너무 커서 인증 깨짐. 로그인은 되는데 페이지를 새로고침하면 다시 로그인하라고 합니다. 토큰이 4KB를 넘어 쿠키가 잘렸을 가능성이 높습니다. Redis 세션 저장소로 전환합니다.

대용량 요청 본문에서 인증 타임아웃. 큰 파일 업로드 경로에서 서브리퀘스트 처리가 지연될 수 있습니다. ingress-nginx에서는 auth-request 관련 버퍼와 타임아웃 설정을 점검합니다.

# oauth2-proxy 로그로 거절 사유 확인
kubectl logs -n auth deploy/oauth2-proxy -f | grep -i "denied\|401\|error"
# 서브리퀘스트가 실제로 어디로 가는지 nginx 설정 확인
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A5 auth_request

운영과 튜닝

forward auth는 모든 보호 요청마다 서브리퀘스트를 한 번씩 더 발생시킵니다. 즉 oauth2-proxy는 트래픽의 핵심 경로에 놓입니다. 운영 시 다음을 챙겨야 합니다.

  • 가용성. oauth2-proxy가 죽으면 모든 보호 앱이 함께 막힙니다. replicas를 2 이상으로 두고, PodDisruptionBudget을 설정합니다.
  • 세션 저장소 가용성. Redis 세션을 쓴다면 Redis도 단일 장애점입니다. HA 구성을 고려합니다.
  • 레이턴시. 서브리퀘스트가 추가 지연을 만듭니다. oauth2-proxy를 같은 클러스터 내, 가능하면 같은 가용 영역에 두어 네트워크 왕복을 줄입니다.
  • 캐싱. ingress-nginx는 동일 세션에 대한 auth 응답을 짧게 캐시할 수 있습니다. 다만 보안과 신선도 사이의 트레이드오프를 이해하고 적용해야 합니다.
# 인증 응답 캐시 (ingress-nginx) - 주의해서 사용
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-cache-key: '$cookie_oauth2_proxy'
    nginx.ingress.kubernetes.io/auth-cache-duration: '200 202 5m, 401 30s'

Gateway API와의 관계

앞서 말했듯 Ingress API는 동결 상태이고 Gateway API가 후속 표준입니다. 그렇다면 forward auth는 어떻게 될까요?

현재 Gateway API 코어 스펙에는 external auth가 표준화되어 있지 않습니다. 인증/인가는 아직 컨트롤러 구현마다 확장으로 제공되거나, ext_authz 같은 데이터 평면 메커니즘에 의존합니다. Gateway API에는 인증 정책을 표현하기 위한 정책 어태치먼트(policy attachment) 패턴이 논의되고 있고, 일부 구현은 BackendTLSPolicy나 별도 인증 정책 CRD를 제공합니다.

실무적으로 보면, 지금 ingress-nginx 어노테이션으로 구축한 forward auth는 Gateway API로 이전할 때 그대로 옮겨지지 않습니다. 컨트롤러별 정책 CRD나 Envoy ext_authz로 재구성해야 할 가능성이 큽니다. 그래서 새 클러스터를 설계한다면, oauth2-proxy라는 인증 서비스 자체는 그대로 두고 연결 방식만 Gateway API로 바꿀 수 있도록, 인증 서비스와 라우팅 설정을 느슨하게 결합해 두는 것이 현명합니다. oauth2-proxy는 어느 쪽이든 재사용 가능한 자산입니다.

마치며

Ingress 레벨 인증의 본질은 "인증 책임을 애플리케이션에서 인프라로 옮기는 것"입니다. forward auth와 oauth2-proxy를 조합하면, 인증 기능이 전혀 없는 앱에도 코드 한 줄 건드리지 않고 엔터프라이즈급 SSO를 입힐 수 있습니다.

핵심을 다시 정리하면 이렇습니다. external auth 패턴은 Ingress가 백엔드 앞에서 인증 서비스에 서브리퀘스트를 던지는 구조이고, oauth2-proxy가 그 인증 서비스로 가장 널리 쓰입니다. ingress-nginx는 어노테이션, Traefik은 미들웨어, Contour는 ext_authz로 같은 패턴을 다르게 표현합니다. 쿠키 도메인을 상위 도메인으로 두고 oauth2-proxy 하나를 공유하면 멀티 앱 SSO가 완성됩니다.

마지막으로 2026년의 현실을 잊지 마십시오. Ingress API는 동결되었고 ingress-nginx는 유지보수 모드입니다. 지금 forward auth를 구축하되, 인증 서비스와 라우팅을 느슨하게 결합해 Gateway API 시대로의 이전을 미리 준비하는 것이 현명한 선택입니다.

참고 자료