Projekte

cedar-ts

4 min Lesezeit GitHub
TypeScript Authorization Cedar Edge Runtime

AWS Cedar ist eine Policy Language für Authorization. Du schreibst Regeln wie “User in Team Engineering darf Dokumente im Folder Docs lesen, wenn er Admin ist”, und Cedar evaluiert das gegen einen Entity Store. Analyzable, auditable, schnell.

Das Problem ist die JavaScript-Seite. Das offizielle SDK (@cedar-policy/cedar-wasm) kompiliert die Rust Engine zu einem 2MB WASM Blob. Opaque für jeden Debugger. Kein Tree-Shaking. Async Initialization. Und auf Edge Runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge) stößt du an WASM Size Limits, bevor du irgendetwas shippst.

cedar-ts reimplementiert Cedar komplett in purem TypeScript. 15KB gzipped. Synchron. Step-through debuggable. Zero Dependencies.

Component Architecture

Der Parser

Hand-geschriebener Recursive Descent Parser in zwei Phasen: Tokenizer (Source → Tokens) und Parser (Tokens → AST).

Der Tokenizer trackt Line/Column Positionen für präzise Fehlermeldungen. Verarbeitet String Escapes (\n, \t, \r, \\, \", \0, \*), Keywords via Lookup Table, Line und Block Comments, Two-Character Operators (==, !=, <=, >=, &&, ||, ::).

Der Parser nutzt Precedence Climbing für Expressions: parseOr → parseAnd → parseRelation → parseHasIn → parseAddSub → parseMulDiv → parseUnary → parseAccess → parsePrimary. Zero Backtracking, Single-Pass. Kein Parser Generator, weil die Grammatik klein genug ist, dass ein Generator keinen Mehrwert bringt, aber die Kontrolle über Fehlermeldungen nimmt: “Expected ’}’ but got ‘EOF’ at line 5, column 12” statt generischem “Parse Error”.

Fünf ADRs dokumentieren die Design-Entscheidungen. ADR-002 erklärt, warum Recursive Descent statt PEG oder Combinators: Cedar hat keine ambiguousen Konstrukte, Lookahead reicht, und die Fehlermeldungen müssen exzellent sein.

Der Evaluator

Implementiert die vollständige Cedar Semantik:

Default Deny. Kein Permit? Deny. Forbid überschreibt Permit. Absolut, keine Ausnahmen. Independent Evaluation. Policies interagieren nicht miteinander, werden erst bei der Decision kombiniert. Short-Circuit. && evaluiert die rechte Seite nicht, wenn die linke false ist. || analog. Error Handling nach Spec. Errors in Conditions werden gecatcht, in Diagnostics aufgezeichnet, und die Policy wird als “not satisfied” behandelt. Kein Crash, kein Silent Fail.

Jede AuthorizationResponse enthält Decision + Diagnostics: welche Policies matched haben (Reasons), welche Evaluation Errors aufgetreten sind. Du weißt genau, warum ein Request denied wurde.

Scope Matching für Principal, Action und Resource unterstützt alle Cedar-Formen: any, == entity, in entity, is Type, is Type in entity, und Action Sets. Entity Hierarchy Traversal via store.getAncestors() mit transitiver Closure.

Entity Store mit Cycle Protection

MemoryEntityStore speichert Entities in einer Map, keyed by Type::"id". Transitive Ancestry via DFS:

private collectAncestors(uid, ancestors, visited) {
  const key = entityUIDKey(uid);
  if (visited.has(key)) return;  // Cycle Detection
  visited.add(key);
  const entity = this.entities.get(key);
  if (!entity) return;
  for (const parent of entity.parents) {
    ancestors.add(parentKey);
    this.collectAncestors(parent, ancestors, visited);
  }
}

Ancestor Sets werden pro Entity gecacht und nur bei Mutations invalidiert. Das Interface ist abstrakt: du kannst einen PostgreSQL-backed oder Redis-cached Store implementieren, ohne den Evaluator anzufassen.

Schema Validator

Separater Validation Pass über den AST. Konservative Type Inference (gibt Any zurück, wenn der Typ nicht bestimmbar ist). Prüft:

Alle Errors werden gesammelt statt Fail-Fast. Jeder Error enthält Message, Policy ID und Source Span. Fängt Tippfehler, bevor sie Production erreichen.

Performance

OperationScaleZeit
Parse100 Policies< 5ms
Evaluate100 Policies × 1 Request< 1ms
Entity Ancestry20-Level Hierarchy< 0.1ms
Full Authorization100 Entities, komplexe Policies< 2ms

Sub-Millisekunde für typische Workloads. Stress Tests verifizieren: 100 Policies pro Request, 100 Entities pro Store, 50+ Level tiefe Expression Nesting. Kein Stack Overflow, lineares Scaling.

Vergleich

cedar-ts@cedar-policy/cedar-wasm
Bundle Size15KB gzipped~2MB
Initializationsynchronasync (await init())
DebuggingStep-through in DevToolsopaque WASM
Edge RuntimesüberallWASM Limits
Dependencies0WASM Runtime
Tree-Shakingja (ESM)nein

Test Suite

3.890 Zeilen Tests über 6 Dateien. 1.5:1 Test-to-Code Ratio. Parser Tests für jede Syntax-Form und jeden Error Case mit Line/Column Accuracy. Evaluator Tests für Decision Logic, Scope Matching, Entity Hierarchy, Error Handling. Stress Tests für Deep Nesting und große Policy Sets.