Skip to content
Published on

Zero Trust와 Identity-Aware Proxy — BeyondCorp 모델 직접 구축하기

Authors

들어가며 — 왜 지금 Identity-Aware Proxy인가

2026년 현재, "사내망에 있으면 신뢰한다"는 가정은 사실상 폐기된 보안 모델입니다. 원격 근무가 표준이 되었고, 워크로드는 멀티 클라우드에 흩어져 있으며, SaaS와 사내 도구의 경계도 흐려졌습니다. 여기에 AI agent가 사람을 대신해 내부 시스템에 접근하는 흐름까지 더해지면서, "누가(또는 무엇이) 어떤 컨텍스트에서 접근하는가"를 매 요청마다 검증하는 identity-first 보안이 기본값이 되었습니다.

이 글에서는 Zero Trust의 핵심 구현체인 Identity-Aware Proxy(IAP)를 다룹니다. Google의 BeyondCorp 논문이 제시한 모델을 이해하고, oauth2-proxy와 Keycloak을 조합해 IAP를 직접 구축하는 전 과정을 YAML과 설정 파일 수준까지 따라가 봅니다. 마지막에는 Cloudflare Access, Google Cloud IAP, Pomerium 같은 상용/오픈소스 대안을 비교하고, 단계적 도입 로드맵과 AI agent 시대의 접근 제어까지 살펴봅니다.

경계 보안의 종말과 identity-first 보안

성곽과 해자 모델의 한계

전통적 네트워크 보안은 흔히 "성곽과 해자(castle-and-moat)" 모델로 불립니다. 방화벽과 VPN으로 경계를 치고, 일단 내부로 들어온 트래픽은 폭넓게 신뢰하는 방식입니다. 이 모델의 문제는 명확합니다.

  • 공격자가 경계를 한 번 뚫으면(피싱, VPN 자격 증명 탈취, 취약점) 내부에서 수평 이동(lateral movement)이 자유롭습니다.
  • VPN은 네트워크 전체에 대한 접근을 부여합니다. 특정 앱 하나만 쓰려는 외주 인력에게도 사실상 사내망 전체가 열립니다.
  • 위치 기반 신뢰는 원격 근무, BYOD, 멀티 클라우드 환경에서 의미를 잃습니다.
  • 내부자 위협과 손상된 단말에 대해 무방비입니다.

Zero Trust의 핵심 원칙

Zero Trust는 NIST SP 800-207에서 표준화된 개념으로, 다음 원칙으로 요약됩니다.

  1. 네트워크 위치는 신뢰의 근거가 아니다 — 사내망이든 카페 와이파이든 동일하게 취급합니다.
  2. 모든 접근은 인증되고 인가되어야 한다 — 세션 단위가 아니라 요청 단위로 검증합니다.
  3. 최소 권한 — 사용자와 디바이스는 업무에 필요한 리소스에만 접근합니다.
  4. 컨텍스트 기반 동적 정책 — 사용자 신원, 디바이스 상태, 위치, 시간 등을 종합해 접근을 결정합니다.
  5. 지속적 검증 — 한 번 통과했다고 끝이 아니라, 신호가 바뀌면 접근도 재평가됩니다.

이 원칙을 HTTP 애플리케이션 계층에서 구현하는 컴포넌트가 바로 Identity-Aware Proxy입니다.

BeyondCorp 논문의 핵심

Google은 2009년 Operation Aurora 침해 사고를 계기로 사내 VPN을 단계적으로 제거하고, 모든 사내 애플리케이션을 인터넷에 노출하되 접근 프록시로 보호하는 BeyondCorp 프로젝트를 시작했습니다. BeyondCorp 논문 시리즈의 핵심 구성요소는 다음과 같습니다.

  • Access Proxy — 모든 애플리케이션 앞단에 위치하는 리버스 프록시. TLS 종료, 인증, 인가를 중앙에서 처리합니다.
  • Device Inventory Database — 회사가 관리하는 모든 디바이스의 상태(OS 버전, 패치 수준, 디스크 암호화 여부)를 추적합니다.
  • User/Group Database — HR 시스템과 동기화되는 사용자 신원 저장소입니다.
  • Trust Inference — 사용자와 디바이스 신호를 종합해 신뢰 등급(trust tier)을 동적으로 산출합니다.
  • Access Control Engine — "이 사용자가, 이 디바이스로, 이 애플리케이션에" 접근 가능한지 정책으로 판단합니다.

BeyondCorp가 남긴 가장 중요한 교훈은 두 가지입니다. 첫째, 인증과 인가를 애플리케이션에서 떼어내 프록시 계층으로 중앙화하면 수백 개 사내 앱의 보안 수준을 일괄 상향할 수 있습니다. 둘째, 사용자 신원만으로는 부족하며 디바이스 신뢰가 함께 평가되어야 합니다.

IAP 동작 원리 — 모든 요청에 인증과 인가

IAP의 동작을 한 장으로 표현하면 다음과 같습니다.

                        +----------------------------+
                        |   Identity Provider (IdP)  |
                        |   Keycloak / OIDC          |
                        +-------------+--------------+
                                      | (2) OIDC 인증
                                      | Authorization Code + PKCE
+----------+   (1) HTTPS   +----------+-----------+   (4) 헤더 주입   +-------------+
|  사용자   +-------------->+  Identity-Aware Proxy +----------------->+  내부 앱     |
|  브라우저 |               |  (oauth2-proxy 등)    |  X-Forwarded-*   |  (admin 등)  |
+----------+               +----------+-----------+                  +-------------+
                                      |
                                      | (3) 정책 평가
                                      |  - 사용자 그룹/역할
                                      |  - 디바이스 상태
                                      |  - 위치/시간 컨텍스트
                                      v
                           +----------------------+
                           |  Policy / Device DB  |
                           +----------------------+

요청 처리 흐름을 단계별로 풀면 다음과 같습니다.

  1. 사용자가 보호 대상 앱의 URL에 접근하면, 요청은 반드시 IAP를 거칩니다. 앱은 IAP를 우회한 직접 접근을 네트워크 정책으로 차단합니다.
  2. 유효한 세션이 없으면 IAP가 OIDC Authorization Code Flow(PKCE 포함)로 IdP에 인증을 위임합니다.
  3. 인증이 끝나면 IAP는 ID 토큰의 클레임(그룹, 역할)과 추가 컨텍스트(디바이스, IP, 시간)를 바탕으로 인가 정책을 평가합니다.
  4. 통과한 요청에만 사용자 신원 헤더(또는 서명된 JWT)를 붙여 업스트림 앱으로 전달합니다.
  5. 이 과정은 최초 로그인 이후에도 모든 요청마다 세션 쿠키 검증과 정책 재평가 형태로 반복됩니다. 세션은 짧게 유지하고, refresh 시점마다 IdP의 최신 상태(계정 비활성화, 그룹 변경)를 반영합니다.

전통적 VPN과의 차이를 표로 정리하면 다음과 같습니다.

항목VPNIdentity-Aware Proxy
신뢰 단위네트워크 (들어오면 끝)요청 (매 요청 인증/인가)
접근 범위사내망 전체 또는 서브넷애플리케이션 단위
인가 기준IP, 계정신원 + 그룹 + 디바이스 + 컨텍스트
가시성터널 내부는 불투명요청 단위 감사 로그
사용자 경험클라이언트 설치, 접속 단절브라우저만으로 SSO
L3/L4 지원가능기본은 HTTP, TCP는 별도 솔루션

실전 구축 — oauth2-proxy + Keycloak으로 IAP 만들기

이제 직접 만들어 보겠습니다. 구성은 다음과 같습니다.

  • IdP: Keycloak 26.6 (2026년 5월 기준 26.6.2가 최신이며, passkeys 로그인 통합과 FAPI 2.0 Final 지원이 포함되어 있습니다)
  • IAP: oauth2-proxy 7.x
  • 리버스 프록시: nginx ingress (auth_request 패턴)
  • 보호 대상: 쿠버네티스에 배포된 내부 admin 대시보드

1단계 — Keycloak 클라이언트 설정

먼저 Keycloak에 oauth2-proxy용 confidential client를 만듭니다. realm은 internal이라고 가정합니다. Keycloak Admin CLI(kcadm)로 다음과 같이 생성합니다.

# Keycloak admin CLI 로그인
kcadm.sh config credentials \
  --server https://keycloak.example.com \
  --realm master \
  --user admin

# oauth2-proxy용 클라이언트 생성
kcadm.sh create clients -r internal -f - <<'EOF'
{
  "clientId": "oauth2-proxy",
  "protocol": "openid-connect",
  "publicClient": false,
  "standardFlowEnabled": true,
  "directAccessGrantsEnabled": false,
  "serviceAccountsEnabled": false,
  "redirectUris": ["https://admin.example.com/oauth2/callback"],
  "attributes": {
    "pkce.code.challenge.method": "S256",
    "post.logout.redirect.uris": "https://admin.example.com"
  }
}
EOF

그룹 정보를 토큰에 담기 위해 groups 클레임 매퍼를 추가합니다.

# groups 클레임을 ID 토큰에 포함시키는 매퍼
CLIENT_UUID=$(kcadm.sh get clients -r internal -q clientId=oauth2-proxy --fields id --format csv --noquotes)

kcadm.sh create clients/$CLIENT_UUID/protocol-mappers/models -r internal -f - <<'EOF'
{
  "name": "groups",
  "protocol": "openid-connect",
  "protocolMapper": "oidc-group-membership-mapper",
  "config": {
    "claim.name": "groups",
    "full.path": "false",
    "id.token.claim": "true",
    "access.token.claim": "true",
    "userinfo.token.claim": "true"
  }
}
EOF

Keycloak 26.6부터는 로그인 폼에 passkeys가 조건부 UI로 통합되어 있으므로, internal realm의 인증 플로우에서 WebAuthn Passwordless 정책을 활성화하면 비밀번호 없는 1차 인증을 기본값으로 만들 수 있습니다. Zero Trust에서 피싱 저항성 있는 인증 수단은 사실상 필수입니다 (WebAuthn Level 3, FIDO passkeys 참고).

2단계 — oauth2-proxy 배포

쿠버네티스에 oauth2-proxy를 배포합니다. 전체 매니페스트는 다음과 같습니다.

apiVersion: v1
kind: Secret
metadata:
  name: oauth2-proxy-secrets
  namespace: iap
type: Opaque
stringData:
  client-secret: 'REPLACE_WITH_KEYCLOAK_CLIENT_SECRET'
  # python -c 'import secrets; print(secrets.token_urlsafe(32))'
  cookie-secret: 'REPLACE_WITH_32_BYTE_RANDOM'
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: iap
  labels:
    app: oauth2-proxy
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.8.1
          args:
            - --provider=keycloak-oidc
            - --client-id=oauth2-proxy
            - --oidc-issuer-url=https://keycloak.example.com/realms/internal
            - --code-challenge-method=S256
            - --redirect-url=https://admin.example.com/oauth2/callback
            - --email-domain=example.com
            - --allowed-group=platform-admins
            - --scope=openid email profile groups
            - --cookie-secure=true
            - --cookie-samesite=lax
            - --cookie-refresh=4m
            - --cookie-expire=8h
            - --session-store-type=redis
            - --redis-connection-url=redis://redis.iap.svc.cluster.local:6379
            - --set-xauthrequest=true
            - --set-authorization-header=true
            - --pass-access-token=false
            - --skip-provider-button=true
            - --reverse-proxy=true
            - --http-address=0.0.0.0:4180
            - --metrics-address=0.0.0.0:44180
          env:
            - name: OAUTH2_PROXY_CLIENT_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: client-secret
            - name: OAUTH2_PROXY_COOKIE_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: cookie-secret
          ports:
            - containerPort: 4180
              name: http
            - containerPort: 44180
              name: metrics
          readinessProbe:
            httpGet:
              path: /ping
              port: 4180
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
  name: oauth2-proxy
  namespace: iap
spec:
  selector:
    app: oauth2-proxy
  ports:
    - name: http
      port: 4180
      targetPort: 4180

설정에서 눈여겨볼 부분은 다음과 같습니다.

  • code-challenge-method=S256 — OAuth 2.1 draft가 사실상 의무화한 PKCE를 confidential client에도 적용합니다 (RFC 7636, draft-ietf-oauth-v2-1).
  • allowed-group=platform-admins — Keycloak 그룹 기반의 1차 인가를 프록시에서 수행합니다.
  • cookie-refresh=4m — 4분마다 refresh token으로 세션을 갱신하면서 IdP의 최신 상태를 반영합니다. 계정이 비활성화되면 수 분 내 접근이 차단됩니다.
  • session-store-type=redis — 쿠키에 토큰 전체를 넣지 않고 Redis에 세션을 저장해 쿠키 크기 문제와 토큰 노출을 줄입니다.
  • set-xauthrequest=true — nginx auth_request 응답에 X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Groups 헤더를 실어 업스트림에 전달할 수 있게 합니다.

3단계 — nginx auth_request 패턴 연동

nginx의 auth_request는 "본 요청을 처리하기 전에 서브요청을 보내 인증 여부를 확인"하는 지시어입니다. IAP 패턴의 핵심 접착제입니다.

server {
    listen 443 ssl;
    server_name admin.example.com;

    ssl_certificate     /etc/nginx/tls/tls.crt;
    ssl_certificate_key /etc/nginx/tls/tls.key;

    # 인증 검사 서브요청 — oauth2-proxy의 /oauth2/auth 는
    # 인증되어 있으면 202, 아니면 401을 반환한다
    location = /oauth2/auth {
        internal;
        proxy_pass http://oauth2-proxy.iap.svc.cluster.local:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        # 바디는 전달할 필요 없음
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
    }

    # 로그인 플로우 (리다이렉트, 콜백, 로그아웃)
    location /oauth2/ {
        proxy_pass http://oauth2-proxy.iap.svc.cluster.local:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location / {
        auth_request /oauth2/auth;
        # 401이면 로그인으로 리다이렉트
        error_page 401 = /oauth2/start?rd=$scheme://$host$request_uri;

        # auth 서브요청 응답 헤더에서 신원 정보를 꺼내 업스트림에 주입
        auth_request_set $user   $upstream_http_x_auth_request_user;
        auth_request_set $email  $upstream_http_x_auth_request_email;
        auth_request_set $groups $upstream_http_x_auth_request_groups;
        proxy_set_header X-Forwarded-User   $user;
        proxy_set_header X-Forwarded-Email  $email;
        proxy_set_header X-Forwarded-Groups $groups;

        # 사용자가 위조한 신원 헤더가 통과하지 못하도록
        # 위 proxy_set_header가 항상 덮어쓰는 구조를 유지한다

        proxy_pass http://admin-dashboard.apps.svc.cluster.local:8080;
    }
}

쿠버네티스 ingress-nginx를 쓴다면 같은 패턴을 어노테이션으로 표현할 수 있습니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: admin-dashboard
  namespace: apps
  annotations:
    nginx.ingress.kubernetes.io/auth-url: 'https://admin.example.com/oauth2/auth'
    nginx.ingress.kubernetes.io/auth-signin: 'https://admin.example.com/oauth2/start?rd=$scheme://$host$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: admin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: admin-dashboard
                port:
                  number: 8080
  tls:
    - hosts:
        - admin.example.com
      secretName: admin-dashboard-tls

4단계 — 프록시 우회 차단

IAP는 우회 경로가 하나라도 있으면 무력화됩니다. NetworkPolicy로 업스트림 앱이 ingress 컨트롤러에서 오는 트래픽만 받도록 강제합니다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: admin-dashboard-only-from-ingress
  namespace: apps
spec:
  podSelector:
    matchLabels:
      app: admin-dashboard
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080

여기에 더해, 업스트림 앱은 신원 헤더를 신뢰하기 전에 "이 요청이 정말 프록시에서 왔는가"를 확인해야 합니다. 가장 견고한 방법은 oauth2-proxy의 set-authorization-header로 서명된 ID 토큰(JWT)을 전달받아 앱에서 서명을 검증하는 것입니다. 평문 헤더만 신뢰하는 구조는 내부 네트워크 침해 시 위조 위험이 있습니다.

디바이스 신뢰와 컨텍스트 기반 정책

BeyondCorp 모델의 절반은 디바이스 신뢰입니다. 사용자 인증이 아무리 강해도 악성코드에 감염된 단말이라면 접근을 제한해야 합니다. 실무에서 단계적으로 적용할 수 있는 방법은 다음과 같습니다.

  1. mTLS 클라이언트 인증서 — MDM(Intune, Jamf 등)으로 회사 관리 단말에만 클라이언트 인증서를 배포하고, 프록시에서 TLS 클라이언트 인증을 요구합니다. "관리 단말 여부"를 가장 단순하게 증명하는 방식입니다.
  2. 디바이스 포스처 신호 — EDR/MDM API에서 OS 버전, 디스크 암호화, 화면 잠금 여부를 조회해 정책 엔진에 공급합니다. Cloudflare Access나 Pomerium은 디바이스 포스처 연동을 내장하고 있습니다.
  3. 신뢰 등급(trust tier) — BeyondCorp처럼 디바이스를 fully-trusted, managed, unknown 등급으로 나누고, 애플리케이션 민감도와 매칭합니다. 예를 들어 결제 admin은 fully-trusted 단말에서만, 위키는 managed 이상에서 접근 가능하게 합니다.

nginx 계층에서 mTLS를 요구하는 설정 예시는 다음과 같습니다.

server {
    listen 443 ssl;
    server_name payments-admin.example.com;

    ssl_client_certificate /etc/nginx/tls/device-ca.crt;
    ssl_verify_client on;

    location / {
        auth_request /oauth2/auth;
        # 디바이스 인증서 정보를 업스트림과 감사 로그에 전달
        proxy_set_header X-Device-Cert-DN $ssl_client_s_dn;
        proxy_set_header X-Device-Cert-Verify $ssl_client_verify;
        proxy_pass http://payments-admin.apps.svc.cluster.local:8080;
    }
}

컨텍스트 기반 정책은 "동일한 사용자라도 상황에 따라 다른 결정"을 내리는 것입니다. 대표적인 신호와 정책 예시는 다음과 같습니다.

신호정책 예시
디바이스 등급미관리 단말은 읽기 전용 앱만 허용
위치허용 국가 외 접속 시 step-up 인증(passkey 재인증) 요구
시간업무 시간 외 프로덕션 admin 접근은 승인 워크플로우 필요
인증 강도비밀번호만으로 로그인한 세션은 민감 앱 접근 불가
이상 징후불가능 이동(impossible travel) 감지 시 세션 즉시 폐기

Keycloak에서는 인증 플로우의 조건부 실행기와 step-up authentication(ACR/LoA)으로 인증 강도 정책을 구현할 수 있고, 26.6의 Workflows 기능으로 계정 라이프사이클 자동화(장기 미사용 계정 비활성화 등)를 realm 수준에서 구성할 수 있습니다.

VPN 대체 시나리오

IAP로 VPN을 대체하는 전형적인 시나리오를 정리합니다.

[Before] 모든 직원 --> VPN Gateway --> 사내망 전체 (위키, CI, admin, DB, SSH ...)

[After]
  직원/외주 --> IAP (HTTPS) --> 위키, CI, admin 등 웹 앱        (대부분의 트래픽)
  운영자    --> SSH/DB 전용 솔루션(Boundary, Teleport 등)       (비 HTTP 프로토콜)
  레거시    --> 잔존 VPN (점진적 축소, 접근 대상 서브넷 최소화)

핵심 포인트는 다음과 같습니다.

  • 사내 트래픽의 대부분을 차지하는 웹 애플리케이션부터 IAP로 옮기면 VPN 동시 접속 부하와 헬프데스크 티켓이 급감합니다.
  • SSH, 데이터베이스, RDP 같은 비 HTTP 프로토콜은 IAP의 영역이 아니므로 Teleport, Boundary, Cloudflare Tunnel(TCP) 같은 별도 솔루션으로 같은 identity-first 원칙을 적용합니다.
  • 외주 인력과 협력사에는 VPN 계정 대신 특정 앱에 대한 IAP 접근만 부여합니다. 오프보딩은 IdP에서 계정을 비활성화하는 것으로 끝납니다.

Cloudflare Access vs Google Cloud IAP vs Pomerium vs oauth2-proxy

항목Cloudflare AccessGoogle Cloud IAPPomeriumoauth2-proxy
형태SaaS (엣지 네트워크)GCP 매니지드오픈소스 + 상용오픈소스
배포 위치Cloudflare 엣지GCP LB 통합자체 호스팅자체 호스팅
IdP 연동모든 OIDC/SAMLGoogle + 외부 IdP 연동모든 OIDC모든 OIDC
디바이스 포스처내장 (WARP 클라이언트)내장 (Endpoint Verification)내장 (Pomerium Zero 포함)없음 (직접 조합 필요)
정책 표현력그룹, 국가, 디바이스 등IAM 조건 + Access LevelsRego/표현식 기반 정교한 정책그룹/이메일 수준
비 HTTP 지원TCP/UDP (Tunnel)TCP forwardingTCP 지원없음
잠금 효과Cloudflare 의존GCP 의존낮음낮음
비용사용자당 과금GCP 요금 내 포함OSS 무료 + 상용 옵션무료

선택 가이드는 다음과 같습니다.

  • GCP 중심 인프라라면 Google Cloud IAP가 LB와 IAM에 자연스럽게 통합되어 운영 부담이 가장 적습니다.
  • 멀티 클라우드 + SaaS 엣지 선호라면 Cloudflare Access가 디바이스 포스처와 비 HTTP까지 포함한 가장 완성된 패키지입니다.
  • 자체 호스팅 + 정교한 정책이 필요하면 Pomerium이 oauth2-proxy보다 정책 표현력(컨텍스트 조건, 디바이스 신원)에서 앞섭니다.
  • 단순한 그룹 기반 보호 + 최소 의존성이라면 oauth2-proxy + nginx 조합이 가볍고 검증된 선택입니다.

사례 — 내부 admin 도구 보호

실무에서 가장 흔하고 효과가 큰 적용 대상은 내부 admin 도구입니다. Grafana, ArgoCD, Airflow, 자체 개발 백오피스처럼 "인증이 약하거나 별도 계정 체계를 가진" 도구들이 대상입니다.

전형적인 적용 패턴은 다음과 같습니다.

  1. admin 도구의 자체 로그인 화면을 비활성화하거나 헤더/OIDC 인증 모드로 전환합니다. 예를 들어 Grafana는 auth.proxy 설정으로 X-Forwarded-User 헤더를 신뢰하게 할 수 있습니다.
# grafana.ini — IAP 뒤에서 헤더 기반 인증
[auth.proxy]
enabled = true
header_name = X-Forwarded-User
header_property = username
auto_sign_up = true
sync_ttl = 60
# 프록시 IP만 신뢰 (위조 방지의 마지막 보루)
whitelist = 10.0.0.0/8
  1. IAP에서 도구별 허용 그룹을 분리합니다. ArgoCD는 platform 팀, Airflow는 data 팀 그룹만 통과시키는 식입니다. oauth2-proxy를 도구별로 따로 띄우거나, Pomerium처럼 라우트별 정책을 지원하는 프록시를 사용합니다.
  2. 모든 접근을 구조화 로그로 남깁니다. 누가, 언제, 어떤 디바이스로, 어떤 앱에 접근했는지가 요청 단위로 기록되므로 감사 대응이 수월해집니다.
  3. break-glass 경로를 설계합니다. IdP 장애 시 admin 도구에 접근할 비상 절차(임시 정적 자격 증명 + 강한 감사)를 문서화합니다.

단계적 도입 로드맵

Zero Trust는 빅뱅 전환이 아니라 여정입니다. 현실적인 로드맵은 다음과 같습니다.

Phase 0  현황 파악 (1개월)
         - 사내 앱 인벤토리, 인증 방식, 민감도 분류
         - IdP 정비: 그룹/역할 체계, MFA(가능하면 passkey) 활성화

Phase 1  파일럿 (1~2개월)
         - 저위험 앱 2~3개 (위키, 대시보드)를 IAP 뒤로 이전
         - 로그인 UX, 세션 정책, 장애 모드 검증

Phase 2  확장 (3~6개월)
         - admin 도구 전체 + 신규 앱은 기본 IAP 적용
         - 디바이스 인증서(mTLS) 배포, 신뢰 등급 도입
         - 비 HTTP 접근(SSH/DB)에 Teleport 등 도입

Phase 3  VPN 축소 (6~12개월)
         - VPN 접근 대상을 레거시 시스템으로 한정
         - 컨텍스트 기반 정책(위치, 시간, step-up) 고도화
         - 정책의 코드화(IaC)와 정기 접근 리뷰 자동화

Phase 4  지속 개선
         - 세션 이상 탐지, 위험 기반 재인증
         - 워크로드 identity(SPIFFE)와 사용자 identity 정책 통합

각 단계에서 측정할 지표도 함께 정의하는 것이 좋습니다. VPN 동시 접속 수, IAP 뒤로 이전된 앱 비율, 평균 온보딩/오프보딩 시간, 인증 관련 헬프데스크 티켓 수가 대표적입니다.

2026년 관점 — AI agent 접근 제어까지

2026년의 새로운 과제는 사람이 아닌 주체, 특히 AI agent의 내부 시스템 접근입니다. agent가 사내 위키를 검색하고 admin API를 호출하는 시나리오가 일상화되면서, IAP의 정책 대상이 "사용자 + 디바이스"에서 "사용자 + 디바이스 + agent"로 확장되고 있습니다.

실무적으로 정리하면 다음과 같습니다.

  • agent도 1급 identity여야 합니다. 사람 계정의 API 키를 agent에 복사해 주는 안티패턴 대신, agent 전용 클라이언트를 IdP에 등록하고 짧은 수명의 토큰을 발급합니다. Keycloak 26.6의 OAuth Client ID Metadata Document(CIMD) 실험 지원은 MCP(Model Context Protocol) 기반 agent가 사전 등록 없이도 표준적으로 클라이언트 신원을 제시하는 흐름을 가능하게 합니다.
  • 위임 체인의 보존 — "어떤 사용자를 대신해 어떤 agent가 호출하는가"를 토큰에 담아야 합니다. OAuth Token Exchange(RFC 8693)의 actor 클레임과 delegation 패턴이 표준 도구입니다.
  • agent 전용 정책 — agent에는 사람보다 좁은 스코프, 짧은 토큰 수명, 더 엄격한 rate limit과 감사를 적용합니다. IAP 계층에서 agent 트래픽을 식별(클라이언트 신원 기반)하고 별도 정책 번들로 평가하는 구조가 자리 잡고 있습니다.
  • 비 HTTP 도구 호출 — MCP 서버처럼 agent가 사용하는 도구 엔드포인트 역시 IAP 뒤에 두고, agent의 OAuth 토큰을 검증하는 패턴이 권장됩니다. Keycloak이 MCP authorization server 역할을 할 수 있게 된 것도 이 맥락입니다.

BeyondCorp가 "위치에서 신원으로"의 전환이었다면, 지금은 "사람의 신원에서 모든 주체(human + non-human)의 신원으로"의 전환이 진행 중입니다. IAP를 도입할 때 정책 모델을 사람 전용으로 좁게 설계하지 말고, 주체(subject) 추상화를 넓게 잡아 두는 것이 2026년의 설계 포인트입니다.

운영 베스트 프랙티스와 안티패턴

마지막으로 운영 관점의 체크리스트를 정리합니다.

베스트 프랙티스

  • 세션 쿠키는 Secure + HttpOnly + SameSite=Lax 이상, cookie-refresh로 IdP 상태를 주기 반영합니다.
  • IdP, IAP, 업스트림의 시계가 어긋나면 토큰 검증이 깨집니다. NTP 동기화를 모니터링합니다.
  • oauth2-proxy의 메트릭(44180 포트)을 Prometheus로 수집해 인증 실패율, 세션 갱신 실패율에 알람을 겁니다.
  • IdP 장애가 곧 전사 앱 장애가 되므로, Keycloak은 다중 인스턴스 + 26.6의 zero-downtime rolling patch update로 무중단 패치를 운영합니다.
  • 정책 변경은 코드 리뷰를 거치는 IaC로 관리하고, "누가 어떤 앱에 접근 가능한가"를 주기적으로 리뷰합니다.

안티패턴

  • 신원 헤더를 서명 검증 없이 신뢰하면서 NetworkPolicy도 없는 구성 — 헤더 위조 한 방에 뚫립니다.
  • IAP 도입 후에도 앱 자체 로그인 경로를 남겨 두는 것 — 우회 경로는 반드시 제거합니다.
  • 세션 만료를 지나치게 길게(수 주) 잡는 것 — Zero Trust의 "지속적 검증"과 모순됩니다.
  • 모든 앱에 동일 정책을 적용하는 것 — 민감도 기반 차등 정책이 Zero Trust의 본질입니다.
  • VPN을 하루아침에 끄는 빅뱅 전환 — 레거시와 비 HTTP 트래픽 대책 없이 강행하면 업무가 멈춥니다.

마치며

Identity-Aware Proxy는 Zero Trust를 가장 빠르게 체감할 수 있는 구현체입니다. oauth2-proxy + Keycloak + nginx auth_request라는 검증된 오픈소스 조합만으로도 BeyondCorp의 핵심인 "모든 요청에 인증과 인가"를 오늘 바로 시작할 수 있습니다. 중요한 것은 도구가 아니라 원칙입니다. 위치가 아닌 신원, 세션이 아닌 요청, 정적 규칙이 아닌 컨텍스트. 그리고 2026년에는 그 신원의 범위가 AI agent까지 넓어졌다는 점을 설계 초기부터 반영하시기 바랍니다.

다음 글에서는 IAP 뒤편, 즉 API Gateway와 서비스 메시 계층에서의 토큰 검증(Istio, Envoy)을 다룹니다.

참고 자료