본문 바로가기

Network/Zero Trust

[Zero Trust - Policy Engine] OPA와 Cedar: 두 정책 언어가 같은 문제를 다르게 정의한 이유

 

외부 감사가 시작됐다. 감사팀은 간단한 질문을 던졌다.

"현재 프로덕션 데이터베이스에 접근 가능한 사람 전체 목록을 주세요."

 

담당 엔지니어는 그 질문에 즉시 답할 수 없었다. 프로덕션 DB 접근 권한은 세 가지 경로로 나뉘어 있었다.

  • 첫째는 사용자 서비스의 JWT 클레임에서 db_admin 역할을 확인하는 로직
  • 둘째는 내부 배포 도구가 LDAP 그룹 멤버십을 직접 조회하는 방식
  • 셋째는 데이터 분석 플랫폼이 자체 권한 테이블을 관리하는 구조

세 군데를 각각 쿼리하고, 중복을 걸러내고, 이미 퇴사한 계정이 혹시 남아있지는 않은지 수동으로 확인하는 데 이틀이 걸렸다.

문제는 감사팀이 다음 질문을 이어서 던진다는 것이다.

"이 중에서 지난 90일간 실제로 접근한 사람은 몇 명이고, 접근 권한이 있지만 한 번도 쓰지 않은 사람은 누구인가요?"

 

그 질문에는 사흘이 걸렸다.

 

정책이 코드 안에 분산돼있을 때, "지금 이 정책이 무엇인가"를 스스로 설명하는 것조차 불가능에 가까워진다. 앞 선 포스팅에서 다룬 PDP(Policy Decision Point)의 역할이 여기 있다. 접근 결정 로직을 하나의 컴포넌트로 모으는 것. 그런데 PDP를 만들겠다고 결심한 다음에 맞닥뜨리는 질문이 있다. 정책을 어떤 언어로 표현할 것인가.

 

그 질문에 두 가지 다른 답을 내놓은 것이 OPA와 Cedar다.


정책을 코드에서 꺼낸다는 것의 의미

정책 언어가 해결해야 할 문제의 목록을 먼저 정리해보자.

 

정책을 애플리케이션 코드 안에 두면 세 가지 문제가 생긴다고 앞 선 포스트에서 다뤘다. 배포 결합(정책 변경이 코드 배포를 요구), 정책 파편화(같은 정책이 여러 서비스에 다른 형태로 구현), 감사 불가(어떤 정책이 언제 어디서 적용됐는지 추적 불가).

 

그런데 정책을 외부 언어로 추출한다고 해서 이 문제들이 자동으로 해결되지는 않는다. 정책 언어가 갖춰야 할 조건이 있다.

  • 첫째, 표현력이다. 실무의 접근 제어 정책은 단순하지 않다. "finance 팀 직원이고, 본인 소속 부서의 보고서에 접근하며, 평일 업무시간이고, 현재 기기가 MDM에 등록돼 있을 때만 허용"처럼 여러 속성이 교차한다. 이것을 자연스럽게 표현할 수 있어야 한다.

  • 둘째, 안전성이다. 정책 언어는 실수가 치명적인 영역에서 사용된다. 잘못 작성한 정책이 모든 접근을 허용하거나 모든 접근을 막을 수 있다. 정책이 의도한 대로 동작하는지를 사전에 검증할 수 있어야 한다.

  • 셋째, 분석 가능성이다. 감사팀의 질문처럼, "이 정책 아래에서 어떤 사람이 어떤 자원에 접근할 수 있는가"를 정책 자체에서 도출할 수 있어야 한다. 일반적인 정책 평가는 "이 사용자가 이 리소스에 접근할 수 있는가"를 묻는다. 분석은 그 반대 방향이다.
    정책이 고정된 상태에서 "어떤 조건이 주어졌을 때 허용이 발생하는가"를 정책 코드 없이 추론하는 것. 이것이 로그 분석과 다른 이유다. 로그는 과거에 실제로 일어난 일을 보여주지만, 정책 분석은 아직 일어나지 않은 접근의 가능성을 미리 파악한다.

OPA와 Cedar는 이 세 가지 조건의 우선순위를 다르게 선택했다. 그 선택이 두 도구의 설계 전체를 결정했다.


OPA : Datalog에서 출발한 범용 정책 엔진

Open Policy Agent(OPA)는 2016년 Styra가 처음 개발했고, 2021년 2월 클라우드 네이티브 컴퓨팅 재단(CNCF)의 졸업 프로젝트(graduated project)가 됐다. Kubernetes admission control, API 인가, Terraform 정책 검증, Kafka 접근 제어 등 접근 제어가 필요한 어느 곳에서나 같은 도구를 쓸 수 있다는 것이 설계 목표였다.

 

OPA의 정책 언어인 Rego를 이해하려면 그 뿌리를 알아야 한다. Rego는 Datalog에 기반한다. Datalog는 Prolog의 제한된 부분집합으로, 1970년대부터 데이터베이스 쿼리 언어로 연구된 논리 프로그래밍 언어다.

 

왜 논리 프로그래밍인가. 접근 제어 정책의 본질이 논리 명제이기 때문이다. "사용자가 admin 역할을 가지고 있으며, 리소스가 개발 환경에 있을 때, 접근을 허용한다"는 문장은 if-else 블록이 아니라 논리적 함의(implication)다. 논리 프로그래밍은 이것을 직접 표현한다.

input · data · policy: 세 가지 문서의 역할

OPA가 정책을 평가할 때 사용하는 정보는 세 종류로 나뉜다.

input    — 지금 이 요청의 컨텍스트 (사용자, 리소스, 액션, 환경)
data     — 정책이 참조하는 배경 상태 (역할 목록, 허용 IP, 민감도 등급 테이블)
policy   — 정책 규칙 자체 (Rego로 작성된 판단 로직)

 

input은 매 요청마다 달라진다. data는 OPA에 미리 로드된 외부 상태로, 정책과 별도로 업데이트된다. policy는 판단 로직이다. 이 세 가지를 분리한다는 것이 핵심이다. 정책 자체를 바꾸지 않고 data만 업데이트해서 허용 IP 목록을 변경할 수 있다. input 구조가 달라져도 datapolicy는 그대로다.

Rego의 선언형 의미론

Rego 규칙의 가장 중요한 특징은 규칙 본문의 조건들이 결합 방식과 규칙 간 결합 방식이 다르다는 것이다.

단일 규칙 본문 안의 조건들은 AND로 결합된다. 모두 참이어야 규칙이 성립한다.

 

같은 이름으로 선언된 여러 규칙은 OR로 결합된다. 하나라도 성립하면 결과가 결정된다.

package authz

import rego.v1

# 기본값 명시적 선언 — 아무 규칙도 성립하지 않을 때의 값
default allow = false

# 규칙 1: engineer이면서 dev 환경일 때 허용 (AND)
allow if {
    input.user.role == "engineer"
    input.resource.environment == "development"
}

# 규칙 2: admin은 항상 허용 (별도 규칙 — 위 규칙과 OR)
allow if {
    input.user.role == "admin"
}

# 규칙 3: 부서 소속 리소스에 낮은 민감도 수준일 때 허용 (OR)
allow if {
    input.user.department == input.resource.owner_dept
    input.resource.sensitivity_level < 3
    is_weekday(input.context.time)  # 별도 정의된 헬퍼 함수
}

 

이 구조는 명령형 언어와 근본적으로 다르다. if-elif-else 블록은 순서가 있다. 앞 조건이 참이면 뒤는 평가하지 않는다. Rego 규칙들은 순서가 없다. OPA는 가능한 모든 규칙을 평가하고, 그중 하나라도 allowtrue로 만들면 결과는 true다.

 

순서 없는 평가는 규칙을 추가하거나 재배열해도 기존 결과가 바뀌지 않는다는 보장을 제공한다. 규칙의 순서에 의존하는 숨겨진 동작이 없다.

가상 문서(Virtual Document)

OPA의 데이터 모델에서 data는 두 종류를 포함한다. 하나는 외부에서 로드된 정적 데이터(base document), 다른 하나는 규칙에 의해 계산되는 가상 문서(virtual document)다.

package authz

import rego.v1

# 가상 문서: 규칙에 의해 계산되는 집합 (OPA v1.0 문법)
authorized_actions contains action if {
    input.user.role == "engineer"
    action in {"read", "list", "create"}
}

authorized_actions contains action if {
    input.user.role == "admin"
    action in {"read", "list", "create", "update", "delete"}
}

 

authorized_actions는 미리 저장된 데이터가 아니다. 매 평가 시마다 input.user.role 값에 따라 계산되는 집합이다. 이 집합을 다른 규칙에서 참조할 수 있다.

allow if {
    input.action in authorized_actions  # 가상 문서를 조건으로 참조
}

 

외부에서 POST /v1/data/authz/authorized_actions{"input": {"user": {"role": "engineer"}}} 를 담아 질의하면 해당 역할 기준으로 허용된 액션 목록이 반환된다. 정책 자체가 쿼리 가능한 데이터가 된다는 것은 이 의미다.

 

감사팀 질문인 "admin이 할 수 있는 모든 액션"을 도출하려면 OPA의 부분 평가(partial evaluation)가 더 정확한 도구다.

opa eval --partialinput을 고정하지 않은 채 평가하면, OPA가 "어떤 input 조건에서 allow가 true가 되는가"를 조건식으로 반환한다. 각 역할이 접근 가능한 리소스 범위를 코드를 뒤지지 않고 정책 자체에서 도출할 수 있는 것이다.

OPA 평가 엔진: 후향 연쇄

OPA의 평가 엔진은 후향 연쇄(backward chaining) 방식으로 동작한다. 증명하고자 하는 목표(goal)에서 출발해 그것을 성립시키는 조건을 역방향으로 탐색한다.

 

"allow가 true인가?"라는 질문에서 출발한다. OPA는 allow를 결론으로 가진 규칙들을 찾는다. 각 규칙의 본문 조건을 확인한다. 조건이 참이 되려면 어떤 변수 값이 필요한지를 계속 역방향으로 추적한다.

이 메커니즘은 OPA의 강력한 기능인 부분 평가(partial evaluation)를 가능하게 한다. input이 고정되지 않은 채로 평가를 실행하면, OPA는 "어떤 input이 들어왔을 때 이 정책이 allow를 true로 만드는가"를 계산한다. 이것은 정책 분석의 기반이다.

번들 아키텍처와 정책 배포

OPA에서 정책과 데이터는 번들(bundle)로 패키징돼 배포된다. 번들은 .tar.gz 형식으로 압축된 파일로, .rego 파일들과 data.json 파일들을 포함한다.

bundle/
├── authz/
│   ├── policy.rego
│   └── data.json        ← 역할-권한 매핑 테이블
├── kubernetes/
│   ├── admission.rego
│   └── data.json        ← 허용 이미지 레지스트리 목록
└── .manifest            ← 번들 메타데이터, 루트 경로 선언

OPA는 번들 서버(HTTP 엔드포인트)를 폴링하거나 푸시로 번들을 수신한다. 정책이 변경되면 번들을 다시 패키징해서 서버에 올린다. OPA가 새 번들을 감지하고 로드한다. 애플리케이션 코드는 건드리지 않는다.

 

번들에는 루트(root) 개념이 있다. .manifest에 번들이 소유하는 데이터 경로를 선언한다. OPA는 선언된 경로 아래의 데이터를 이 번들이 단독으로 관리한다고 인식한다. 여러 팀이 서로 다른 번들로 다른 정책 영역을 관리할 수 있고, 경로 충돌이 방지된다.

OPA 배포 패턴

OPA는 세 가지 방식으로 배포할 수 있다. 각 패턴은 레이턴시와 정책 일관성 사이의 트레이드오프를 다르게 선택한다.

  • 독립 서버 패턴은 전체 서비스가 단일 OPA 인스턴스를 공유한다. 정책 번들을 업데이트하면 모든 서비스에 즉시 동일한 정책이 적용된다. 일관성은 보장되지만 모든 인가 요청이 네트워크를 거친다.

  • 사이드카 패턴은 각 서비스 파드에 OPA 컨테이너를 함께 배포한다. 로컬호스트 통신이므로 네트워크 왕복이 없다. 단, 각 사이드카가 독립적으로 번들 서버를 폴링하기 때문에 정책 전파 지연(propagation delay)이 발생한다. 새 번들이 배포된 후 폴링 주기가 지나야 반영되고, 그 사이에는 인스턴스마다 서로 다른 정책 버전이 실행될 수 있다. 정책 변경이 보안에 민감한 경우 이 지연을 설계에 반영해야 한다.

  • Go 라이브러리 패턴은 OPA 평가 엔진을 Go 바이너리에 직접 임베드한다. 함수 호출 수준으로 동작해 네트워크 오버헤드가 없다. 다만 Go 언어에서만 사용할 수 있다.

Rego의 함정

Rego를 처음 작성할 때 가장 많이 마주치는 문제들이 있다. 이것들은 언어의 결함이 아니라 선언형 언어의 의미론을 명령형 방식으로 생각할 때 생기는 오해다.

undefined는 false가 아니다

Rego에서 undefinedfalse는 다른 값이다.

default allow = false

allow if {
    input.user.role == "admin"
}

 

input.user.role이 존재하지 않는 경우(undefined input), 규칙 본문 평가에서 undefined가 발생한다. undefined가 발생한 규칙은 성립하지 않는다. allow에 대한 어떤 규칙도 성립하지 않으면 default 값인 false가 반환된다.

 

문제는 default 선언이 없는 경우다. 어떤 규칙도 성립하지 않으면 allow 자체가 undefined다. OPA REST API는 이 경우 HTTP 200을 반환하지만 응답 본문에 result 키가 아예 없다. {} 빈 객체만 돌아온다. 애플리케이션이 이것을 false로 처리할 수도 있고, 파싱 예외로 처리할 수도 있다. 어떻게 처리하느냐는 전적으로 클라이언트 코드에 달려있다. Zero Trust 원칙에서 undefined는 반드시 거부여야 한다. default allow = false 선언은 선택이 아니라 보안 요구사항이다.

빈 컬렉션의 함정

# 의도: users 중 하나라도 admin이면 허용
allow if {
    some user in input.users
    user.role == "admin"
}

 

input.users가 빈 배열([])인 경우, some user in input.users는 한 번도 성립하지 않는다. 규칙 전체가 undefined가 된다. default allow = false가 없으면 위험하다.

빈 컬렉션이 들어올 수 있는 경우, 명시적으로 처리해야 한다.

default allow = false

allow if {
    count(input.users) > 0
    some user in input.users
    user.role == "admin"
}

부정(negation)의 닫힌 세계 가정

Rego의 부정은 닫힌 세계 가정(Closed World Assumption)을 따른다. not X는 "X가 거짓이다"가 아니라 "X가 증명되지 않는다"는 의미다.

allow if {
    not data.blocklist[input.user.id]
}

 

data.blocklist가 OPA에 로드되지 않은 경우, data.blocklist[input.user.id]는 undefined다. 그 부정인 not data.blocklist[input.user.id]는 참이 된다. 차단 목록 데이터가 없으면 모든 접근이 허용되는 결과가 나온다. 이것이 의도된 동작인지 아닌지를 설계 단계에서 명확히 해야 한다.


OPA 정책 테스트

정책을 코드처럼 관리한다는 것은 테스트도 코드처럼 작성한다는 의미다. OPA는 opa test 명령으로 단위 테스트를 실행하는 기능을 내장 제공한다.

# policy_test.rego

package authz_test

import rego.v1
import data.authz.allow

# 테스트 이름은 test_ 접두사 필수
test_engineer_can_access_dev if {
    allow with input as {
        "user": {"role": "engineer", "department": "platform"},
        "resource": {"environment": "development", "sensitivity_level": 2},
        "context": {"time": "2026-04-13T10:00:00Z"}
    }
}

test_engineer_cannot_access_prod if {
    not allow with input as {
        "user": {"role": "engineer", "department": "platform"},
        "resource": {"environment": "production", "sensitivity_level": 4}
    }
}

test_deny_when_input_malformed if {
    not allow with input as {
        "user": {}
    }
}

 

with input as 구문이 테스트의 핵심이다. 실제 요청 없이 input을 원하는 값으로 교체해서 규칙을 평가한다. 외부 의존성 없이 순수하게 정책 로직만 테스트할 수 있다.

 

정책 테스트를 CI 파이프라인에 포함하면 정책 변경이 기존 허용/거부 동작을 깨지 않는지 자동으로 검증한다. 정책 변경도 코드 변경처럼 PR 리뷰 → 테스트 통과 → 배포 경로를 따른다.


Cedar: 다른 질문을 하는 언어

2023년 11월, AWS는 Cedar를 오픈소스로 공개했다. Amazon Verified Permissions와 AWS IoT에서 이미 사용하고 있던 정책 언어였다. Cedar는 단순히 OPA의 경쟁 도구가 아니다. 다른 질문에서 출발한다.

 

OPA가 "어떤 컨텍스트에서도 정책을 표현할 수 있는가"를 목표로 한다면, Cedar의 질문은 이것이다.

정책이 의도한 대로 동작한다는 것을 실행 전에 수학적으로 보장할 수 있는가.

 

2024년 프로그래밍 언어 최고 학술대회 PLDI(Programming Language Design and Implementation)에 발표된 Cedar 논문은 Cedar의 설계 제약을 명확하게 서술한다. Cedar는 의도적으로 튜링 완전하지 않다. 반복문이 없고, 재귀가 없으며, 임의의 외부 HTTP 호출을 지원하지 않는다. 이 제약의 이유는 정책 분석의 결정 가능성(decidability)을 보장하기 위해서다.

 

"이 정책 세트에서 alice가 읽을 수 있는 모든 문서는 무엇인가"라는 질문은 임의의 튜링 완전 언어에서는 해결 불가능한 문제일 수 있다(정지 문제로 환원된다). Cedar는 표현력을 제한함으로써 이 분석이 항상 유한 시간 안에 끝난다고 보장한다.

Cedar의 엔티티 모델

Cedar 정책의 기반은 엔티티(entity)다. 접근 주체(principal), 액션(action), 리소스(resource) 모두 타입이 지정된 엔티티다.

// Cedar 스키마 — 엔티티 타입, 속성, 계층 구조를 한 번에 선언
entity User in [Group] = {
    department: String,
    clearance_level: Long,
};

entity Group;

entity Document in [Folder] = {
    owner: User,
    classification: String,
    department: String,
};

entity Folder;

 

스키마의 핵심은 in [Group] 선언이다. User in [Group]은 "User 엔티티는 Group의 멤버가 될 수 있다"는 계층 관계를 타입 수준에서 정의한다. 정책에서 principal in Group::"finance-team"을 쓰면 Cedar는 이 계층을 따라 멤버십을 탐색한다.

 

스키마를 선언하면 Cedar 검증기(validator)가 정책 작성 시점에 타입 오류를 잡는다. User 엔티티에 존재하지 않는 속성(예: principal.salary)을 정책에서 참조하면 검증 단계에서 오류가 난다. 런타임에 오류를 만나는 것이 아니라, 정책 배포 전에 문제를 발견한다.

Cedar 정책 구조

Cedar 정책은 네 부분으로 구성된다. 효과(effect), 주체(principal) 조건, 액션(action) 조건, 리소스(resource) 조건. 그리고 선택적으로 when/unless 조건절이 붙는다.

// 정책 1: finance-team은 업무 시간 + 관리 기기에서 보고서 열람/내보내기 허용
permit (
    principal in Group::"finance-team",
    action in [Action::"View", Action::"Export"],
    resource in Folder::"financial-reports"
) when {
    context.current_time.hour >= 9 &&
    context.current_time.hour < 18 &&
    context.device.is_managed == true
};

// 정책 2 (예외): "CONFIDENTIAL" 분류 문서는 clearance 4 미만이면 내보내기 금지
forbid (
    principal in Group::"finance-team",
    action == Action::"Export",
    resource is Document
) unless {
    resource.classification != "CONFIDENTIAL" ||
    principal.clearance_level >= 4
};

 

두 정책이 동시에 적용될 때 Cedar의 평가 순서가 중요하다. forbidpermit보다 무조건 우선한다. 정책 1이 Export를 허용하더라도, 정책 2의 forbid 조건이 성립하면 최종 결과는 거부다. 적용 가능한 permit 규칙이 하나도 없어도 거부된다. 이것이 Zero Trust의 기본 원칙인 명시적으로 허용되지 않으면 거부와 정확히 일치한다.

 

Cedar의 context — 요청 시점에 전달하는 외부 데이터

OPA에서 data는 사전에 OPA에 로드해두는 배경 상태였다. Cedar에는 이에 해당하는 별도 메커니즘이 없다. 대신 인가 요청을 보낼 때 context 필드로 요청별 환경 정보를 함께 전달한다.

 

context.current_time.hourcontext.device.is_managed는 Cedar가 스스로 조회하지 않는다. 호출 측 애플리케이션이 현재 시각과 기기 관리 상태를 미리 수집해서 인가 요청 본문에 담아 전달한다. Cedar 정책은 그것을 받아 조건을 평가하는 것뿐이다.

 

이 설계가 Cedar의 분석 가능성을 지키는 이유 중 하나다. 정책 평가 도중 외부 HTTP 호출이 발생하지 않으므로, 정책 실행 결과가 네트워크 상태나 외부 서비스 응답에 영향받지 않는다. 평가는 항상 결정론적(deterministic)이다.

엔티티 계층과 상속

Cedar 엔티티는 계층 구조를 가진다. User::"alice" in Group::"finance-team"은 alice가 finance-team의 멤버라는 관계다. 정책의 principal in Group::"finance-team" 조건은 이 관계를 탐색해서 평가한다.

 

이것은 이전에 다룬 Zanzibar의 Relation Tuple과 유사하다. 관계 그래프를 탐색해서 접근을 결정하는 ReBAC의 아이디어가 엔티티 모델에 녹아있다. Cedar 논문은 이 관계 탐색이 비순환 방향 그래프(DAG)에서만 허용된다고 명시한다. 순환 참조가 없으므로 탐색이 항상 종료된다.

형식 검증: 수학으로 정책을 검증한다

Cedar의 가장 독특한 특성은 형식 검증(formal verification)이다. Cedar 팀은 Cedar의 평가 의미론을 Lean 4 정리 증명기(theorem prover)로 형식화했다. 이 형식 명세에서 Cedar의 핵심 속성들을 수학적으로 증명했다.

 

증명된 속성 중 가장 중요한 것들은:

  • 평가 건전성(Soundness): Cedar 검증기가 타입 오류 없다고 판정한 정책은 런타임에 타입 오류를 일으키지 않는다. 검증을 통과한 정책에서 속성 접근 오류나 타입 불일치는 발생하지 않는다.

  • 평가 종료성(Termination): 모든 Cedar 정책 평가는 유한 시간 안에 종료된다. 무한 루프가 구조적으로 불가능하다.

  • 정책 슬라이싱(Policy Slicing): 주어진 요청과 관련 없는 정책들을 평가 전에 제거하는 최적화를 정확하게 수행할 수 있다. 슬라이싱 후 남은 정책들만 평가해도 전체 정책 세트와 동일한 결과가 나온다는 것이 증명됐다.

  • 정책 분석: "이 정책 세트에서 어떤 요청이 허용되는가"를 분석하는 알고리즘이 다항 시간 안에 완료된다.

이 보장들은 OPA/Rego에서는 원칙적으로 제공할 수 없다. Rego는 HTTP 호출(http.send)이나 사용자 정의 함수를 통해 임의의 계산을 수행할 수 있기 때문이다.


두 설계 철학의 트레이드오프

OPA와 Cedar의 차이는 도구의 차이가 아니다. 접근 제어 문제를 어떻게 정의하는가의 차이다.

 

표현력과 분석 가능성은 동시에 최대화할 수 없다. 임의의 계산을 표현할 수 있으면 "이 정책이 멈추는가"를 결정하는 문제가 정지 문제(Halting Problem)로 환원된다. 정지 문제는 결정 불가능하므로 분석도 불가능해진다. 분석 가능성을 보장하려면 언어의 표현력에 상한을 두어야 한다. 이것은 이론적 제약이지 설계 실수가 아니다.

 

두 도구는 이 트레이드오프에서 서로 다른 점을 선택했다.

OPA(Rego)가 선택한 것: 어떤 접근 제어 로직도 표현할 수 있는 범용성. Rego는 명시적 반복문이나 재귀를 지원하지 않는 Datalog 기반 언어지만, comprehension으로 복잡한 집합·배열 변환이 가능하고 http.send로 외부 서비스를 호출할 수 있다. Kubernetes admission control처럼 도메인이 다양하고, "API 응답 본문에 민감한 필드가 있으면 제거한다"처럼 데이터 변환까지 포함하는 복잡한 로직도 정책으로 표현한다. 대신 정책이 올바른지는 테스트 코드에 의존한다.

 

Cedar가 선택한 것: 실행 전 정책의 정확성을 보장할 수 있는 분석 가능성. 접근 제어에 특화된 언어로 표현력을 좁혔다. 대신 "이 정책이 의도한 대로 동작하는가"를 수학적으로 검증할 수 있다. HTTP 호출이나 복잡한 계산 로직은 정책 안에 넣을 수 없다.

  OPA (Rego) Cedar
기반 이론 Datalog (논리 프로그래밍) 제한된 1차 논리
표현력 높음 — 임의 계산 가능 중간 — 접근 제어 특화
형식 검증 불가 Lean 4로 증명됨
타입 안전성 런타임에 확인 배포 전 정적 검증
정책 분석 부분적 (partial evaluation) 완전 (다항 시간)
외부 데이터 조회 가능 (http.send) 불가
적용 범위 범용 (K8s, Terraform, Kafka...) 접근 제어 전용
생태계 CNCF, 광범위 AWS 중심, 오픈소스 성장 중
학습 곡선 가파름 (Datalog 패러다임) 완만함 (SQL 유사 구조)

두 언어 사이에서 판단하기

두 도구 중 하나를 선택해야 한다면, 기술 스택보다 먼저 아래 질문에 답해야 한다.

 

"정책이 올바른지를 어떻게 보장할 것인가"

OPA를 선택한다면 정책의 정확성은 테스트 코드에 달려있다. 테스트 커버리지가 높아야 하고, 경계 조건(edge case)을 적극적으로 테스트해야 한다. opa test로 단위 테스트를 작성하고, conftest 같은 도구로 통합 테스트를 구성하는 것이 사실상 필수다. Rego의 undefined 함정, 빈 컬렉션 문제, negation의 닫힌 세계 가정을 팀 전체가 이해해야 한다.

 

Cedar를 선택한다면 스키마 정의에 공을 들여야 한다. 스키마가 정확할수록 검증기가 더 많은 오류를 사전에 잡아준다. 대신 Rego처럼 "뭐든 표현할 수 있다"는 유연함은 없다. 정책 안에서 외부 서비스 호출이 필요한 로직은 Cedar로 표현할 수 없으며 그러한 데이터는 요청 시점의 context로 미리 채워서 전달해야 한다.

 

환경과 에코시스템 기준:

Kubernetes 환경이 주축이라면 OPA가 자연스럽다. Kubernetes webhook admission controller에 OPA를 붙이는 패턴(Gatekeeper)이 성숙해있고, Kubernetes 리소스 스키마와 Rego가 잘 맞는다.

 

AWS 기반으로 서비스를 구축하고 있고, Amazon Verified Permissions(AVP)를 인가 서비스로 활용할 계획이라면 Cedar가 기본 선택이다. AVP는 Cedar를 관리형 서비스로 제공하며, IAM과 통합된다.

 

멀티클라우드이거나 AWS에 종속되지 않으려는 경우, OPA의 범용성이 강점이다.

 

팀의 정책 복잡도 기준:

정책이 "이 사용자가 이 리소스에 대해 이 액션을 할 수 있는가" 유형으로 명확하게 정의된다면 Cedar의 제약이 오히려 장점이다. 표현할 수 없는 것을 억지로 표현하지 않아도 되기 때문에 정책이 단순해진다.

 

정책이 쿠버네티스 오브젝트 구조를 검사하거나, 요청 본문의 특정 필드 조합을 검증하거나, 복잡한 데이터 변환이 필요한 경우 OPA가 적합하다.


정책 언어가 해결하지 못하는 것

OPA든 Cedar든, 정책 언어 자체가 해결할 수 없는 문제가 있다. 정책의 내용이 옳은가다.

 

"finance 팀은 보고서를 읽을 수 있다"는 정책이 기술적으로 올바르게 표현됐는지는 도구가 검증할 수 있다. 그런데 finance 팀이 정말로 그 보고서를 읽어야 하는 업무상 이유가 있는지는 도구가 판단하지 않는다. 정책의 비즈니스 정당성은 Access Review(접근 권한 검토) 프로세스가 담당한다. 이것은 앞으로 다룰 주제다.

 

정책 언어는 "어떻게 표현하고 실행하는가"를 해결한다. "무엇을 허용해야 하는가"는 여전히 사람의 판단이 필요한 질문이다.


PDP 결정이 실제 트래픽으로 이어지는 경로

OPA나 Cedar가 "허용(permit)"이라고 결정했다. 이 결정이 실제로 사용자의 요청을 내부 서버로 전달하려면 무엇이 필요한가.

 

정책 결정은 "통과해도 된다"는 판정이다. 실제로 트래픽을 통과시키는 것은 PEP(Policy Enforcement Point)다. 그리고 PEP가 외부 인터넷에서 내부 서비스까지 트래픽을 전달하는 인프라가 필요하다. 


참고 자료

[1] CNCF, "Cloud Native Computing Foundation Announces Open Policy Agent Graduation", February 4, 2021.
https://www.cncf.io/announcements/2021/02/04/cloud-native-computing-foundation-announces-open-policy-agent-graduation/

[2] Open Policy Agent, "Policy Testing", OPA Official Documentation.
https://www.openpolicyagent.org/docs/latest/policy-testing/

[3] AWS Open Source Blog, "Using Open Source Cedar to Write and Enforce Custom Authorization Policies", November 2023.
https://aws.amazon.com/blogs/opensource/using-open-source-cedar-to-write-and-enforce-custom-authorization-policies/

[4] Joseph W. Cutler, Craig Disselkoen, Aaron Eline, Shaobo He, Kyle Headley, Michael Hicks, Kesha Hietala, Eleftherios Ioannidis, Anwar Mamat, Matt McCutchen, Neha Rungta, Emina Torlak, Andrew Wells, "Cedar: A New Language for Expressive, Fast, Safe, and Analyzable Authorization", PLDI 2024, Proceedings of the ACM on Programming Languages.
https://dl.acm.org/doi/10.1145/3656435

[5] Open Policy Agent, "Policy Language (Rego) Reference", OPA Official Documentation.
https://www.openpolicyagent.org/docs/latest/policy-language/

[6] Cedar Policy Language, Official Documentation.
https://docs.cedarpolicy.com/

[7] Amazon Verified Permissions, AWS Documentation.
https://docs.aws.amazon.com/verifiedpermissions/latest/userguide/

[8] Open Policy Agent, "Rego Playground", https://play.openpolicyagent.org/

[9] Open Policy Agent, "OPA 1.0 Compatibility", OPA Official Documentation.
https://www.openpolicyagent.org/docs/latest/v1-compatibility/