You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+44Lines changed: 44 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,3 +20,47 @@ uv run --directory src --extra dev pre-commit run --all-files
20
20
```bash
21
21
cd src && uv run pytest -q
22
22
```
23
+
24
+
## Architecture Overview
25
+
26
+
Terraform module that deploys AWS Lambda functions for just-in-time AWS SSO access management via Slack. Two access models exist: **account access** (temporary permission set assignment on AWS accounts) and **group access** (temporary SSO group membership).
27
+
28
+
### Terraform Layer
29
+
30
+
-`vars.tf` — Module inputs. Two separate config variables: `config` (account access statements) and `group_config` (group access statements), both `type = any`.
31
+
-`s3.tf` — Serializes both to a single S3 object as `{"statements": var.config, "group_statements": var.group_config}`.
32
+
-`locals.tf` — Validation logic (group/attribute-sync overlap checks, extracting group names from `group_config`).
33
+
-`slack_handler_lambda.tf` / `perm_revoker_lambda.tf` — Lambda function definitions. Both read config from the same S3 object.
34
+
35
+
### Python Layer (`src/`)
36
+
37
+
**Config & Models:**
38
+
-`config.py` — Loads config from S3 (`load_approval_config_from_s3`), parses into `Statement`/`GroupStatement` via `parse_statement`/`parse_group_statement`. `Config` is a Pydantic `BaseSettings` singleton with `frozenset` fields for statements, accounts, groups, permission_sets.
39
+
-`statement.py` — `BaseStatement` (has `permission_set`, `required_group_membership`, etc.) → `Statement` (account access). `GroupStatement` is separate (does not inherit `BaseStatement`, lacks `permission_set` and `required_group_membership`). Eligibility filtering (`get_eligible_statements_for_user`) currently only applies to `Statement`, not `GroupStatement`.
40
+
-`events.py` — `RevokeEvent`/`GroupRevokeEvent` and their `Scheduled*` wrappers. Tagged union via `action` field literal types.
41
+
-`entities/` — Pydantic models for AWS resources (`Account`, `PermissionSet`, `SSOGroup`) and Slack users.
42
+
43
+
**Request Flow (Slack → Decision → Execution):**
44
+
-`main.py` — Slack bolt app. Handles account access shortcuts, modal interactions, button clicks. Registers handlers via `app.shortcut()`, `app.view()`, `app.action()`. The `handle_button_click` function falls through to `group.handle_group_button_click` on parse failure.
45
+
-`group.py` — Parallel handlers for group access shortcuts, submission, and button clicks.
46
+
-`slack_helpers.py` — View builders (`RequestForAccessView`, `RequestForGroupAccessView`), payload parsers (`ButtonClickedPayload`, `ButtonGroupClickedPayload`), message block builders. Modal uses dynamic view updates: accounts load first, then permission sets update on account selection via `dispatch_action=True`.
47
+
-`access_control.py` — Decision logic (`make_decision_on_access_request`, `make_decision_on_approve_request`) is already unified for both Statement and GroupStatement. Execution is split: `execute_decision` (account) vs `execute_decision_on_group_request` (group).
48
+
49
+
**Revocation & Scheduling:**
50
+
-`schedule.py` — `schedule_revoke_event`/`schedule_group_revoke_event` create EventBridge schedules that fire the revoker Lambda.
51
+
-`revoker.py` — Handles scheduled revocations, early revocations, inconsistency checks, extend-grant button posting. Parallel functions for account and group flows.
52
+
53
+
**SSO Operations:**
54
+
-`sso.py` — AWS SSO/Identity Store API calls. Account operations (create/delete assignment) and group operations (add/remove membership) are fundamentally different APIs. User lookup (`get_user_principal_id_by_email`) is shared.
55
+
56
+
**Attribute Sync (separate feature):**
57
+
-`attribute_syncer.py`, `attribute_mapper.py`, `sync_state.py`, `sync_config.py`, `sync_notifications.py` — Automatic user-to-group sync based on Identity Store attributes. Runs on its own Lambda/schedule.
58
+
59
+
### Key Design Patterns
60
+
61
+
-**Config uses two separate Terraform variables** (`config` and `group_config`) that map directly to `statements` and `group_statements` in the S3 JSON. Python reads these as separate lists.
62
+
-**Pydantic models use `frozen=True`** (`ConfigDict(frozen=True)` in `entities/model.py`). Extra fields are silently ignored (default Pydantic v2 behavior, no `extra="forbid"`).
63
+
-**`parse_group_statement` manually extracts keys** from the raw dict (doesn't pass the whole dict to Pydantic), so extra keys like `ResourceType` in the S3 JSON are harmlessly ignored.
64
+
-**Decision logic is unified** in `access_control.py` — same `make_decision_on_access_request` handles both `FrozenSet[Statement]` and `FrozenSet[GroupStatement]`.
65
+
-**Execution logic is deliberately split** — account and group execution have different SSO API calls, audit fields, and scheduling.
66
+
-**Tests use Hypothesis** (`src/tests/strategies.py`) for property-based testing of statement parsing alongside standard pytest.
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -189,6 +189,7 @@ output "api_endpoint_url" {
189
189
| <aname="input_logs_retention_in_days"></a> [logs\_retention\_in\_days](#input\_logs\_retention\_in\_days)| The number of days you want to retain log events in the log group for both Lambda functions and API Gateway. |`number`|`365`| no |
190
190
| <aname="input_max_permissions_duration_time"></a> [max\_permissions\_duration\_time](#input\_max\_permissions\_duration\_time)| Maximum duration (in hours) for permissions granted by Elevator. Max number - 48 hours.<br/> Due to Slack's dropdown limit of 100 items, anything above 48 hours will cause issues when generating half-hour increments<br/> and Elevator will not display more then 48 hours in the dropdown. |`number`|`24`| no |
191
191
| <aname="input_permission_duration_list_override"></a> [permission\_duration\_list\_override](#input\_permission\_duration\_list\_override)| An explicit list of duration values to appear in the drop-down menu users use to select how long to request permissions for.<br/> Each entry in the list should be formatted as "hh:mm", e.g. "01:30" for an hour and a half. Note that while the number of minutes<br/> must be between 0-59, the number of hours can be any number.<br/> If this variable is set, the max\_permission\_duration\_time is ignored. |`list(string)`|`[]`| no |
192
+
| <aname="input_permission_set_display_names"></a> [permission\_set\_display\_names](#input\_permission\_set\_display\_names)| Optional mapping of permission set names (or ARNs) to human-friendly labels<br/>shown in the Slack dropdown. Keys are AWS SSO permission set names or ARNs<br/>(as used in config statements), values are display labels.<br/>Example: { "eks-developer" = "EKS/kubectl access", "secrets-editor" = "Manage secrets" }<br/>Permission sets without a mapping display their AWS name as-is.<br/>Note: Slack limits dropdown option text to 75 characters. |`map(string)`|`{}`| no |
192
193
| <aname="input_posthog_api_key"></a> [posthog\_api\_key](#input\_posthog\_api\_key)| PostHog API key for analytics. Leave empty to disable analytics tracking. |`string`|`""`| no |
193
194
| <aname="input_posthog_host"></a> [posthog\_host](#input\_posthog\_host)| PostHog host URL for analytics. |`string`|`"https://us.i.posthog.com"`| no |
194
195
| <aname="input_request_expiration_hours"></a> [request\_expiration\_hours](#input\_request\_expiration\_hours)| After how many hours should the request expire? If set to 0, the request will never expire. |`number`|`8`| no |
0 commit comments