Skip to content

Commit dd18f7d

Browse files
committed
feat(identity): enable field selector support for staff users
Pass field selectors from REST layer to backend provider to enable staff users to query other users' identities and sessions. This is required for the staff portal to view user identity provider links and active sessions for support purposes. Changes: - Pass field selector from ListOptions to backend in useridentities - Pass field selector from ListOptions to backend in sessions - Update README documentation with field selector usage and security model - Add test documentation for field selector authorization scenarios Authorization model: 1. Milo RBAC: PolicyBinding grants access to identity resources 2. Backend authorization: Provider validates group membership - Regular users: Can only see their own data (no field selector) - Staff users: Can use field selectors to query other users - Groups: staff-users, fraud-manager (configured in Zitadel) Security: - Backward compatible: Regular users unaffected - Defense in depth: Two layers of authorization - Audit logging: All requests logged with user context - Explicit deny: Non-staff users cannot use field selectors This changes the previous 'self-scoped' design where field selectors were intentionally ignored. The new behavior maintains self-scoped access for regular users while enabling cross-user queries for staff. Related: datum-cloud/staff-portal (staff user authorization) Related: datum-cloud/auth-provider-zitadel (backend authorization)
1 parent 43e28be commit dd18f7d

5 files changed

Lines changed: 183 additions & 2 deletions

File tree

internal/apiserver/identity/sessions/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,30 @@ Naming & structure
3838
- internal/apiserver/identity/sessions/rest.go — REST storage
3939
- internal/apiserver/identity/sessions/dynamic.go — provider implementation
4040

41+
Field Selector Support for Staff Users
42+
The Sessions API supports field selectors to enable staff users to query
43+
other users' active sessions. This is required for support and administrative
44+
purposes in the staff portal.
45+
46+
Supported field selectors:
47+
- status.userUID=<user-id> — Query sessions for a specific user
48+
49+
Authorization:
50+
- Regular users: Can only list their own sessions (field selectors are ignored)
51+
- Staff users: Can use field selectors to query other users' sessions
52+
- Must be members of privileged groups in the identity provider (e.g., staff-users, fraud-manager)
53+
- Authorization is enforced by the backend provider (auth-provider-zitadel)
54+
- Requires appropriate PolicyBinding in Milo for RBAC
55+
56+
Example usage:
57+
# Regular user (sees only their own sessions)
58+
kubectl get sessions
59+
60+
# Staff user (can query specific user's sessions)
61+
kubectl get sessions --field-selector=status.userUID=<target-user-id>
62+
63+
Security model:
64+
1. Milo RBAC: PolicyBinding grants access to sessions resource
65+
2. Backend authorization: Provider validates group membership for field selector usage
66+
3. Audit logging: All requests are logged with user context for compliance
67+

internal/apiserver/identity/sessions/rest.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ func (r *REST) List(ctx context.Context, opts *metainternalversion.ListOptions)
4949
groups = u.GetGroups()
5050
}
5151
logger.V(4).Info("Listing sessions", "username", username, "uid", uid, "groups", groups)
52-
// ignore selectors; self-scoped list delegated to provider
52+
// Pass field selector to backend provider for staff user queries
5353
lo := metav1.ListOptions{}
54+
if opts != nil && opts.FieldSelector != nil {
55+
lo.FieldSelector = opts.FieldSelector.String()
56+
}
5457
res, err := r.backend.ListSessions(ctx, u, &lo)
5558
if err != nil {
5659
logger.Error(err, "List sessions failed")

internal/apiserver/identity/useridentities/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,33 @@ Naming & structure
3838
- internal/apiserver/identity/useridentities/rest.go — REST storage
3939
- internal/apiserver/identity/useridentities/dynamic.go — provider implementation
4040

41+
Field Selector Support for Staff Users
42+
The UserIdentities API supports field selectors to enable staff users to query
43+
other users' identity provider links. This is required for support and administrative
44+
purposes in the staff portal.
45+
46+
Supported field selectors:
47+
- status.userUID=<user-id> — Query identities for a specific user
48+
49+
Authorization:
50+
- Regular users: Can only list their own identities (field selectors are ignored)
51+
- Staff users: Can use field selectors to query other users' identities
52+
- Must be members of privileged groups in the identity provider (e.g., staff-users, fraud-manager)
53+
- Authorization is enforced by the backend provider (auth-provider-zitadel)
54+
- Requires appropriate PolicyBinding in Milo for RBAC
55+
56+
Example usage:
57+
# Regular user (sees only their own identities)
58+
kubectl get useridentities
59+
60+
# Staff user (can query specific user's identities)
61+
kubectl get useridentities --field-selector=status.userUID=<target-user-id>
62+
63+
Security model:
64+
1. Milo RBAC: PolicyBinding grants access to useridentities resource
65+
2. Backend authorization: Provider validates group membership for field selector usage
66+
3. Audit logging: All requests are logged with user context for compliance
67+
4168
Read-only resource and admission webhook for deletion
4269
Unlike sessions, useridentities is a read-only resource. Users cannot create,
4370
update, or delete user identities through the Kubernetes API. Identity linking

internal/apiserver/identity/useridentities/rest.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,11 @@ func (r *REST) List(ctx context.Context, opts *metainternalversion.ListOptions)
4747
groups = u.GetGroups()
4848
}
4949
logger.V(4).Info("Listing user identities", "username", username, "uid", uid, "groups", groups)
50-
// ignore selectors; self-scoped list delegated to provider
50+
// Pass field selector to backend provider for staff user queries
5151
lo := metav1.ListOptions{}
52+
if opts != nil && opts.FieldSelector != nil {
53+
lo.FieldSelector = opts.FieldSelector.String()
54+
}
5255
res, err := r.backend.ListUserIdentities(ctx, u, &lo)
5356
if err != nil {
5457
logger.Error(err, "List user identities failed")
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Field Selector Authorization Tests
2+
3+
This test suite validates that field selector authorization works correctly for
4+
UserIdentities and Sessions resources in the Identity API.
5+
6+
## Test Scenarios
7+
8+
### 1. Regular User - Self-Scoped Access (Default Behavior)
9+
**Given:** A regular user without staff privileges
10+
**When:** User lists useridentities without field selector
11+
**Then:** User sees only their own identity provider links
12+
13+
**When:** User attempts to use field selector for another user
14+
**Then:** Request is rejected with 403 Forbidden error
15+
16+
### 2. Staff User - Cross-User Access with Field Selector
17+
**Given:** A user in the staff-users group
18+
**When:** User lists useridentities with field selector for another user
19+
**Then:** User successfully retrieves the target user's identity provider links
20+
21+
### 3. Field Selector Validation
22+
**Given:** Any authenticated user
23+
**When:** User provides invalid field selector (e.g., metadata.name)
24+
**Then:** Request is rejected with appropriate error message
25+
26+
## Authorization Model
27+
28+
```
29+
┌─────────────────────────────────────────────────────────────┐
30+
│ 1. Milo RBAC Check │
31+
│ - PolicyBinding grants access to useridentities resource │
32+
│ - Required for both regular and staff users │
33+
└─────────────────────────────────────────────────────────────┘
34+
35+
┌─────────────────────────────────────────────────────────────┐
36+
│ 2. Field Selector Passed to Backend │
37+
│ - Milo passes field selector to auth-provider-zitadel │
38+
│ - No validation at Milo layer │
39+
└─────────────────────────────────────────────────────────────┘
40+
41+
┌─────────────────────────────────────────────────────────────┐
42+
│ 3. Backend Authorization (auth-provider-zitadel) │
43+
│ - If no field selector: use authenticated user's UID │
44+
│ - If field selector with different UID: │
45+
│ → Check user groups (staff-users, fraud-manager) │
46+
│ → Allow if staff, deny if not │
47+
└─────────────────────────────────────────────────────────────┘
48+
```
49+
50+
## Required Setup
51+
52+
### PolicyBindings
53+
```yaml
54+
# Grant staff-users access to useridentities
55+
apiVersion: iam.miloapis.com/v1alpha1
56+
kind: PolicyBinding
57+
metadata:
58+
name: staff-useridentities-viewer
59+
namespace: milo-system
60+
spec:
61+
resourceSelector:
62+
resourceKind:
63+
apiGroup: identity.miloapis.com
64+
kind: UserIdentity
65+
roleRef:
66+
name: identity-user-session-viewer
67+
namespace: milo-system
68+
subjects:
69+
- kind: Group
70+
name: staff-users
71+
namespace: milo-system
72+
uid: <staff-users-group-uid>
73+
```
74+
75+
### Zitadel Configuration
76+
- Create group: `staff-users`
77+
- Assign users to group via project roles
78+
- Configure JWT claims to include groups
79+
80+
## Manual Testing
81+
82+
### Test 1: Regular User Cannot Use Field Selector
83+
```bash
84+
# As regular user
85+
kubectl get useridentities --field-selector=status.userUID=<other-user-id>
86+
87+
# Expected: 403 Forbidden
88+
# Error: "only staff users can query other users' identities"
89+
```
90+
91+
### Test 2: Staff User Can Use Field Selector
92+
```bash
93+
# As staff user (member of staff-users group)
94+
kubectl get useridentities --field-selector=status.userUID=<target-user-id>
95+
96+
# Expected: 200 OK
97+
# Response: List of target user's identity provider links
98+
```
99+
100+
### Test 3: Regular User Can See Own Data
101+
```bash
102+
# As regular user
103+
kubectl get useridentities
104+
105+
# Expected: 200 OK
106+
# Response: List of own identity provider links
107+
```
108+
109+
## Security Considerations
110+
111+
1. **Defense in Depth**: Two layers of authorization (Milo RBAC + Backend groups)
112+
2. **Audit Logging**: All requests logged with user context
113+
3. **Principle of Least Privilege**: Regular users cannot access others' data
114+
4. **Explicit Deny**: Field selector attempts by non-staff users are explicitly denied
115+
116+
## Future Enhancements
117+
118+
- [ ] Add automated E2E tests using Chainsaw
119+
- [ ] Add rate limiting for staff user queries
120+
- [ ] Add metrics for field selector usage
121+
- [ ] Consider adding SubjectAccessReview checks in Milo layer

0 commit comments

Comments
 (0)