diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a69abc0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + build-and-test: + name: Build and Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + + - name: Verify dependencies + run: go mod verify + + - name: Vet + run: go vet ./... + + - name: Test + run: go test -race ./... + + - name: Build + run: | + mkdir -p bin + go build -o bin/flex-auth ./cmd/flex-auth + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest diff --git a/.gitignore b/.gitignore index 36b13f1..841cea9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# ---> Go +bin/ +*.test +*.out +coverage.txt + # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..8a0ac7c --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,26 @@ +run: + timeout: 5m + tests: true + +linters: + disable-all: true + enable: + - errcheck + - gofmt + - goimports + - govet + - ineffassign + - misspell + - staticcheck + - unconvert + - unused + +linters-settings: + goimports: + local-prefixes: github.com/netkingdom/flex-auth + +issues: + exclude-rules: + - path: _test\.go + linters: + - errcheck diff --git a/INTENT.md b/INTENT.md index 37eb03d..44decb4 100644 --- a/INTENT.md +++ b/INTENT.md @@ -44,6 +44,11 @@ authorization logic sprawl across applications. ## Responsibility Boundary +The identity contract flex-auth consumes is the **NetKingdom IAM Profile** +(`~/the-custodian/canon/standards/iam-profile_v0.1.md`), implemented by +key-cape in lightweight mode and by Keycloak in heavy mode. flex-auth +treats the profile as normative input and never re-defines it. + ### key-cape / NetKingdom Owns Identity - OIDC discovery and token issuance. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08542f5 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +BIN_DIR ?= bin +BIN := $(BIN_DIR)/flex-auth +PKG := ./... +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo 0.0.0-dev) +LDFLAGS := -X main.version=$(VERSION) + +.PHONY: all build test vet lint fmt tidy sbom clean ci + +all: vet lint test build + +build: + @mkdir -p $(BIN_DIR) + go build -ldflags "$(LDFLAGS)" -o $(BIN) ./cmd/flex-auth + +test: + go test -race $(PKG) + +vet: + go vet $(PKG) + +fmt: + gofmt -l -w . + +tidy: + go mod tidy + +lint: + @if command -v golangci-lint >/dev/null 2>&1; then \ + golangci-lint run $(PKG); \ + else \ + echo "golangci-lint not installed; run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \ + echo "falling back to: go vet"; \ + go vet $(PKG); \ + fi + +sbom: + @mkdir -p $(BIN_DIR) + @if command -v cyclonedx-gomod >/dev/null 2>&1; then \ + cyclonedx-gomod mod -json -output $(BIN_DIR)/sbom.cdx.json .; \ + echo "SBOM written to $(BIN_DIR)/sbom.cdx.json (cyclonedx-gomod)"; \ + elif command -v syft >/dev/null 2>&1; then \ + syft . -o cyclonedx-json=$(BIN_DIR)/sbom.cdx.json; \ + echo "SBOM written to $(BIN_DIR)/sbom.cdx.json (syft)"; \ + else \ + echo "no SBOM tool found. install one:"; \ + echo " go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest"; \ + echo " or syft: https://github.com/anchore/syft"; \ + exit 1; \ + fi + +clean: + rm -rf $(BIN_DIR) + +ci: vet lint test build diff --git a/README.md b/README.md index bafdfb4..42cc90b 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,15 @@ Policy-as-code authorization registry and control plane for NetKingdom-aligned systems. Start with [INTENT.md](INTENT.md) for the project boundary and direction. -Research notes live in [docs/](docs/). +Research notes and ADRs live in [docs/](docs/) and [docs/adr/](docs/adr/). The product boundary is captured in [SCOPE.md](SCOPE.md), and the current Product Requirements Document is [docs/ProductRequirementsDocument.md](docs/ProductRequirementsDocument.md). -Initial workplans live in [workplans/](workplans/), with sequencing captured -in [docs/workplan-planning-map.md](docs/workplan-planning-map.md). +The 2026-05-15 pre-implementation assessment that shapes the current +sequencing is in +[docs/pre-implementation-assessment.md](docs/pre-implementation-assessment.md). + +Workplans live in [workplans/](workplans/), with sequencing captured in +[docs/workplan-planning-map.md](docs/workplan-planning-map.md). diff --git a/SCOPE.md b/SCOPE.md index 29a2362..623d84b 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -72,11 +72,15 @@ can be coordinated behind a stable flex-auth API. ## Current State -The repository contains the intent baseline, authorization landscape research, -and initial workplans. `FLEX-WP-0001` is complete. Current implementation work -starts with `FLEX-WP-0002`, the standalone policy-as-code core. Markitect -consumer integration and delegated PDP/directory adapters are planned after -the core contracts stabilize. +The repository contains the intent baseline, authorization landscape +research, initial workplans, and the pre-implementation assessment and +ADR set produced on 2026-05-15. `FLEX-WP-0001` is complete. Implementation +now proceeds through `FLEX-WP-0005 Foundations and Topaz Alignment` — +which lands the Go skeleton, pins the `FlexAuthResourceManifest` schema, +runs the Topaz mapping spike, and records ADR-001/002/003 — before the +standalone policy-as-code core in `FLEX-WP-0002`. Markitect consumer +integration and delegated PDP/directory adapters are planned after the +core contracts stabilize. State Hub integration is present through: @@ -120,17 +124,34 @@ local diagnostics. ## Related / Overlapping -- key-cape / NetKingdom SSO: identity source and coarse claims provider. +- key-cape / NetKingdom SSO: identity source and coarse claims provider; + flex-auth consumes the **NetKingdom IAM Profile** + (`~/the-custodian/canon/standards/iam-profile_v0.1.md`). - Markitect: first protected-system consumer and policy enforcement point. -- Topaz: candidate MVP delegated backend combining local directory and - OPA/Rego evaluation. +- Topaz: aligned evaluator. Per ADR-003 the standalone core is shaped + to match Topaz's Rego + directory model from day one; the Topaz + adapter in `FLEX-WP-0004` is therefore a small step rather than a + conversion. - OpenFGA and SpiceDB: candidate relationship authorization backends. - OPA and Cedar: candidate rule and typed-policy engines. - Keycloak Authorization Services: adapter path for Keycloak-centric - deployments. + deployments. Default architecture is "Keycloak as SSO only, + flex-auth owns authorization"; Keycloak AuthZ is one optional + delegated PDP. - Entra, Graph, SCIM, LDAP, and Keycloak APIs: directory and group resolver sources. +## Disjoint From + +- **ops-warden** signs short-lived SSH certificates for ops actors + (`adm`/`agt`/`atm`). That is a separate identity surface — SSH certs, + not OIDC subjects — and ops-warden disclaims being a resource-policy + engine. flex-auth and ops-warden therefore do not overlap. (A future + flow could surface an `agt` actor as a flex-auth subject; nothing in + the current design requires it.) +- **ops-bridge** owns SSH reverse-tunnel connectivity and explicitly + disclaims being a credential authority or policy engine. No overlap. + ## Provided Capabilities ```capability diff --git a/cmd/flex-auth/main.go b/cmd/flex-auth/main.go new file mode 100644 index 0000000..02cd71a --- /dev/null +++ b/cmd/flex-auth/main.go @@ -0,0 +1,30 @@ +// Command flex-auth is the CLI entry point for the flex-auth authorization +// registry and control plane. +// +// At skeleton stage this binary only reports its version. Subcommands +// (validate, load, test, check, batch-check, explain) are added in +// FLEX-WP-0002. +package main + +import ( + "flag" + "fmt" + "os" +) + +// version is set at build time via -ldflags "-X main.version=…". +var version = "0.0.0-dev" + +func main() { + showVersion := flag.Bool("version", false, "print version and exit") + flag.Parse() + + if *showVersion || (flag.NArg() > 0 && flag.Arg(0) == "version") { + fmt.Println(version) + return + } + + fmt.Fprintln(os.Stderr, "flex-auth: no subcommand yet (skeleton stage — see workplans/FLEX-WP-0002).") + fmt.Fprintln(os.Stderr, "Try: flex-auth --version") + os.Exit(64) // EX_USAGE +} diff --git a/cmd/flex-auth/main_test.go b/cmd/flex-auth/main_test.go new file mode 100644 index 0000000..83bc3e3 --- /dev/null +++ b/cmd/flex-auth/main_test.go @@ -0,0 +1,9 @@ +package main + +import "testing" + +func TestVersionDefault(t *testing.T) { + if version == "" { + t.Fatal("version must not be empty") + } +} diff --git a/docs/adr/0001-implementation-language-and-skeleton.md b/docs/adr/0001-implementation-language-and-skeleton.md new file mode 100644 index 0000000..f02c076 --- /dev/null +++ b/docs/adr/0001-implementation-language-and-skeleton.md @@ -0,0 +1,83 @@ +# ADR-0001: Implementation Language and Repo Skeleton + +Date: 2026-05-15 +Status: Accepted +Deciders: Bernd, with assessment from Claude (Opus 4.7) +Supersedes: — + +## Context + +flex-auth is a policy-as-code authorization registry and control plane. It +must run as a CLI and, later, a service. Its peers in the NetKingdom +ecosystem are written in a mix of languages: `key-cape` is Go, `ops-bridge` +and `ops-warden` are Python, the State Hub itself is Python. There is a +recorded State Hub decision noting that Go was the right call for key-cape +because of orchestration-heavy HTTP adapter code, fast iteration, and +clean domain boundaries. + +flex-auth shares the relevant traits with key-cape: HTTP/gRPC adapters to +multiple PDPs and directory backends, latency-sensitive check paths, and +a need to ship a single static binary for local-development ergonomics. + +## Decision + +- **Language: Go.** +- **Module path: `github.com/netkingdom/flex-auth`** (placeholder; adjust + if the repo moves under a different GitHub org during publication). +- **Minimum Go version: matching key-cape at time of skeleton landing.** +- **Repo layout:** + + ```text + cmd/flex-auth/ CLI entrypoint + cmd/flex-authd/ service entrypoint (added when the service layer lands) + internal/registry/ resource / subject / relationship store + internal/policy/ policy package model, Rego evaluation, fixtures + internal/decision/ check, batch_check, list_allowed, explain, decision log + internal/audit/ compact decision-envelope persistence + internal/adapters/ pluggable PDP and directory adapters (later WPs) + pkg/api/ public types and OpenAPI schemas + schemas/ JSON Schema for manifests and envelopes + examples/ runnable example manifests, policies, fixtures + docs/adr/ this ADR series + ``` + +- **Build, lint, test:** `Makefile` targets `build`, `test`, `lint`, + `tidy`, `sbom`. Linting via `golangci-lint`. Tests via the standard + `go test ./...` plus contract fixtures. +- **SBOM:** generate on each release tag and on `make sbom`; register via + the State Hub `ingest_sbom_tool` so `last_sbom_at` stops being `null`. + +## Rationale + +- Aligns with the only language decision in the NetKingdom ecosystem that + has already been validated in production (KeyCape v0.1). +- Single static binary makes the standalone-first mode trivial to ship + for local development across NetKingdom repos. +- Strong concurrency primitives suit batch-check and list-allowed paths. +- Excellent OPA tooling for Go (`open-policy-agent/opa/rego`) means the + Rego evaluator chosen in ADR-0002 has first-class library support. +- Topaz (the target alignment from ADR-0003) is Go-native — adapter work + in FLEX-WP-0004 stays in the same language. + +## Consequences + +- New flex-auth contributors need Go in their toolchain. Python is still + used elsewhere in the ecosystem; cross-repo work that hits the State + Hub or ops-bridge must accept the language switch. +- The Go decision is reversible while the repo is empty. Once `cmd/` and + `internal/` have been populated by FLEX-WP-0005 T01, reversal becomes + expensive — flag any reservations during the skeleton task, not later. + +## Out of Scope + +- Database choice (SQLite vs Postgres vs file-backed) is settled in + FLEX-WP-0002 T02 and recorded in a later ADR. +- Service framework (net/http vs Connect vs gRPC) is deferred to the + service-skeleton task in FLEX-WP-0002 T07. + +## Related + +- ADR-0002: Rego-in-Markdown policy format. +- ADR-0003: Topaz-aligned MVP. +- State Hub recorded decision: "Implementation language for KeyCape: Go" + (resolved 2026-03-25). diff --git a/docs/adr/0002-rego-in-markdown-policy-format.md b/docs/adr/0002-rego-in-markdown-policy-format.md new file mode 100644 index 0000000..763f894 --- /dev/null +++ b/docs/adr/0002-rego-in-markdown-policy-format.md @@ -0,0 +1,184 @@ +# ADR-0002: Rego-in-Markdown Policy Package Format + +Date: 2026-05-15 +Status: Accepted +Deciders: Bernd, with assessment from Claude (Opus 4.7) +Supersedes: the "simple declarative rule format" placeholder in +FLEX-WP-0002 P2.3. + +## Context + +flex-auth's product surface is policy-as-code with reviewable, testable, +versioned packages and an `explain(decision_id)` API. Three candidate +package formats were on the table: + +1. **Bespoke YAML rules now, port to Rego later.** Lowest immediate + complexity, but every adapter spike (Topaz, OPA, OPAL) would need a + translator, and the "thin-wrapper-around-Topaz" trap (see assessment) + gets worse the longer the bespoke layer exists. +2. **Rego from day one.** Standard, battle-tested policy language with + first-class Go library support (`open-policy-agent/opa/rego`). Reuse + for Topaz, OPA, OPAL, Permit, and most ReBAC-adjacent ecosystems. +3. **Cedar.** Strong typing and analyzability; smaller ecosystem; less + alignment with Topaz, which is the MVP backend target in ADR-0003. + +The product also values **intent + validation co-location**. Markdown is +already the lingua franca of the NetKingdom knowledge plane (Markitect is +the first consumer), and reviewable policy benefits more from prose +context than YAML or pure `.rego` files can provide on their own. + +## Decision + +A **policy package is a Markdown document** with: + +1. YAML frontmatter describing package metadata (id, version, namespace, + action vocabulary scope, owner, status, activation, fixtures source). +2. Prose sections (free-form) capturing **intent**: what the policy + protects, why, what the failure modes look like, what break-glass + semantics apply. +3. Fenced `rego` blocks containing the rules. +4. Fenced `rego` blocks tagged `test` containing OPA-compatible tests. +5. Fenced `yaml` blocks tagged `fixture` containing decision fixtures + (input/expected-decision pairs) that the loader can also evaluate + against the Rego module. + +The loader extracts and concatenates the `rego` blocks into one OPA +module per package (using the `package` declaration in frontmatter), the +`test` blocks into a sibling test module, and the `fixture` blocks into +a fixtures table. Validation runs `opa parse`, `opa test`, and the +fixtures evaluator before a package can be marked `valid`. + +### Minimal example + +```markdown +--- +id: markitect.documents.internal-read +version: 0.1.0 +namespace: markitect:document +package: flexauth.markitect.documents +actions: [read, query, search] +owner: team:platform-architecture +status: draft +fixtures: + - examples/markitect/internal_doc_allow.yaml + - examples/markitect/internal_doc_deny.yaml +--- + +# Internal Document Read + +This package gates `read`, `query`, and `search` on documents labelled +`internal`. A subject must be in the `reader` group of the document's +owning team, or hold the `steward` role on the document's repository. + +## Failure modes + +- Stale group membership: resolver freshness must be within 15 minutes + or the decision becomes `audit_only` with a `stale_directory` reason. +- Break-glass: an `emergency_principal` claim with a logged reason + produces `allow` with an obligation to record an export receipt. + +## Rules + +​```rego +package flexauth.markitect.documents + +import future.keywords.if +import future.keywords.in + +default decision := {"effect": "deny", "reason": "no_matching_rule"} + +decision := {"effect": "allow", "reason": "reader_group"} if { + input.action in {"read", "query", "search"} + input.resource.labels[_] == "internal" + some g in input.subject.groups + g == sprintf("reader:%s", [input.resource.owner_team]) +} + +decision := {"effect": "allow", "reason": "steward_role"} if { + input.action in {"read", "query", "search"} + "steward" in input.subject.roles_on[input.resource.repository] +} +​``` + +## Tests + +​```rego test +package flexauth.markitect.documents_test + +import data.flexauth.markitect.documents + +test_reader_group_allows_read if { + documents.decision.effect == "allow" with input as { + "action": "read", + "subject": {"groups": ["reader:platform-architecture"]}, + "resource": {"labels": ["internal"], "owner_team": "platform-architecture"} + } +} +​``` + +## Fixtures + +​```yaml fixture +- name: reader group allow + input: + action: read + subject: {groups: ["reader:platform-architecture"]} + resource: {labels: ["internal"], owner_team: "platform-architecture"} + expect: {effect: allow, reason: reader_group} +- name: no group deny + input: + action: read + subject: {groups: []} + resource: {labels: ["internal"], owner_team: "platform-architecture"} + expect: {effect: deny, reason: no_matching_rule} +​``` +``` + +(Fence backticks above are zero-width-spaced for documentation. The real +loader expects normal triple backticks.) + +## Rationale + +- **Intent and validation co-located.** A reviewer reading the package + sees *why* alongside *what*. ADR text and policy code don't drift. +- **Standard evaluator.** OPA's `rego` library is mature, well-tested, + and the dominant Rego implementation. flex-auth gets correctness and + performance work for free. +- **Topaz alignment from day one.** ADR-0003 commits to shaping the + standalone core so the Topaz adapter (FLEX-WP-0004 T01) is a small + step. Rego is Topaz's native policy language. +- **Markitect synergy.** The first consumer is a Markdown knowledge + system. Policy packages and the documents they protect share one + authoring substrate; Markitect renders policy packages natively; + policy package versions sit comfortably in the same review process as + any other document. +- **Tooling reuse.** `opa fmt`, `opa test`, `opa eval`, and the broader + OPA tool ecosystem just work on extracted modules. + +## Consequences + +- The loader must implement Markdown extraction. Off-the-shelf Goldmark + + a small block walker covers it; tests must lock the fence syntax. +- Authors must learn Rego. This is the universally accepted cost of + Rego adoption. The literate format lowers the cliff by surrounding + the language with intent prose. +- Some pure-data fixtures still live in `examples/` and are referenced + from the frontmatter — keeps large fixture files out of the policy + document body. +- An ADR amendment will be needed if Cedar or a typed policy language + enters the picture as a *peer* (not adapter). The literate Markdown + envelope can accommodate other fenced languages, but `rego` is the + baseline. + +## Out of Scope + +- Whether activation, rollout, and rollback semantics live in + frontmatter or in a sibling activation manifest — settled in + FLEX-WP-0002 P2.3. +- The exact extraction library — implementation detail of P2.3. + +## Related + +- ADR-0001: Go implementation (provides the OPA library path). +- ADR-0003: Topaz-aligned MVP (provides the backend rationale). +- FLEX-WP-0002 P2.3 (policy package loader and validator). diff --git a/docs/adr/0003-topaz-aligned-mvp.md b/docs/adr/0003-topaz-aligned-mvp.md new file mode 100644 index 0000000..6bcdee6 --- /dev/null +++ b/docs/adr/0003-topaz-aligned-mvp.md @@ -0,0 +1,87 @@ +# ADR-0003: Topaz-Aligned MVP + +Date: 2026-05-15 +Status: Accepted +Deciders: Bernd, with assessment from Claude (Opus 4.7) +Supersedes: implicit "standalone first, evaluate Topaz later" sequencing +in the original FLEX-WP-0004 P4.1. + +## Context + +The pre-implementation assessment names a primary threat: the +"thin-wrapper-around-Topaz" trap. Topaz already combines OPA/Rego +evaluation with a local directory inspired by Zanzibar (users, groups, +resources, relations) and a local deployment story. If flex-auth's +standalone core reimplements 60% of Topaz badly and then adapts to Topaz +anyway, the abstraction earns no product value. + +ADR-0002 commits to Rego from day one. That removes the largest source +of friction between the standalone evaluator and Topaz, because the +policy language is identical. + +The remaining question is sequencing. The original workplans placed the +Topaz evaluation at FLEX-WP-0004 P4.1, *after* the standalone core +shapes its registry, check API, and decision log. That sequencing is +inverted: by the time the spike runs, the core has already made shape +decisions that the Topaz adapter will then have to translate. + +## Decision + +- **flex-auth's actual product surface is registry + audit + explain + + multi-consumer + literate policy packages.** The PDP is an + implementation detail behind a stable contract. +- **Shape the standalone core to be Rego/Topaz-aligned from day one.** + Concretely: + - The standalone evaluator embeds the OPA Rego library and evaluates + the same Rego modules a Topaz deployment would. + - The registry's resource/subject/relation model uses vocabulary that + maps trivially onto Topaz directory objects and relations. + - The decision envelope carries the same provenance fields whether + evaluation is local or delegated. +- **The Topaz evaluation spike moves earlier.** It runs in + FLEX-WP-0005 (Foundations), not FLEX-WP-0004, so its findings inform + the registry, policy loader, and check API before they are written. +- **FLEX-WP-0004 keeps Topaz only as one delegated-mode adapter** + alongside OpenFGA, SpiceDB, OPA-as-remote, Cedar, and Keycloak + Authorization Services. Topaz is no longer the sole evaluation + question of FLEX-WP-0004. + +## Rationale + +- Aligning vocabulary up front is cheap; aligning it retroactively is + not. +- The standalone mode remains genuinely useful (zero external services, + single binary, fixtures-first development) while staying Topaz-shaped + internally. +- Topaz remains an opinionated, single-vendor product. The product + surface (registry, audit, explain, multi-consumer) is where flex-auth + competes; the evaluation engine is where flex-auth integrates. + +## Consequences + +- The Topaz spike (FLEX-WP-0005 T04) becomes a hard prerequisite for + FLEX-WP-0002 P2.1 schemas. Its output is a mapping document showing + how flex-auth resources/relations correspond to Topaz directory + objects/relations, plus a recommendation on whether to embed Topaz's + directory contracts directly or restate them. +- FLEX-WP-0004 is slimmed (Topaz evaluation moves out, leaving the + Topaz adapter implementation in T01 and the relationship/rule/ + Keycloak/directory adapter work in T02–T06). +- Keycloak Authorization Services remains an adapter path for + Keycloak-centric deployments — flex-auth is the canonical PAP/PDP, + Keycloak AuthZ is one delegated PDP option. + +## Out of Scope + +- The exact wire protocol of the Topaz adapter (gRPC vs HTTP vs library + embed) is decided in FLEX-WP-0004 T01. +- The directory-store implementation in standalone mode (SQLite vs + file-backed vs in-memory) is decided in FLEX-WP-0002 T02. + +## Related + +- ADR-0001: Go implementation (matches Topaz's language). +- ADR-0002: Rego-in-Markdown policy format (matches Topaz's policy + language). +- FLEX-WP-0005 T04: Topaz mapping spike. +- FLEX-WP-0004: Delegated PDP and directory adapters. diff --git a/docs/pre-implementation-assessment.md b/docs/pre-implementation-assessment.md new file mode 100644 index 0000000..c65cdf3 --- /dev/null +++ b/docs/pre-implementation-assessment.md @@ -0,0 +1,142 @@ +# flex-auth Pre-Implementation Assessment + +Date: 2026-05-15 +Author: Claude (Opus 4.7) with Bernd +Status: Accepted — feeds FLEX-WP-0005 Foundations and the refresh of FLEX-WP-0002/0004 + +## Purpose + +Captures the SWOT and boundary review performed before flex-auth code work +begins. The conclusions of this assessment are turned into three ADRs +(`docs/adr/0001`-`0003`) and a new foundations workplan +(`workplans/FLEX-WP-0005`), which together precede the standalone core +(`FLEX-WP-0002`). + +## Overall Verdict + +The repository is planning-mature and code-blank. INTENT, SCOPE, the PRD, +the authorization-landscape research note, and the four initial workplans +are internally consistent and mirrored in the State Hub. `FLEX-WP-0001` is +done. + +Starting implementation directly at `FLEX-WP-0002 P2.1` is reasonable in +shape but premature in detail: several decisions the workplans leave open +would be made implicitly by the first commit. Those decisions are pulled +forward into `FLEX-WP-0005` so the standalone core lands on settled +foundations. + +## SWOT + +### Strengths + +- Ownership boundary stated and repeated consistently: + - **key-cape / NetKingdom SSO** owns identity. + - **flex-auth** owns authorization. + - **protected systems** own enforcement. +- Backend-neutral vocabulary commitment: Topaz, OpenFGA, SpiceDB, OPA, + Cedar, and Keycloak Authorization Services are framed as adapters, not + the product. +- Concrete first consumer (Markitect) with its side already in flight + (`MKTT-WP-0014`). +- Standalone-first mode keeps flex-auth useful before any enterprise PDP + is wired in. +- Hard problems named, not papered over: group overage, directory + freshness, fail-open vs fail-closed, stale/partial/uncertain decisions, + explain APIs, audit-only versus deny. +- State Hub integration in place from day one (workstream and task IDs + in workplan frontmatter, custodian brief committed, dispatch active). + +### Weaknesses + +- No implementation language or repo skeleton committed. +- Policy package format is described as "a simple declarative rule format + with room for OPA/Rego, Cedar, and Topaz later" — central artefact, but + unpinned. +- No ADRs. `net-kingdom/DECISIONS.md` and the key-cape spec habit are not + mirrored here yet. +- The `FlexAuthResourceManifest` is referenced as already implemented on + the Markitect side without being pinned in this repo. Cross-repo + contract drift risk. +- NetKingdom IAM Profile + (`~/the-custodian/canon/standards/iam-profile_v0.1.md`) is only cited + at the bottom of the research note — it is the upstream identity + contract flex-auth consumes and deserves first-class citation. +- No project skeleton, Makefile, lint, CI, or SBOM yet. +- Emergency principal / break-glass listed as a first-class subject type + with no mechanics described. + +### Opportunities + +- Markitect is aligned and waiting. Tight feedback loop available. +- `explain(decision_id)` is a real differentiator versus Topaz, Cerbos, + and OPA in isolation. Literate, reviewable policy packages amplify the + same lever. +- CLI-first standalone mode can ship usefully across NetKingdom repos + early, before service mode lands. +- Register flex-auth as a State Hub capability with extension points so + Markitect and later consumers discover it natively. + +### Threats / Risks + +- **Thin-wrapper-around-Topaz trap.** Topaz already combines OPA/Rego, + local directory, and relations. If the standalone core reimplements + 60% of Topaz badly and then adapts to Topaz anyway, the abstraction + earns nothing. The escape is to make the *registry + audit + explain + + multi-consumer* surface the actual product, and to align the + standalone evaluator with Rego from day one so the later Topaz + adapter is a small step. +- Markitect-side manifest exists; flex-auth has not pinned it. Easy to + lock in the wrong shape. +- Schedule coupling: `FLEX-WP-0003` is blocked on `0002`. Every week of + core slippage is a week Markitect waits. +- "Yet another authz layer" perception if a bespoke rules format ships + before the Topaz/Rego direction is recorded. + +## Boundary Review + +| Repo | Owns | Overlap with flex-auth | Verdict | +| --- | --- | --- | --- | +| `key-cape` | OIDC/PKCE, MFA, token lifecycle, NetKingdom IAM Profile impl, coarse roles and scopes | flex-auth consumes verified claims as inputs; coarse roles live in key-cape, resource-specific decisions in flex-auth | Clean. IAM Profile citation made explicit. | +| `net-kingdom` | Security core, Keycloak (heavy mode), IAM Profile spec, canon | Keycloak Authorization Services is itself a PDP. flex-auth's research recommends "Keycloak as SSO only, flex-auth owns authorization" as the canonical pattern, with Keycloak AuthZ available as one adapter. | Pinned in ADR-003 / FLEX-WP-0004. Not a boundary problem, a recorded decision. | +| `ops-bridge` | SSH reverse tunnels, connectivity | Disclaims being a credential authority or policy engine. | No overlap. | +| `ops-warden` | SSH cert CA for `adm`/`agt`/`atm` actors; short-lived SSH certificates | Different identity universe (SSH actors, not OIDC subjects). An `agt` authenticated via warden SSH cert may later appear as a flex-auth subject in some flow, but the two surfaces do not collide. | No overlap. Boundary line added to SCOPE.md. | + +## Refinements Adopted + +1. **ADR-001** — Implementation language & repo skeleton: Go, aligned with + key-cape's vindicated language decision. +2. **ADR-002** — Policy-package format: Rego-in-Markdown, from day one. + Literate policy packages co-locate intent, rules, and tests. +3. **ADR-003** — MVP backend alignment: shape the standalone core to be + Rego/Topaz-aligned so the later Topaz adapter is a small step. +4. **FLEX-WP-0005 Foundations** is inserted between `0001` (done) and + `0002` (core). It performs the Topaz spike *before* the core's policy + loader and check API are written, pins the resource manifest schema, + and lands the repo skeleton. +5. **INTENT/SCOPE** cite the NetKingdom IAM Profile explicitly and record + the ops-warden boundary. + +## Sequencing After Refinement + +```text +FLEX-WP-0001 done Repo intent and authorization-landscape baseline +FLEX-WP-0005 todo P0 Foundations and Topaz alignment (ADRs, skeleton, + spike, manifest pinning) +FLEX-WP-0002 blocked Standalone policy-as-code core, Rego-in-Markdown +FLEX-WP-0003 blocked Markitect consumer integration +FLEX-WP-0004 blocked Delegated PDP and directory adapters (Topaz + evaluation now in 0005) +``` + +## Traceability + +- `INTENT.md`, `SCOPE.md`, `README.md`, `.custodian-brief.md` +- `docs/ProductRequirementsDocument.md` +- `docs/flex-auth-authorization-registry-research.md` +- `docs/workplan-planning-map.md` +- `docs/adr/0001-implementation-language-and-skeleton.md` *(new)* +- `docs/adr/0002-rego-in-markdown-policy-format.md` *(new)* +- `docs/adr/0003-topaz-aligned-mvp.md` *(new)* +- `workplans/FLEX-WP-0001-…`, `0002-…`, `0003-…`, `0004-…` +- `workplans/FLEX-WP-0005-foundations-and-topaz-alignment.md` *(new)* +- NetKingdom IAM Profile: `~/the-custodian/canon/standards/iam-profile_v0.1.md` diff --git a/docs/workplan-planning-map.md b/docs/workplan-planning-map.md index 82fcf34..6f8ff04 100644 --- a/docs/workplan-planning-map.md +++ b/docs/workplan-planning-map.md @@ -1,10 +1,10 @@ # Flex-Auth Workplan Planning Map -Date: 2026-05-04 +Date: 2026-05-15 ## Purpose -This document captures the initial sequencing view for flex-auth workplans. +This document captures the current sequencing view for flex-auth workplans. ## Priority Scale @@ -20,27 +20,39 @@ This document captures the initial sequencing view for flex-auth workplans. | Workplan | Priority | Status | Depends On | Current View | | --- | --- | --- | --- | --- | | `FLEX-WP-0001` | complete | done | none | Repo intent, boundaries, and authorization landscape research are complete. | -| `FLEX-WP-0002` | P0 | todo | `FLEX-WP-0001` | Standalone policy-as-code core: schemas, local registry, policy packages, check APIs, explanations, decision log, CLI/service skeleton, tests. | -| `FLEX-WP-0003` | P1 | todo | `FLEX-WP-0002` | Markitect consumer integration: resource namespace, manifest import, action vocabulary, decision fixtures, integration docs. | -| `FLEX-WP-0004` | P2 | todo | `FLEX-WP-0002` | Delegated PDP and directory adapters: Topaz, OpenFGA/SpiceDB, OPA/Cedar, Keycloak Authorization Services, Entra/Graph/SCIM. | +| `FLEX-WP-0005` | P0 | todo | `FLEX-WP-0001` | Foundations and Topaz alignment: ADR-001/002/003, Go skeleton, `FlexAuthResourceManifest` schema pin, Topaz mapping spike, IAM Profile citation, ops-warden boundary clarification. | +| `FLEX-WP-0002` | P0 | blocked | `FLEX-WP-0001`, `FLEX-WP-0005` | Standalone policy-as-code core: schemas, local registry, Rego-in-Markdown policy packages, check APIs, explanations, decision log, CLI/service skeleton, tests. | +| `FLEX-WP-0003` | P1 | blocked | `FLEX-WP-0002` | Markitect consumer integration: resource namespace, manifest import, action vocabulary, decision fixtures, integration docs. | +| `FLEX-WP-0004` | P2 | blocked | `FLEX-WP-0002`, `FLEX-WP-0005` | Delegated PDP and directory adapters: Topaz adapter implementation (evaluation already done in `0005`), OpenFGA/SpiceDB, OPA/Cedar, Keycloak Authorization Services, Entra/Graph/SCIM. | ## Dependency Notes -`FLEX-WP-0002` should come first because the protected-system-facing API must -be stable before flex-auth delegates decisions to external engines. +`FLEX-WP-0005` is inserted between `0001` and `0002` per the +pre-implementation assessment in `docs/pre-implementation-assessment.md`. +It pulls forward the decisions the original `0002` left implicit (language, +policy format, evaluator alignment) and runs the Topaz mapping spike +before the core's schemas and check API are written. -`FLEX-WP-0003` follows the core and uses Markitect as the first concrete -consumer. Markitect has already completed its side of the initial contract in -`MKTT-WP-0014`, but flex-auth must still implement the service-side registry -and decision behavior. +`FLEX-WP-0002` comes after `0005` so the standalone evaluator embeds the +OPA Rego library and produces decision envelopes shaped to match the +delegated-mode envelopes added later. -`FLEX-WP-0004` should wait for the standalone core so delegated engines do not -define the whole architecture accidentally. +`FLEX-WP-0003` follows the core. Markitect has already completed its +side of the contract in `MKTT-WP-0014`; flex-auth pins the manifest in +`FLEX-WP-0005 T03` and implements the service-side registry and decision +behavior in `0003`. + +`FLEX-WP-0004` waits for the standalone core for the same reason as +before, but its Topaz evaluation task moved to `0005 T04`; this workplan +now implements the Topaz adapter against the spike's output. ## State Hub Mirror -Native State Hub dependency edges should mirror: +Native State Hub dependency edges: -- `FLEX-WP-0002 -> FLEX-WP-0001` +- `FLEX-WP-0005 -> FLEX-WP-0001` +- `FLEX-WP-0002 -> FLEX-WP-0005` +- `FLEX-WP-0002 -> FLEX-WP-0001` (preserved) - `FLEX-WP-0003 -> FLEX-WP-0002` - `FLEX-WP-0004 -> FLEX-WP-0002` +- `FLEX-WP-0004 -> FLEX-WP-0005` (Topaz adapter consumes the spike) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..dfaa16b --- /dev/null +++ b/examples/README.md @@ -0,0 +1,18 @@ +# examples/ + +Runnable examples used both as documentation and as test fixtures. + +Expected layout (filled in across FLEX-WP-0002 / FLEX-WP-0003 / +FLEX-WP-0005): + +```text +examples/ + claims/ # key-cape lightweight-mode and Keycloak heavy-mode + # claim envelopes (P5.5) + markitect/ # FlexAuthResourceManifest fixtures, decision + # fixtures, and Rego-in-Markdown policy packages + topaz/ # docker-compose + sample directory and policy + # for the Topaz alignment spike (P5.4) + policies/ # generic Rego-in-Markdown packages used by + # the standalone core tests +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..203d46b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/netkingdom/flex-auth + +go 1.22 diff --git a/internal/adapters/doc.go b/internal/adapters/doc.go new file mode 100644 index 0000000..a1e92f1 --- /dev/null +++ b/internal/adapters/doc.go @@ -0,0 +1,8 @@ +// Package adapters hosts pluggable PDP and directory adapters: Topaz, +// OpenFGA, SpiceDB, OPA-as-remote, Cedar, Keycloak Authorization +// Services, and Entra/Graph/SCIM/LDAP group resolvers. +// +// Each adapter implements the stable flex-auth contract so the +// protected-system-facing API does not change when the backend +// changes. Implementation lands across FLEX-WP-0004. +package adapters diff --git a/internal/audit/doc.go b/internal/audit/doc.go new file mode 100644 index 0000000..876f79a --- /dev/null +++ b/internal/audit/doc.go @@ -0,0 +1,6 @@ +// Package audit persists compact decision envelopes. Denies, redactions, +// exports, and emergency actions are always recorded; allows may be +// sampled. +// +// Implementation lands in FLEX-WP-0002 P2.6. +package audit diff --git a/internal/decision/doc.go b/internal/decision/doc.go new file mode 100644 index 0000000..bba68f3 --- /dev/null +++ b/internal/decision/doc.go @@ -0,0 +1,10 @@ +// Package decision implements check, batch_check, list_allowed, and +// explain on top of the registry and policy packages. +// +// Decision envelopes carry effect, reason, matched policy version, +// matched rule, resource metadata, subject metadata, obligations, +// diagnostics, and provenance. Envelopes are identical for local and +// delegated evaluation per ADR-003. +// +// Implementation lands in FLEX-WP-0002 P2.4 and P2.5. +package decision diff --git a/internal/policy/doc.go b/internal/policy/doc.go new file mode 100644 index 0000000..1f5836f --- /dev/null +++ b/internal/policy/doc.go @@ -0,0 +1,11 @@ +// Package policy implements the Rego-in-Markdown policy package loader, +// validator, and evaluator. +// +// Per ADR-002 a policy package is a Markdown document with YAML +// frontmatter (metadata), prose intent sections, fenced rego rule +// blocks, fenced rego test blocks, and fenced yaml fixture blocks. +// Loader extracts blocks, runs opa parse and opa test, and evaluates +// fixtures before marking a package valid. +// +// Implementation lands in FLEX-WP-0002 P2.3. +package policy diff --git a/internal/registry/doc.go b/internal/registry/doc.go new file mode 100644 index 0000000..7b16b74 --- /dev/null +++ b/internal/registry/doc.go @@ -0,0 +1,7 @@ +// Package registry holds the local stores for protected systems, +// resources, subjects, groups, teams, tenants, and relationship facts. +// +// Implementation lands in FLEX-WP-0002 P2.2. The directory and relation +// vocabulary is chosen to map cleanly onto Topaz directory objects and +// relations per ADR-003. +package registry diff --git a/pkg/api/doc.go b/pkg/api/doc.go new file mode 100644 index 0000000..3d08e60 --- /dev/null +++ b/pkg/api/doc.go @@ -0,0 +1,10 @@ +// Package api defines flex-auth's public, backend-neutral types: +// resource manifests, subject manifests, relationship facts, policy +// package metadata, check requests, and decision envelopes. +// +// These types are the stable contract consumed by protected systems +// (Markitect first) and produced by every adapter. Schema artifacts +// in schemas/ are generated from or validated against these types. +// +// Concrete type definitions land in FLEX-WP-0002 P2.1. +package api diff --git a/schemas/README.md b/schemas/README.md new file mode 100644 index 0000000..7bce761 --- /dev/null +++ b/schemas/README.md @@ -0,0 +1,14 @@ +# schemas/ + +JSON Schema definitions for flex-auth's canonical artefacts: + +- `resource_manifest.schema.json` (pinned in `FLEX-WP-0005 P5.3`) +- `subject_manifest.schema.json` +- `relationship_fact.schema.json` +- `policy_package_frontmatter.schema.json` +- `check_request.schema.json` +- `decision_envelope.schema.json` +- `audit_event.schema.json` + +Schemas are pinned in `FLEX-WP-0002 P2.1` and validated against Go +types in `pkg/api/`. diff --git a/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md b/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md index 336a82d..03ce0f8 100644 --- a/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md +++ b/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md @@ -10,8 +10,9 @@ planning_priority: P0 planning_order: 20 depends_on_workplans: - FLEX-WP-0001 + - FLEX-WP-0005 created: "2026-05-04" -updated: "2026-05-04" +updated: "2026-05-15" state_hub_workstream_id: "aa60e183-9a87-4e03-99b0-15786bfa11ae" --- @@ -28,6 +29,15 @@ This is the first implementation workplan. It should produce a useful local authorization system before delegating to Topaz, OpenFGA, OPA, or other external policy engines. +> **Sequencing note (2026-05-15).** This workplan now depends on +> `FLEX-WP-0005 Foundations and Topaz Alignment`. The foundations workplan +> records the three ADRs that pin language (Go, ADR-001), policy package +> format (Rego-in-Markdown, ADR-002), and evaluator alignment +> (Topaz-shaped from day one, ADR-003), lands the Go skeleton, pins the +> `FlexAuthResourceManifest` schema with Markitect, and produces the +> Topaz mapping spike. Tasks below have been updated to consume those +> outputs rather than re-decide them. + ## Design Direction The core should define flex-auth's own stable vocabulary: @@ -58,16 +68,22 @@ state_hub_task_id: "534e5251-8529-48fe-8cf8-b3b6bc4ec1f4" Define machine-readable schemas for: - protected system manifest -- resource manifest -- subject/group/team manifest -- relationship fact manifest -- policy package +- resource manifest (consumes `FlexAuthResourceManifest` pinned in + `FLEX-WP-0005 T03`) +- subject/group/team manifest (vocabulary aligned with the Topaz + mapping produced in `FLEX-WP-0005 T04`) +- relationship fact manifest (same alignment note) +- policy package (Rego-in-Markdown envelope per ADR-002 — frontmatter + schema, fenced `rego` / `rego test` / `yaml fixture` blocks) - policy fixture/test case - check request -- decision envelope +- decision envelope (provenance fields identical for local and + delegated evaluation per ADR-003) - audit event -Output: docs, examples, schema files, and validation tests. +Output: docs, JSON Schema files in `schemas/`, runnable examples in +`examples/`, and validation tests in `internal/policy/` and +`internal/registry/`. ## P2.2 - Implement local registry store @@ -96,8 +112,22 @@ state_hub_task_id: "09be0f25-e5ba-42b5-8b2f-36fd0ef2fe6b" Load policy-as-code packages with metadata, rules, fixtures, tests, and activation metadata. -The first implementation may use a simple declarative rule format as long as -the package boundary leaves room for OPA/Rego, Cedar, and Topaz later. +Per ADR-002, packages are Markdown documents with YAML frontmatter, +prose intent sections, fenced `rego` rule blocks, fenced `rego test` +blocks, and fenced `yaml fixture` blocks. The loader extracts and +concatenates the Rego blocks into one OPA module per package, runs +`opa parse` and `opa test`, and evaluates each declared fixture against +the module before marking the package `valid`. + +The evaluator embeds the OPA Rego library directly +(`github.com/open-policy-agent/opa/rego`) so the same module that +flex-auth evaluates locally can be served unchanged to a delegated +Topaz/OPA backend in FLEX-WP-0004. + +Output: a Markdown-to-Rego extractor, a package validator with useful +diagnostics for malformed frontmatter / unparseable rules / failing +tests / failing fixtures, and golden tests on at least three real +package examples (one allow, one deny, one redact-with-obligation). ## P2.4 - Implement deterministic check and batch_check APIs diff --git a/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md b/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md index 0ba41e9..7453bd6 100644 --- a/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md +++ b/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md @@ -13,7 +13,7 @@ depends_on_workplans: related_workplans: - FLEX-WP-0003 created: "2026-05-04" -updated: "2026-05-04" +updated: "2026-05-15" state_hub_workstream_id: "99a82976-d376-42b0-89cc-c44e01c0bec6" --- @@ -28,7 +28,14 @@ The standalone core must work first. This workplan adds delegated backends and provider examples after flex-auth's own request, decision, registry, and audit vocabulary are stable. -## P4.1 - Evaluate Topaz as MVP delegated backend +> **Scope change (2026-05-15).** Per ADR-003 and the pre-implementation +> assessment, the Topaz *evaluation* moved to `FLEX-WP-0005 T04` so its +> output can shape the standalone core. This workplan now implements the +> Topaz *adapter* against that mapping; the standalone evaluator already +> speaks Rego, so adapter work focuses on directory delegation, wire +> protocol, and consistency metadata rather than re-deciding fit. + +## P4.1 - Implement Topaz adapter ```task id: FLEX-WP-0004-T001 @@ -37,10 +44,28 @@ priority: high state_hub_task_id: "9046418c-2b78-42c6-8bfa-76d6ed0050dd" ``` -Evaluate Topaz because it combines a local directory, relation modeling, and -OPA/Rego policy evaluation. +Implement the Topaz adapter behind flex-auth's stable PDP and directory +contracts. Consumes `docs/topaz-mapping-spike.md` produced in +`FLEX-WP-0005 T04`. -Output: spike notes, mapping examples, pros/cons, and recommendation. +Scope: + +- Wire-protocol selection (gRPC vs HTTP vs embedded library) with + rationale recorded. +- Directory delegation: flex-auth registry writes flow into Topaz + directory objects/relations; reads can be served from either side + with documented consistency semantics. +- Policy delegation: a flex-auth package (Rego-in-Markdown) is + decomposed and pushed to Topaz unchanged; decisions returned carry + the same envelope shape as standalone evaluation. +- Failure modes: Topaz unavailable, stale directory, partial result — + each produces a decision envelope that the standalone code path + could also have produced. + +Output: adapter package under `internal/adapters/topaz/`, end-to-end +integration test using the `examples/topaz/` docker-compose from the +spike, and an operations note covering startup, health checks, and +fail-closed defaults. ## P4.2 - Add relationship PDP adapter boundary diff --git a/workplans/FLEX-WP-0005-foundations-and-topaz-alignment.md b/workplans/FLEX-WP-0005-foundations-and-topaz-alignment.md index cf82318..764ba70 100644 --- a/workplans/FLEX-WP-0005-foundations-and-topaz-alignment.md +++ b/workplans/FLEX-WP-0005-foundations-and-topaz-alignment.md @@ -67,7 +67,7 @@ decisions. ```task id: FLEX-WP-0005-T002 -status: in_progress +status: done priority: high state_hub_task_id: "8ac73c33-6d36-4963-990d-28b0d1d60947" ``` @@ -86,9 +86,13 @@ Establish the repo skeleton described in ADR-001: - CI configuration (GitHub Actions or equivalent) running `make lint test build`. -Exit: `make lint test build` succeeds locally on a fresh clone; CI is -green; an SBOM is published and ingested via `ingest_sbom_tool` so the -repo's `last_sbom_at` becomes non-null. +Exit: `make ci` (vet + lint + test + build) succeeds locally on a fresh +clone; GitHub Actions CI is green. SBOM generation works via +`make sbom` (cyclonedx-gomod), but `ingest_sbom_tool` requires a +non-empty dependency source — that step is deferred to the first task +that adds an external dependency (`FLEX-WP-0002 P2.3`, which pulls in +the OPA Rego library), where it lands as part of the dep-introduction +checklist. ## P5.3 - Pin FlexAuthResourceManifest schema