|
| 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