cloudsec/Cedar 레퍼런스 + 직접짜보기·M0형식·깊이 파일럿

Cedar — 옆에 켜두는 레퍼런스, 그리고 직접 짜보기

읽기용 강의가 아니라 실무에서 참고하는 레퍼런스다. 평가 모델 → 연산자 표 → 실무에서 무는 함정 → 이 repo의 검증된 정책 → 직접 손으로 짜보기. 1차 출처는 공식 Cedar 문서.


30초 멘탈 모델 — 이것만은 외운다

default-deny. 매칭되는 permit이 하나 이상이고 forbid가 0개면 Allow. 그 외 전부 Deny.

forbid는 어떤 permit도 이긴다. permit 여러 개는 OR로 결합되지만, forbid 하나가 매칭되면 끝(Deny).

정책 = effect(permit|forbid) + scope(principal, action, resource — 필수) + 선택 when/unless. ;로 끝.

scope에는 == · in · is · 무제약만. 나머지 비교·로직은 전부 when/unless 안에서.


연산자 레퍼런스

범주연산자의미 / 주의
scope 전용== in isin=엔티티 계층(그룹) 멤버십, is=타입. scope엔 이 셋(+무제약)만.
비교== != < <= > >=대소비교는 long/datetime/duration. !=는 when/unless에서만(scope 불가). decimal은 .lessThan() 등.
로직&& || ! if…then…else단락 평가(short-circuit) — 왼쪽이 결정나면 오른쪽 평가 안 함. 에러 가드에 활용.
계층·타입in is is T in Pin전이적·반사적: A in A는 A가 store에 없어도 true.
속성·문자열.attr has likehas=속성 존재 확인(missing 에러 방지). like "a*c" 와일드카드(\*=리터럴).
집합.contains() .containsAll() .containsAny() .isEmpty()set 멤버십/포함/공집합.

실무에서 무는 함정

① 에러나는 정책은 *조용히 스킵*된다 → forbid가 에러나면 fail-OPEN

없는 속성 접근·타입 불일치는 그 정책을 ERROR로 만들고, Cedar는 그 정책을 무시한다. 막아야 할 forbid가 에러나면, 막혀야 할 게 통과한다. 가드: principal has limit && principal.limit > 0 처럼 has로 먼저 막아라(단락 평가).

// 실화: 이 repo의 에이전트 위임 정책에서 dangling 엔티티가 P6 forbid를 ERROR로 만들어 권한이 증폭됐다. 자체 적대 감사가 잡아 entity→scalar로 고침. THREAT_MODEL / cedar/agent 참고.

② 경계값 — "한도 이하"는 <=

<로 쓰면 정확히 한도(1000)인 이체가 거부된다. off-by-one은 인가에서 사고로 직결.

③ 엔티티 ID 재사용은 권한 누수다

떠난 User::"jane"의 이름을 새 사용자에게 재발급하면, 옛 정책이 참조하던 권한이 자동 승계된다. 실무에선 UUID를 쓰고 // jane 주석으로 가독성만 보완.

④ validator는 evaluator보다 엄격하다

평가(런타임)는 통과해도 schema validate에서 막힐 수 있다. 둘 다 돌려라 — 이 repo는 cedar/authz.py가 schema validate + 8 시나리오를 함께 본다.


이 repo의 검증된 정책 (베끼지 말고 읽어라)


직접 짜보기 — 읽기만으로는 안 는다

위 모델·함정을 손에 익힌다. labs/m0/policies.cedar를 채우고 python labs/m0/grade.py로 채점.

1 완성 예시 — 소유자 검증 (R1)

가장 단순한 permit. scope + 한 줄 when.

permit (
    principal,
    action == Action::"ViewAccount",
    resource
)
when { resource.owner == principal };

2 빈칸 — 이체 한도 (R2)

함정 ①(에러 가드)과 ②(경계값)가 둘 다 걸린다. 연산자를 채워라.

permit (
    principal,
    action == Action::"Transfer",
    resource
)
when {
    resource.owner == principal
    && context.amount > 0
    && context.amount __?__ principal.transferLimit
};

힌트 — 정확히 한도만큼(1000) 이체도 허용해야 한다(함정 ②). 그리고 context.amount > 0를 앞에 둔 건 단락 평가로 음수/0을 먼저 쳐내려는 것.

정답 (직접 써 본 뒤)
&& context.amount <= principal.transferLimit

<가 아니라 <=. principaltransferLimit이 항상 있다고 가정 중인데, 없을 수 있다면 principal has transferLimit && …로 가드(함정 ①).

3 혼자 — R3 · R4 · E1

  1. R3 감사역 조회: ViewAuditLogRole::"auditor"만. 계층 멤버십은 principal in Role::"auditor" — when 조건 불필요.
  2. R4 동결 계좌: forbid의 첫 등장. forbid는 permit을 이긴다(멘탈 모델). 이체를 절대 막을 조건 → resource.frozen == true. frozen이 optional이면 resource has frozen 가드.
  3. E1 확장: 감사역이 모든 일반 계좌 조회(이체는 불가). R1·R2·R4 건드리지 말 것 — permit은 독립 평가·OR 결합이니 새 permit 하나 추가. R3 문법 기반, ViewAccount만.
python labs/m0/grade.py          # R1–R4 → 8/8
python labs/m0/grade.py --ext    # E1 포함 → 11/11
python labs/m0/grade.py --hint   # FAIL 행마다 요건 넛지

1차 출처

// 사실은 공식 Cedar 문서 + 이 repo의 검증된 정책에 근거. "실무 숙련"은 이 레퍼런스 + 직접 reps(grade.py)에서 나온다 — 읽기만으로 되지 않는다.