@tideorg/mcp 1.4.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/GAP_REGISTER.md +117 -0
- package/README.md +38 -0
- package/adapters/AGENTS.md +241 -0
- package/adapters/CLAUDE.md +146 -0
- package/adapters/replit.md +224 -0
- package/canon/anti-patterns.md +2133 -0
- package/canon/concepts.md +816 -0
- package/canon/custom-contracts.md +533 -0
- package/canon/delegation.md +195 -0
- package/canon/feature-mapping.md +637 -0
- package/canon/framework-matrix.md +1125 -0
- package/canon/invariants.md +851 -0
- package/canon/redirect-handler.md +254 -0
- package/canon/tidecloak-bootstrap.md +287 -0
- package/canon/tidecloak-endpoints.md +294 -0
- package/canon/troubleshooting.md +1494 -0
- package/canon/version-policy.md +89 -0
- package/mcp-server/dist/index.d.ts +2 -0
- package/mcp-server/dist/index.js +605 -0
- package/package.json +45 -0
- package/playbooks/add-auth-nextjs-existing.md +799 -0
- package/playbooks/add-auth-nextjs-fresh.md +654 -0
- package/playbooks/add-rbac-nextjs.md +190 -0
- package/playbooks/bootstrap-realm-from-template.md +170 -0
- package/playbooks/configure-e2ee-roles-and-policies.md +196 -0
- package/playbooks/deploy-tidecloak-docker.md +792 -0
- package/playbooks/diagnose-broken-login.md +234 -0
- package/playbooks/diagnose-missing-roles-or-claims.md +253 -0
- package/playbooks/initialize-admin-and-link-account.md +235 -0
- package/playbooks/migrate-from-existing-auth.md +198 -0
- package/playbooks/protect-api-nextjs.md +451 -0
- package/playbooks/protect-routes-nextjs.md +544 -0
- package/playbooks/setup-forseti-e2ee.md +756 -0
- package/playbooks/setup-iga-admin-panel.md +344 -0
- package/playbooks/setup-server-delegation.md +142 -0
- package/playbooks/start-tidecloak-dev.md +130 -0
- package/playbooks/verify-jwt-server-side.md +439 -0
- package/prompts/add-admin-approval-flow.md +50 -0
- package/prompts/build-private-customer-portal.md +85 -0
- package/prompts/migrate-generic-auth-to-tide.md +67 -0
- package/prompts/secure-existing-app.md +80 -0
- package/reference-apps/INDEX.md +87 -0
- package/reference-apps/encrypted-communication/anti-patterns.md +123 -0
- package/reference-apps/encrypted-communication/bootstrap-sequence.md +152 -0
- package/reference-apps/encrypted-communication/manifest.yaml +100 -0
- package/reference-apps/encrypted-communication/role-policy-matrix.md +80 -0
- package/reference-apps/encrypted-communication/scenario.md +134 -0
- package/reference-apps/git-pr-signing-service/anti-patterns.md +61 -0
- package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +157 -0
- package/reference-apps/git-pr-signing-service/manifest.yaml +80 -0
- package/reference-apps/git-pr-signing-service/role-policy-matrix.md +99 -0
- package/reference-apps/git-pr-signing-service/scenario.md +169 -0
- package/reference-apps/iga-admin-governance/anti-patterns.md +133 -0
- package/reference-apps/iga-admin-governance/bootstrap-sequence.md +153 -0
- package/reference-apps/iga-admin-governance/manifest.yaml +87 -0
- package/reference-apps/iga-admin-governance/role-policy-matrix.md +67 -0
- package/reference-apps/iga-admin-governance/scenario.md +126 -0
- package/reference-apps/organisation-password-manager/anti-patterns.md +71 -0
- package/reference-apps/organisation-password-manager/bootstrap-sequence.md +127 -0
- package/reference-apps/organisation-password-manager/manifest.yaml +86 -0
- package/reference-apps/organisation-password-manager/role-policy-matrix.md +59 -0
- package/reference-apps/organisation-password-manager/scenario.md +107 -0
- package/reference-apps/policy-governed-signing/anti-patterns.md +67 -0
- package/reference-apps/policy-governed-signing/bootstrap-sequence.md +128 -0
- package/reference-apps/policy-governed-signing/manifest.yaml +78 -0
- package/reference-apps/policy-governed-signing/role-policy-matrix.md +62 -0
- package/reference-apps/policy-governed-signing/scenario.md +113 -0
- package/skills/tide-diagnostics/SKILL.md +99 -0
- package/skills/tide-integration/SKILL.md +136 -0
- package/skills/tide-learning-capture/SKILL.md +174 -0
- package/skills/tide-rbac-and-e2ee/SKILL.md +214 -0
- package/skills/tide-reviewer/SKILL.md +133 -0
- package/skills/tide-route-and-api-protection/SKILL.md +201 -0
- package/skills/tide-scenario-resolver/SKILL.md +81 -0
- package/skills/tide-setup/SKILL.md +218 -0
- package/skills/tide-solutions-architect/SKILL.md +130 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Add Role-Based Access Control (RBAC) in Next.js
|
|
2
|
+
|
|
3
|
+
Implement role-based access control using Tide JWT roles.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- Need different access levels (admin, editor, viewer)
|
|
10
|
+
- Need fine-grained permissions
|
|
11
|
+
- Have configured roles in TideCloak realm
|
|
12
|
+
- Roles assigned to users via IGA
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
**Required — do not skip**:
|
|
19
|
+
|
|
20
|
+
1. API protection implemented: [protect-api-nextjs.md](protect-api-nextjs.md) **must be completed first**. That playbook creates the files this playbook imports (`lib/auth/tideJWT.ts`, `lib/auth/protect.ts`, `lib/auth/tidecloakConfig.ts`) and installs `jose`.
|
|
21
|
+
2. Roles created in TideCloak Admin Console
|
|
22
|
+
3. Users assigned roles (via IGA if enabled)
|
|
23
|
+
|
|
24
|
+
**Verify before proceeding**:
|
|
25
|
+
```bash
|
|
26
|
+
# These files must exist (created by protect-api-nextjs.md)
|
|
27
|
+
ls lib/auth/tideJWT.ts lib/auth/protect.ts lib/auth/tidecloakConfig.ts
|
|
28
|
+
# jose must be installed
|
|
29
|
+
grep '"jose"' package.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If any are missing, complete [protect-api-nextjs.md](protect-api-nextjs.md) first.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Role Storage in Tide
|
|
37
|
+
|
|
38
|
+
Roles appear in JWT token:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"realm_access": {
|
|
43
|
+
"roles": ["admin", "offline_access", "_tide_enabled"]
|
|
44
|
+
},
|
|
45
|
+
"resource_access": {
|
|
46
|
+
"my-client": {
|
|
47
|
+
"roles": ["manage-account"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Realm roles**: Global to realm (`realm_access.roles`)
|
|
54
|
+
**Client roles**: Specific to client (`resource_access.{client-id}.roles`)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Client-Side RBAC (UI Gating Only)
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// components/AdminPanel.tsx
|
|
62
|
+
'use client';
|
|
63
|
+
|
|
64
|
+
import { useTideCloak } from '@tidecloak/nextjs';
|
|
65
|
+
|
|
66
|
+
export function AdminPanel() {
|
|
67
|
+
const { hasRealmRole } = useTideCloak();
|
|
68
|
+
|
|
69
|
+
if (!hasRealmRole('admin')) {
|
|
70
|
+
return <div>Access denied</div>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<h1>Admin Panel</h1>
|
|
76
|
+
{/* admin content */}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Warning**: This is UI gating. Real enforcement happens server-side.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Server-Side RBAC (Real Authorization)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// app/api/admin/users/route.ts
|
|
90
|
+
import { withRole } from '@/lib/auth/protect';
|
|
91
|
+
|
|
92
|
+
// Requires 'admin' role
|
|
93
|
+
export const GET = withRole('admin', async (req, jwt) => {
|
|
94
|
+
return Response.json({ users: [...] });
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Multiple roles** (any match):
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// lib/auth/protect.ts
|
|
102
|
+
export function withAnyRole(roles: string[], handler: AuthenticatedHandler) {
|
|
103
|
+
return withAuth(async (req, jwt) => {
|
|
104
|
+
const hasAnyRole = roles.some(role => hasRole(jwt, role));
|
|
105
|
+
if (!hasAnyRole) {
|
|
106
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
107
|
+
}
|
|
108
|
+
return handler(req, jwt);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Usage
|
|
113
|
+
export const GET = withAnyRole(['admin', 'editor'], async (req, jwt) => {
|
|
114
|
+
return Response.json({ data: [...] });
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Role Hierarchies
|
|
121
|
+
|
|
122
|
+
TideCloak supports composite roles. Define in Admin Console:
|
|
123
|
+
|
|
124
|
+
1. Create `super-admin` role
|
|
125
|
+
2. Add `admin`, `editor`, `viewer` as composite roles
|
|
126
|
+
3. Assign `super-admin` to user → user inherits all roles
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## E2EE Role-Based Encryption
|
|
131
|
+
|
|
132
|
+
Tide E2EE uses tag-based roles for **self-encryption** (user-bound, private data):
|
|
133
|
+
|
|
134
|
+
**Role pattern**: `_tide_<tag>.selfencrypt`, `_tide_<tag>.selfdecrypt`
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Client-side E2EE with role enforcement (self-encryption)
|
|
138
|
+
const { doEncrypt, doDecrypt } = useTideCloak();
|
|
139
|
+
|
|
140
|
+
// Requires _tide_ssn.selfencrypt role
|
|
141
|
+
const ciphertext = await doEncrypt('ssn', '123-45-6789');
|
|
142
|
+
|
|
143
|
+
// Requires _tide_ssn.selfdecrypt role
|
|
144
|
+
const plaintext = await doDecrypt('ssn', ciphertext);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Fabric enforces roles cryptographically. See [canon/concepts.md#tag-based-e2ee-roles](../canon/concepts.md#tag-based-e2ee-roles).
|
|
148
|
+
|
|
149
|
+
**Self-encryption is user-bound**: only the encrypting user can decrypt. Giving another user the `selfdecrypt` role does NOT let them decrypt your data. For shared data between users, use policy-governed VVK encryption with a Forseti contract instead. See [setup-forseti-e2ee.md](setup-forseti-e2ee.md) and [canon/anti-patterns.md AP-24](../canon/anti-patterns.md#ap-24-using-self-encryption-for-shared-data-between-users).
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Verification Checklist
|
|
154
|
+
|
|
155
|
+
- [ ] Admin API rejects non-admin users (403)
|
|
156
|
+
- [ ] Role-based UI elements show/hide correctly
|
|
157
|
+
- [ ] Multiple roles (OR condition) work
|
|
158
|
+
- [ ] Composite roles inherit permissions
|
|
159
|
+
- [ ] E2EE roles enforce encrypt/decrypt access
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Common Failures
|
|
164
|
+
|
|
165
|
+
### Role Always Missing in JWT
|
|
166
|
+
|
|
167
|
+
**Fix**: See [diagnose-missing-roles-or-claims.md](diagnose-missing-roles-or-claims.md).
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Do Not Do This
|
|
172
|
+
|
|
173
|
+
### ❌ Do Not Mix Tide Roles with App Roles
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// ❌ WRONG: Confuse E2EE role with app admin role
|
|
177
|
+
if (hasRealmRole('_tide_ssn.selfencrypt')) {
|
|
178
|
+
// Grant admin access (WRONG)
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Why**: `_tide_*` roles are for E2EE, not app permissions. See [canon/anti-patterns.md AP-18](../canon/anti-patterns.md#ap-18-mixing-tide-roles-with-application-roles).
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## References
|
|
187
|
+
|
|
188
|
+
- [protect-api-nextjs.md](protect-api-nextjs.md) - API protection
|
|
189
|
+
- [canon/concepts.md](../canon/concepts.md#tag-based-e2ee-roles) - E2EE roles
|
|
190
|
+
- [diagnose-missing-roles-or-claims.md](diagnose-missing-roles-or-claims.md) - Debug roles
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Bootstrap Realm from Template
|
|
2
|
+
|
|
3
|
+
Create a TideCloak realm from the canonical template, initialize Tide licensing and IGA, and approve the initial client change requests.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- After `start-tidecloak-dev` — TideCloak is running but has no realm
|
|
10
|
+
- Automating realm setup instead of clicking through Admin Console
|
|
11
|
+
- Rebuilding a realm from scratch
|
|
12
|
+
|
|
13
|
+
**Do not use** if the realm already exists. Check: `curl -sf -H "Authorization: Bearer $TOKEN" http://localhost:8080/admin/realms/$REALM_NAME > /dev/null && echo "Exists"`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- TideCloak running and reachable (playbook `start-tidecloak-dev` complete)
|
|
20
|
+
- `curl`, `jq` installed
|
|
21
|
+
- `templates/shared/realm.json.template` available (or equivalent realm template)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Steps
|
|
26
|
+
|
|
27
|
+
### Step 1: Get master admin token
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
TIDECLOAK_URL="${TIDECLOAK_URL:-http://localhost:8080}"
|
|
31
|
+
REALM_NAME="${REALM_NAME:-myapp}"
|
|
32
|
+
CLIENT_NAME="${CLIENT_NAME:-myclient}"
|
|
33
|
+
CLIENT_APP_URL="${CLIENT_APP_URL:-http://localhost:3000}"
|
|
34
|
+
|
|
35
|
+
get_token() {
|
|
36
|
+
curl -s -X POST "$TIDECLOAK_URL/realms/master/protocol/openid-connect/token" \
|
|
37
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
38
|
+
-d "username=admin&password=password&grant_type=password&client_id=admin-cli" \
|
|
39
|
+
| jq -r '.access_token'
|
|
40
|
+
}
|
|
41
|
+
TOKEN="$(get_token)"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Step 2: Prepare realm template
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
TMP_REALM="$(mktemp)"
|
|
48
|
+
cp templates/shared/realm.json.template "$TMP_REALM"
|
|
49
|
+
sed -i "s|REALM_NAME|$REALM_NAME|g" "$TMP_REALM"
|
|
50
|
+
sed -i "s|CLIENT_NAME|$CLIENT_NAME|g" "$TMP_REALM"
|
|
51
|
+
sed -i "s|CLIENT_APP_URL|$CLIENT_APP_URL|g" "$TMP_REALM"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The template contains placeholders: `REALM_NAME`, `CLIENT_NAME`, `CLIENT_APP_URL`. All three must be replaced before import.
|
|
55
|
+
|
|
56
|
+
### Step 3: Import realm
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
TOKEN="$(get_token)"
|
|
60
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms" \
|
|
61
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
62
|
+
-H "Content-Type: application/json" \
|
|
63
|
+
--data-binary @"$TMP_REALM"
|
|
64
|
+
rm -f "$TMP_REALM"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 4: Initialize Tide realm
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
TOKEN="$(get_token)"
|
|
71
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/vendorResources/setUpTideRealm" \
|
|
72
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
73
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
74
|
+
--data-urlencode "email=admin@yourorg.com" \
|
|
75
|
+
--data-urlencode "isRagnarokEnabled=true"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This activates the Tide license and generates the VRK. No manual "Manage License" step needed.
|
|
79
|
+
|
|
80
|
+
**Critical:** Content-Type must be `application/x-www-form-urlencoded`. JSON causes failure.
|
|
81
|
+
|
|
82
|
+
### Step 5: Enable IGA
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
TOKEN="$(get_token)"
|
|
86
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/toggle-iga" \
|
|
87
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
88
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
89
|
+
--data-urlencode "isIGAEnabled=true"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Step 6: Approve and commit client change requests
|
|
93
|
+
|
|
94
|
+
Realm creation + Tide setup generates change requests that must be approved before proceeding.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
approve_and_commit() {
|
|
98
|
+
local TYPE=$1
|
|
99
|
+
TOKEN="$(get_token)"
|
|
100
|
+
local requests=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/$TYPE/requests" \
|
|
101
|
+
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "[]")
|
|
102
|
+
|
|
103
|
+
echo "$requests" | jq -c '.[]' | while read -r req; do
|
|
104
|
+
local id=$(echo "$req" | jq -r .draftRecordId)
|
|
105
|
+
local cst=$(echo "$req" | jq -r .changeSetType)
|
|
106
|
+
local at=$(echo "$req" | jq -r .actionType)
|
|
107
|
+
local payload="{\"changeSetId\":\"$id\",\"changeSetType\":\"$cst\",\"actionType\":\"$at\"}"
|
|
108
|
+
|
|
109
|
+
TOKEN="$(get_token)"
|
|
110
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/sign" \
|
|
111
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
112
|
+
-H "Content-Type: application/json" -d "$payload"
|
|
113
|
+
|
|
114
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/commit" \
|
|
115
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
116
|
+
-H "Content-Type: application/json" -d "$payload"
|
|
117
|
+
done
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
approve_and_commit clients
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Verification
|
|
126
|
+
|
|
127
|
+
- [ ] `curl -H "Authorization: Bearer $TOKEN" "$TIDECLOAK_URL/admin/realms/$REALM_NAME"` returns realm JSON
|
|
128
|
+
- [ ] Realm has Tide IdP listed: `curl -H "Authorization: Bearer $TOKEN" "$TIDECLOAK_URL/admin/realms/$REALM_NAME/identity-provider/instances"` includes `tide`
|
|
129
|
+
- [ ] IGA is enabled
|
|
130
|
+
- [ ] No pending client change requests remain
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Common Failures
|
|
135
|
+
|
|
136
|
+
| Symptom | Cause | Fix |
|
|
137
|
+
|---------|-------|-----|
|
|
138
|
+
| "Could not create tide realm" | JSON sent to `setUpTideRealm` instead of form data | Use `application/x-www-form-urlencoded` |
|
|
139
|
+
| "email is null or empty" | Missing email parameter | Add `--data-urlencode "email=..."` |
|
|
140
|
+
| 404 on setUpTideRealm | Wrong path (`/tide-admin/setUpTideRealm`) | Correct path: `/vendorResources/setUpTideRealm` |
|
|
141
|
+
| Realm import fails | Placeholders not replaced in template | Verify `sed` replaced all three: `REALM_NAME`, `CLIENT_NAME`, `CLIENT_APP_URL` |
|
|
142
|
+
| Missing `link-tide-account-action` | Minimal realm template used | Use full template from `templates/shared/realm.json.template` |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Anti-Patterns
|
|
147
|
+
|
|
148
|
+
- **Do not** create a minimal realm.json. Every section in the template is required.
|
|
149
|
+
- **Do not** skip IGA enablement. The `jwk` field in adapter JSON only appears with IGA enabled.
|
|
150
|
+
- **Do not** reorder steps. License before IGA. IGA before change request approval.
|
|
151
|
+
- **Do not** leave license activation as a manual step. `setUpTideRealm` handles it automatically.
|
|
152
|
+
- **Do not** blindly set `registrationAllowed: true`. This is a deployment decision — open registration lets any user sign up via Tide IdP. For invite-only apps, keep it `false` and use admin invite links (`tideAdminResources/get-required-action-link`). The admin invite link bypasses the registration gate. VERIFIED (LEARNINGS-batch-008 L-01).
|
|
153
|
+
- **Do not** use the standard Keycloak adapter export path (`/clients/{id}/installation/providers/...`). Use `vendorResources/get-installations-provider?clientId={uuid}&providerId=keycloak-oidc-keycloak-json`. The standard path returns a minimal adapter missing `jwk`, `vendorId`, `homeOrkUrl`. VERIFIED (LEARNINGS-batch-008 L-04).
|
|
154
|
+
|
|
155
|
+
### Realm Deletion on Dev Image (H2)
|
|
156
|
+
|
|
157
|
+
If re-running bootstrap against an existing realm, delete the old realm first. On the H2 dev database, `DELETE /admin/realms/{realm}` fails with FK constraint violations on composite roles. Use this sequence:
|
|
158
|
+
|
|
159
|
+
1. Disable IGA: `POST /tide-admin/toggle-iga` with `isIGAEnabled=false`
|
|
160
|
+
2. Delete Tide IdP: `DELETE /identity-provider/instances/tide`
|
|
161
|
+
3. Strip ALL composite roles (realm-level and per-client) — remove composites from `default-roles-*`, `realm-admin`, `manage-account`, etc.
|
|
162
|
+
4. Delete realm: `DELETE /admin/realms/{realm}`
|
|
163
|
+
|
|
164
|
+
Or: stop the container, delete `data/keycloakdb*`, restart. VERIFIED (LEARNINGS-batch-008 L-06).
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Next Step
|
|
169
|
+
|
|
170
|
+
Proceed to playbook `initialize-admin-and-link-account`.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Configure E2EE Roles and Policies
|
|
2
|
+
|
|
3
|
+
Create encryption/decryption realm roles, assign them to default users, and approve the resulting change requests. Optionally deploy a Forseti contract for shared (policy-governed) encryption.
|
|
4
|
+
|
|
5
|
+
This is realm-level preparation. App-level E2EE wiring (`doEncrypt`/`doDecrypt`) is covered in playbook `setup-forseti-e2ee` and skill `tide-rbac-and-e2ee`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
- After `initialize-admin-and-link-account` — admin linked, adapter exported
|
|
12
|
+
- App needs self-encryption (private data, only the encrypting user can decrypt)
|
|
13
|
+
- App needs shared encryption (policy-governed, multiple users can decrypt)
|
|
14
|
+
|
|
15
|
+
**Do not use** if E2EE roles already exist. Check: `curl -H "Authorization: Bearer $TOKEN" "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" | jq '.[].name' | grep '_tide_'`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- TideCloak running with realm fully bootstrapped (all three prior playbooks complete)
|
|
22
|
+
- Admin has linked Tide account
|
|
23
|
+
- Decision: which data tags to protect (e.g., `secret`, `medical`, `vault`)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Two Encryption Models
|
|
28
|
+
|
|
29
|
+
Choose the right model before creating roles.
|
|
30
|
+
|
|
31
|
+
| Model | SDK method | Who can decrypt | Roles needed |
|
|
32
|
+
|-------|-----------|----------------|--------------|
|
|
33
|
+
| Self-encryption | `doEncrypt`/`doDecrypt` (no policy) | Only the encrypting user | `_tide_{tag}.selfencrypt`, `_tide_{tag}.selfdecrypt` |
|
|
34
|
+
| Policy-governed (VVK) | `IAMService.doEncrypt(data, policyBytes)` | Any user whose doken satisfies the Forseti contract | `_tide_{tag}.selfencrypt`, `_tide_{tag}.selfdecrypt` (voucher gates) + a regular role for the contract |
|
|
35
|
+
|
|
36
|
+
Self-encryption is simpler. Start here unless you explicitly need sharing.
|
|
37
|
+
|
|
38
|
+
**After completing this playbook**: Self-encryption works immediately. Shared encryption does NOT — it additionally requires the admin signing ceremony from `setup-forseti-e2ee`. That ceremony is browser-only (cannot be scripted) and must happen before any user can encrypt shared data. If the app offers a "shared" mode, it must be blocked/gated until the signing ceremony is complete. VERIFIED (session-002).
|
|
39
|
+
|
|
40
|
+
**Anti-pattern:** Using self-encryption (`doEncrypt`/`doDecrypt` without policy) for shared data. Ciphertext is bound to the encrypting user's identity. Another user with the same `selfdecrypt` role cannot decrypt it. VERIFIED.
|
|
41
|
+
|
|
42
|
+
**Anti-pattern:** Renaming `selfencrypt`/`selfdecrypt` to `encrypt`/`decrypt` to "enable sharing". The role suffix does not change the encryption model. The SDK call path determines self vs shared. See AP-26.
|
|
43
|
+
|
|
44
|
+
**If self-encryption fails**, fix the self-encryption setup (roles, IGA approval, re-login). Do not switch models as a "fix". See T-13.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Steps: Self-Encryption Roles
|
|
49
|
+
|
|
50
|
+
### Step 1: Add roles to realm template
|
|
51
|
+
|
|
52
|
+
Add role entries for each tag to `templates/shared/realm.json.template` under `roles.realm`, and include them in the default composite role:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{ "name": "_tide_secret.selfencrypt", "description": "Encrypt data with tag: secret" },
|
|
56
|
+
{ "name": "_tide_secret.selfdecrypt", "description": "Decrypt data with tag: secret" }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Update the default composite:
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"name": "default-roles-REALM_NAME",
|
|
63
|
+
"composites": { "realm": ["_tide_enabled", "appUser", "_tide_secret.selfencrypt", "_tide_secret.selfdecrypt"] }
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
See `templates/shared/role-matrix.md` for the full role reference.
|
|
68
|
+
|
|
69
|
+
### Step 2: Create roles via API (existing realm)
|
|
70
|
+
|
|
71
|
+
If the realm already exists (no template re-import), create roles via API:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
TOKEN="$(get_token)"
|
|
75
|
+
for ROLE in "_tide_secret.selfencrypt" "_tide_secret.selfdecrypt"; do
|
|
76
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" \
|
|
77
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
78
|
+
-H "Content-Type: application/json" \
|
|
79
|
+
-d "{\"name\":\"$ROLE\",\"description\":\"E2EE role for tag: secret\"}"
|
|
80
|
+
done
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 3: Assign roles to default composite
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
TOKEN="$(get_token)"
|
|
87
|
+
DEFAULT_ROLE_ID=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles/default-roles-$REALM_NAME" \
|
|
88
|
+
-H "Authorization: Bearer $TOKEN" | jq -r '.id')
|
|
89
|
+
|
|
90
|
+
ROLES_JSON=$(curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" \
|
|
91
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
92
|
+
| jq '[.[] | select(.name | startswith("_tide_secret."))]')
|
|
93
|
+
|
|
94
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles-by-id/$DEFAULT_ROLE_ID/composites" \
|
|
95
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
96
|
+
-H "Content-Type: application/json" \
|
|
97
|
+
-d "$ROLES_JSON"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Step 4: Approve change requests
|
|
101
|
+
|
|
102
|
+
Role assignments produce IGA change requests that must be approved:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
approve_and_commit roles
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
(Use the `approve_and_commit` function from prior playbooks.)
|
|
109
|
+
|
|
110
|
+
### Step 5: Users must re-login
|
|
111
|
+
|
|
112
|
+
After roles are committed, users must log out and back in (or call `forceUpdateToken()`) for the new roles to appear in their doken. Token refresh delay is up to 120 seconds.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Steps: Policy-Governed Encryption (Shared)
|
|
117
|
+
|
|
118
|
+
For shared data where multiple users need to decrypt, you need voucher gate roles AND a regular role checked by a Forseti contract.
|
|
119
|
+
|
|
120
|
+
### Step 1: Create all three role types
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
TOKEN="$(get_token)"
|
|
124
|
+
for ROLE in "_tide_shared.selfencrypt" "_tide_shared.selfdecrypt" "shared-data-access"; do
|
|
125
|
+
curl -s -X POST "$TIDECLOAK_URL/admin/realms/$REALM_NAME/roles" \
|
|
126
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
127
|
+
-H "Content-Type: application/json" \
|
|
128
|
+
-d "{\"name\":\"$ROLE\"}"
|
|
129
|
+
done
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Role | Purpose |
|
|
133
|
+
|------|---------|
|
|
134
|
+
| `_tide_shared.selfencrypt` | Voucher gate — enables `vendorsign` action at ORK |
|
|
135
|
+
| `_tide_shared.selfdecrypt` | Voucher gate — enables `vendordecrypt` action at ORK |
|
|
136
|
+
| `shared-data-access` | Forseti contract checks this for actual access control |
|
|
137
|
+
|
|
138
|
+
Add all three to the default composite role.
|
|
139
|
+
|
|
140
|
+
### Step 2: Forseti contract deployment (browser signing flow only)
|
|
141
|
+
|
|
142
|
+
**There is no REST API for deploying Forseti contracts.** The endpoint `PUT /tide-admin/forseti-contracts` does not exist on the dev or staging image (returns 404/405). VERIFIED (learning-batch-003, L-06; session-001, RB-010).
|
|
143
|
+
|
|
144
|
+
The contract is deployed as part of the PolicySignRequest flow in the admin's browser:
|
|
145
|
+
1. Embed the contract C# source code in the admin page
|
|
146
|
+
2. Compute the SHA-512 hash client-side as the `contractId`
|
|
147
|
+
3. Use `policyRequest.addForsetiContractToUpload(contractSource)` during the signing flow
|
|
148
|
+
|
|
149
|
+
The contract source code is included in the signing request and uploaded to the ORKs automatically. No prior deployment step is needed.
|
|
150
|
+
|
|
151
|
+
See `setup-forseti-e2ee.md` for the full browser-based signing flow.
|
|
152
|
+
|
|
153
|
+
### Step 3: Policy signing (frontend only)
|
|
154
|
+
|
|
155
|
+
Policy signing requires the admin's browser enclave. It cannot be done from a script.
|
|
156
|
+
|
|
157
|
+
The admin must:
|
|
158
|
+
1. Log in to the app as `tide-realm-admin`
|
|
159
|
+
2. Sign the role policy (init-cert) via approval popup
|
|
160
|
+
3. Sign the encryption policy via approval popup
|
|
161
|
+
|
|
162
|
+
Both policies must be signed before the group is usable. See playbook `setup-forseti-e2ee` for the full frontend flow.
|
|
163
|
+
|
|
164
|
+
**Anti-pattern:** Trying to sign policies from the init script or backend. Signing requires `requiresApprovalPopup: true` → browser enclave. The script can prepare (deploy contracts, attach policies), but signing must happen from the frontend. VERIFIED.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Verification
|
|
169
|
+
|
|
170
|
+
- [ ] `_tide_{tag}.selfencrypt` and `_tide_{tag}.selfdecrypt` roles exist in realm
|
|
171
|
+
- [ ] Roles are in the default composite (or assigned to target users)
|
|
172
|
+
- [ ] All role change requests approved and committed
|
|
173
|
+
- [ ] (Self-encryption) `doEncrypt`/`doDecrypt` works in app after user re-login
|
|
174
|
+
- [ ] (Shared encryption) Forseti contract deployed, both policies signed by admin
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Common Failures
|
|
179
|
+
|
|
180
|
+
| Symptom | Cause | Fix |
|
|
181
|
+
|---------|-------|-----|
|
|
182
|
+
| `doEncrypt` throws "unauthorized" | User lacks `_tide_{tag}.selfencrypt` role | Create and assign the role, approve via IGA |
|
|
183
|
+
| "vendordecrypt voucher not allowed" | User lacks `_tide_{tag}.selfdecrypt` role | Same — create, assign, approve |
|
|
184
|
+
| "Policy supplied has not been signed" | Admin policy not signed via browser enclave | Admin must sign both policies from frontend |
|
|
185
|
+
| Decrypt fails for another user (self-encryption) | Self-encryption binds to encrypting user | Use policy-governed VVK encryption for sharing |
|
|
186
|
+
| Roles assigned but encrypt still fails | User hasn't refreshed token | Log out/in or call `forceUpdateToken()` |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Anti-Patterns
|
|
191
|
+
|
|
192
|
+
- **Do not** assign decrypt roles to all users by default for shared data. Encrypt roles can be broad; decrypt roles are your access control boundary.
|
|
193
|
+
- **Do not** encrypt everything with the same tag. Different sensitivity levels should use different tags mapped to different roles.
|
|
194
|
+
- **Do not** skip tags on encrypted fields. Tags define who can decrypt.
|
|
195
|
+
- **Do not** use the built-in `GenericRealmAccessThresholdRole` contract for production. Deploy a custom Forseti contract.
|
|
196
|
+
- **Do not** use `_tide_*` roles in your Forseti contract's `ValidateExecutor`. Those are voucher gates only. Use a regular realm role for access control.
|