Skip to content

Commit 92d7868

Browse files
committed
Add IAM permissions and roles documentation
Document permission structure (service.resource.action), wildcard usage, and deny-by-default security model. Explain role and permission bindings, project-scoped access, and token introspection flow. Include API examples for creating permissions, roles, and bindings. Add service integration guide with Python code examples for permission checks and middleware. Cover best practices, error handling, and important notes on caching and access control.
1 parent 5e91770 commit 92d7868

1 file changed

Lines changed: 345 additions & 0 deletions

File tree

docs/iam/permissions_overview.md

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
## 1. Permissions Overview
2+
3+
In the Identity and Access Management (IAM) system, a **Permission** is a core entity that defines the right to perform a specific action on a specific resource. IAM operates on a **"deny by default" model**: if a user or service does not have an explicitly granted permission for an action, that action is blocked.
4+
5+
### 1.1. Permission Structure
6+
7+
Each permission is represented as a string consisting of three parts separated by dots:
8+
9+
`<service_name>.<resource_name>.<action>`
10+
11+
* **`<service_name>`**:
12+
* A unique identifier for a service or module in your system
13+
* Examples: `billing`, `compute`, `auth`, `storage`
14+
15+
* **`<resource_name>`**:
16+
* Defines the type of object an action can be performed on
17+
* Expressed in the **singular form**
18+
* Examples: `account`, `vm`, `user`, `policy`
19+
20+
* **`<action>`**:
21+
* Defines the operation that can be performed on the specified resource
22+
* Examples: `read`, `write`, `create`, `delete`, `activate`
23+
24+
**Permission examples:**
25+
* `billing.account.read` — view account information in the billing service
26+
* `compute.vm.create` — create a new virtual machine in the compute service
27+
* `auth.user.deactivate` — deactivate a user in the authentication service
28+
29+
You can also use `*` (wildcard) in any of the three parts. It means **all** values are allowed in that part.
30+
31+
**How `*` works by part:**
32+
* `*` in the first part (`service_name`) — access for **all services**
33+
* `*` in the second part (`resource_name`) — access for **all resources** in the selected service
34+
* `*` in the third part (`action`) — access for **all actions** on the selected resource
35+
36+
**Wildcard permission examples:**
37+
* `*.vm.read` — allows reading `vm` in all services
38+
* `compute.*.read` — allows reading any resource in the `compute` service
39+
* `compute.vm.*` — allows any action on `vm` in the `compute` service
40+
* `*.*.*` — full access to all services, resources, and actions
41+
42+
### 1.2. Security Model: "Deny by Default"
43+
44+
An important IAM philosophy is the principle of **explicit permission**:
45+
* Initially, any user or service account has no access rights
46+
* To perform any action, the subject must be granted the corresponding permission
47+
* Services integrated with IAM must check for the required permission before executing an operation
48+
49+
### 1.3. Permissions API Examples
50+
51+
#### Create a Permission
52+
```http
53+
POST /v1/iam/permissions/
54+
Content-Type: application/json
55+
Authorization: Bearer <token>
56+
57+
{
58+
"name": "compute.vm.create",
59+
"description": "Permission to create a virtual machine"
60+
}
61+
```
62+
63+
#### Response
64+
```json
65+
{
66+
"uuid": "5a1b2c3d-4e5f-6789-abcd-ef0123456789",
67+
"name": "compute.vm.create",
68+
"description": "Permission to create a virtual machine",
69+
"created_at": "2025-08-21T07:38:04.778680Z",
70+
"updated_at": "2025-08-21T07:38:04.778688Z",
71+
"status": "ACTIVE"
72+
}
73+
```
74+
75+
#### Get a Permission
76+
```http
77+
GET /v1/iam/permissions/5a1b2c3d-4e5f-6789-abcd-ef0123456789
78+
Authorization: Bearer <token>
79+
```
80+
81+
#### Filter Permissions
82+
```http
83+
GET /v1/iam/permissions/?name=compute.vm.create&status=ACTIVE
84+
Authorization: Bearer <token>
85+
```
86+
87+
## 2. Roles
88+
89+
A **Role** is a named collection of Permissions. While a Permission defines the right for one specific action, a Role groups these rights into logical blocks corresponding to job functions, responsibilities, or the access level of a user or service.
90+
91+
### 2.1. Permission Binding: Linking Roles and Permissions
92+
93+
To assign permissions to a role, the **Permission Binding** entity is used. This entity establishes a many-to-many relationship between roles and permissions.
94+
95+
* **One Permission** can be bound to **several different roles**
96+
* **One Role** can contain **many different permissions**
97+
98+
**Example:**
99+
* The `BillingViewer` role receives the following via Permission Binding:
100+
* `billing.account.read`
101+
* `billing.invoice.read`
102+
* The `BillingOperator` role receives the following via Permission Binding:
103+
* `billing.account.read`
104+
* `billing.invoice.read`
105+
* `billing.invoice.pay`
106+
107+
### 2.2. Role Binding: Assigning Roles to Users
108+
109+
To grant a user access, a role must be assigned to them. The **Role Binding** entity is used for this purpose. This entity establishes a many-to-many relationship between users and roles.
110+
111+
* **One user** can be assigned **several roles**
112+
* **One role** can be assigned to **many users**
113+
114+
### 2.3. Final Access Model
115+
116+
The access verification process:
117+
1. A **User** calls a service with a token
118+
2. The **Service** calls IAM token introspection
119+
3. The service gets the list of permissions available for this token from the introspection response
120+
4. The **Service** checks whether the required permission exists for the requested action
121+
5. If the permission is present, access is **granted**
122+
123+
### 2.4. Roles API Examples
124+
125+
#### Create a Role
126+
```http
127+
POST /v1/iam/roles/
128+
Content-Type: application/json
129+
Authorization: Bearer <token>
130+
131+
{
132+
"name": "BillingOperator",
133+
"description": "Billing operator with rights to manage accounts"
134+
}
135+
```
136+
137+
#### Response
138+
```json
139+
{
140+
"uuid": "6b2c3d4e-5f67-789a-bcde-f01234567890",
141+
"name": "BillingOperator",
142+
"description": "Billing operator with rights to manage accounts",
143+
"created_at": "2025-08-21T07:38:04.779416Z",
144+
"updated_at": "2025-08-21T07:38:04.779424Z",
145+
"status": "ACTIVE",
146+
"project_id": null
147+
}
148+
```
149+
150+
#### Create a Permission Binding
151+
```http
152+
POST /v1/iam/permission_bindings/
153+
Content-Type: application/json
154+
Authorization: Bearer <token>
155+
156+
{
157+
"role": "6b2c3d4e-5f67-789a-bcde-f01234567890",
158+
"permission": "5a1b2c3d-4e5f-6789-abcd-ef0123456789"
159+
}
160+
```
161+
162+
#### Create a Role Binding
163+
```http
164+
POST /v1/iam/role_bindings/
165+
Content-Type: application/json
166+
Authorization: Bearer <token>
167+
168+
{
169+
"user": "7c3d4e5f-6789-89ab-cdef-123456789012",
170+
"role": "6b2c3d4e-5f67-789a-bcde-f01234567890",
171+
"project": "8d4e5f67-789a-9abc-def1-234567890123"
172+
}
173+
```
174+
175+
#### Get User Roles
176+
```http
177+
GET /v1/iam/users/7c3d4e5f-6789-89ab-cdef-123456789012/actions/get_my_roles
178+
Authorization: Bearer <token>
179+
```
180+
181+
### 2.5. Projects and Role/Permission Scope
182+
183+
In IAM, roles can be assigned as:
184+
* **Global** (without project, `project = null`)
185+
* **Project-scoped** (via the `project` field in `Role Binding`)
186+
187+
This directly affects the effective permission set in a token.
188+
189+
When issuing a token, IAM determines the project from `scope`:
190+
* `project:<uuid>` — token is bound to the specified project
191+
* `project:default` — token is bound to the user's default project
192+
* if no `project:...` segment is present in `scope`, the token is issued **without project** (`project = null`)
193+
194+
During introspection, IAM returns permissions only in the context of the token's project.
195+
In other words, the effective permission set depends on which project the token is issued for.
196+
197+
Important:
198+
getting a token **without project** does not automatically grant access to all projects.
199+
Such token includes only permissions valid in `project = null` context (global assignments) and does not include project-scoped assignments from other projects.
200+
201+
### 2.6. How Services Should Check Permissions (Practical Template)
202+
203+
Below is a recommended integration model for a business service with IAM.
204+
205+
#### Basic flow
206+
1. Service receives a user token (`Authorization: Bearer ...`)
207+
2. Service calls IAM token introspection endpoint
208+
3. Service gets the permission list from introspection for the current token
209+
4. Before each protected action, service checks that required permission is present
210+
5. If permission is missing, service returns `403 Forbidden`
211+
212+
#### Minimal service code structure
213+
```python
214+
class IamClient:
215+
def introspect(self, token: str) -> dict:
216+
# HTTP GET /v1/iam/clients/<client_uuid>/actions/introspect/invoke
217+
# with Authorization: Bearer <token>
218+
...
219+
220+
221+
class AuthContext:
222+
def __init__(self, token: str, introspection: dict):
223+
self.token = token
224+
self.introspection = introspection
225+
self.permissions = {
226+
p["name"] if isinstance(p, dict) else str(p)
227+
for p in introspection.get("permissions", [])
228+
}
229+
230+
def has_permission(self, permission_name: str) -> bool:
231+
return permission_name in self.permissions
232+
233+
234+
class PermissionDenied(Exception):
235+
pass
236+
```
237+
238+
#### Guard/decorator for permission checks
239+
```python
240+
def require_permission(permission_name: str):
241+
def wrapper(handler):
242+
def inner(request, *args, **kwargs):
243+
ctx: AuthContext = request.auth_context
244+
if not ctx.has_permission(permission_name):
245+
raise PermissionDenied(
246+
f"Missing required permission: {permission_name}"
247+
)
248+
return handler(request, *args, **kwargs)
249+
return inner
250+
return wrapper
251+
```
252+
253+
#### Endpoint example
254+
```python
255+
@require_permission("compute.vm.create")
256+
def create_vm(request):
257+
payload = request.json
258+
# VM creation business logic
259+
return {"status": "ok"}, 201
260+
```
261+
262+
#### Context initialization in middleware
263+
```python
264+
def auth_middleware(request, iam_client: IamClient):
265+
token = extract_bearer_token(request.headers)
266+
if not token:
267+
return {"error": "Unauthorized"}, 401
268+
269+
introspection = iam_client.introspect(token)
270+
request.auth_context = AuthContext(token=token, introspection=introspection)
271+
return None # continue
272+
```
273+
274+
#### Practical recommendations
275+
* Check permissions as close as possible to action execution point (endpoint/use-case)
276+
* Do not infer rights in the service — IAM must stay the source of truth
277+
* Only short-lived server-side cache for introspection response is acceptable; do not cache permissions on the client side
278+
* Log access denials with required permission and user/token context
279+
280+
## 3. Best Practices
281+
282+
### 3.1. Principle of Least Privilege
283+
Create roles that provide exactly the level of access required to perform a task, and nothing more.
284+
285+
### 3.2. Semantic Naming
286+
Give roles and permissions clear names that reflect their purpose:
287+
* Roles: `NetworkReadOnly`, `DatabaseSuperUser`
288+
* Permissions: `compute.vm.read`, `storage.bucket.delete`
289+
290+
### 3.3. Regular Audits
291+
Periodically review:
292+
* Which roles are assigned to whom
293+
* Which permissions are included in roles
294+
* Remove unnecessary access promptly
295+
296+
### 3.4. Using Projects for Isolation
297+
Use project-scoped Role Binding to isolate access between environments.
298+
Detailed project-context behavior is described in section **2.5**.
299+
300+
## 4. Error Handling
301+
302+
The following errors may occur when working with the IAM API:
303+
304+
### 4.1. Access Error (403 Forbidden)
305+
```json
306+
{
307+
"status": 403,
308+
"json": {
309+
"code": 403,
310+
"type": "PermissionDeniedException",
311+
"message": "User does not have required permission: compute.vm.create"
312+
}
313+
}
314+
```
315+
316+
### 4.2. Not Found (404 Not Found)
317+
```json
318+
{
319+
"status": 404,
320+
"json": {
321+
"code": 404,
322+
"type": "NotFoundException",
323+
"message": "Role with uuid 6b2c3d4e-5f67-789a-bcde-f01234567890 not found"
324+
}
325+
}
326+
```
327+
328+
### 4.3. Bad Request (400 Bad Request)
329+
```json
330+
{
331+
"status": 400,
332+
"json": {
333+
"code": 400,
334+
"type": "ValidationErrorException",
335+
"message": "Field 'name' must be between 0 and 255 characters"
336+
}
337+
}
338+
```
339+
340+
## 5. Important Notes
341+
342+
1. All changes to permissions and role bindings take effect immediately
343+
2. Do not cache permissions on the client side; if caching is needed, use only short-lived server-side introspection cache
344+
3. For service accounts, use separate roles with the minimum required permissions
345+
4. Regularly update and review role assignments within the system

0 commit comments

Comments
 (0)