@tideorg/mcp 1.4.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/GAP_REGISTER.md +18 -8
- package/PRIVACY.md +41 -0
- package/README.md +204 -197
- package/adapters/AGENTS.md +3 -2
- package/adapters/CLAUDE.md +1 -1
- package/canon/anti-patterns.md +55 -62
- package/canon/concepts.md +46 -34
- package/canon/custom-contracts.md +12 -8
- package/canon/feature-mapping.md +27 -75
- package/canon/framework-matrix.md +69 -22
- package/canon/hosting-options.md +111 -0
- package/canon/iga-change-requests-api.md +211 -0
- package/canon/invariants.md +33 -35
- package/canon/security-gap-mapping.md +506 -0
- package/canon/security-runtime-probes.md +177 -0
- package/canon/tidecloak-bootstrap.md +21 -16
- package/canon/tidecloak-endpoints.md +60 -98
- package/canon/troubleshooting.md +115 -53
- package/canon/version-policy.md +8 -12
- package/mcp-server/dist/server.js +221 -19
- package/package.json +48 -45
- package/playbooks/add-auth-nextjs-existing.md +33 -17
- package/playbooks/add-auth-nextjs-fresh.md +1 -1
- package/playbooks/add-rbac-nextjs.md +7 -2
- package/playbooks/bootstrap-realm-from-template.md +33 -18
- package/playbooks/deploy-tidecloak-docker.md +31 -28
- package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
- package/playbooks/initialize-admin-and-link-account.md +22 -19
- package/playbooks/protect-api-nextjs.md +40 -1
- package/playbooks/protect-aspnet-core-asgard.md +313 -0
- package/playbooks/protect-routes-nextjs.md +24 -10
- package/playbooks/provision-tidecloak-skycloak.md +213 -0
- package/playbooks/setup-forseti-e2ee.md +32 -50
- package/playbooks/setup-iga-admin-panel.md +113 -171
- package/playbooks/verify-jwt-server-side.md +20 -1
- package/prompts/build-private-customer-portal.md +1 -1
- package/prompts/security-gap-analysis.md +55 -0
- package/reference-apps/encrypted-communication/anti-patterns.md +3 -3
- package/reference-apps/encrypted-communication/bootstrap-sequence.md +2 -2
- package/reference-apps/encrypted-communication/scenario.md +2 -2
- package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
- package/reference-apps/iga-admin-governance/anti-patterns.md +18 -16
- package/reference-apps/iga-admin-governance/bootstrap-sequence.md +10 -5
- package/reference-apps/iga-admin-governance/role-policy-matrix.md +12 -13
- package/reference-apps/iga-admin-governance/scenario.md +15 -13
- package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
- package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
- package/skills/tide-diagnostics/SKILL.md +1 -1
- package/skills/tide-integration/SKILL.md +9 -18
- package/skills/tide-mcp-qa/SKILL.md +139 -0
- package/skills/tide-rbac-and-e2ee/SKILL.md +5 -5
- package/skills/tide-reviewer/SKILL.md +1 -1
- package/skills/tide-security-analyst/SKILL.md +182 -0
- package/skills/tide-setup/SKILL.md +1 -1
- package/canon/delegation.md +0 -195
- package/playbooks/setup-server-delegation.md +0 -142
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Security Runtime Probes
|
|
2
|
+
|
|
3
|
+
Turns **INFERRED** static findings into **VERIFIED** ones by observing the live target's actual behavior. This is the second tier of the `tide-security-analyst` workflow: static inspection finds *candidate* gaps in code; runtime probing *confirms* which are real against the running system.
|
|
4
|
+
|
|
5
|
+
A static finding says "this route appears unprotected." A runtime probe says "this route returned 200 with no credentials — here is the response." The second is evidence; the first is a hypothesis.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Authorization gate — read before probing anything
|
|
10
|
+
|
|
11
|
+
Runtime probing sends real requests to a real system. It is only permitted when **all** of these hold. If any is unmet, stop and stay in static-only mode.
|
|
12
|
+
|
|
13
|
+
1. **Explicit authorization.** The operator owns the target or has written authorization to test it. Confirm this in the report's scope line. No probing of third-party systems on a hunch.
|
|
14
|
+
2. **Non-production or agreed window.** Probing production is allowed only with explicit operator consent; prefer staging. Never probe a system you were merely *shown*, only one you were *asked to test*.
|
|
15
|
+
3. **Target is a network endpoint the operator named.** Do not discover-and-probe adjacent hosts, subdomains, or internal services you happened to find in config.
|
|
16
|
+
|
|
17
|
+
**Absolute limits — these are not "best-effort", they are the boundary of this role:**
|
|
18
|
+
|
|
19
|
+
- **Non-destructive only.** GET and safe HEAD/OPTIONS. Never send probes that create, modify, or delete data. For write endpoints, confirm the *auth response* (401/403 vs 200) using a request the server rejects *before* it mutates — never complete a mutating call to "see if it works."
|
|
20
|
+
- **No exploitation.** Confirm a gap exists; do not weaponize it. Reaching an unauthorized endpoint and reading one response header confirms SG-05. Dumping the database behind it does not — that is an attack, not an analysis.
|
|
21
|
+
- **No credential attacks.** No brute force, no password spraying, no token cracking, no fuzzing that could lock accounts or trip abuse defenses. Observing that no rate limit exists is a note (out of scope, not a Tide gap); actually exhausting it is not permitted.
|
|
22
|
+
- **Rate-limited and low-volume.** A handful of deliberate requests per finding, not a scan. You are confirming named hypotheses, not enumerating the attack surface.
|
|
23
|
+
- **Stop on the first confirmation.** Once a probe confirms a finding, record it and move on. Do not escalate.
|
|
24
|
+
|
|
25
|
+
If the operator asks for active exploitation, penetration testing, or load/abuse testing, that is a different engagement with different rules — decline within this role and say so.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## How probing upgrades evidence
|
|
30
|
+
|
|
31
|
+
Each finding carries a confidence tag (`canon/security-gap-mapping.md`). Runtime probes move findings up this ladder:
|
|
32
|
+
|
|
33
|
+
| Before (static) | Probe | After |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| INFERRED — route has no visible auth in code | Unauthenticated GET returns 200 with data | **VERIFIED** |
|
|
36
|
+
| INFERRED — bearer tokens, no DPoP handling seen | Replay a captured token from a second client; it is accepted | **VERIFIED** (replayable) |
|
|
37
|
+
| ASSUMED — operator says "we verify JWTs" | Send a token with `alg: none` / tampered payload; it is accepted | **VERIFIED** (SG-13 real) |
|
|
38
|
+
| INFERRED — remote JWKS fetch in code | Observe a JWKS request to the certs endpoint at verification time | **VERIFIED** (SG-10) |
|
|
39
|
+
|
|
40
|
+
A probe that *fails to confirm* is just as valuable: it downgrades or drops a false positive. Record both outcomes. A finding that survived a probe attempt is far stronger than one that was only read from code.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Probe procedures by gap
|
|
45
|
+
|
|
46
|
+
Each probe: the observation that confirms the gap, the safe request to make, and how to read the result. `$T` = the operator-named base URL.
|
|
47
|
+
|
|
48
|
+
### SG-04 / SG-05 — client-only authz / unprotected APIs
|
|
49
|
+
|
|
50
|
+
**Confirms**: an endpoint enforces nothing server-side.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Enumerate candidate routes from static analysis, then for each sensitive one:
|
|
54
|
+
curl -s -o /dev/null -w "%{http_code}\n" "$T/api/<route>"
|
|
55
|
+
# 200 with no Authorization header on a sensitive route = CONFIRMED unprotected (SG-05)
|
|
56
|
+
# Compare: same route WITH a valid session vs WITHOUT — identical output = no server-side check (SG-04)
|
|
57
|
+
curl -s "$T/api/<route>" | head -c 300 # inspect: is real data returned unauthenticated?
|
|
58
|
+
```
|
|
59
|
+
Read: 401/403 unauthenticated = protected (drop/downgrade the finding). 200 with data = confirmed. 200 with an empty/error body = ambiguous, note it, do not overclaim.
|
|
60
|
+
|
|
61
|
+
### SG-03 — bearer token replay (no proof-of-possession)
|
|
62
|
+
|
|
63
|
+
**Confirms**: a stolen token works from anywhere, no key binding.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# With operator-provided test token (their own session), replay from a clean client / different IP context:
|
|
67
|
+
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TESTTOKEN" "$T/api/<protected>"
|
|
68
|
+
# 200 with a plain replayed bearer and no DPoP header = CONFIRMED replayable (SG-03)
|
|
69
|
+
# If the server demands a fresh DPoP proof, it will reject the bare bearer.
|
|
70
|
+
```
|
|
71
|
+
Read: acceptance of a bare replayed bearer confirms the gap. Use only a token the operator supplied from their own account.
|
|
72
|
+
|
|
73
|
+
### SG-13 — JWT algorithm confusion / unverified signature
|
|
74
|
+
|
|
75
|
+
**Confirms**: the verifier trusts the token's claimed algorithm or skips verification.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Using the operator's own valid token as a base, craft a NON-mutating GET with:
|
|
79
|
+
# (a) alg swapped to "none" and signature stripped, or
|
|
80
|
+
# (b) a modified claim (e.g. a role) re-signed with an empty/guessed key
|
|
81
|
+
# Send to a read-only protected route and observe acceptance.
|
|
82
|
+
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $FORGED" "$T/api/<protected>"
|
|
83
|
+
# 200 = CONFIRMED the signature is not properly verified (SG-13 / SG-02 exposure)
|
|
84
|
+
```
|
|
85
|
+
Read: this is the one probe that constructs a token — keep it read-only, use the operator's own account as the base, and do not use any elevated claim to perform an action. Acceptance confirms; rejection (401) means the verifier is sound on this vector. Never chain a confirmed forgery into a real privileged operation.
|
|
86
|
+
|
|
87
|
+
### SG-10 — remote JWKS fetch
|
|
88
|
+
|
|
89
|
+
**Confirms**: the verifier fetches keys over the network at verification time.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Observe from the operator's own logs/network capture whether a request goes to
|
|
93
|
+
# /.well-known/jwks.json or /protocol/openid-connect/certs during token verification.
|
|
94
|
+
curl -s -o /dev/null -w "%{http_code}\n" "$T/.well-known/jwks.json"
|
|
95
|
+
# Endpoint existing is not proof of use; correlate with a verification-time fetch in the app's egress logs.
|
|
96
|
+
```
|
|
97
|
+
Read: confirmation requires observing the *app* fetch keys during verify, not just that the endpoint responds. If you can only see the endpoint, keep the finding INFERRED.
|
|
98
|
+
|
|
99
|
+
### SG-06 / SG-17 — server-readable sensitive data / user secrets
|
|
100
|
+
|
|
101
|
+
**Confirms**: sensitive data is returned in the clear or is app-decryptable.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# With the operator's authorization, request a record the authenticated user owns:
|
|
105
|
+
curl -s -H "Authorization: Bearer $TESTTOKEN" "$T/api/<record>" | head -c 500
|
|
106
|
+
# Plaintext sensitive fields in the response = server can read them (SG-06/SG-17 confirmed for data-in-transit-from-server)
|
|
107
|
+
```
|
|
108
|
+
Read: plaintext in the API response proves the server holds readable data. Do NOT attempt to reach other users' records to "prove" IDOR — that is exploitation and a separate (out-of-scope) class. Confirm only against the operator's own test account.
|
|
109
|
+
|
|
110
|
+
### SG-15 — session lifecycle
|
|
111
|
+
|
|
112
|
+
**Confirms**: sessions don't rotate / don't expire as claimed.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Non-destructive: capture the session id/cookie before and after login with the operator's test account.
|
|
116
|
+
# Same id after authentication = no rotation (fixation exposure). Operator drives the login; you observe.
|
|
117
|
+
# For expiry: note token exp; re-use after the claimed lifetime in a read-only call.
|
|
118
|
+
```
|
|
119
|
+
Read: an unrotated identifier across a privilege boundary confirms fixation exposure. Observe only; the operator performs the login.
|
|
120
|
+
|
|
121
|
+
### SG-01 / SG-02 — surface confirmation only
|
|
122
|
+
|
|
123
|
+
These are confirmed from **artifacts**, not live probing: a schema showing a password-hash column (SG-01), a signing secret visible in the operator-shared config/env (SG-02). Do not attempt to crack hashes or extract keys — possession of the artifact plus its role in the auth path is the confirmation. Keep these static.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Probes that are NOT allowed in this role
|
|
128
|
+
|
|
129
|
+
- Reaching **other users'** data to demonstrate IDOR/broken-object-level-authz (exploitation; also an out-of-scope class per `canon/security-gap-mapping.md`).
|
|
130
|
+
- Any **write/delete** to confirm a mutating endpoint is unprotected — confirm via the auth-layer response before the mutation, never by completing it.
|
|
131
|
+
- **Injection, XSS, SSRF, traversal** payloads — these confirm out-of-scope classes and constitute active exploitation.
|
|
132
|
+
- **Rate-limit / brute-force / load** testing — a different engagement.
|
|
133
|
+
- Probing anything the operator did not explicitly name as in scope.
|
|
134
|
+
|
|
135
|
+
Finding one of these classes statically is reportable (in the out-of-scope section); *actively exploiting* it to confirm is not part of this role.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Recording probe results
|
|
140
|
+
|
|
141
|
+
For every probe, the finding in the report gains a **Runtime confirmation** line:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
- Runtime confirmation: <exact request> → <observed response, e.g. "200, JSON with 14 user records, no auth header"> [VERIFIED]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
or, when a probe failed to confirm:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
- Runtime confirmation: <request> → <e.g. "401 Unauthorized"> — finding NOT confirmed at runtime; downgraded to <INFERRED/dropped>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A report that ran the runtime tier states, in its Method line, `both (static + runtime)` and lists which SG findings were probed. Findings that were *only* inferred and not probed stay tagged INFERRED — never present an unprobed hypothesis as though it were confirmed.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Verification (of this probe pass)
|
|
158
|
+
|
|
159
|
+
- [ ] Authorization and scope confirmed in writing before any request was sent.
|
|
160
|
+
- [ ] Every probe was non-destructive (no create/update/delete completed).
|
|
161
|
+
- [ ] No probe reached data outside the operator's own test account.
|
|
162
|
+
- [ ] No out-of-scope class was actively exploited to "confirm" it.
|
|
163
|
+
- [ ] Each probed finding has a Runtime confirmation line with the exact request and observed response.
|
|
164
|
+
- [ ] Findings not probed remain tagged at their static confidence level.
|
|
165
|
+
|
|
166
|
+
## Anti-Patterns
|
|
167
|
+
|
|
168
|
+
- **AP-SEC-7** — Probing a system the operator showed but did not authorize testing. Authorization is per-target and explicit.
|
|
169
|
+
- **AP-SEC-8** — Completing a mutating request to "check if it's protected." Confirm at the auth layer; never mutate.
|
|
170
|
+
- **AP-SEC-9** — Escalating a confirmed gap into exploitation (dumping data, reaching other accounts, chaining a forged token into a real action). Confirm and stop.
|
|
171
|
+
- **AP-SEC-10** — Presenting an unprobed static hypothesis with runtime-level confidence. Only a probe upgrades the tag.
|
|
172
|
+
|
|
173
|
+
## Status Legend
|
|
174
|
+
|
|
175
|
+
- **VERIFIED** — observed directly (static artifact or runtime response)
|
|
176
|
+
- **INFERRED** — strongly implied by code, not yet probed
|
|
177
|
+
- **ASSUMED** — operator statement, unconfirmed
|
|
@@ -61,12 +61,12 @@ For projects that do not use the init script, follow the manual sequence below.
|
|
|
61
61
|
|
|
62
62
|
## Docker Images
|
|
63
63
|
|
|
64
|
-
| Image | Use when | Forseti support |
|
|
65
|
-
|
|
66
|
-
| `tideorg/tidecloak-dev:latest` | Standard development, full Tide protocol | Yes | **
|
|
67
|
-
| `tideorg/tidecloak-stg-dev:latest` | Staging, testing pre-release features | Yes | **
|
|
64
|
+
| Image | Use when | Forseti support | ORK env vars required |
|
|
65
|
+
|-------|----------|----------------|-----------------------|
|
|
66
|
+
| `tideorg/tidecloak-dev:latest` | Standard development, full Tide protocol | Yes | **No** — built-in defaults |
|
|
67
|
+
| `tideorg/tidecloak-stg-dev:latest` | Staging, testing pre-release features | Yes | **Yes** — must be provided |
|
|
68
68
|
|
|
69
|
-
**
|
|
69
|
+
**Role mapper fallback (defensive)**: The Tide role/claim mappers in the shipped realm templates use **stock Keycloak mapper types** — `oidc-usermodel-attribute-mapper` (for `tideUserKey`/`vuid`) and `oidc-hardcoded-claim-mapper` (for `t.uho`). There is no distinct `tide-roles-mapper` provider in the extension repos on `main`; a mapper by that instance name only appears in some external app realm.json files. A prior note claimed a `tide-roles-mapper` protocolMapper provider exists on the prod image but is silently dropped on the staging image — this could not be confirmed against source and image contents were not inspected. Treat it as unverified. As a defensive measure, include standard Keycloak role mappers (`realm roles`, `client roles`) in the template so token role claims survive regardless of image.
|
|
70
70
|
|
|
71
71
|
Use `tidecloak-dev` for standard development. It has built-in ORK/threshold defaults and does not need `KC_HOSTNAME`, `SYSTEM_HOME_ORK`, `USER_HOME_ORK`, `THRESHOLD_T`, `THRESHOLD_N`, or `PAYER_PUBLIC`. Use `tidecloak-stg-dev` only when testing against pre-release Tide features.
|
|
72
72
|
|
|
@@ -140,13 +140,13 @@ Order matters. Do not skip or reorder steps.
|
|
|
140
140
|
| 4 | Get master admin token | `POST /realms/master/protocol/openid-connect/token` | Bearer token |
|
|
141
141
|
| 5 | Create realm | `POST /admin/realms` with realm.json template | Realm + client |
|
|
142
142
|
| 6 | Initialize Tide + enable IGA | `POST /admin/realms/{realm}/vendorResources/setUpTideRealm` then `POST /admin/realms/{realm}/tide-admin/toggle-iga` | License activated, VRK generated, IGA enabled |
|
|
143
|
-
| 7 | Approve client + role change requests | `
|
|
144
|
-
| 8 | Create admin user | `POST /admin/realms/{realm}/users` | User creation change request created |
|
|
145
|
-
| 8b | Approve user creation | `
|
|
143
|
+
| 7 | Approve client + role change requests | `authorize` + `commit` on `/iga/change-requests` (bulk-authorize then commit ready CRs; see `canon/iga-change-requests-api.md`). **Roles must be approved here** — with IGA enabled, change requests are created in `PENDING` status (there is no `DRAFT` status token); `setUpTideRealm` creates `tide-realm-admin` as a draft. `tide-realm-admin` is a client role on `realm-management`; it is granted to the first admin via firstAdmin auto-commit during the login-closure converge, after which a `REGEN_ADMIN_POLICY` change request flips the authorizer from `firstAdmin` to `multiAdmin`. Without approving the pending role CRs here, role lookups in Step 8c fail. | Client config + roles committed |
|
|
144
|
+
| 8 | Create admin user | `POST /admin/realms/{realm}/users` (returns 202 → user-creation change request) | User creation change request created |
|
|
145
|
+
| 8b | Approve user creation | `authorize` + `commit` on `/iga/change-requests` | User queryable. **Must happen before role assignment.** |
|
|
146
146
|
| 8c | Assign tide-realm-admin role | Assign `tide-realm-admin` client role on `realm-management`. Validate role lookup returns a role, not an error. | Admin user has role |
|
|
147
147
|
| 9 | Generate invite link | `POST /tideAdminResources/get-required-action-link` with `["link-tide-account-action"]` | URL for admin to link Tide account |
|
|
148
148
|
| 10 | Wait for account linking | Poll user `tideUserKey` attribute until non-empty | Admin has Tide identity |
|
|
149
|
-
| 11 | Approve role assignment change requests | `
|
|
149
|
+
| 11 | Approve role assignment change requests | `authorize` + `commit` on `/iga/change-requests` | Admin roles committed |
|
|
150
150
|
| 12 | Update CustomAdminUIDomain | `PUT /identity-provider/instances/tide` then `POST /vendorResources/sign-idp-settings` | Enclave approval popups work from app origin |
|
|
151
151
|
| 13 | Export adapter JSON | `GET /vendorResources/get-installations-provider?clientId={uuid}&providerId=keycloak-oidc-keycloak-json` | `tidecloak.json` with `jwk`, `vendorId`, `homeOrkUrl` |
|
|
152
152
|
|
|
@@ -163,7 +163,7 @@ Order matters. Do not skip or reorder steps.
|
|
|
163
163
|
|
|
164
164
|
**Anti-pattern:** Using `/tide-admin/setUpTideRealm`. Wrong path. Returns 404. VERIFIED.
|
|
165
165
|
|
|
166
|
-
**Anti-pattern:** Sending JSON to this endpoint. Causes
|
|
166
|
+
**Anti-pattern:** Sending JSON to this endpoint (instead of `x-www-form-urlencoded`). Causes the setup to fail with "Tide realm setup failed" / "Could not set up the Tide realm". VERIFIED.
|
|
167
167
|
|
|
168
168
|
### toggle-iga
|
|
169
169
|
|
|
@@ -173,16 +173,21 @@ Order matters. Do not skip or reorder steps.
|
|
|
173
173
|
|
|
174
174
|
### Approve/commit change requests
|
|
175
175
|
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
176
|
+
Current `/iga/change-requests/...` surface (replaces legacy `/tide-admin/change-set/...`, GAP-065). **Full spec + bootstrap loop: `canon/iga-change-requests-api.md`.**
|
|
177
|
+
- List pending: `GET /admin/realms/{realm}/iga/change-requests?status=PENDING` (objects keyed by `id`)
|
|
178
|
+
- Authorize (sign): `POST /admin/realms/{realm}/iga/change-requests/{id}/authorize` (body `{}` optional; records an approval, does **not** commit; simple firstAdmin/Tideless lane — refuses multiAdmin CRs)
|
|
179
|
+
- Batch authorize (bootstrap): `POST /admin/realms/{realm}/iga/change-requests/bulk-authorize` with `{ "actionTypeIn": ["CREATE","DELETE"], "limit": 100 }`
|
|
180
|
+
- Approve (multiAdmin): `POST /admin/realms/{realm}/iga/change-requests/{id}/approve` (enclave lane; approves AND auto-commits once quorum met — the console "Authorize" button)
|
|
181
|
+
- Commit: `POST /admin/realms/{realm}/iga/change-requests/{id}/commit` (explicit apply-only; 412 if under threshold / unmet dependency)
|
|
182
|
+
- FirstAdmin/Tideless: authorize signs server-side. Tide MultiAdmin: use the `{id}/approval-model` enclave exchange, then `approve`.
|
|
183
|
+
|
|
184
|
+
> **Legacy-bootstrap note.** The bootstrap init script (`init_tidecloak.sh`) on the keycloak-IGA fork still drives the **legacy** `/tide-admin/change-set/*` surface (`sign`/`commit`, and `/sign/batch` + `/commit/batch` with `{ "changeSets": [...] }`). That surface is stale — it is kept here only to explain what the existing script calls; reach for `/iga/change-requests/*` in new integration code.
|
|
180
185
|
|
|
181
186
|
### Adapter config export
|
|
182
187
|
|
|
183
188
|
- Path: `GET /admin/realms/{realm}/vendorResources/get-installations-provider`
|
|
184
189
|
- Query: `clientId={uuid}&providerId=keycloak-oidc-keycloak-json`
|
|
185
|
-
- The `jwk` field is only
|
|
190
|
+
- The `jwk` field is injected only when a `tide-vendor-key` component and its EdDSA signing key exist on the realm. `setUpTideRealm` creates both, so `jwk` appears once licensing has run. It is **not** gated on IGA. If `jwk` is missing, the remedy is to ensure `setUpTideRealm` ran successfully (not to enable IGA).
|
|
186
191
|
|
|
187
192
|
---
|
|
188
193
|
|
|
@@ -249,7 +254,7 @@ When bootstrapping realms programmatically on a hosted TideCloak instance (e.g.,
|
|
|
249
254
|
|
|
250
255
|
### Two-Phase Provisioning
|
|
251
256
|
|
|
252
|
-
Role assignment MUST happen after the admin user links their Tide account. IGA change requests require a Tide doken to be signable. Without a linked identity, the
|
|
257
|
+
Role assignment MUST happen after the admin user links their Tide account. IGA change requests require a Tide doken to be signable. Without a linked identity, the authorize endpoint returns 200 but the change request remains unsigned/uncommitted permanently.
|
|
253
258
|
|
|
254
259
|
**Phase A** (automated, before user interaction):
|
|
255
260
|
Steps 1–9 from the standard sequence, EXCEPT step 8c (role assignment). Generate the invite link at the end.
|
|
@@ -35,21 +35,16 @@ GET /realms/{realm}/public/get-tide-config?clientId={clientId}
|
|
|
35
35
|
|
|
36
36
|
Public endpoint. Returns `vendorId`, `homeOrkUrl`, client auth origins, and branding URLs for a client. No auth required.
|
|
37
37
|
|
|
38
|
-
###
|
|
38
|
+
### Role / Encryption Policies
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
GET /realms/{realm}/tide-policy-resources/admin-policy
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Public endpoint. Returns the base64-encoded admin policy needed for policy-based encryption operations. Also available as `/admin-policy/bytes` (raw) and `/admin-policy/display` (human-readable).
|
|
45
|
-
|
|
46
|
-
### Role Policy
|
|
40
|
+
Signed role/encryption policies are managed through the admin IGA surface:
|
|
47
41
|
|
|
48
42
|
```
|
|
49
|
-
GET /realms/{realm}/
|
|
43
|
+
GET /admin/realms/{realm}/iga/role-policies
|
|
44
|
+
GET /admin/realms/{realm}/iga/role-policies/name/{name}
|
|
50
45
|
```
|
|
51
46
|
|
|
52
|
-
|
|
47
|
+
Each role-policy record carries the signed `policy` bytes and `policySig` used by policy-based encryption/decryption. The older public `tide-policy-resources/admin-policy` and `.../policy` endpoints are not present on current main (`tidecloak-iga-extensions`); see `canon/custom-contracts.md`.
|
|
53
48
|
|
|
54
49
|
### Vouchers
|
|
55
50
|
|
|
@@ -73,20 +68,18 @@ GET /realms/{realm}/tide-idp-resources/clientIframe/iss/{iss}/aud/{aud}/tide_dpo
|
|
|
73
68
|
|
|
74
69
|
Serves the DPoP authentication iframe HTML. `iss` and `aud` are hex-encoded. The SDK uses this internally.
|
|
75
70
|
|
|
76
|
-
### Token Exchange (
|
|
71
|
+
### Token Exchange (RFC 8693)
|
|
77
72
|
|
|
78
73
|
```
|
|
79
74
|
POST /realms/{realm}/protocol/openid-connect/token
|
|
80
75
|
```
|
|
81
76
|
|
|
82
|
-
Standard OIDC token endpoint used for
|
|
77
|
+
Standard OIDC token endpoint used for RFC 8693 token exchange (server-to-server, e.g. a backend calling a downstream protected service on behalf of the user):
|
|
83
78
|
- `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`
|
|
84
79
|
- `subject_token=<user access token>`
|
|
85
|
-
- `actor_token=<
|
|
86
|
-
- `dpop_approval=<session key approval>`
|
|
87
|
-
- `DPoP` header: server's DPoP proof
|
|
80
|
+
- `actor_token=<acting-party token>` (optional)
|
|
88
81
|
|
|
89
|
-
|
|
82
|
+
For the shipped .NET wiring, see `playbooks/protect-aspnet-core-asgard.md` (Step 5). Browser-driven server-side delegation (the asgard-tide Node SDK, the same one keylessh uses) is unmerged and intentionally not documented here.
|
|
90
83
|
|
|
91
84
|
---
|
|
92
85
|
|
|
@@ -134,113 +127,82 @@ Generates a link for the admin user to link their Tide account. Time-limited (de
|
|
|
134
127
|
|
|
135
128
|
---
|
|
136
129
|
|
|
137
|
-
## IGA Change-
|
|
130
|
+
## IGA Change-Request Endpoints
|
|
138
131
|
|
|
139
|
-
Used for multi-admin governance approval flows.
|
|
132
|
+
Used for multi-admin governance approval flows. **Current surface: `/iga/change-requests/...`** (iga-core), confirmed by Tide 2026-07-07 (GAP-065). This **replaces** the legacy `/tide-admin/change-set/...` surface. Full spec, payloads, status codes, and the bootstrap approve/commit loop: **`canon/iga-change-requests-api.md`**.
|
|
140
133
|
|
|
141
|
-
### List
|
|
134
|
+
### List / get change requests
|
|
142
135
|
|
|
143
136
|
```
|
|
144
|
-
GET /admin/realms/{realm}/
|
|
145
|
-
GET /admin/realms/{realm}/
|
|
146
|
-
GET /admin/realms/{realm}/tide-admin/change-set/clients/requests
|
|
147
|
-
GET /admin/realms/{realm}/tide-admin/change-set/groups/requests
|
|
148
|
-
GET /admin/realms/{realm}/tide-admin/change-set/all/requests
|
|
149
|
-
GET /admin/realms/{realm}/tide-admin/change-set/counts
|
|
137
|
+
GET /admin/realms/{realm}/iga/change-requests?status=PENDING # PENDING|APPROVED|DENIED|CANCELLED
|
|
138
|
+
GET /admin/realms/{realm}/iga/change-requests/{id}
|
|
150
139
|
```
|
|
151
140
|
|
|
152
|
-
|
|
141
|
+
Each CR object is keyed by `id` (replaces legacy `draftRecordId`), with `entityType`, `actionType`, `status`, `readyToCommit`, `threshold`, `authorizers[]`, `dependsOn`/`blocked`.
|
|
153
142
|
|
|
154
|
-
|
|
155
|
-
POST /admin/realms/{realm}/tide-admin/change-set/sign
|
|
156
|
-
Body: { "changeSetId": "...", "changeSetType": "...", "actionType": "..." }
|
|
143
|
+
### Authorize (sign) and Commit
|
|
157
144
|
|
|
158
|
-
|
|
159
|
-
Body: {
|
|
145
|
+
```
|
|
146
|
+
POST /admin/realms/{realm}/iga/change-requests/{id}/authorize Body: {} (optional) → 200/403/409
|
|
147
|
+
POST /admin/realms/{realm}/iga/change-requests/{id}/commit → 200/412
|
|
160
148
|
```
|
|
161
149
|
|
|
162
|
-
Batch
|
|
150
|
+
Batch approve (bootstrap): `POST /admin/realms/{realm}/iga/change-requests/bulk-authorize` with `{ "actionTypeIn": ["CREATE","DELETE"], "limit": 100 }` (429 if a bulk run is already in progress). Bulk authorizes but does not commit — commit the CRs that become `readyToCommit`.
|
|
163
151
|
|
|
164
|
-
###
|
|
152
|
+
### Deny
|
|
165
153
|
|
|
166
154
|
```
|
|
167
|
-
POST /admin/realms/{realm}/
|
|
168
|
-
Body: { "changeSetId": "...", "changeSetType": "...", "actionType": "..." }
|
|
155
|
+
POST /admin/realms/{realm}/iga/change-requests/{id}/deny → 204
|
|
169
156
|
```
|
|
170
157
|
|
|
171
|
-
###
|
|
158
|
+
### Two-phase approval (Tide MultiAdmin, enclave)
|
|
172
159
|
|
|
173
160
|
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
POST /admin/realms/{realm}/tide-admin/add-rejection
|
|
179
|
-
Content-Type: multipart/form-data
|
|
180
|
-
Fields: changeSetId, actionType, changeSetType
|
|
161
|
+
GET /admin/realms/{realm}/iga/change-requests/{id}/approval-model → { changeRequestId, requestModel(base64) }
|
|
162
|
+
POST /admin/realms/{realm}/iga/change-requests/{id}/approval-model Body: { "requestModel": "<base64 doken>" }
|
|
163
|
+
→ { recorded, authCount, threshold }
|
|
181
164
|
```
|
|
182
165
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
### Activity and Comments
|
|
166
|
+
### Comments and ADOPT
|
|
186
167
|
|
|
187
168
|
```
|
|
188
|
-
GET
|
|
189
|
-
|
|
190
|
-
Body: { "
|
|
169
|
+
GET/POST /admin/realms/{realm}/iga/change-requests/{id}/comments Body: { "comment": "..." } (≤2000)
|
|
170
|
+
PUT/DELETE /admin/realms/{realm}/iga/change-requests/{id}/comments/{commentId}
|
|
171
|
+
POST /admin/realms/{realm}/iga/adopt Body: { "entityType": "USER", "entityId": "..." } → 201/409
|
|
172
|
+
GET /admin/realms/{realm}/iga/change-requests/{id}/diagnostic-bundle
|
|
191
173
|
```
|
|
192
174
|
|
|
175
|
+
**Legacy → current:** `change-set/{type}/requests` → `iga/change-requests?status=PENDING`; `change-set/sign[/batch]` → `iga/change-requests/{id}/authorize` (+ `bulk-authorize`); `change-set/commit[/batch]` → `iga/change-requests/{id}/commit`; `change-set/cancel` → `.../{id}/deny`; `tideAdminResources/add-review` → `.../{id}/approval-model`; field `draftRecordId` → `id`. Enabling IGA is unchanged: `POST /admin/realms/{realm}/tide-admin/toggle-iga`.
|
|
176
|
+
|
|
193
177
|
---
|
|
194
178
|
|
|
195
179
|
## Policy and Contract Endpoints
|
|
196
180
|
|
|
197
|
-
|
|
181
|
+
Served under `@Path("iga")`. (The older `tide-admin/policy-templates`, `tide-admin/ssh-policies`, `tide-admin/realm-policy`, and `role-policy/{id}/init-cert` endpoints no longer exist on main.)
|
|
198
182
|
|
|
199
|
-
|
|
200
|
-
POST /admin/realms/{realm}/tide-admin/policy-templates
|
|
201
|
-
GET /admin/realms/{realm}/tide-admin/policy-templates
|
|
202
|
-
PUT /admin/realms/{realm}/tide-admin/policy-templates/{id}
|
|
203
|
-
DELETE /admin/realms/{realm}/tide-admin/policy-templates/{id}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Body: `{ name, description, contractCode, modelId, parameters }`
|
|
207
|
-
|
|
208
|
-
### Realm Policy
|
|
183
|
+
### Role Policies
|
|
209
184
|
|
|
210
185
|
```
|
|
211
|
-
GET /admin/realms/{realm}/
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
POST /admin/realms/{realm}/
|
|
215
|
-
|
|
216
|
-
DELETE /admin/realms/{realm}/
|
|
186
|
+
GET /admin/realms/{realm}/iga/role-policies
|
|
187
|
+
GET /admin/realms/{realm}/iga/role-policies/{id}
|
|
188
|
+
GET /admin/realms/{realm}/iga/role-policies/name/{name}
|
|
189
|
+
POST /admin/realms/{realm}/iga/role-policies
|
|
190
|
+
DELETE /admin/realms/{realm}/iga/role-policies/{id}
|
|
191
|
+
DELETE /admin/realms/{realm}/iga/role-policies/name/{name}
|
|
217
192
|
```
|
|
218
193
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
```
|
|
222
|
-
PUT /admin/realms/{realm}/tide-admin/ssh-policies
|
|
223
|
-
GET /admin/realms/{realm}/tide-admin/ssh-policies
|
|
224
|
-
DELETE /admin/realms/{realm}/tide-admin/ssh-policies?roleId={id}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Body: `{ roleId, contractCode, approvalType, executionType, threshold, policyData }`
|
|
194
|
+
POST body (`IgaRolePolicyRepresentation`): `{ name, policy, policySig, contractId, approvalType, executionType, threshold, policyData }`. `name`, `policy`, and `policySig` are required (`policySig` ≤ 512 chars).
|
|
228
195
|
|
|
229
196
|
### Forseti Contracts
|
|
230
197
|
|
|
231
198
|
```
|
|
232
|
-
|
|
233
|
-
GET
|
|
199
|
+
GET /admin/realms/{realm}/iga/forseti-contracts
|
|
200
|
+
GET /admin/realms/{realm}/iga/forseti-contracts/{id}
|
|
201
|
+
POST /admin/realms/{realm}/iga/forseti-contracts
|
|
202
|
+
DELETE /admin/realms/{realm}/iga/forseti-contracts/{id}
|
|
234
203
|
```
|
|
235
204
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
### Role Init Cert
|
|
239
|
-
|
|
240
|
-
```
|
|
241
|
-
POST /admin/realms/{realm}/tide-admin/role-policy/{roleId}/init-cert
|
|
242
|
-
Body: { initCert, initCertSig }
|
|
243
|
-
```
|
|
205
|
+
POST body (`IgaForsetiContractRepresentation`): `{ contractCode, name }`. Returns the stored contract with its `contractHash` (SHA-512 of the source).
|
|
244
206
|
|
|
245
207
|
---
|
|
246
208
|
|
|
@@ -250,34 +212,34 @@ Infrastructure-level. Used during initial setup and license rotation.
|
|
|
250
212
|
|
|
251
213
|
```
|
|
252
214
|
POST /admin/realms/{realm}/vendorResources/generate-initial-key
|
|
253
|
-
POST /admin/realms/{realm}/vendorResources/generate-initial-vrk-v1
|
|
254
|
-
POST /admin/realms/{realm}/vendorResources/confirm-initial-vrk
|
|
255
|
-
POST /admin/realms/{realm}/vendorResources/rotate-vrk
|
|
256
215
|
POST /admin/realms/{realm}/vendorResources/switch-vrk
|
|
257
|
-
POST /admin/realms/{realm}/vendorResources/requestFreeTierLicense
|
|
258
216
|
GET /admin/realms/{realm}/vendorResources/isPendingLicenseActive
|
|
259
217
|
GET /admin/realms/{realm}/vendorResources/getLicenseDetails
|
|
260
218
|
GET /admin/realms/{realm}/vendorResources/licenseHistory
|
|
261
219
|
```
|
|
262
220
|
|
|
221
|
+
Initial license issuance is driven by `setUpTideRealm` (free-tier); ongoing license drafts/trigger/history are handled by the IGA licensing endpoints (`/iga/licensing/*`).
|
|
222
|
+
|
|
263
223
|
---
|
|
264
224
|
|
|
265
225
|
## Utility Endpoints
|
|
266
226
|
|
|
267
227
|
```
|
|
268
|
-
POST /admin/realms/{realm}/vendorResources/sign-message
|
|
269
|
-
FormParam: data
|
|
270
|
-
|
|
271
228
|
GET /admin/realms/{realm}/vendorResources/get-tide-jwk
|
|
229
|
+
```
|
|
272
230
|
|
|
273
|
-
|
|
274
|
-
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Other IGA Endpoints
|
|
275
234
|
|
|
276
|
-
|
|
277
|
-
Body: { changeSetType, actionType, userId, clientId }
|
|
235
|
+
Also served under `@Path("iga")` / `@Path("iga-tve")` (plus a `tide-admin` user-context read):
|
|
278
236
|
|
|
279
|
-
|
|
280
|
-
GET /admin/realms/{realm}/
|
|
237
|
+
```
|
|
238
|
+
GET|POST|DELETE /admin/realms/{realm}/iga/authorizers[/{id}] # realm authorizer (firstAdmin/multiAdmin) config
|
|
239
|
+
GET|POST /admin/realms/{realm}/iga/server-certs[...] # server-identity / mTLS cert issuance (request/issue/revoke/active/instance)
|
|
240
|
+
GET|POST /admin/realms/{realm}/iga/licensing/[...] # license drafts / trigger / history
|
|
241
|
+
POST /admin/realms/{realm}/iga-tve/tve-bundle # TVE attestation-unit bundle export (CBOR/JSON)
|
|
242
|
+
GET /admin/realms/{realm}/tide-admin/user-context/{userId}/{clientId} # effective user context (roles/claims) for a client
|
|
281
243
|
```
|
|
282
244
|
|
|
283
245
|
---
|