64c498a97a
- .npmrc routes the @insignia scope to https://git.lynkedup.cloud/api/packages/insignia/npm/ (auth via ${GITEA_TOKEN} env — no secret committed). - The 9 frontend SDK packages (contracts, kernel-client, adapter-sdk, *-web) are now publishable: private dropped, version 0.1.0, publishConfig pinned to Gitea. iios-service and iios-testkit stay private (pnpm publish skips them). - Root `release` / `release:dry` scripts; a Gitea Actions workflow publishes on a v* tag. - PUBLISHING.md documents publish + consumer (.npmrc) setup. Verified: dry-run packs cleanly and workspace:* deps resolve to 0.1.0 in the tarball. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
114 lines
8.0 KiB
Markdown
114 lines
8.0 KiB
Markdown
# Insignia Platform — Live State (from the mesh-verify probe, 2026-07-10)
|
|
|
|
> Distilled from the authenticated `mesh-verify.lynkedup.cloud` dashboard (`/api/results` +
|
|
> `/api/journey`). This is the **real** platform IIOS is meant to plug into — service inventory,
|
|
> the identity/session/governance flow, and the exact contracts to wire IIOS's platform ports.
|
|
> Tokens redacted (the raw JSON dumps contain live SAT/PAT/refresh tokens — do not commit them).
|
|
|
|
## Cluster / mesh
|
|
- **Cluster:** `lynkedup-tech` (NYC2 / DigitalOcean). **Istio** (istio-envoy) + **SPIFFE/SPIRE**,
|
|
trust domain **`spiffe://insignia.tech`** (SVIDs like `spiffe://insignia.tech/ns/sre/sa/default`,
|
|
`.../sa/realmdm-sas`). mTLS **PERMISSIVE**. Reached by ClusterIP DNS.
|
|
- **Namespaces:** `sre` (MDM, OPA, misc), `cmp` (consent platform), `insignia` (identity/session/
|
|
app-facing services), `istio-system`.
|
|
- Public edges: `*.lynkedup.cloud` (behind oauth2-proxy → Keycloak).
|
|
|
|
## The identity/session/governance flow (7 steps — the doctrine)
|
|
> **Separate authorities:** Session Broker *authenticates*, OPA *authorizes*, CMP decides *purpose*,
|
|
> RealMDM *resolves identity*. Purpose-proof ≠ authorization. The PAT carries the external `sub`
|
|
> only as a **SHA-256 hash**, never raw; `canonical_person_id` is null until MDM VERIFIES (MDM never
|
|
> blocks login).
|
|
|
|
1. **Anonymous consent (CMP Edge)** — browser CMP SDK → `POST /edge/v1/cache-policy` → EdDSA-signed
|
|
cache-category manifest; a ConsentReceipt goes to CMP over gRPC. Purpose proof only.
|
|
2. **External login (Supabase)** → **SAT** (ES256 JWT). Claims: `iss` (project `/auth/v1`), opaque
|
|
UUID `sub`, `aud=authenticated`, `email`, `user_metadata` (full_name, avatar_url), `aal`, `amr`.
|
|
Proves the *session*, not the person/permission. Verified via Supabase **JWKS** (kid-selected).
|
|
3. **Session Broker exchange SAT → PAT** — `POST /v1/sessions/exchange` (Bearer SAT +
|
|
`X-Client-Authorization` = BFF Keycloak client-creds, aud=session-broker). Broker verifies the
|
|
SAT, calls the MDM bridge, resolves scope from **memberships**, mints the **PAT** (~5 min).
|
|
3b. **Workload identity (SPIFFE/mTLS)** — each meshed pod gets an X.509-SVID; OPA receives **both**
|
|
the user (`principal`) and the caller (`caller.spiffe_id`) — different layers, never merged.
|
|
4. **MDM Auth-Subject Bridge** — `POST /v1/auth-subjects/resolve {issuer, subject}` → `{platform_
|
|
principal_id, canonical_person_id (null until VERIFIED), link_state (PENDING/VERIFIED),
|
|
link_version, match_method}`. Keyed on **iss+sub** (never email). Idempotent.
|
|
5. **OPA decision** — `POST /v1/decisions` → `{decision_id, allow, reason_codes, obligations,
|
|
policy_version}`. Obligations = masks / row-filters / denied fields / audit level / ttl. A
|
|
decision, not a 50-line entitlement JWT — the PEP MUST enforce every obligation.
|
|
6. **AppShell assembles the ACE** — combines PAT + OPA obligations + CMP consent into an App
|
|
Context Envelope (HttpOnly cookie): `capabilities[]` (policy-derived), `ui_obligations`
|
|
(hide/mask/step_up), `consent`. **No tokens, no raw PII in the browser.**
|
|
7. **CRM renders** — applies OPA `row_filter` (SQL WHERE) + `mask_fields` + `deny_fields`
|
|
server-side; rows reference `canonical_person_id`, not email.
|
|
|
|
## Service inventory + real endpoints
|
|
**Identity / session (`insignia` ns):**
|
|
- **Session Broker** `session-broker.insignia:80` — `POST /v1/sessions/exchange` (SAT→PAT).
|
|
- **Memberships** `memberships.insignia` (public `insignia-memberships.lynkedup.cloud`) —
|
|
`POST /v1/internal/resolve`, `GET /v1/memberships` (Bearer PAT) → scope tuple + `allowed[]`.
|
|
- **AppShell BFF** `appshell-bff.insignia:80` (public `insignia-appshell.lynkedup.cloud`) —
|
|
`GET /apps/crm-web/bootstrap` (Bearer PAT) → ACE.
|
|
- **Profile** `profile.insignia:80` — `GET /v1/profile`, `GET /v1/stats`. Backed by **sqlite3**
|
|
(`/data/profiles.db`, PVC `insignia-profile-data`, WAL, persistent, single-replica RWO).
|
|
- **CRM** `crm.insignia:80` — `GET /v1/leads?view=list` (applies obligations).
|
|
- **Policy Gateway** `policy-gateway.insignia:80` — `POST /v1/decisions`, `POST /v1/decisions/batch`.
|
|
|
|
**MDM (`sre` ns):** `mdm-kernel.sre:80` (`/v1/auth-subjects/resolve`, `/healthz`, `/readyz`; auth =
|
|
Keycloak service token **aud=realmdm**) · `mdm-ai.sre:80` · `mdm-sas.sre:9090` (**gRPC** — tokenization/SAS).
|
|
|
|
**OPA / policy (`sre` ns):** `opa.sre:8181` (`/health`, `GET /v1/data` = live policy tree;
|
|
default-deny) · `realmdm-opa.sre:8181` · `opal-server.sre:7002` (OPAL policy distribution).
|
|
|
|
**CMP (`cmp` ns), backed by Postgres + NATS + Redis:** `cmp-core:8080` (real `CheckConsent` gRPC;
|
|
service token aud=cmp) · `cmp-admin:8086` · `cmp-evidence:8081` · `cmp-sync:8085` ·
|
|
`cmp-tollgate:8082` (NATS JetStream gating) · `cmp-media:8083` · `cmp-worker:8084`. Plus
|
|
`insignia-consent-edge.insignia` (`POST /edge/v1/cache-policy`) and `insignia-consent-adapter.insignia`
|
|
(`POST /v1/consent/evaluate {purpose}` → `{permitted, legal_basis, state, consent_epoch}`).
|
|
|
|
**Other (`sre` ns):** `poi-api:5000` · `roof:8002` (YOLO segmentation) · `commit:8081` ·
|
|
`presign:8080` · `egs:8090` · `artifact-retrieval:8082` (was 503 on probe day) · `cmp-docs`.
|
|
|
|
## The two contracts IIOS must match
|
|
|
|
### PAT (Platform Access Token) — what IIOS should verify
|
|
`iss = https://identity.insignia.internal` · **ES256** (EC P-256), verify via the broker **JWKS**
|
|
(`.../.well-known/jwks.json`, kid-selected — **no shared secret**) · `aud` includes the app (e.g.
|
|
`crm-web`) · `lifetime ~300s`. Claims:
|
|
```
|
|
sub = platform_principal_id app_id tenant_id org_id bu_id
|
|
region environment role aal amr auth_source
|
|
external_subject_hash = sha256:… (NOT the raw external sub)
|
|
canonical_person_id (null until VERIFIED)
|
|
session_epoch · policy_epoch · consent_epoch (stale-detection)
|
|
sid (platform_session_id) · typ = platform-access+jwt
|
|
```
|
|
|
|
### OPA decision — `POST http://policy-gateway.insignia.svc.cluster.local/v1/decisions`
|
|
Request `{ input: { principal{platform_principal_id, canonical_person_id, auth_source, aal, roles,
|
|
memberships[]}, caller{spiffe_id}, resource{type, id, tenant_id, classification[]}, action,
|
|
context{purpose, device_trust, consent_receipt_ids[], network_zone, time} } }`
|
|
Response `{ decision_id, allow, reason_codes[], obligations{ row_filter, allow_fields[], mask_fields{},
|
|
deny_fields[], audit, decision_ttl_seconds }, policy_version }`.
|
|
|
|
## What this means for wiring IIOS's ports
|
|
1. **Auth — verify the PAT, not the Supabase SAT.** IIOS today verifies the Supabase SAT directly
|
|
(a dev shortcut). In the real platform the **Session Broker** does SAT→PAT; a platform workload
|
|
verifies the **PAT**. The PAT already carries the full scope tuple + `platform_principal_id`, so
|
|
`MessagePrincipal` maps ~1:1: `userId = platform_principal_id` (→ `canonical_person_id` once
|
|
VERIFIED), `appId = app_id`, `orgId = org_id`, `tenantId = tenant_id`, `+ buId`. Wire it by adding
|
|
the broker as an `AUTH_ISSUERS` entry (iss `https://identity.insignia.internal`, ES256, its JWKS).
|
|
2. **OPA — point `OpaPort` at the Policy Gateway** (`POST /v1/decisions`). Build `{principal, caller.
|
|
spiffe_id, resource, action, context}` from the PAT + the op; `decideOrThrow` maps `allow` → proceed
|
|
and **must enforce the obligations** (masks/row-filter/deny). The gateway's input is richer than
|
|
IIOS's current `{action,…}` — that's the adapter's job to assemble.
|
|
3. **MDM — usually don't call it.** The PAT already carries `platform_principal_id`/`canonical_person_id`
|
|
(the broker resolved at login). Only call `/v1/auth-subjects/resolve` if IIOS is the identity-exchange
|
|
edge (it isn't — the Broker is).
|
|
4. **CMP — consent gate** via `insignia-consent-adapter /v1/consent/evaluate {purpose}` when processing
|
|
content for AI/analytics/marketing.
|
|
5. **SAS — tokenization/masking** is `mdm-sas` (gRPC) — the "tokenize sensitive parts before storage"
|
|
requirement.
|
|
|
|
*Source: two API payloads captured 2026-07-10 (`result.json` = probes, `journey.json` = the CRM
|
|
first-vertical-slice journey). Re-capture from the authenticated dashboard to refresh.*
|