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