Network/Zero Trust

[Zero Trust - Policy Engine] PDP · PEP · PAP · PIP: 접근 결정의 분리

러러 2026. 4. 8. 20:11

새벽 2시, 보안팀의 슬랙 채널에 경보가 울렸다. 시니어 엔지니어 계정의 자격증명이 피싱 공격으로 탈취됐다는 신고가 들어왔다. 해당 계정은 프로덕션 DB, 내부 API, CI/CD 파이프라인, 로그 시스템에 접근 권한이 있었다.

 

대응팀이 해야 할 일은 단순했다. 해당 계정의 접근을 지금 당장 막는 것. 하지만 생각만큼 단순하지 않았다.

 

그 회사의 접근 제어 로직은 각 서비스에 분산되어 있었다. API 서버는 JWT 클레임을 파싱해서 if claims["role"] == "engineer" 조건을 검사했다. 내부 대시보드는 LDAP 그룹 멤버십을 직접 조회했다. CI/CD 시스템은 자체 권한 테이블을 가지고 있었다. 로그 집계 서비스는 IP 기반 allowlist를 쓰고 있었다. 공통 정책 레이어 같은 것은 없었다. 각 팀이 각자의 방식으로 "접근 제어를 구현"했다.

 

탈취된 계정의 세션을 무효화하고, LDAP 그룹에서 제거하고, 각 서비스의 세션 스토어와 토큰 블랙리스트를 업데이트하는 데 40분이 걸렸다. 그 40분 동안 공격자가 무엇을 했는지는 나중에 로그를 수집해서 파악해야 했다. 즉각적인 가시성도 없었다.

 

문제는 그 엔지니어 계정이 취약했다는 게 아니었다. 문제는 접근 결정이 어디에 있는지조차 파악하는 데 시간이 걸렸다는 것이었다.


정책이 코드 안에 있으면 생기는 일

보안 사고가 터진 회사의 코드베이스를 상상해보자. 수십 개의 서비스가 있고, 각각의 서비스에는 접근 제어 로직이 다른 형태로 들어있다.

service-A:  if user.groups.include?("engineering") && resource.env == "prod"
service-B:  acl.check(user_id, "read", resource_id)
service-C:  ldap.member_of?(user.dn, "cn=engineers,dc=company,dc=com")
service-D:  ip_allowlist.include?(request.remote_ip)

 

네 서비스가 각자 다른 언어, 다른 방식으로 "엔지니어는 프로덕션에 접근할 수 있다"는 정책을 구현하고 있다. 이 상태에서 "senior engineer만 접근 가능하다라는 정책 변경을 해야 한다면, 네 서비스를 모두 수정하고 배포해야 한다. 모두 동시에 배포하지 않으면 잠시 동안 불일치 상태가 생긴다.

 

정책 변경의 속도는 코드 배포의 속도에 종속된다. 보안 사고는 새벽 2시에도 발생하고, 정책 변경이 배포를 요구한다면 대응 속도는 코드 리뷰 → CI → 배포 파이프라인의 속도를 넘을 수 없다.

 

더 근본적인 문제가 있다. 이 분산된 코드들이 "같은 정책을 구현하고 있는가"를 누가 어떻게 보장하는가. service-A가 senior engineer를 체크하도록 업데이트됐는데 service-C가 빠졌다면, 두 서비스 사이의 정책 일관성은 깨져있다. 그러나 이 사실을 즉시 알 수 있는 메커니즘은 없다. 감사 로그를 남긴다고 해도, 어떤 정책 버전이 어느 시점에 각 서비스에 적용됐는지를 추적하는 것은 사실상 불가능에 가깝다.

 

이 문제들은 관리의 문제가 아니다. 아키텍처의 문제다. 정책과 집행이 한 곳에 결합돼있는 구조 자체가 만들어내는 불가피한 결과다.


XACML이 제시한 답: 역할을 분리한다

이 문제를 구조적으로 해결하려는 시도는 2000년대 초반에 시작됐다.

OASIS(Organization for the Advancement of Structured Information Standards) 기술 위원회가 2003년 XACML(eXtensible Access Control Markup Language) 1.0을 발표하고, 2005년 2.0, 2013년 3.0을 발표하면서 접근 제어 아키텍처에 대한 표준 프레임워크를 제시했다.

 

XACML이 제안한 핵심은 하나의 직관에서 시작한다. 접근 제어에는 서로 다른 생명주기를 가진 네 가지 역할이 있고, 이것들은 분리돼야 한다.

  • 정책을 작성하고 관리하는 것 → PAP (Policy Administration Point)
  • 정책을 평가해 결정을 내리는 것 → PDP (Policy Decision Point)
  • 결정에 필요한 속성 데이터를 제공하는 것 → PIP (Policy Information Point)
  • 결정을 집행하는 것 → PEP (Policy Enforcement Point)

코드 안에 모든 것이 섞여있던 이전 구조에서는 이 네 가지가 구분 없이 하나의 if-else 블록 안에 존재했다. XACML은 이것들을 분리하고, 각각의 책임과 인터페이스를 정의했다.

 

이 분리가 만들어내는 가장 중요한 효과는 하나다. 정책의 생명주기와 애플리케이션의 생명주기가 분리된다. 새벽 2시 대응팀이 해야 할 일은 PAP에서 정책을 변경하고, PDP에 반영하는 것뿐이다.


PEP: 결정하지 않고 집행한다

Policy Enforcement Point(정책 집행점, PEP)의 역할은 단순하고 명확하다. 접근 요청이 리소스에 도달하기 전에 가로채고, PDP에 질의하고, 받은 결정을 집행하는 것. PEP는 스스로 판단하지 않는다.

 

PEP가 위치하는 곳은 트래픽 흐름상 리소스 직전이어야 한다. 실제 구현에서 PEP는 여러 형태를 취한다.

  • API 게이트웨이: 모든 외부 API 요청이 통과하는 진입점. Kong, Envoy, AWS API Gateway가 PEP 역할을 할 수 있다.
  • 리버스 프록시: 내부 서비스 앞에 위치해 HTTP 요청을 가로채는 프록시.
  • 서비스 메시 사이드카: Istio 같은 서비스 메시에서 각 Pod 옆에 붙는 Envoy 사이드카가 PEP 역할을 한다.
  • 애플리케이션 미들웨어: 서비스 내부의 인터셉터 또는 미들웨어. 단, 이 경우 PEP 로직이 다시 코드에 섞이는 위험이 있다.

PEP의 신뢰 모델은 이렇다. PEP는 PDP의 결정을 신뢰한다. PEP가 "왜 허용됐는가"를 직접 분석하지 않는다. PDP가 Permit을 반환하면 허용하고, Deny를 반환하면 차단한다. 이 단순함이 PEP를 교체하거나 추가하기 쉽게 만든다. 새로운 서비스가 배포될 때 PEP만 붙이면 기존 정책이 즉시 적용된다. 새로운 정책 로직을 서비스에 추가할 필요가 없다.

 

그런데 PEP에는 단순한 "허용/차단" 외에 중요한 책임이 하나 더 있다. Obligations(의무) 처리다. 이것은 이후 섹션에서 별도로 다룬다.


PDP: 어떻게 결정에 도달하는가

Policy Decision Point(정책 결정점, PDP)는 시스템 전체에서 접근 여부를 판단하는 두뇌다. PEP로부터 Authorization Request Context(인가 요청 컨텍스트)를 받아 활성화된 정책들을 평가하고, 결정과 함께 Obligations/Advice를 반환한다.

Obligations = “반드시 실행해야 하는 후속 작업”
Advice = “선택적으로 참고할 수 있는 힌트”

요청 컨텍스트의 구조

PDP가 받는 요청은 단순한 "user X가 resource Y에 접근하려 한다"가 아니다. XACML 3.0 스펙은 요청 컨텍스트를 네 범주의 속성 집합으로 정의한다.

  • Subject 속성: 누가 요청하는가. 사용자 ID, 역할, 부서, 트러스트 스코어, 인증 수준(AAL) 등.
  • Resource 속성: 무엇에 접근하는가. 리소스 식별자, 분류 등급, 소유자, 민감도 레이블 등.
  • Action 속성: 어떤 행동을 하려는가. HTTP 메서드, 데이터베이스 쿼리 유형, 파일 시스템 작업 등.
  • Environment 속성: 어떤 맥락에서 요청하는가. 요청 시각, 네트워크 위치, 지리적 위치, Device Posture 상태 등.

이 네 범주가 ABAC(Attribute-Based Access Control)의 네 속성 차원과 정확히 대응한다. 초기 포스팅에서 다룬 ABAC 모델이 XACML에서 구체적인 요청 구조로 실체화된 것이다.

결정 값: 네 가지

PDP의 응답은 단순한 boolean이 아니다. XACML 3.0은 네 가지 결정 값을 정의한다.[1]

결정 의미
Permit 적용 가능한 정책이 있고, 그 결과가 허용
Deny 적용 가능한 정책이 있고, 그 결과가 거부
Indeterminate 정책 평가 중 오류 발생 (PIP 응답 실패, 속성 파싱 오류 등)
NotApplicable 이 요청에 적용 가능한 정책이 없음

 

여기서 중요한 것은 Indeterminate와 NotApplicable의 차이다. Indeterminate는 "판단을 시도했지만 실패했다"는 의미고, NotApplicable은 "이 요청을 다루는 정책이 존재하지 않는다"는 의미다. 두 경우를 기본 결정(default decision)을 permit으로 할지 deny로 할지는 아키텍처 수준에서 명시적으로 결정해야 한다. 이 설계 선택을 명시하지 않으면 보안 홀이 생긴다. NotApplicable을 Permit으로 처리하면, 정책을 정의하지 않은 새로운 리소스가 배포될 때 기본적으로 열려있게 된다.

정책 구조: PolicySet → Policy → Rule

PDP가 평가하는 정책은 계층 구조를 가진다.

각 Rule의 핵심 구성 요소는 세 가지다.

  • Target: 이 규칙이 적용되는 요청의 범위를 정의한다. Subject/Resource/Action/Environment 속성의 조합으로 매칭 조건을 표현한다. 선택적 요소이며, 생략 시 모든 요청에 매칭된다.
  • Condition: Target을 통과한 요청에 대해 추가적으로 평가하는 불리언 표현식. Target보다 복잡한 속성 조합이나 함수 호출을 포함할 수 있다. 선택적 요소다.
  • Effect: Permit 또는 Deny. 이 규칙이 매칭됐을 때의 결과. Rule의 유일한 필수 요소다.

여기에 Obligations와 Advice도 Rule 수준에서 붙을 수 있다.

 

Rule이 여러 개 있을 때 최종 결과는 어떻게 결정되는가. 이것이 정책 결합 알고리즘(Policy Combining Algorithm)의 영역이다.


정책 결합 알고리즘: 충돌을 어떻게 해소하는가

"user.role == admin"인 규칙과 "env.time이 업무 시간 외"인 경우 거부 규칙이 동시에 존재할 때, 새벽 3시에 접속하는 admin은 어떻게 처리되는가. 두 규칙이 충돌할 때 어떤 규칙이 이기는가.

 

이것을 결정하는 것이 Policy Combining Algorithm이다.

XACML 3.0은 [deny-overrides, ordered-deny-overrides, permit-overrides, ordered-permit-overrides, first-applicable, only-one-applicable, deny-unless-permit, permit-unless-deny] 총 여덟 가지 표준 결합 알고리즘을 정의한다.

 

이 중 보안 자세를 가장 직접적으로 결정하는 네 가지 계열을 살펴보자.

deny-overrides

어느 하나의 Rule이라도 Deny를 반환하면 최종 결과는 Deny다. Deny를 반환한 규칙이 없고 적어도 하나의 규칙이 Permit을 반환했을 때만 전체 결과가 Permit이 된다. 가장 보수적인 알고리즘이다.

 

새벽 3시 admin 예시에서: "업무 시간 외" 규칙이 Deny를 반환하면, "admin은 허용" 규칙이 Permit을 반환하더라도 최종 결과는 Deny다.

 

이 알고리즘의 장점은 명확하다. 어떤 규칙도 "거부 정책을 무력화하는 허용 정책"을 만들 수 없다. 단점은, Deny 규칙이 너무 광범위하면 정당한 접근까지 차단된다는 것이다.

permit-overrides

어느 하나의 Rule이라도 Permit을 반환하면 최종 결과는 Permit이다. deny-overrides와 우선순위 방향이 반대로 동작한다.

이 알고리즘을 쓰면 위험해지는 패턴이 있다. 모든 사용자에게 특정 리소스에 대한 기본 Permit 규칙이 있고, 특정 조건에서 Deny하는 규칙이 있을 때, Deny 규칙은 Permit 규칙에 의해 무력화된다. "예외 차단" 패턴이 제대로 동작하지 않는다.

first-applicable

매칭되는 첫 번째 Rule의 결과를 사용한다. 순서가 중요해진다. 규칙의 순서를 관리해야 하므로 대규모 정책 집합에서 유지보수가 어렵다.

ordered- 변형과 only-one-applicable: 여덟 가지 알고리즘 중 ordered-deny-overrides와 ordered-permit-overrides는 각각 deny-overrides, permit-overrides의 변형이다. 핵심 차이는 Indeterminate 결과를 처리할 때 규칙 평가 순서가 결과에 영향을 미친다는 점이다. only-one-applicable은 PolicySet 수준에서만 사용하며, 적용 가능한 Policy가 정확히 하나여야만 결정을 내리고 그 외에는 Indeterminate를 반환한다. 여러 Policy가 의도치 않게 동시에 적용되는 것을 방지할 때 유용하다.

deny-unless-permit / permit-unless-deny

XACML 3.0에서 추가된 알고리즘이다. deny-unless-permit은 명시적으로 Permit하는 규칙이 없으면 기본적으로 Deny한다. 이것은 "명시적 허용 목록" 모델을 구현하는 가장 적합한 알고리즘이다. 새로운 리소스나 새로운 사용자 유형이 등장했을 때, 정책이 정의되지 않으면 자동으로 거부된다.

 

Zero Trust 원칙의 "Never Trust, Always Verify"와 가장 잘 정렬되는 알고리즘이 deny-unless-permit이다. 기본 상태가 거부(deny by default)이기 때문이다.

알고리즘 선택은 단순한 구현 세부사항이 아니다. 이 선택이 시스템의 기본 보안 자세(default security posture)를 결정한다. 레거시 시스템과의 호환성 때문에 permit-overrides나 permit-unless-deny를 선택하고, 나중에 "왜 이 리소스가 열려있는지 모르겠다"는 상황을 만난다.


PAP: 정책의 작성과 배포는 다르다

Policy Administration Point(정책 관리점, PAP)는 보안 정책이 작성되고, 검토되고, 버전 관리되고, 배포되는 곳이다.

 

PAP가 PDP와 분리되는 이유는 두 컴포넌트의 요구사항이 근본적으로 다르기 때문이다.

 

PDP의 요구사항: 빠른 평가, 불변성(immutable deployed policy), 높은 가용성. PDP가 응답하는 동안 정책이 변경되면 안 된다. PDP는 특정 시점의 정책 스냅샷을 사용해 결정을 내린다.

 

PAP의 요구사항: 편집 가능성, 버전 관리, 협업, 검증. 보안팀이 정책을 드래프트하고, 검토하고, 테스트하고, 승인하는 워크플로우가 필요하다.

 

PAP는 다음 기능들을 포함한다.

 

정책 편집과 버전 관리: 정책은 코드처럼 관리돼야 한다. 누가 언제 어떤 정책을 변경했는지 이력이 남아야 하고, 이전 버전으로 롤백할 수 있어야 한다. "3월 15일에 적용된 정책"이 무엇이었는지 감사 시 재현할 수 있어야 한다.

 

정책 테스트: 정책을 프로덕션에 배포하기 전에 시뮬레이션할 수 있어야 한다. "이 정책이 적용됐다면 지난달 접근 요청들은 어떻게 처리됐을까"를 실제 요청 로그를 사용해 dry-run할 수 있는 기능이다.

 

배포 파이프라인: 검증된 정책을 PDP에 전달하는 메커니즘. 정책을 번들(bundle)로 패키징해 PDP에 push하거나, PDP가 PAP에서 pull하는 방식이 있다. 이 시점에 PDP는 새 정책 버전을 로드하고, 이후 요청부터는 새 정책을 적용한다.


PIP: 정책 결정에는 데이터가 필요하다

"이 사용자가 finance-team 소속이면 허용한다"는 정책이 있다. PDP가 이 정책을 평가하려면 요청을 보낸 사용자의 그룹 속성이 필요하다. 그 정보는 어디서 오는가.

 

Policy Information Point(정책 정보점, PIP)는 PDP가 정책 평가에 필요한 속성 데이터를 조회하는 모든 외부 소스를 통칭한다. PIP는 단일 시스템이 아니다. 여러 데이터 소스의 논리적 집합이다.

각 속성 소스는 독립적인 시스템이고, 각자의 가용성과 레이턴시를 가진다. 이것이 PIP의 핵심 설계 과제다.

속성 조회 타이밍: eager vs. lazy

PDP가 요청을 받았을 때 어떤 속성을 언제 조회하는가에 대한 두 가지 전략이 있다.

  • Eager (선제 조회): 요청이 들어오면 결정에 필요할 것으로 예상되는 모든 속성을 먼저 수집한다. 병렬로 조회하면 총 소요 시간을 줄일 수 있지만, 결국 사용하지 않을 속성까지 조회하는 낭비가 생긴다.

  • Lazy (필요 시 조회): 정책 평가 중 실제로 속성이 필요해지는 시점에 조회한다. 불필요한 조회를 줄이지만, 조회가 순차적으로 발생하면 레이턴시가 누적될 수 있다.

실제 자주 사용되는 속성(사용자 역할, 기기 상태 등)을 PDP 또는 PEP 레벨에서 캐싱하고, 덜 자주 사용되는 속성만 요청 시 조회하는 하이브리드 전략이 일반적이다.

PIP 장애 시 PDP의 동작

PIP 소스 중 하나가 응답하지 않으면 어떻게 되는가. 이것은 반드시 설계해야 하는 시나리오다.

 

MDM 서버가 일시적으로 다운됐다. PDP는 device.compliant 속성을 조회할 수 없다. 이 상태에서 접근 요청이 들어왔다.

세 가지 선택지가 있다.

  1. Indeterminate 반환: 정보 부족으로 결정을 내릴 수 없다. PEP가 이것을 받으면 기본적으로 Deny 처리한다. 가장 안전하지만, MDM이 다운되면 모든 기기 기반 접근이 차단된다.
  2. 캐시된 값 사용: 마지막으로 조회된 기기 상태를 TTL 기간 내에서 사용한다. 서비스 연속성은 보장되지만, 캐시가 만료됐거나 기기 상태가 실제로 변경됐을 때 잘못된 결정을 내릴 수 있다.
  3. 속성 없이 평가: device.compliant를 요구하는 정책을 건너뛰고, 해당 속성을 요구하지 않는 정책들로만 평가한다. NotApplicable이 될 수도 있다.

어떤 PIP 소스가 얼마나 중요한가에 따라 장애 대응 전략이 달라진다. 기기 상태는 보안에 직결되므로 Indeterminate → Deny를 선택하는 경향이 있고, 비교적 덜 중요한 속성은 캐시 fallback을 허용할 수 있다. 이 선택을 정책 문서로 명시하지 않으면, 구현 단계에서 개발자 재량으로 결정된다.


Obligations와 Advice: 허용은 조건부일 수 있다

PDP의 응답이 Permit이라고 해서 "무조건적인 허용"을 의미하지 않는다. PDP는 결정과 함께 Obligations(의무) 또는 Advice(권고)를 반환할 수 있다.

  • Obligations: PEP가 반드시 이행해야 하는 조건이다. PEP가 Obligation을 이행할 수 없으면, PEP는 접근을 허용하지 말아야 한다. Obligation은 결정의 전제 조건이다.
  • Advice: PEP가 고려할 수 있지만 반드시 이행할 필요는 없는 권고다. Advice를 이행할 수 없어도 접근은 허용된다.

이 구분은 중요하다. "이 접근은 허용하되, 감사 로그를 반드시 남겨야 한다"는 Obligation이다. PEP가 로그 시스템에 쓰기를 실패하면, 감사 추적이 없는 접근은 보안 제어의 의미를 소급해서 없애버리기 때문에 접근 자체를 허용하지 말아야 한다. 반면 "이 접근 후 사용자에게 보안 정책 확인을 권고하라"는 Advice다. 알림을 보낼 수 없어도 접근에는 영향이 없다.

Obligations를 활용하는 대표적인 패턴들이 있다.

  • 감사 로그 Obligation: "이 리소스에 대한 모든 접근을 감사 로그에 기록하라." PEP가 감사 시스템에 기록하지 않으면 접근을 막아야 한다. 컴플라이언스 환경에서는 이 패턴이 필수다.

  • Step-up 인증 Obligation: "허용하되, 현재 세션의 인증 수준이 충분하지 않으면 추가 인증을 요구하라." Permit을 반환하지만 PEP는 추가 MFA를 완료하기 전까지 리소스에 접근시키지 않는다. 앞선 포스트에서 다룬 Step-up 인증의 아키텍처적 구현이 바로 이것이다.

  • 다운로드 제한 Obligation: "접근은 허용하되, 파일 다운로드는 제한하라." HTTP 응답에서 특정 헤더를 추가하거나, 응답 본문의 민감 데이터를 마스킹하는 DLP(Data Loss Prevention) 처리를 PEP가 수행한다.

  • TTL 기반 접근 제한 Obligation: "허용하되, 이 세션은 4시간 후 재인증을 요구하라." PEP가 세션 타이머를 설정하고 만료 시 강제 로그아웃한다.

요청 처리 전체 흐름

네 컴포넌트가 실제로 어떻게 협력하는지 전체 흐름을 따라가보자.


BeyondCorp: 같은 원리의 현대적 구현

XACML은 XML 기반 표준으로, 구현 복잡도와 XML 파싱 오버헤드에 대한 비판을 받았다. Google이 2014년부터 발표한 BeyondCorp 논문 시리즈는 같은 원리를 경량화된 형태로 구현했다.

 

BeyondCorp 아키텍처에서 각 XACML 컴포넌트는 구체적인 구현체로 매핑된다.

XACML 컴포넌트 BeyondCorp 구현 역할
PEP Access Proxy HTTP 리버스 프록시. 모든 내부 앱 앞에 위치. TLS 종료, 사용자 어설션 헤더 삽입
PDP Access Control Engine 중앙 인가 서비스. Device Inventory + User DB + 접근 정책 조합으로 결정
PAP Policy Database + Admin UI 앱 오너가 자신의 앱에 대한 접근 정책을 정의하고 관리하는 시스템
PIP Device Inventory DB / User/Group DB / Trust Engine 기기 상태, 사용자 속성, 신뢰 점수 등 속성 데이터 소스

 

BeyondCorp에서 주목할 구현 결정이 있다.

 

Access Proxy(PEP)는 인가 결정을 직접 내리지 않는다. Access Control Engine(PDP)에 질의한다. 이 분리 덕분에 Google은 내부적으로 여러 종류의 Access Proxy를 운영하면서 모두 같은 PDP를 사용할 수 있었다. 새로운 유형의 PEP가 추가되더라도 정책 로직은 변경할 필요가 없다.

 

또한 BeyondCorp의 핵심 설계 원칙 중 하나가 "내부망을 신뢰하지 않는다"는 것이다. 초기 포스팅에서 다룬 Castle-and-Moat 모델의 탈피가 아키텍처적으로 구현된 방식이 바로 이것이다. Google 직원이 사무실 내부 네트워크에 있더라도, 내부 앱에 접근하려면 반드시 Access Proxy를 거쳐야 한다. 네트워크 위치는 신뢰의 근거가 되지 않는다. 신원(Identity)과 기기 상태(Device Posture)만이 기준이다.


NIST SP 800-207과의 매핑

NIST의 Zero Trust Architecture 가이드라인 SP 800-207(2020년 8월 발행)은 이 아키텍처를 세 논리 컴포넌트로 표현한다.

NIST SP 800-207 XACML 대응 역할
Policy Engine (PE) PDP (+ PAP 일부) 접근 결정 수행. 신뢰 알고리즘(trust algorithm) 적용
Policy Administrator (PA) PE → PEP 명령 전달 레이어 PE의 결정을 받아 PEP에 집행 명령을 전달하고 세션 토큰/자격증명 발급
Policy Enforcement Point (PEP) PEP 접근 요청을 수신해 집행하는 지점

 

NIST가 PAP와 PIP를 독립 컴포넌트로 명시하지 않은 이유는, 이 표준이 아키텍처 패턴을 추상적으로 기술하기 때문이다. PAP에 해당하는 기능은 PE의 지원 인프라로, PIP에 해당하는 기능은 "CDM(Continuous Diagnostics and Mitigation) 시스템" 및 "Industry Compliance System"과 같은 데이터 소스로 별도 분류돼있다.

 

NIST SP 800-207은 PE가 사용하는 "신뢰 알고리즘(Trust Algorithm)"의 입력 소스도 명시한다: CDM 시스템, 산업 컴플라이언스 데이터, 위협 인텔리전스, 네트워크·트래픽 로그, 데이터 접근 정책, PKI. 이 모두가 XACML 용어로는 PIP 소스에 해당한다.


PDP 배포 패턴: 중앙집중 vs. 분산

PDP를 어디에 어떻게 배포하는가는 레이턴시와 가용성에 직접적인 영향을 미친다. 실무에서 선택 가능한 주요 패턴 세 가지가 있다.

  • 중앙 PDP 서비스: 모든 PEP가 단일 PDP 클러스터에 네트워크 호출을 한다. 정책 변경이 즉시 모든 PEP에 반영된다는 장점이 있다. 단점은 모든 인가 결정에 네트워크 레이턴시가 추가된다는 것이다. 또한 PDP가 단일 장애 지점(Single Point of Failure)이 될 수 있으므로 고가용성 설계가 필수다.

  • 사이드카 PDP: 각 서비스 옆에 로컬 PDP 인스턴스를 배치한다. 로컬 호출이므로 레이턴시가 대폭 줄어든다. 단, 중앙 PAP에서 정책을 주기적으로 동기화하므로, 정책 변경이 모든 사이드카에 전파되는 데 시간이 걸린다. 이 전파 지연(propagation delay) 동안 정책 불일치가 존재할 수 있다. OPA를 사이드카로 배포하는 것이 이 패턴의 대표적인 구현이다.

  • 임베디드 PDP: PDP 로직을 라이브러리 형태로 각 서비스에 포함한다. 로컬 함수 호출이므로 레이턴시가 거의 없다. 단점은 명확하다. 서비스마다 PDP 라이브러리 버전이 다를 수 있고, 정책 업데이트가 라이브러리 배포와 엮인다. "정책과 코드의 분리"라는 원칙에서 가장 멀어지는 방향이다.

세 패턴 중 어느 것이 옳다는 답은 없다. 서비스의 레이턴시 요구사항, 정책 변경의 긴급성, 운영 복잡도 감수 능력에 따라 선택이 달라진다. 많은 조직이 중요도가 높은 리소스(금융 거래, 의료 데이터)에는 중앙 PDP를 사용하고, 레이턴시 요구사항이 엄격한 서비스에는 사이드카 패턴을 병행한다.


설계 시 고려사항

 

PDP 레이턴시가 핫 패스에 추가된다

인가 결정이 모든 API 요청에 추가되면, PDP 응답 시간이 전체 서비스 레이턴시에 직접 더해진다. P50 레이턴시 5ms인 서비스에 PDP 네트워크 왕복 20ms가 추가되면 전체 25ms로 5배 증가된다. 단순 캐싱으로 완화할 수 있는 경우도 있지만, 캐시는 항상 신선도(freshness) 문제를 수반한다.

 

설계 접근: 변경이 드문 속성(역할, 부서)은 PEP에서 세션 수준으로 캐싱하고, 자주 변하는 속성(Trust Score, Device Posture)은 TTL을 짧게 유지한다. 또한 PDP 응답 자체를 캐싱할 때는 캐시 키를 요청 컨텍스트의 해시로 구성해 컨텍스트가 달라지면 반드시 새로 평가한다.

NotApplicable을 암묵적으로 Permit 처리한다

새로운 마이크로서비스가 배포됐다. 아직 그 서비스를 대상으로 하는 정책이 PAP에 없다. PDP는 NotApplicable을 반환한다. PEP가 NotApplicable을 "정책이 없으니 허용"으로 처리하면, 보안 정책이 정의되지 않은 모든 신규 서비스는 기본적으로 누구나 접근 가능한 상태가 된다.

 

설계 접근: PEP의 기본 처리 규칙을 명시적으로 정의한다. NotApplicable → Deny가 Zero Trust 원칙에 부합한다. 신규 서비스 배포 시 먼저 정책을 PAP에 등록하고, 이후 PEP를 활성화하는 순서로 진행한다.

Obligations Creep

처음에는 단순했던 Obligation 목록이 시간이 지나면서 계속 늘어난다. PEP가 이행해야 하는 Obligation이 10개가 넘어가면, PEP 구현의 복잡도가 폭발한다. 특히 PEP 종류가 여러 가지(API 게이트웨이, 서비스 메시 사이드카, 레거시 미들웨어)인 환경에서, 모든 PEP가 동일한 Obligation을 지원하는지 보장하기 어렵다.

 

설계 접근: Obligation을 최소화하고, 가능한 경우 Advice로 대체한다. "이것이 없으면 접근 허용이 보안상 불가능한가"를 기준으로 Obligation vs. Advice를 구분한다. 예를 들면 감사 로그는 Obligation (감사 없는 접근은 컴플라이언스 위반이기 때문), 사용자 알림은 Advice (알림 실패가 접근 허용을 막을 이유가 없기 때문).

정책 결합 알고리즘 충돌

다른 팀이 작성한 두 Policy가 동일한 리소스에 적용될 때, 두 Policy의 Rule들이 충돌할 수 있다. 한 Policy는 "finance 팀은 허용", 다른 Policy는 "야간에는 거부". 어떤 결합 알고리즘이 적용됐는지에 따라 결과가 달라진다. 정책이 수십, 수백 개로 쌓이면 "왜 이 요청이 거부됐는가"를 역추적하는 것이 어렵다.

 

설계 접근: PAP에 정책 시뮬레이션 기능을 구축한다. 특정 요청 컨텍스트를 입력했을 때 어떤 Policy, 어떤 Rule이 매칭되고 어떻게 결합 알고리즘이 적용됐는지 trace 출력이 가능해야 한다. OPA의 경우 opa eval --explain 옵션이 이 목적으로 사용된다.


접근 결정을 코드 바깥으로 꺼냈을 때 얻는 것

처음의 사고 시나리오로 돌아가보자. 새벽 2시, 계정이 침해됐다. 이 아키텍처가 구축돼 있다면 대응은 어떻게 달라지는가.

 

PAP에서 해당 계정에 대한 Deny 정책을 즉시 추가한다. PDP에 정책이 전파된다 (중앙 PDP 모델에서는 즉시, 사이드카 모델에서는 수 초 내). 이후 해당 계정의 모든 접근 요청이 어떤 서비스에 대한 것이든, PEP가 어디에 위치하든 Deny를 받는다. 서비스 코드를 수정할 필요가 없다. 배포도 필요 없다. "정책 변경이 즉시 집행됐다"는 것을 PAP의 감사 로그로 확인할 수 있다.

 

40분이 걸리던 작업이 수 초로 줄어든다. 더 중요한 것은, 정책이 전파되기까지의 짧은 시간 동안 어떤 접근이 발생했는지를 중앙화된 PDP 감사 로그로 즉시 확인할 수 있다.

 

이것이 PDP · PEP · PAP · PIP 분리가 단순한 아키텍처 우아함이 아닌 이유다. 보안 대응 속도 자체가 이 구조에 의존한다.


참고 문헌

[1] OASIS, eXtensible Access Control Markup Language (XACML) Version 3.0, OASIS Standard, January 2013. https://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html

[2] R. Ward and B. Beyer, "BeyondCorp: A New Approach to Enterprise Security," USENIX ;login:, vol. 39, no. 6, December 2014. https://www.usenix.org/publications/login/dec14

[3] B. Beyer et al., "BeyondCorp: The Access Proxy," USENIX ;login:, 2016. https://www.usenix.org/publications/login/dec16

[4] S. Rose et al., Zero Trust Architecture, NIST Special Publication 800-207, National Institute of Standards and Technology, August 2020. https://doi.org/10.6028/NIST.SP.800-207