workspaces-euc-mcp-server 0.1.3__tar.gz → 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/CHANGELOG.md +32 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/DESIGN.md +22 -8
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/PKG-INFO +7 -2
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/README.md +6 -1
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/tier0-diagnostics.json +7 -2
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/tier1-cost.json +7 -2
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/tier2-lifecycle.json +7 -2
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/tier3-destructive.json +7 -2
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/pyproject.toml +1 -1
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_cost.py +43 -0
- workspaces_euc_mcp_server-0.1.5/tests/test_governance.py +133 -0
- workspaces_euc_mcp_server-0.1.5/tests/test_images.py +115 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/__init__.py +1 -1
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/consts.py +46 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/models.py +121 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/server.py +4 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/cost.py +27 -1
- workspaces_euc_mcp_server-0.1.5/workspaces_euc_mcp_server/tools/governance.py +361 -0
- workspaces_euc_mcp_server-0.1.5/workspaces_euc_mcp_server/tools/images.py +222 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/.dockerignore +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/.github/workflows/ci.yml +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/.github/workflows/docker-publish.yml +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/.github/workflows/publish.yml +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/.gitignore +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/.pre-commit-config.yaml +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/Dockerfile +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/LICENSE +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/README.md +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/scripts/smoke_readonly.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/__init__.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_clients.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_destructive.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_diagnostics.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_inventory.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_lifecycle.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_naming.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_no_embedded_secrets.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_performance.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_pricing.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_reporting.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/tests/test_secure_browser.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/clients.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/__init__.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/_common.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/destructive.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/diagnostics.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/inventory.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/lifecycle.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/performance.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/pricing.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/reporting.py +0 -0
- {workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/workspaces_euc_mcp_server/tools/secure_browser.py +0 -0
|
@@ -5,6 +5,38 @@ All notable changes to this project are documented here. The format is based on
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.1.5] - 2026-06-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `get_euc_audit_trail` — a new read-only (Tier 0) tool that reports recent EUC management activity
|
|
12
|
+
from CloudTrail (always-on `LookupEvents`, 90-day window, no trail required) across WorkSpaces
|
|
13
|
+
Personal/Pools/Core, WorkSpaces Applications, Secure Browser, and Core Managed Instances.
|
|
14
|
+
Mutations-only by default ("who created/modified/terminated what"); flags destructive actions and
|
|
15
|
+
errors (e.g. AccessDenied). Adds `cloudtrail:LookupEvents` to every IAM tier.
|
|
16
|
+
- `get_euc_service_quotas` — a new read-only (Tier 0) tool that reports Service Quotas limits per
|
|
17
|
+
EUC service and, where AWS publishes a linked usage metric (`AWS/Usage` `ResourceCount`), the
|
|
18
|
+
current usage and utilisation %, flagging quotas approaching their limit (capacity planning).
|
|
19
|
+
Adds `servicequotas:ListServiceQuotas` / `GetServiceQuota` to every IAM tier.
|
|
20
|
+
- `audit_application_images` — a new read-only (Tier 0) tool that audits WorkSpaces Applications
|
|
21
|
+
(AppStream 2.0) **images and image builders**: lists your PRIVATE/SHARED images (skipping PUBLIC
|
|
22
|
+
base images) and flags stale base images (likely unpatched OS), pinned/old AppStream agents,
|
|
23
|
+
non-AVAILABLE or errored images, SHARED cross-account visibility, and image builders left
|
|
24
|
+
**RUNNING** (per-hour cost + interactive admin surface). Adds `appstream:DescribeImages` and
|
|
25
|
+
`appstream:DescribeImageBuilders` to every IAM tier.
|
|
26
|
+
|
|
27
|
+
### Docs
|
|
28
|
+
- README and DESIGN.md reconciled to the shipped state: 21 read-only tools (Tiers 0–1) + 10 write
|
|
29
|
+
(Tier 2) + 3 destructive (Tier 3); `tools/` layout, §5 tool catalog (image audit + governance),
|
|
30
|
+
and the Tier 0 IAM action list all updated.
|
|
31
|
+
|
|
32
|
+
## [0.1.4] - 2026-06-02
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- `get_euc_cost_summary` now returns `by_period` — a per-bucket time series (one entry per day for
|
|
36
|
+
`DAILY`, per month for `MONTHLY`), each with its own per-service split. Previously the tool
|
|
37
|
+
collapsed all periods into per-service totals, so a `DAILY` request lost the daily breakdown and
|
|
38
|
+
clients had to query each day individually to chart trends.
|
|
39
|
+
|
|
8
40
|
## [0.1.3] - 2026-06-02
|
|
9
41
|
|
|
10
42
|
### Fixed
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
> inventory, troubleshooting, cost/utilization optimization, and guarded lifecycle management
|
|
5
5
|
> across the Amazon WorkSpaces family of End User Computing services.
|
|
6
6
|
>
|
|
7
|
-
> **Shipped:**
|
|
7
|
+
> **Shipped:** 21 read-only tools (Tier 0/1) + 10 guarded write tools (Tier 2) + 3 destructive
|
|
8
8
|
> tools (Tier 3), all behind opt-in flags with dry-run/confirm/blast-radius/typed-acknowledgement
|
|
9
|
-
> guards.
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
9
|
+
> guards. Read-only coverage now includes image auditing and governance (CloudTrail audit trail +
|
|
10
|
+
> Service Quotas headroom). All four IAM policy tiers ship in `iam/`. CI
|
|
11
|
+
> (ruff/format/pyright/bandit/pytest on Py 3.11–3.13) is green; published to PyPI and GHCR.
|
|
12
|
+
> **`README.md` is the source of truth for the live tool catalog and exact tool names** — §5 below
|
|
13
|
+
> records the original design intent and is kept reconciled with what shipped. See `CHANGELOG.md`
|
|
14
|
+
> for per-version detail.
|
|
13
15
|
|
|
14
16
|
## 1. Principles
|
|
15
17
|
|
|
@@ -72,6 +74,8 @@
|
|
|
72
74
|
reporting.py
|
|
73
75
|
secure_browser.py
|
|
74
76
|
pricing.py # AWS Price List lookups for $ estimates
|
|
77
|
+
images.py # WorkSpaces Applications image / image-builder audit
|
|
78
|
+
governance.py # CloudTrail audit trail + Service Quotas headroom
|
|
75
79
|
lifecycle.py # Tier 2 (guarded writes)
|
|
76
80
|
destructive.py # Tier 3 (terminate/rebuild/restore)
|
|
77
81
|
iam/ # shippable least-privilege policy docs per tier (0–3)
|
|
@@ -137,8 +141,15 @@ and return a synthesized result, not raw API passthroughs.
|
|
|
137
141
|
| Tool | Purpose | IAM actions |
|
|
138
142
|
|---|---|---|
|
|
139
143
|
| `audit_security_posture` | Encryption at rest, IP access control groups, directory config, portal policies | `workspaces:Describe*`, `workspaces-web:Get*/List*`, `appstream:Describe*` |
|
|
144
|
+
| `audit_application_images` | WorkSpaces Applications image/image-builder audit — stale base, pinned agent, errored/SHARED images, RUNNING builders | `appstream:DescribeImages`, `appstream:DescribeImageBuilders` |
|
|
140
145
|
| `list_unused_resources` | Idle desktops / empty fleets / orphaned resources | describes + `cloudwatch:GetMetricData` |
|
|
141
146
|
|
|
147
|
+
**Governance** (cross-service)
|
|
148
|
+
| Tool | Purpose | IAM actions |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `get_euc_audit_trail` | "Who changed what" — recent EUC management events (mutations by default), 90-day CloudTrail history | `cloudtrail:LookupEvents` |
|
|
151
|
+
| `get_euc_service_quotas` | Quota limits + usage headroom per EUC service (capacity planning) | `servicequotas:ListServiceQuotas`, `servicequotas:GetServiceQuota`, `cloudwatch:GetMetricData` |
|
|
152
|
+
|
|
142
153
|
### Phase 2 — Guarded lifecycle (writes; Tier 2, `--enable-writes`)
|
|
143
154
|
All support `dry_run`, return a plan + blast-radius before acting, and honor `--max-bulk-targets`.
|
|
144
155
|
|
|
@@ -162,9 +173,12 @@ All support `dry_run`, return a plan + blast-radius before acting, and honor `--
|
|
|
162
173
|
|
|
163
174
|
We ship a managed policy document per tier so customers grant exactly what they enable.
|
|
164
175
|
|
|
165
|
-
- **Tier 0 — Diagnostics (read-only):** `workspaces:Describe*`, `appstream:Describe
|
|
166
|
-
`workspaces-web:Get*`/`List*`,
|
|
167
|
-
`
|
|
176
|
+
- **Tier 0 — Diagnostics (read-only):** `workspaces:Describe*`, `appstream:Describe*` (incl.
|
|
177
|
+
`DescribeImages`/`DescribeImageBuilders`), `workspaces-web:Get*`/`List*`,
|
|
178
|
+
`workspaces-instances:List*`/`Get*`, `ds:DescribeDirectories`,
|
|
179
|
+
`cloudwatch:GetMetricData`/`ListMetrics`, `application-autoscaling:DescribeScalingActivities`,
|
|
180
|
+
`ec2:DescribeInstances`, `cloudtrail:LookupEvents`,
|
|
181
|
+
`servicequotas:ListServiceQuotas`/`GetServiceQuota`.
|
|
168
182
|
- **Tier 1 — Cost/optimization (read-only):** Tier 0 + `ce:GetCostAndUsage`,
|
|
169
183
|
`ce:GetDimensionValues`, `pricing:GetProducts`.
|
|
170
184
|
- **Tier 2 — Lifecycle (writes):** Tier 1 + `workspaces:Start/Stop/Reboot/ModifyWorkspace*`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: workspaces-euc-mcp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: MCP server for administering the Amazon WorkSpaces family of End User Computing services (Personal, Pools, Applications, Secure Browser, Core).
|
|
5
5
|
Project-URL: Homepage, https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/bengroeneveldsg/aws-workspaces-euc-mcp
|
|
@@ -70,7 +70,9 @@ diagnosis, a right-sizing recommendation) instead of returning raw API output. S
|
|
|
70
70
|
|
|
71
71
|
## Status
|
|
72
72
|
|
|
73
|
-
**
|
|
73
|
+
**Shipped** (published to PyPI + GHCR) — **21 read-only tools** (Tiers 0–1) plus opt-in **10 write**
|
|
74
|
+
(Tier 2) and **3 destructive** (Tier 3) tools. The read-only inventory, troubleshooting, cost,
|
|
75
|
+
audit, and governance tools:
|
|
74
76
|
|
|
75
77
|
| Tool | Description |
|
|
76
78
|
|------|-------------|
|
|
@@ -89,6 +91,9 @@ diagnosis, a right-sizing recommendation) instead of returning raw API output. S
|
|
|
89
91
|
| `get_euc_cost_summary` | EUC spend by service over a window (or an explicit `start_date`/`end_date` calendar month) via Cost Explorer, account-wide. Services are matched by keyword so naming variants (e.g. AppStream 2.0) aren't dropped; note Cost Explorer bills WorkSpaces Personal/Pools/Core together as one "Amazon WorkSpaces" line (Tier 1). |
|
|
90
92
|
| `generate_inventory_report` | Detailed per-resource inventory (desktops **with assigned user / computer name / IP**, pools, fleets, **stacks + their associated fleets**, portals) with key attributes (Tier 0). |
|
|
91
93
|
| `audit_security_posture` | Cross-service: flags unencrypted WorkSpace volumes, directories without IP access control groups, and **Secure Browser portals / Applications stacks that allow data egress** (clipboard/download/print) (Tier 0). |
|
|
94
|
+
| `audit_application_images` | Audits WorkSpaces Applications (AppStream 2.0) **images and image builders** — flags stale base images (unpatched OS), pinned/old AppStream agents, non-AVAILABLE/errored images, SHARED cross-account visibility, and **image builders left RUNNING** (cost + admin surface) (Tier 0). |
|
|
95
|
+
| `get_euc_audit_trail` | **"Who changed what"** — recent EUC management events from CloudTrail (last 90 days, no trail required) across all services; mutations-only by default, flags destructive actions and errors (e.g. AccessDenied) (Tier 0). |
|
|
96
|
+
| `get_euc_service_quotas` | **Service-quota limits + usage headroom** per EUC service; pairs limits with current usage (where AWS publishes a usage metric) to flag quotas approaching their limit — capacity planning (Tier 0). |
|
|
92
97
|
| `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, and attached policies (Tier 0). |
|
|
93
98
|
| `get_secure_browser_portal_usage` | A Secure Browser portal's `AWS/WorkSpacesWeb` session metrics over a window (empty until the portal has sessions; Session Logger gives detail) (Tier 0). |
|
|
94
99
|
| `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
|
|
@@ -40,7 +40,9 @@ diagnosis, a right-sizing recommendation) instead of returning raw API output. S
|
|
|
40
40
|
|
|
41
41
|
## Status
|
|
42
42
|
|
|
43
|
-
**
|
|
43
|
+
**Shipped** (published to PyPI + GHCR) — **21 read-only tools** (Tiers 0–1) plus opt-in **10 write**
|
|
44
|
+
(Tier 2) and **3 destructive** (Tier 3) tools. The read-only inventory, troubleshooting, cost,
|
|
45
|
+
audit, and governance tools:
|
|
44
46
|
|
|
45
47
|
| Tool | Description |
|
|
46
48
|
|------|-------------|
|
|
@@ -59,6 +61,9 @@ diagnosis, a right-sizing recommendation) instead of returning raw API output. S
|
|
|
59
61
|
| `get_euc_cost_summary` | EUC spend by service over a window (or an explicit `start_date`/`end_date` calendar month) via Cost Explorer, account-wide. Services are matched by keyword so naming variants (e.g. AppStream 2.0) aren't dropped; note Cost Explorer bills WorkSpaces Personal/Pools/Core together as one "Amazon WorkSpaces" line (Tier 1). |
|
|
60
62
|
| `generate_inventory_report` | Detailed per-resource inventory (desktops **with assigned user / computer name / IP**, pools, fleets, **stacks + their associated fleets**, portals) with key attributes (Tier 0). |
|
|
61
63
|
| `audit_security_posture` | Cross-service: flags unencrypted WorkSpace volumes, directories without IP access control groups, and **Secure Browser portals / Applications stacks that allow data egress** (clipboard/download/print) (Tier 0). |
|
|
64
|
+
| `audit_application_images` | Audits WorkSpaces Applications (AppStream 2.0) **images and image builders** — flags stale base images (unpatched OS), pinned/old AppStream agents, non-AVAILABLE/errored images, SHARED cross-account visibility, and **image builders left RUNNING** (cost + admin surface) (Tier 0). |
|
|
65
|
+
| `get_euc_audit_trail` | **"Who changed what"** — recent EUC management events from CloudTrail (last 90 days, no trail required) across all services; mutations-only by default, flags destructive actions and errors (e.g. AccessDenied) (Tier 0). |
|
|
66
|
+
| `get_euc_service_quotas` | **Service-quota limits + usage headroom** per EUC service; pairs limits with current usage (where AWS publishes a usage metric) to flag quotas approaching their limit — capacity planning (Tier 0). |
|
|
62
67
|
| `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, and attached policies (Tier 0). |
|
|
63
68
|
| `get_secure_browser_portal_usage` | A Secure Browser portal's `AWS/WorkSpacesWeb` session metrics over a window (empty until the portal has sessions; Session Logger gives detail) (Tier 0). |
|
|
64
69
|
| `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
|
{workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/tier0-diagnostics.json
RENAMED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"appstream:DescribeStacks",
|
|
24
24
|
"appstream:ListAssociatedFleets",
|
|
25
25
|
"appstream:ListAssociatedStacks",
|
|
26
|
-
"appstream:DescribeSessions"
|
|
26
|
+
"appstream:DescribeSessions",
|
|
27
|
+
"appstream:DescribeImages",
|
|
28
|
+
"appstream:DescribeImageBuilders"
|
|
27
29
|
],
|
|
28
30
|
"Resource": "*"
|
|
29
31
|
},
|
|
@@ -60,7 +62,10 @@
|
|
|
60
62
|
"cloudwatch:GetMetricData",
|
|
61
63
|
"cloudwatch:ListMetrics",
|
|
62
64
|
"application-autoscaling:DescribeScalingActivities",
|
|
63
|
-
"ec2:DescribeInstances"
|
|
65
|
+
"ec2:DescribeInstances",
|
|
66
|
+
"cloudtrail:LookupEvents",
|
|
67
|
+
"servicequotas:ListServiceQuotas",
|
|
68
|
+
"servicequotas:GetServiceQuota"
|
|
64
69
|
],
|
|
65
70
|
"Resource": "*"
|
|
66
71
|
}
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"appstream:DescribeStacks",
|
|
24
24
|
"appstream:ListAssociatedFleets",
|
|
25
25
|
"appstream:ListAssociatedStacks",
|
|
26
|
-
"appstream:DescribeSessions"
|
|
26
|
+
"appstream:DescribeSessions",
|
|
27
|
+
"appstream:DescribeImages",
|
|
28
|
+
"appstream:DescribeImageBuilders"
|
|
27
29
|
],
|
|
28
30
|
"Resource": "*"
|
|
29
31
|
},
|
|
@@ -60,7 +62,10 @@
|
|
|
60
62
|
"cloudwatch:GetMetricData",
|
|
61
63
|
"cloudwatch:ListMetrics",
|
|
62
64
|
"application-autoscaling:DescribeScalingActivities",
|
|
63
|
-
"ec2:DescribeInstances"
|
|
65
|
+
"ec2:DescribeInstances",
|
|
66
|
+
"cloudtrail:LookupEvents",
|
|
67
|
+
"servicequotas:ListServiceQuotas",
|
|
68
|
+
"servicequotas:GetServiceQuota"
|
|
64
69
|
],
|
|
65
70
|
"Resource": "*"
|
|
66
71
|
},
|
{workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/tier2-lifecycle.json
RENAMED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"appstream:DescribeStacks",
|
|
24
24
|
"appstream:ListAssociatedFleets",
|
|
25
25
|
"appstream:ListAssociatedStacks",
|
|
26
|
-
"appstream:DescribeSessions"
|
|
26
|
+
"appstream:DescribeSessions",
|
|
27
|
+
"appstream:DescribeImages",
|
|
28
|
+
"appstream:DescribeImageBuilders"
|
|
27
29
|
],
|
|
28
30
|
"Resource": "*"
|
|
29
31
|
},
|
|
@@ -60,7 +62,10 @@
|
|
|
60
62
|
"cloudwatch:GetMetricData",
|
|
61
63
|
"cloudwatch:ListMetrics",
|
|
62
64
|
"application-autoscaling:DescribeScalingActivities",
|
|
63
|
-
"ec2:DescribeInstances"
|
|
65
|
+
"ec2:DescribeInstances",
|
|
66
|
+
"cloudtrail:LookupEvents",
|
|
67
|
+
"servicequotas:ListServiceQuotas",
|
|
68
|
+
"servicequotas:GetServiceQuota"
|
|
64
69
|
],
|
|
65
70
|
"Resource": "*"
|
|
66
71
|
},
|
{workspaces_euc_mcp_server-0.1.3 → workspaces_euc_mcp_server-0.1.5}/iam/tier3-destructive.json
RENAMED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"appstream:DescribeStacks",
|
|
24
24
|
"appstream:ListAssociatedFleets",
|
|
25
25
|
"appstream:ListAssociatedStacks",
|
|
26
|
-
"appstream:DescribeSessions"
|
|
26
|
+
"appstream:DescribeSessions",
|
|
27
|
+
"appstream:DescribeImages",
|
|
28
|
+
"appstream:DescribeImageBuilders"
|
|
27
29
|
],
|
|
28
30
|
"Resource": "*"
|
|
29
31
|
},
|
|
@@ -60,7 +62,10 @@
|
|
|
60
62
|
"cloudwatch:GetMetricData",
|
|
61
63
|
"cloudwatch:ListMetrics",
|
|
62
64
|
"application-autoscaling:DescribeScalingActivities",
|
|
63
|
-
"ec2:DescribeInstances"
|
|
65
|
+
"ec2:DescribeInstances",
|
|
66
|
+
"cloudtrail:LookupEvents",
|
|
67
|
+
"servicequotas:ListServiceQuotas",
|
|
68
|
+
"servicequotas:GetServiceQuota"
|
|
64
69
|
],
|
|
65
70
|
"Resource": "*"
|
|
66
71
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "workspaces-euc-mcp-server"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.5"
|
|
4
4
|
description = "MCP server for administering the Amazon WorkSpaces family of End User Computing services (Personal, Pools, Applications, Secure Browser, Core)."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -172,6 +172,49 @@ def test_cost_summary_keyword_matches_variants_and_excludes_noneuc():
|
|
|
172
172
|
assert summary.total == 1701.82
|
|
173
173
|
|
|
174
174
|
|
|
175
|
+
def test_cost_summary_daily_returns_per_period_time_series():
|
|
176
|
+
# DAILY granularity must preserve the per-day breakdown in by_period (for charts), not just
|
|
177
|
+
# collapse everything into by_service totals.
|
|
178
|
+
ce = types.SimpleNamespace(
|
|
179
|
+
get_cost_and_usage=lambda **_: {
|
|
180
|
+
"ResultsByTime": [
|
|
181
|
+
{
|
|
182
|
+
"TimePeriod": {"Start": "2026-05-01", "End": "2026-05-02"},
|
|
183
|
+
"Groups": [
|
|
184
|
+
{
|
|
185
|
+
"Keys": ["Amazon WorkSpaces Applications"],
|
|
186
|
+
"Metrics": {"UnblendedCost": {"Amount": "30.00", "Unit": "USD"}},
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"TimePeriod": {"Start": "2026-05-02", "End": "2026-05-03"},
|
|
192
|
+
"Groups": [
|
|
193
|
+
{
|
|
194
|
+
"Keys": ["Amazon WorkSpaces Applications"],
|
|
195
|
+
"Metrics": {"UnblendedCost": {"Amount": "45.00", "Unit": "USD"}},
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
factory = FakeFactory({consts.COST_EXPLORER_API: ce})
|
|
203
|
+
|
|
204
|
+
summary = cost.get_euc_cost_summary_core(
|
|
205
|
+
factory, granularity="DAILY", start_date="2026-05-01", end_date="2026-05-03"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Aggregate total preserved...
|
|
209
|
+
assert summary.total == 75.0
|
|
210
|
+
# ...and the daily series is available, ordered by date.
|
|
211
|
+
assert [(p.start, p.total) for p in summary.by_period] == [
|
|
212
|
+
("2026-05-01", 30.0),
|
|
213
|
+
("2026-05-02", 45.0),
|
|
214
|
+
]
|
|
215
|
+
assert summary.by_period[0].by_service[0].service == "Amazon WorkSpaces Applications"
|
|
216
|
+
|
|
217
|
+
|
|
175
218
|
def test_cost_summary_explicit_date_range_overrides_lookback():
|
|
176
219
|
captured: dict = {}
|
|
177
220
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Copyright bengroeneveldsg. Licensed under the Apache License, Version 2.0 (the "License").
|
|
2
|
+
# You may not use this file except in compliance with the License.
|
|
3
|
+
# A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
"""Tests for the governance tools (audit trail + service quotas)."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import types
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
|
|
12
|
+
from workspaces_euc_mcp_server import consts
|
|
13
|
+
from workspaces_euc_mcp_server.tools import governance
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FakeFactory:
|
|
17
|
+
region = "ap-southeast-1"
|
|
18
|
+
|
|
19
|
+
def __init__(self, clients: dict[str, object]) -> None:
|
|
20
|
+
self._clients = clients
|
|
21
|
+
|
|
22
|
+
def client(self, service_name: str, region: str | None = None):
|
|
23
|
+
assert service_name in self._clients, f"unexpected client: {service_name}"
|
|
24
|
+
return self._clients[service_name]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _event(name, source, user, *, read_only=False, error=None, resource=None):
|
|
28
|
+
detail = {"sourceIPAddress": "203.0.113.5", "awsRegion": "ap-southeast-1"}
|
|
29
|
+
if error:
|
|
30
|
+
detail["errorCode"] = error
|
|
31
|
+
return {
|
|
32
|
+
"EventName": name,
|
|
33
|
+
"EventSource": source,
|
|
34
|
+
"Username": user,
|
|
35
|
+
"ReadOnly": read_only,
|
|
36
|
+
"EventTime": datetime(2026, 5, 30, 9, 0, tzinfo=UTC),
|
|
37
|
+
"Resources": [{"ResourceName": resource}] if resource else [],
|
|
38
|
+
"CloudTrailEvent": json.dumps(detail),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_audit_trail_filters_euc_and_flags_destructive():
|
|
43
|
+
events = [
|
|
44
|
+
_event("TerminateWorkspaces", "workspaces.amazonaws.com", "admin", resource="ws-abc"),
|
|
45
|
+
_event("CreateFleet", "appstream.amazonaws.com", "ops"),
|
|
46
|
+
_event("RunInstances", "ec2.amazonaws.com", "someone"), # non-EUC, must be dropped
|
|
47
|
+
]
|
|
48
|
+
trail = types.SimpleNamespace(lookup_events=lambda **_: {"Events": events})
|
|
49
|
+
factory = FakeFactory({consts.CLOUDTRAIL_API: trail})
|
|
50
|
+
|
|
51
|
+
report = governance.get_euc_audit_trail_core(factory, "ap-southeast-1", lookback_days=7)
|
|
52
|
+
|
|
53
|
+
names = {e.event_name for e in report.events}
|
|
54
|
+
assert names == {"TerminateWorkspaces", "CreateFleet"} # EC2 dropped
|
|
55
|
+
assert report.total_events == 2
|
|
56
|
+
assert any(
|
|
57
|
+
"Destructive" in f.issue and "TerminateWorkspaces" in f.issue for f in report.findings
|
|
58
|
+
)
|
|
59
|
+
# Service label resolved from the event source.
|
|
60
|
+
assert any(e.service == "Amazon WorkSpaces" for e in report.events)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_audit_trail_flags_access_denied():
|
|
64
|
+
events = [_event("DeletePortal", "workspaces-web.amazonaws.com", "x", error="AccessDenied")]
|
|
65
|
+
trail = types.SimpleNamespace(lookup_events=lambda **_: {"Events": events})
|
|
66
|
+
factory = FakeFactory({consts.CLOUDTRAIL_API: trail})
|
|
67
|
+
|
|
68
|
+
report = governance.get_euc_audit_trail_core(factory, "ap-southeast-1")
|
|
69
|
+
|
|
70
|
+
assert any("AccessDenied" in f.issue and f.severity == "warning" for f in report.findings)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_audit_trail_lookback_capped_at_90():
|
|
74
|
+
trail = types.SimpleNamespace(lookup_events=lambda **_: {"Events": []})
|
|
75
|
+
factory = FakeFactory({consts.CLOUDTRAIL_API: trail})
|
|
76
|
+
report = governance.get_euc_audit_trail_core(factory, "ap-southeast-1", lookback_days=365)
|
|
77
|
+
assert report.lookback_days == 90
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_service_quotas_computes_headroom_and_flags():
|
|
81
|
+
quotas = {
|
|
82
|
+
"Quotas": [
|
|
83
|
+
{
|
|
84
|
+
"QuotaName": "WorkSpaces",
|
|
85
|
+
"QuotaCode": "L-1",
|
|
86
|
+
"Value": 200.0,
|
|
87
|
+
"Adjustable": True,
|
|
88
|
+
"UsageMetric": {
|
|
89
|
+
"MetricNamespace": "AWS/Usage",
|
|
90
|
+
"MetricName": "ResourceCount",
|
|
91
|
+
"MetricDimensions": {"Service": "WorkSpaces", "Resource": "WorkSpace"},
|
|
92
|
+
"MetricStatisticRecommendation": "Maximum",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"QuotaName": "WorkSpaces Pools",
|
|
97
|
+
"QuotaCode": "L-2",
|
|
98
|
+
"Value": 10.0,
|
|
99
|
+
"Adjustable": False,
|
|
100
|
+
"UsageMetric": {
|
|
101
|
+
"MetricNamespace": "AWS/Usage",
|
|
102
|
+
"MetricName": "ResourceCount",
|
|
103
|
+
"MetricDimensions": {"Service": "WorkSpaces", "Resource": "Pool"},
|
|
104
|
+
"MetricStatisticRecommendation": "Maximum",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{"QuotaName": "Zero thing", "QuotaCode": "L-3", "Value": 0.0, "Adjustable": True},
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
sq = types.SimpleNamespace(list_service_quotas=lambda **_: quotas)
|
|
111
|
+
# u0 -> WorkSpaces (14/200 = 7%), u1 -> Pools (9/10 = 90% -> flagged, not adjustable)
|
|
112
|
+
cw = types.SimpleNamespace(
|
|
113
|
+
get_metric_data=lambda **_: {
|
|
114
|
+
"MetricDataResults": [
|
|
115
|
+
{"Id": "u0", "Values": [14.0]},
|
|
116
|
+
{"Id": "u1", "Values": [9.0]},
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
factory = FakeFactory({consts.SERVICE_QUOTAS_API: sq, consts.CLOUDWATCH_API: cw})
|
|
121
|
+
|
|
122
|
+
report = governance.get_euc_service_quotas_core(
|
|
123
|
+
factory, "ap-southeast-1", service="workspaces", approaching_pct=80.0
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
by_name = {q.quota_name: q for q in report.quotas}
|
|
127
|
+
assert "Zero thing" not in by_name # zero-limit hidden by default
|
|
128
|
+
assert by_name["WorkSpaces"].utilization_pct == 7.0
|
|
129
|
+
assert by_name["WorkSpaces Pools"].utilization_pct == 90.0
|
|
130
|
+
# Only the Pools quota (90% >= 80) is flagged, and noted as non-adjustable.
|
|
131
|
+
assert len(report.findings) == 1
|
|
132
|
+
assert "WorkSpaces Pools" in report.findings[0].target
|
|
133
|
+
assert "NOT adjustable" in report.findings[0].issue
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Copyright bengroeneveldsg. Licensed under the Apache License, Version 2.0 (the "License").
|
|
2
|
+
# You may not use this file except in compliance with the License.
|
|
3
|
+
# A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
"""Tests for the WorkSpaces Applications image-audit tool, using fake boto3 clients."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import types
|
|
9
|
+
from datetime import UTC, datetime, timedelta
|
|
10
|
+
|
|
11
|
+
from workspaces_euc_mcp_server import consts
|
|
12
|
+
from workspaces_euc_mcp_server.tools import images
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeFactory:
|
|
16
|
+
region = "ap-southeast-1"
|
|
17
|
+
|
|
18
|
+
def __init__(self, client: object) -> None:
|
|
19
|
+
self._client = client
|
|
20
|
+
|
|
21
|
+
def client(self, service_name: str, region: str | None = None):
|
|
22
|
+
assert service_name == consts.APPSTREAM_API
|
|
23
|
+
return self._client
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _appstream(images_by_type: dict[str, list[dict]], builders: list[dict]):
|
|
27
|
+
def describe_images(**kwargs):
|
|
28
|
+
return {"Images": images_by_type.get(kwargs.get("Type"), [])}
|
|
29
|
+
|
|
30
|
+
def describe_image_builders(**_):
|
|
31
|
+
return {"ImageBuilders": builders}
|
|
32
|
+
|
|
33
|
+
return types.SimpleNamespace(
|
|
34
|
+
describe_images=describe_images, describe_image_builders=describe_image_builders
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_audit_flags_stale_base_pinned_agent_and_running_builder():
|
|
39
|
+
old_base = datetime.now(UTC) - timedelta(days=400)
|
|
40
|
+
fresh_base = datetime.now(UTC) - timedelta(days=10)
|
|
41
|
+
client = _appstream(
|
|
42
|
+
{
|
|
43
|
+
"PRIVATE": [
|
|
44
|
+
{
|
|
45
|
+
"Name": "StaleImage",
|
|
46
|
+
"Visibility": "PRIVATE",
|
|
47
|
+
"Platform": "WINDOWS_SERVER_2022",
|
|
48
|
+
"State": "AVAILABLE",
|
|
49
|
+
"AppstreamAgentVersion": "10-02-2025", # pinned, not LATEST
|
|
50
|
+
"Applications": [{"Name": "chrome", "Enabled": True}],
|
|
51
|
+
"PublicBaseImageReleasedDate": old_base,
|
|
52
|
+
"CreatedTime": old_base,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"Name": "GoodImage",
|
|
56
|
+
"Visibility": "PRIVATE",
|
|
57
|
+
"Platform": "WINDOWS_SERVER_2022",
|
|
58
|
+
"State": "AVAILABLE",
|
|
59
|
+
"AppstreamAgentVersion": "LATEST",
|
|
60
|
+
"Applications": [{"Name": "vscode", "Enabled": True}],
|
|
61
|
+
"PublicBaseImageReleasedDate": fresh_base,
|
|
62
|
+
"CreatedTime": fresh_base,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
"SHARED": [],
|
|
66
|
+
},
|
|
67
|
+
builders=[
|
|
68
|
+
{"Name": "Builder-Idle", "State": "STOPPED", "Platform": "WINDOWS_SERVER_2022"},
|
|
69
|
+
{"Name": "Builder-Live", "State": "RUNNING", "Platform": "WINDOWS_SERVER_2025"},
|
|
70
|
+
],
|
|
71
|
+
)
|
|
72
|
+
report = images.audit_application_images_core(FakeFactory(client), "ap-southeast-1")
|
|
73
|
+
|
|
74
|
+
assert report.image_count == 2
|
|
75
|
+
assert report.image_builder_count == 2
|
|
76
|
+
assert report.running_image_builders == 1
|
|
77
|
+
|
|
78
|
+
issues = {(f.target, f.issue) for f in report.findings}
|
|
79
|
+
# Stale base + pinned agent on StaleImage; running builder flagged; GoodImage clean.
|
|
80
|
+
assert any(t == "StaleImage" and "base image released" in i for t, i in issues)
|
|
81
|
+
assert any(t == "StaleImage" and "pinned" in i for t, i in issues)
|
|
82
|
+
assert any(t == "Builder-Live" and "RUNNING" in i for t, i in issues)
|
|
83
|
+
assert not any(t == "GoodImage" for t, _ in issues)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_audit_flags_shared_visibility_and_records_errors():
|
|
87
|
+
from botocore.exceptions import ClientError
|
|
88
|
+
|
|
89
|
+
def describe_images(**kwargs):
|
|
90
|
+
if kwargs.get("Type") == "SHARED":
|
|
91
|
+
return {
|
|
92
|
+
"Images": [
|
|
93
|
+
{
|
|
94
|
+
"Name": "SharedIn",
|
|
95
|
+
"Visibility": "SHARED",
|
|
96
|
+
"State": "AVAILABLE",
|
|
97
|
+
"AppstreamAgentVersion": "LATEST",
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
return {"Images": []}
|
|
102
|
+
|
|
103
|
+
def describe_image_builders(**_):
|
|
104
|
+
raise ClientError(
|
|
105
|
+
{"Error": {"Code": "AccessDeniedException", "Message": "no"}},
|
|
106
|
+
"DescribeImageBuilders",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
client = types.SimpleNamespace(
|
|
110
|
+
describe_images=describe_images, describe_image_builders=describe_image_builders
|
|
111
|
+
)
|
|
112
|
+
report = images.audit_application_images_core(FakeFactory(client), "ap-southeast-1")
|
|
113
|
+
|
|
114
|
+
assert any(f.target == "SharedIn" and "SHARED" in f.issue for f in report.findings)
|
|
115
|
+
assert any(e.operation == "DescribeImageBuilders" for e in report.errors)
|
|
@@ -22,6 +22,8 @@ CLOUDWATCH_API = "cloudwatch" # Telemetry for diagnostics/cost tools.
|
|
|
22
22
|
EC2_API = "ec2" # Used to enrich WorkSpaces Core Managed Instances with EC2 details.
|
|
23
23
|
COST_EXPLORER_API = "ce" # Cost Explorer (global; account-wide, not region-scoped).
|
|
24
24
|
PRICING_API = "pricing" # AWS Price List (global).
|
|
25
|
+
CLOUDTRAIL_API = "cloudtrail" # Management-event history (LookupEvents) for the audit trail.
|
|
26
|
+
SERVICE_QUOTAS_API = "service-quotas" # Quota limits + linked usage metrics for headroom.
|
|
25
27
|
|
|
26
28
|
# Cost Explorer is a global endpoint served from us-east-1 regardless of the working region.
|
|
27
29
|
COST_EXPLORER_REGION = "us-east-1"
|
|
@@ -44,6 +46,50 @@ PRODUCT_WORKSPACES_APPLICATIONS = "Amazon WorkSpaces Applications"
|
|
|
44
46
|
PRODUCT_SECURE_BROWSER = "Amazon WorkSpaces Secure Browser"
|
|
45
47
|
PRODUCT_WORKSPACES_CORE_INSTANCES = "Amazon WorkSpaces Core Managed Instances"
|
|
46
48
|
|
|
49
|
+
# --- Governance: CloudTrail event sources + Service Quotas codes per EUC service group ---
|
|
50
|
+
# CloudTrail EventSource -> human product label (workspaces source covers Personal/Pools/Core).
|
|
51
|
+
EUC_AUDIT_SOURCES = {
|
|
52
|
+
"workspaces.amazonaws.com": "Amazon WorkSpaces",
|
|
53
|
+
"appstream.amazonaws.com": PRODUCT_WORKSPACES_APPLICATIONS,
|
|
54
|
+
"workspaces-web.amazonaws.com": PRODUCT_SECURE_BROWSER,
|
|
55
|
+
"workspaces-instances.amazonaws.com": PRODUCT_WORKSPACES_CORE_INSTANCES,
|
|
56
|
+
}
|
|
57
|
+
# Friendly service filter -> the CloudTrail EventSource(s) it selects.
|
|
58
|
+
EUC_AUDIT_SERVICE_FILTER = {
|
|
59
|
+
"all": list(EUC_AUDIT_SOURCES),
|
|
60
|
+
"workspaces": ["workspaces.amazonaws.com"],
|
|
61
|
+
"applications": ["appstream.amazonaws.com"],
|
|
62
|
+
"secure-browser": ["workspaces-web.amazonaws.com"],
|
|
63
|
+
"core": ["workspaces-instances.amazonaws.com"],
|
|
64
|
+
}
|
|
65
|
+
# Service Quotas service code -> human product label.
|
|
66
|
+
EUC_QUOTA_SERVICE_CODES = {
|
|
67
|
+
"workspaces": "Amazon WorkSpaces",
|
|
68
|
+
"appstream2": PRODUCT_WORKSPACES_APPLICATIONS,
|
|
69
|
+
"workspaces-web": PRODUCT_SECURE_BROWSER,
|
|
70
|
+
"workspaces-instances": PRODUCT_WORKSPACES_CORE_INSTANCES,
|
|
71
|
+
}
|
|
72
|
+
# Friendly service filter -> the Service Quotas code(s) it selects.
|
|
73
|
+
EUC_QUOTA_SERVICE_FILTER = {
|
|
74
|
+
"all": list(EUC_QUOTA_SERVICE_CODES),
|
|
75
|
+
"workspaces": ["workspaces"],
|
|
76
|
+
"applications": ["appstream2"],
|
|
77
|
+
"secure-browser": ["workspaces-web"],
|
|
78
|
+
"core": ["workspaces-instances"],
|
|
79
|
+
}
|
|
80
|
+
# CloudTrail event-name prefixes treated as destructive/high-impact in audit findings.
|
|
81
|
+
AUDIT_DESTRUCTIVE_PREFIXES = (
|
|
82
|
+
"Terminate",
|
|
83
|
+
"Delete",
|
|
84
|
+
"Reboot",
|
|
85
|
+
"Rebuild",
|
|
86
|
+
"Restore",
|
|
87
|
+
"Revoke",
|
|
88
|
+
"Disassociate",
|
|
89
|
+
"Stop",
|
|
90
|
+
"Expire",
|
|
91
|
+
)
|
|
92
|
+
|
|
47
93
|
# Legacy / former product names mapped to their current official name. Accept these as INPUT
|
|
48
94
|
# (users will keep saying them) but always emit the current name in output. This is surfaced to the
|
|
49
95
|
# MCP client model via the server instructions and tool descriptions so a query about, say,
|