generated from coulomb/repo-seed
Topaz alignment spike — mapping doc + green e2e example
Closes FLEX-WP-0005 T04. Validates ADR-003's commitment to shape the
standalone core for cheap Topaz adapter work.
Spike output:
- docs/topaz-mapping-spike.md — vocabulary map (subject, group, tenant,
knowledge_base, document, plus parent / owner_team / reader / steward /
member relations), Rego module shape, decision envelope, wire-protocol
ranking (gRPC primary, REST fallback, embedding rejected), schema
restatement recommendation, implications for FLEX-WP-0002 / 0004.
- examples/topaz/ — runnable docker-compose deploying Topaz with the
flex-auth-shaped manifest. seed and probe one-shots cover three
scenarios: alice (steward) allow, bob (group→reader) allow, eve
(outsider) deny. End-to-end green on 2026-05-16:
probe: steward-allow OK (check=true)
probe: reader-allow OK (check=true)
probe: outsider-deny OK (check=false)
probe: all checks passed
Key findings recorded as Implementation Notes in the spike doc:
- Rego input contract bridging (Topaz raw shape ↔ flex-auth canonical
shape) is adapter scope, not core scope.
- Topaz identity objects are a Topaz convention; the adapter
materializes them at directory import time.
- Directory-only permission resolution is sufficient for the common
case; Rego is reserved for context-dependent decisions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
99
examples/topaz/README.md
Normal file
99
examples/topaz/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Topaz alignment example
|
||||
|
||||
Runnable validation for the alignment commitment in ADR-003 and the
|
||||
mapping recorded in `docs/topaz-mapping-spike.md`. Boots Topaz, seeds
|
||||
a directory shaped like flex-auth's canonical vocabulary, and probes
|
||||
three permission scenarios.
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
cd examples/topaz
|
||||
docker compose up --abort-on-container-exit --exit-code-from probe
|
||||
```
|
||||
|
||||
Expected outcome (exit code 0):
|
||||
|
||||
```
|
||||
probe-1 | probe: steward-allow OK (check=true)
|
||||
probe-1 | probe: reader-allow OK (check=true)
|
||||
probe-1 | probe: outsider-deny OK (check=false)
|
||||
probe-1 | probe: all checks passed
|
||||
```
|
||||
|
||||
Tear down:
|
||||
|
||||
```sh
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## What the example proves
|
||||
|
||||
- Topaz's v3 manifest can express flex-auth's canonical object types
|
||||
(`user`, `group`, `tenant`, `knowledge_base`, `document`) and
|
||||
relations (`member`, `parent`, `owner_team`, `reader`, `steward`).
|
||||
- The Markitect fixture data
|
||||
(`examples/markitect/resource_manifest.yaml`, mirrored here) seeds
|
||||
the directory without translation.
|
||||
- Group→reader edges (`reader:platform-architecture` group with a
|
||||
`member` relation, plus a `reader` relation from the document to
|
||||
that group with `subject_relation=member`) resolve correctly via
|
||||
the manifest's `reader | group#member` union.
|
||||
- The `check` decision is fully derivable from directory data for the
|
||||
read-path case; no Rego is involved.
|
||||
|
||||
## File map
|
||||
|
||||
```
|
||||
manifest.yaml # Topaz v3 directory manifest
|
||||
policy/markitect.documents.rego # Rego module showing flex-auth's
|
||||
# canonical input shape (used by the
|
||||
# standalone evaluator; FLEX-WP-0004
|
||||
# T01 will bridge to Topaz's input)
|
||||
bundle/ # OPA bundle loaded into Topaz
|
||||
bundle/.manifest # OPA bundle root manifest
|
||||
bundle/policy/markitect.documents.rego # same Rego, mounted into Topaz
|
||||
data/objects.json # seed objects
|
||||
data/relations.json # seed relations
|
||||
cfg/config.yaml # Topaz config
|
||||
scripts/seed.sh # writes manifest + objects + relations
|
||||
scripts/probe.sh # three directory checks via REST
|
||||
docker-compose.yml # topaz, seed (one-shot), probe (one-shot)
|
||||
```
|
||||
|
||||
## Ports
|
||||
|
||||
When running, Topaz exposes (on `127.0.0.1` only):
|
||||
|
||||
| Port | Service |
|
||||
| --- | --- |
|
||||
| 8282 | authorizer gRPC |
|
||||
| 8383 | authorizer REST |
|
||||
| 9292 | directory gRPC (reader, writer, model, exporter, importer) |
|
||||
| 9393 | directory REST gateway |
|
||||
| 9494 | health |
|
||||
| 9696 | metrics |
|
||||
|
||||
Plaintext HTTP on the gateways. Internal gRPC uses TLS with
|
||||
auto-generated self-signed certs in a `topaz-certs` named volume; the
|
||||
`remote_directory.insecure: true` flag tells the in-process clients to
|
||||
accept them.
|
||||
|
||||
## Caveats
|
||||
|
||||
- Plaintext gateways are for the spike only. Real deployments use
|
||||
certs everywhere; see `docs/topaz-mapping-spike.md` §"Wire-Protocol
|
||||
Candidates" for the production posture.
|
||||
- The probe deliberately uses the directory `check` API instead of the
|
||||
authorizer `is` API. Bridging flex-auth's Rego input shape into
|
||||
Topaz's raw authorizer input is the Topaz adapter's job
|
||||
(FLEX-WP-0004 T01) and is intentionally out of scope for this
|
||||
validation. See `docs/topaz-mapping-spike.md` §"Implementation Notes
|
||||
Surfaced By The Spike".
|
||||
|
||||
## Pinned Topaz version
|
||||
|
||||
`ghcr.io/aserto-dev/topaz:latest` as resolved on 2026-05-16
|
||||
(digest `sha256:11fa7e2075870f3fe523afaadd942a6559b612f44b6bdb1296fe65299f5831fa`).
|
||||
FLEX-WP-0004 T01 will pin a specific tagged version once the adapter
|
||||
lands.
|
||||
64
examples/topaz/bundle/policy/markitect.documents.rego
Normal file
64
examples/topaz/bundle/policy/markitect.documents.rego
Normal file
@@ -0,0 +1,64 @@
|
||||
package flexauth.markitect.documents
|
||||
|
||||
import future.keywords.if
|
||||
import future.keywords.in
|
||||
|
||||
# This module is the Rego extracted from a flex-auth Rego-in-Markdown
|
||||
# policy package (ADR-002). Identical bytes ship to the standalone
|
||||
# evaluator and to Topaz; only the resolution of ds.* differs.
|
||||
#
|
||||
# Decision shape per ADR-002:
|
||||
# decision := {"effect": "...", "reason": "...", "obligations": [...]}
|
||||
# flex-auth wraps this into the canonical decision envelope.
|
||||
|
||||
default decision := {"effect": "deny", "reason": "no_matching_rule"}
|
||||
|
||||
# Reader on the document (direct or via group, or inherited from the
|
||||
# parent knowledge_base) is allowed to read/query/search.
|
||||
decision := {"effect": "allow", "reason": "reader_relation"} if {
|
||||
input.action in {"read", "query", "search"}
|
||||
input.resource.type == "document"
|
||||
is_reader
|
||||
}
|
||||
|
||||
# A steward on the document or parent may always read and may also
|
||||
# export (which carries an audit-export obligation).
|
||||
decision := {"effect": "allow", "reason": "steward_role"} if {
|
||||
input.action in {"read", "query", "search"}
|
||||
input.resource.type == "document"
|
||||
is_steward
|
||||
}
|
||||
|
||||
decision := {
|
||||
"effect": "allow",
|
||||
"reason": "steward_export",
|
||||
"obligations": [{"type": "record_export_receipt"}],
|
||||
} if {
|
||||
input.action == "export"
|
||||
input.resource.type == "document"
|
||||
is_steward
|
||||
}
|
||||
|
||||
# Helpers — these consult the directory shim (standalone) or Topaz's
|
||||
# ds.* builtins (delegated). The standalone evaluator registers
|
||||
# ds.check_relation / ds.check_permission with identical signatures.
|
||||
|
||||
is_reader if {
|
||||
ds.check_relation({
|
||||
"object_type": "document",
|
||||
"object_id": input.resource.id,
|
||||
"relation": "reader",
|
||||
"subject_type": "user",
|
||||
"subject_id": input.subject.id,
|
||||
})
|
||||
}
|
||||
|
||||
is_steward if {
|
||||
ds.check_relation({
|
||||
"object_type": "document",
|
||||
"object_id": input.resource.id,
|
||||
"relation": "steward",
|
||||
"subject_type": "user",
|
||||
"subject_id": input.subject.id,
|
||||
})
|
||||
}
|
||||
107
examples/topaz/cfg/config.yaml
Normal file
107
examples/topaz/cfg/config.yaml
Normal file
@@ -0,0 +1,107 @@
|
||||
# Topaz config for the flex-auth alignment spike.
|
||||
# Plaintext HTTP gateways for local convenience — never use this shape
|
||||
# in production. See docs/topaz-mapping-spike.md.
|
||||
|
||||
version: 2
|
||||
|
||||
logging:
|
||||
prod: false
|
||||
log_level: info
|
||||
|
||||
directory:
|
||||
db_path: /db/directory.db
|
||||
request_timeout: 5s
|
||||
seed_metadata: false
|
||||
|
||||
remote_directory:
|
||||
address: "0.0.0.0:9292"
|
||||
insecure: true
|
||||
|
||||
jwt:
|
||||
acceptable_time_skew_seconds: 5
|
||||
|
||||
api:
|
||||
health:
|
||||
listen_address: "0.0.0.0:9494"
|
||||
metrics:
|
||||
listen_address: "0.0.0.0:9696"
|
||||
services:
|
||||
reader:
|
||||
grpc:
|
||||
listen_address: "0.0.0.0:9292"
|
||||
certs:
|
||||
tls_key_path: "/certs/grpc.key"
|
||||
tls_cert_path: "/certs/grpc.crt"
|
||||
tls_ca_cert_path: "/certs/grpc-ca.crt"
|
||||
gateway:
|
||||
listen_address: "0.0.0.0:9393"
|
||||
allowed_origins:
|
||||
- "*"
|
||||
http: true
|
||||
read_timeout: 2s
|
||||
write_timeout: 2s
|
||||
idle_timeout: 30s
|
||||
writer:
|
||||
needs: [reader]
|
||||
grpc:
|
||||
listen_address: "0.0.0.0:9292"
|
||||
certs:
|
||||
tls_key_path: "/certs/grpc.key"
|
||||
tls_cert_path: "/certs/grpc.crt"
|
||||
tls_ca_cert_path: "/certs/grpc-ca.crt"
|
||||
gateway:
|
||||
listen_address: "0.0.0.0:9393"
|
||||
allowed_origins: ["*"]
|
||||
http: true
|
||||
model:
|
||||
needs: [reader]
|
||||
grpc:
|
||||
listen_address: "0.0.0.0:9292"
|
||||
certs:
|
||||
tls_key_path: "/certs/grpc.key"
|
||||
tls_cert_path: "/certs/grpc.crt"
|
||||
tls_ca_cert_path: "/certs/grpc-ca.crt"
|
||||
gateway:
|
||||
listen_address: "0.0.0.0:9393"
|
||||
allowed_origins: ["*"]
|
||||
http: true
|
||||
exporter:
|
||||
needs: [reader]
|
||||
grpc:
|
||||
listen_address: "0.0.0.0:9292"
|
||||
certs:
|
||||
tls_key_path: "/certs/grpc.key"
|
||||
tls_cert_path: "/certs/grpc.crt"
|
||||
tls_ca_cert_path: "/certs/grpc-ca.crt"
|
||||
importer:
|
||||
needs: [reader]
|
||||
grpc:
|
||||
listen_address: "0.0.0.0:9292"
|
||||
certs:
|
||||
tls_key_path: "/certs/grpc.key"
|
||||
tls_cert_path: "/certs/grpc.crt"
|
||||
tls_ca_cert_path: "/certs/grpc-ca.crt"
|
||||
authorizer:
|
||||
needs: [reader]
|
||||
grpc:
|
||||
connection_timeout_seconds: 2
|
||||
listen_address: "0.0.0.0:8282"
|
||||
certs:
|
||||
tls_key_path: "/certs/grpc.key"
|
||||
tls_cert_path: "/certs/grpc.crt"
|
||||
tls_ca_cert_path: "/certs/grpc-ca.crt"
|
||||
gateway:
|
||||
listen_address: "0.0.0.0:8383"
|
||||
allowed_origins: ["*"]
|
||||
http: true
|
||||
read_timeout: 2s
|
||||
write_timeout: 2s
|
||||
idle_timeout: 30s
|
||||
|
||||
opa:
|
||||
instance_id: "flex-auth-spike"
|
||||
graceful_shutdown_period_seconds: 2
|
||||
local_bundles:
|
||||
paths:
|
||||
- "/bundle"
|
||||
skip_verification: true
|
||||
20
examples/topaz/data/objects.json
Normal file
20
examples/topaz/data/objects.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"objects": [
|
||||
{"type": "tenant", "id": "platform"},
|
||||
{"type": "group", "id": "team:platform-architecture", "display_name": "Platform Architecture"},
|
||||
{"type": "group", "id": "reader:platform-architecture", "display_name": "Platform Architecture Readers"},
|
||||
{"type": "user", "id": "alice@example.test", "display_name": "Alice (steward)"},
|
||||
{"type": "user", "id": "bob@example.test", "display_name": "Bob (reader)"},
|
||||
{"type": "user", "id": "eve@example.test", "display_name": "Eve (outsider)"},
|
||||
{
|
||||
"type": "knowledge_base",
|
||||
"id": "knowledge-base:markitect-example",
|
||||
"properties": {"trust_zone": "public", "labels": ["public"]}
|
||||
},
|
||||
{
|
||||
"type": "document",
|
||||
"id": "document:internal-note",
|
||||
"properties": {"trust_zone": "internal", "labels": ["internal"], "path": "examples/policy/private/internal-note.md"}
|
||||
}
|
||||
]
|
||||
}
|
||||
10
examples/topaz/data/relations.json
Normal file
10
examples/topaz/data/relations.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"relations": [
|
||||
{"object_type": "group", "object_id": "team:platform-architecture", "relation": "member", "subject_type": "user", "subject_id": "alice@example.test"},
|
||||
{"object_type": "group", "object_id": "reader:platform-architecture", "relation": "member", "subject_type": "user", "subject_id": "bob@example.test"},
|
||||
{"object_type": "knowledge_base", "object_id": "knowledge-base:markitect-example", "relation": "owner_team", "subject_type": "group", "subject_id": "team:platform-architecture"},
|
||||
{"object_type": "document", "object_id": "document:internal-note", "relation": "parent", "subject_type": "knowledge_base", "subject_id": "knowledge-base:markitect-example"},
|
||||
{"object_type": "document", "object_id": "document:internal-note", "relation": "steward", "subject_type": "user", "subject_id": "alice@example.test"},
|
||||
{"object_type": "document", "object_id": "document:internal-note", "relation": "reader", "subject_type": "group", "subject_id": "reader:platform-architecture", "subject_relation": "member"}
|
||||
]
|
||||
}
|
||||
68
examples/topaz/docker-compose.yml
Normal file
68
examples/topaz/docker-compose.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
# Runnable Topaz example for the flex-auth alignment spike.
|
||||
#
|
||||
# Boot order:
|
||||
# 1. topaz — runs topazd with the spike config; serves authorizer
|
||||
# on :8282 (gRPC) and :8383 (REST), directory on :9292
|
||||
# (gRPC) and :9393 (REST), health on :9494.
|
||||
# 2. seed — one-shot container that pushes the manifest and seeds
|
||||
# directory objects/relations via REST. Exits on success.
|
||||
# 3. probe — one-shot container that runs three authorizer checks
|
||||
# (steward allow, reader allow, outsider deny) and exits
|
||||
# non-zero if any decision is unexpected.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose up --abort-on-container-exit --exit-code-from probe
|
||||
#
|
||||
# See docs/topaz-mapping-spike.md and README.md.
|
||||
|
||||
services:
|
||||
topaz:
|
||||
image: ghcr.io/aserto-dev/topaz:latest
|
||||
command: ["run", "--config-file", "/cfg/config.yaml", "--bundle", "/bundle"]
|
||||
ports:
|
||||
- "127.0.0.1:8282:8282" # authorizer gRPC
|
||||
- "127.0.0.1:8383:8383" # authorizer REST
|
||||
- "127.0.0.1:9292:9292" # directory gRPC
|
||||
- "127.0.0.1:9393:9393" # directory REST
|
||||
- "127.0.0.1:9494:9494" # health
|
||||
volumes:
|
||||
- ./cfg:/cfg:ro
|
||||
- ./bundle:/bundle:ro
|
||||
- topaz-db:/db
|
||||
- topaz-certs:/certs
|
||||
healthcheck:
|
||||
# Topaz's image has no curl/wget; nc is in busybox. Probe TCP on
|
||||
# the authorizer REST port — the gateway only listens once the
|
||||
# backing gRPC service is ready.
|
||||
test: ["CMD-SHELL", "nc -z 127.0.0.1 8383 || exit 1"]
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 30
|
||||
|
||||
seed:
|
||||
image: alpine:3.20
|
||||
depends_on:
|
||||
topaz:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./data:/data:ro
|
||||
- ./scripts:/scripts:ro
|
||||
- ./manifest.yaml:/manifest.yaml:ro
|
||||
entrypoint: ["/bin/sh", "/scripts/seed.sh"]
|
||||
environment:
|
||||
DIRECTORY_REST: "http://topaz:9393"
|
||||
|
||||
probe:
|
||||
image: alpine:3.20
|
||||
depends_on:
|
||||
seed:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
- ./scripts:/scripts:ro
|
||||
entrypoint: ["/bin/sh", "/scripts/probe.sh"]
|
||||
environment:
|
||||
AUTHORIZER_REST: "http://topaz:8383"
|
||||
|
||||
volumes:
|
||||
topaz-db:
|
||||
topaz-certs:
|
||||
49
examples/topaz/manifest.yaml
Normal file
49
examples/topaz/manifest.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
# Topaz v3 manifest for the flex-auth alignment spike.
|
||||
#
|
||||
# Mirrors flex-auth's canonical resource/subject/group/relation
|
||||
# vocabulary, scoped to the subset the Markitect internal-document
|
||||
# fixture exercises. Reference: docs/topaz-mapping-spike.md.
|
||||
#
|
||||
# Notes on Topaz syntax:
|
||||
# - relations: union types only ( | ) and group-member shorthand ( # ).
|
||||
# - permissions: also support the parent-walk operator ( -> ).
|
||||
# yaml-language-server: $schema=https://www.topaz.sh/schema/manifest.json
|
||||
---
|
||||
model:
|
||||
version: 3
|
||||
|
||||
types:
|
||||
user:
|
||||
relations:
|
||||
manager: user
|
||||
|
||||
group:
|
||||
relations:
|
||||
member: user | group#member
|
||||
|
||||
tenant:
|
||||
relations:
|
||||
member: user | group#member
|
||||
|
||||
knowledge_base:
|
||||
relations:
|
||||
tenant: tenant
|
||||
owner_team: group
|
||||
reader: user | group#member
|
||||
steward: user | group#member
|
||||
permissions:
|
||||
read: reader | steward
|
||||
admin: steward
|
||||
|
||||
document:
|
||||
relations:
|
||||
parent: knowledge_base
|
||||
owner_team: group
|
||||
reader: user | group#member
|
||||
steward: user | group#member
|
||||
permissions:
|
||||
read: reader | steward | parent->read
|
||||
query: reader | steward | parent->read
|
||||
search: reader | steward | parent->read
|
||||
export: steward
|
||||
admin: steward | parent->admin
|
||||
64
examples/topaz/policy/markitect.documents.rego
Normal file
64
examples/topaz/policy/markitect.documents.rego
Normal file
@@ -0,0 +1,64 @@
|
||||
package flexauth.markitect.documents
|
||||
|
||||
import future.keywords.if
|
||||
import future.keywords.in
|
||||
|
||||
# This module is the Rego extracted from a flex-auth Rego-in-Markdown
|
||||
# policy package (ADR-002). Identical bytes ship to the standalone
|
||||
# evaluator and to Topaz; only the resolution of ds.* differs.
|
||||
#
|
||||
# Decision shape per ADR-002:
|
||||
# decision := {"effect": "...", "reason": "...", "obligations": [...]}
|
||||
# flex-auth wraps this into the canonical decision envelope.
|
||||
|
||||
default decision := {"effect": "deny", "reason": "no_matching_rule"}
|
||||
|
||||
# Reader on the document (direct or via group, or inherited from the
|
||||
# parent knowledge_base) is allowed to read/query/search.
|
||||
decision := {"effect": "allow", "reason": "reader_relation"} if {
|
||||
input.action in {"read", "query", "search"}
|
||||
input.resource.type == "document"
|
||||
is_reader
|
||||
}
|
||||
|
||||
# A steward on the document or parent may always read and may also
|
||||
# export (which carries an audit-export obligation).
|
||||
decision := {"effect": "allow", "reason": "steward_role"} if {
|
||||
input.action in {"read", "query", "search"}
|
||||
input.resource.type == "document"
|
||||
is_steward
|
||||
}
|
||||
|
||||
decision := {
|
||||
"effect": "allow",
|
||||
"reason": "steward_export",
|
||||
"obligations": [{"type": "record_export_receipt"}],
|
||||
} if {
|
||||
input.action == "export"
|
||||
input.resource.type == "document"
|
||||
is_steward
|
||||
}
|
||||
|
||||
# Helpers — these consult the directory shim (standalone) or Topaz's
|
||||
# ds.* builtins (delegated). The standalone evaluator registers
|
||||
# ds.check_relation / ds.check_permission with identical signatures.
|
||||
|
||||
is_reader if {
|
||||
ds.check_relation({
|
||||
"object_type": "document",
|
||||
"object_id": input.resource.id,
|
||||
"relation": "reader",
|
||||
"subject_type": "user",
|
||||
"subject_id": input.subject.id,
|
||||
})
|
||||
}
|
||||
|
||||
is_steward if {
|
||||
ds.check_relation({
|
||||
"object_type": "document",
|
||||
"object_id": input.resource.id,
|
||||
"relation": "steward",
|
||||
"subject_type": "user",
|
||||
"subject_id": input.subject.id,
|
||||
})
|
||||
}
|
||||
66
examples/topaz/scripts/probe.sh
Executable file
66
examples/topaz/scripts/probe.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
# Probe the Topaz directory's Check API to verify the seeded manifest
|
||||
# correctly resolves reader/steward/outsider permissions for the
|
||||
# Markitect internal-document fixture. Exits 0 if all checks match
|
||||
# expectations.
|
||||
#
|
||||
# This probe deliberately uses the directory Check API rather than the
|
||||
# authorizer Is API. The manifest permissions are the substrate the
|
||||
# Topaz adapter (FLEX-WP-0004 T01) and the standalone evaluator both
|
||||
# consult; demonstrating it works end-to-end here is the spike's actual
|
||||
# validation question. Bridging flex-auth's Rego input shape into
|
||||
# Topaz's raw authorizer input is adapter work, intentionally out of
|
||||
# this spike's scope (see docs/topaz-mapping-spike.md §"Implementation
|
||||
# Notes").
|
||||
|
||||
set -eu
|
||||
|
||||
apk add --no-cache curl jq >/dev/null
|
||||
|
||||
DIR="${DIRECTORY_REST:-http://topaz:9393}"
|
||||
echo "probe: directory REST = $DIR"
|
||||
|
||||
check() {
|
||||
name="$1"
|
||||
subject="$2"
|
||||
resource="$3"
|
||||
permission="$4"
|
||||
expect="$5" # "true" or "false"
|
||||
|
||||
body=$(cat <<EOF
|
||||
{
|
||||
"object_type": "document",
|
||||
"object_id": "$resource",
|
||||
"relation": "$permission",
|
||||
"subject_type": "user",
|
||||
"subject_id": "$subject"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
response=$(curl -sf -X POST "$DIR/api/v3/directory/check" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$body")
|
||||
|
||||
echo "probe: $name => $response"
|
||||
|
||||
got=$(echo "$response" | jq -r '.check')
|
||||
if [ "$got" = "$expect" ]; then
|
||||
echo "probe: $name OK (check=$got)"
|
||||
else
|
||||
echo "probe: $name FAIL (check=$got; expected=$expect)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Three scenarios on the seeded directory:
|
||||
# 1. Alice is a steward on the document, so read should be permitted.
|
||||
# 2. Bob is a member of reader:platform-architecture, which is the
|
||||
# reader on the document via subject_relation=member, so read should
|
||||
# be permitted via the reader|group#member union in the manifest.
|
||||
# 3. Eve has no relation to the document, so read should be denied.
|
||||
check "steward-allow" "alice@example.test" "document:internal-note" "read" "true"
|
||||
check "reader-allow" "bob@example.test" "document:internal-note" "read" "true"
|
||||
check "outsider-deny" "eve@example.test" "document:internal-note" "read" "false"
|
||||
|
||||
echo "probe: all checks passed"
|
||||
43
examples/topaz/scripts/seed.sh
Executable file
43
examples/topaz/scripts/seed.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
# Seed the Topaz directory: push the manifest, then objects and relations.
|
||||
# Uses Topaz's directory REST gateway. Exits 0 on success.
|
||||
|
||||
set -eu
|
||||
|
||||
apk add --no-cache curl jq >/dev/null
|
||||
|
||||
DIR="${DIRECTORY_REST:-http://topaz:9393}"
|
||||
echo "seed: directory REST = $DIR"
|
||||
|
||||
# 1. Push the directory model (manifest).
|
||||
echo "seed: setting model"
|
||||
curl -sf -X POST "$DIR/api/v3/directory/manifest" \
|
||||
-H 'Content-Type: application/yaml' \
|
||||
--data-binary @/manifest.yaml \
|
||||
|| curl -sf -X POST "$DIR/api/v3/model" \
|
||||
-H 'Content-Type: application/yaml' \
|
||||
--data-binary @/manifest.yaml
|
||||
|
||||
echo
|
||||
|
||||
# 2. Push objects.
|
||||
echo "seed: writing objects"
|
||||
jq -c '.objects[]' /data/objects.json | while IFS= read -r obj; do
|
||||
curl -sf -X POST "$DIR/api/v3/directory/object" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"object\":$obj}" >/dev/null
|
||||
printf '.'
|
||||
done
|
||||
echo
|
||||
|
||||
# 3. Push relations.
|
||||
echo "seed: writing relations"
|
||||
jq -c '.relations[]' /data/relations.json | while IFS= read -r rel; do
|
||||
curl -sf -X POST "$DIR/api/v3/directory/relation" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"relation\":$rel}" >/dev/null
|
||||
printf '.'
|
||||
done
|
||||
echo
|
||||
|
||||
echo "seed: done"
|
||||
Reference in New Issue
Block a user