workspaces-euc-mcp-server 0.1.5__tar.gz → 0.1.6__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.5 → workspaces_euc_mcp_server-0.1.6}/CHANGELOG.md +12 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/DESIGN.md +2 -2
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/PKG-INFO +3 -3
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/README.md +2 -2
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier0-diagnostics.json +2 -1
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier1-cost.json +2 -1
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier2-lifecycle.json +2 -1
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier3-destructive.json +2 -1
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/pyproject.toml +1 -1
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_diagnostics.py +39 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_secure_browser.py +43 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/__init__.py +1 -1
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/models.py +7 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/diagnostics.py +14 -1
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/secure_browser.py +47 -2
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.dockerignore +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/ci.yml +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/docker-publish.yml +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/publish.yml +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.gitignore +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.pre-commit-config.yaml +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/Dockerfile +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/LICENSE +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/README.md +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/scripts/smoke_readonly.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/__init__.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_clients.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_cost.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_destructive.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_governance.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_images.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_inventory.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_lifecycle.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_naming.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_no_embedded_secrets.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_performance.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_pricing.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_reporting.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/clients.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/consts.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/server.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/__init__.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/_common.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/cost.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/destructive.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/governance.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/images.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/inventory.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/lifecycle.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/performance.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/pricing.py +0 -0
- {workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/workspaces_euc_mcp_server/tools/reporting.py +0 -0
|
@@ -5,6 +5,18 @@ All notable changes to this project are documented here. The format is based on
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.1.6] - 2026-06-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `check_directory_health` now surfaces each directory's **registration properties** in its
|
|
12
|
+
signals — notably the target **OU** (`WorkspaceCreationProperties.DefaultOu`), plus custom
|
|
13
|
+
security group, local-admin / internet-access / maintenance-mode flags, and directory/workspace
|
|
14
|
+
type. (AD-backed directories carry an OU; WorkSpaces-managed ones return none.)
|
|
15
|
+
- `get_secure_browser_portal_details` now resolves the portal's **data-protection configuration**
|
|
16
|
+
when attached: the redacted built-in/custom inline-redaction patterns, global confidence level,
|
|
17
|
+
and enforced/exempt URLs — not just whether data protection is on. Adds
|
|
18
|
+
`workspaces-web:GetDataProtectionSettings` to every IAM tier.
|
|
19
|
+
|
|
8
20
|
## [0.1.5] - 2026-06-02
|
|
9
21
|
|
|
10
22
|
### Added
|
|
@@ -117,7 +117,7 @@ and return a synthesized result, not raw API passthroughs.
|
|
|
117
117
|
| `diagnose_workspace_connectivity` | Correlate a Personal WorkSpace's state + connection status + directory health + CloudWatch into a root-cause narrative | `workspaces:Describe*`, `ds:DescribeDirectories`, `cloudwatch:GetMetricData` |
|
|
118
118
|
| `diagnose_pool` | Why a Pool is unhealthy/queued — capacity status, sessions, errors, scaling | `workspaces:DescribeWorkspacesPool*`, `cloudwatch:GetMetricData` |
|
|
119
119
|
| `diagnose_application_fleet` | Fleet state, capacity, scaling activity, fleet errors | `appstream:DescribeFleets`, `appstream:ListAssociatedStacks`, `cloudwatch:GetMetricData`, `application-autoscaling:DescribeScalingActivities` |
|
|
120
|
-
| `check_directory_health` | Shared dependency: directory reachability/registration (skips `ds` for WorkSpaces-managed `wsd-` directories
|
|
120
|
+
| `check_directory_health` | Shared dependency: directory reachability/registration + registration properties (target OU, security group, flags); skips `ds` for WorkSpaces-managed `wsd-` directories | `ds:DescribeDirectories`, `workspaces:DescribeWorkspaceDirectories` |
|
|
121
121
|
|
|
122
122
|
**Cost, utilization & performance**
|
|
123
123
|
| Tool | Purpose | IAM actions |
|
|
@@ -134,7 +134,7 @@ and return a synthesized result, not raw API passthroughs.
|
|
|
134
134
|
**Secure Browser**
|
|
135
135
|
| Tool | Purpose | IAM actions |
|
|
136
136
|
|---|---|---|
|
|
137
|
-
| `get_secure_browser_portal_details` | Portal config + associated settings (browser/network/user/IP-access) | `workspaces-web:GetPortal`, `workspaces-web:List*`, `workspaces-web:Get*Settings` |
|
|
137
|
+
| `get_secure_browser_portal_details` | Portal config + associated settings (browser/network/user/IP-access) + resolved data-protection redaction config | `workspaces-web:GetPortal`, `workspaces-web:List*`, `workspaces-web:Get*Settings`, `workspaces-web:GetDataProtectionSettings` |
|
|
138
138
|
| `get_secure_browser_portal_usage` | Portal session/usage metrics | `workspaces-web:ListPortals`, `cloudwatch:GetMetricData` |
|
|
139
139
|
|
|
140
140
|
**Reporting & audit**
|
|
@@ -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.6
|
|
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
|
|
@@ -81,7 +81,7 @@ audit, and governance tools:
|
|
|
81
81
|
| `diagnose_application_fleet` | A WorkSpaces Applications fleet's health and capacity — fleet state, fleet errors, compute capacity, auto-scaling activity, and insufficient-capacity errors. |
|
|
82
82
|
| `diagnose_pool` | A WorkSpaces Pool's health — state, pool errors, user-session capacity, backing directory health, and session-utilization. |
|
|
83
83
|
| `get_application_fleet_usage` | A WorkSpaces Applications fleet's **usage history** — AWS/AppStream capacity/utilization time-series over a window, with a plain-language summary (e.g. idle running capacity) (Tier 0). |
|
|
84
|
-
| `check_directory_health` | Registration state
|
|
84
|
+
| `check_directory_health` | Registration state, AWS Directory Service stage, and **registration properties** (target **OU**, custom security group, local-admin / internet-access / maintenance-mode flags) for one or all WorkSpaces-registered directories. |
|
|
85
85
|
| `analyze_workspace_utilization` | Classifies WorkSpaces Personal desktops as unused / idle / active from the `UserConnected` metric (Tier 1). |
|
|
86
86
|
| `recommend_running_mode` | Flags AlwaysOn desktops with low usage as AutoStop candidates, with an **estimated $/mo saving** where the bundle price can be matched (Tier 1). |
|
|
87
87
|
| `get_workspace_performance` | Native CPU / memory / disk / GPU / latency / uptime metrics per desktop from `AWS/WorkSpaces` — no CloudWatch agent (Tier 0). |
|
|
@@ -94,7 +94,7 @@ audit, and governance tools:
|
|
|
94
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
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
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). |
|
|
97
|
-
| `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network,
|
|
97
|
+
| `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, attached policies, and — when configured — the **data-protection redaction config** (which built-in/custom patterns are redacted, confidence level, enforced/exempt URLs) (Tier 0). |
|
|
98
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). |
|
|
99
99
|
| `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
|
|
100
100
|
|
|
@@ -51,7 +51,7 @@ audit, and governance tools:
|
|
|
51
51
|
| `diagnose_application_fleet` | A WorkSpaces Applications fleet's health and capacity — fleet state, fleet errors, compute capacity, auto-scaling activity, and insufficient-capacity errors. |
|
|
52
52
|
| `diagnose_pool` | A WorkSpaces Pool's health — state, pool errors, user-session capacity, backing directory health, and session-utilization. |
|
|
53
53
|
| `get_application_fleet_usage` | A WorkSpaces Applications fleet's **usage history** — AWS/AppStream capacity/utilization time-series over a window, with a plain-language summary (e.g. idle running capacity) (Tier 0). |
|
|
54
|
-
| `check_directory_health` | Registration state
|
|
54
|
+
| `check_directory_health` | Registration state, AWS Directory Service stage, and **registration properties** (target **OU**, custom security group, local-admin / internet-access / maintenance-mode flags) for one or all WorkSpaces-registered directories. |
|
|
55
55
|
| `analyze_workspace_utilization` | Classifies WorkSpaces Personal desktops as unused / idle / active from the `UserConnected` metric (Tier 1). |
|
|
56
56
|
| `recommend_running_mode` | Flags AlwaysOn desktops with low usage as AutoStop candidates, with an **estimated $/mo saving** where the bundle price can be matched (Tier 1). |
|
|
57
57
|
| `get_workspace_performance` | Native CPU / memory / disk / GPU / latency / uptime metrics per desktop from `AWS/WorkSpaces` — no CloudWatch agent (Tier 0). |
|
|
@@ -64,7 +64,7 @@ audit, and governance tools:
|
|
|
64
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
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
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). |
|
|
67
|
-
| `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network,
|
|
67
|
+
| `get_secure_browser_portal_details` | Resolves a Secure Browser portal's user settings (clipboard/print/download controls + timeouts), network, attached policies, and — when configured — the **data-protection redaction config** (which built-in/custom patterns are redacted, confidence level, enforced/exempt URLs) (Tier 0). |
|
|
68
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). |
|
|
69
69
|
| `list_unused_resources` | Unused WorkSpaces desktops and stopped/zero-capacity fleets worth reclaiming (Tier 0). |
|
|
70
70
|
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier0-diagnostics.json
RENAMED
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"workspaces-web:ListUserSettings",
|
|
40
40
|
"workspaces-web:ListNetworkSettings",
|
|
41
41
|
"workspaces-web:GetUserSettings",
|
|
42
|
-
"workspaces-web:GetNetworkSettings"
|
|
42
|
+
"workspaces-web:GetNetworkSettings",
|
|
43
|
+
"workspaces-web:GetDataProtectionSettings"
|
|
43
44
|
],
|
|
44
45
|
"Resource": "*"
|
|
45
46
|
},
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"workspaces-web:ListUserSettings",
|
|
40
40
|
"workspaces-web:ListNetworkSettings",
|
|
41
41
|
"workspaces-web:GetUserSettings",
|
|
42
|
-
"workspaces-web:GetNetworkSettings"
|
|
42
|
+
"workspaces-web:GetNetworkSettings",
|
|
43
|
+
"workspaces-web:GetDataProtectionSettings"
|
|
43
44
|
],
|
|
44
45
|
"Resource": "*"
|
|
45
46
|
},
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier2-lifecycle.json
RENAMED
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"workspaces-web:ListUserSettings",
|
|
40
40
|
"workspaces-web:ListNetworkSettings",
|
|
41
41
|
"workspaces-web:GetUserSettings",
|
|
42
|
-
"workspaces-web:GetNetworkSettings"
|
|
42
|
+
"workspaces-web:GetNetworkSettings",
|
|
43
|
+
"workspaces-web:GetDataProtectionSettings"
|
|
43
44
|
],
|
|
44
45
|
"Resource": "*"
|
|
45
46
|
},
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/iam/tier3-destructive.json
RENAMED
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"workspaces-web:ListUserSettings",
|
|
40
40
|
"workspaces-web:ListNetworkSettings",
|
|
41
41
|
"workspaces-web:GetUserSettings",
|
|
42
|
-
"workspaces-web:GetNetworkSettings"
|
|
42
|
+
"workspaces-web:GetNetworkSettings",
|
|
43
|
+
"workspaces-web:GetDataProtectionSettings"
|
|
43
44
|
],
|
|
44
45
|
"Resource": "*"
|
|
45
46
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "workspaces-euc-mcp-server"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.6"
|
|
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"
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_diagnostics.py
RENAMED
|
@@ -161,6 +161,45 @@ def test_directory_health_skips_ds_for_workspaces_managed_directory():
|
|
|
161
161
|
assert any("WorkSpaces-managed" in f.title for f in d.findings)
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
def test_directory_health_surfaces_registration_ou_and_properties():
|
|
165
|
+
# The registration OU (WorkspaceCreationProperties.DefaultOu) and related properties must be
|
|
166
|
+
# exposed in the diagnosis signals.
|
|
167
|
+
workspaces = types.SimpleNamespace(
|
|
168
|
+
describe_workspace_directories=lambda **_: {
|
|
169
|
+
"Directories": [
|
|
170
|
+
{
|
|
171
|
+
"DirectoryId": "d-0123456789",
|
|
172
|
+
"State": "REGISTERED",
|
|
173
|
+
"DirectoryType": "AD_CONNECTOR",
|
|
174
|
+
"WorkspaceType": "PERSONAL",
|
|
175
|
+
"WorkspaceCreationProperties": {
|
|
176
|
+
"DefaultOu": "OU=AmazonWorkspaces,OU=Singapore,DC=bg,DC=local",
|
|
177
|
+
"CustomSecurityGroupId": "sg-0abc",
|
|
178
|
+
"UserEnabledAsLocalAdministrator": True,
|
|
179
|
+
"EnableInternetAccess": False,
|
|
180
|
+
"EnableMaintenanceMode": True,
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
ds = types.SimpleNamespace(
|
|
187
|
+
describe_directories=lambda **_: {
|
|
188
|
+
"DirectoryDescriptions": [{"DirectoryId": "d-0123456789", "Stage": "Active"}]
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
factory = FakeFactory({consts.WORKSPACES_API: workspaces, consts.DIRECTORY_API: ds})
|
|
192
|
+
|
|
193
|
+
report = diagnostics.check_directory_health_core(factory, "d-0123456789", "us-east-1")
|
|
194
|
+
|
|
195
|
+
sig = report.directories[0].signals
|
|
196
|
+
assert sig["default_ou"] == "OU=AmazonWorkspaces,OU=Singapore,DC=bg,DC=local"
|
|
197
|
+
assert sig["directory_type"] == "AD_CONNECTOR"
|
|
198
|
+
assert sig["custom_security_group_id"] == "sg-0abc"
|
|
199
|
+
assert sig["user_enabled_as_local_administrator"] is True
|
|
200
|
+
assert sig["enable_maintenance_mode"] is True
|
|
201
|
+
|
|
202
|
+
|
|
164
203
|
def test_directory_health_flags_impaired_stage():
|
|
165
204
|
workspaces = types.SimpleNamespace(
|
|
166
205
|
describe_workspace_directories=lambda **_: {
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_secure_browser.py
RENAMED
|
@@ -65,6 +65,49 @@ def test_portal_details_resolves_settings():
|
|
|
65
65
|
assert d.has_data_protection is False
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
def test_portal_details_resolves_data_protection_config():
|
|
69
|
+
web = types.SimpleNamespace(
|
|
70
|
+
get_portal=lambda **_: {
|
|
71
|
+
"portal": {
|
|
72
|
+
"portalArn": "arn:portal/2",
|
|
73
|
+
"displayName": "Okta",
|
|
74
|
+
"dataProtectionSettingsArn": "arn:dp/1",
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
get_data_protection_settings=lambda **_: {
|
|
78
|
+
"dataProtectionSettings": {
|
|
79
|
+
"displayName": "ip-address-dp",
|
|
80
|
+
"inlineRedactionConfiguration": {
|
|
81
|
+
"inlineRedactionPatterns": [
|
|
82
|
+
{"builtInPatternId": "ipAddr"},
|
|
83
|
+
{"builtInPatternId": "macAddr"},
|
|
84
|
+
{
|
|
85
|
+
"customPattern": {
|
|
86
|
+
"patternName": "EmployeeId",
|
|
87
|
+
"keywordRegex": "EMP-\\d+",
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
"globalEnforcedUrls": ["*"],
|
|
92
|
+
"globalConfidenceLevel": 2,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
factory = FakeFactory({consts.SECURE_BROWSER_API: web})
|
|
98
|
+
|
|
99
|
+
d = secure_browser.get_secure_browser_portal_details_core(factory, "arn:portal/2", "us-east-1")
|
|
100
|
+
|
|
101
|
+
assert d.has_data_protection is True
|
|
102
|
+
dp = d.data_protection
|
|
103
|
+
assert dp["display_name"] == "ip-address-dp"
|
|
104
|
+
assert dp["redacted_pattern_count"] == 3
|
|
105
|
+
assert dp["builtin_patterns"] == ["ipAddr", "macAddr"]
|
|
106
|
+
assert dp["custom_patterns"][0]["name"] == "EmployeeId"
|
|
107
|
+
assert dp["global_confidence_level"] == 2
|
|
108
|
+
assert dp["global_enforced_urls"] == ["*"]
|
|
109
|
+
|
|
110
|
+
|
|
68
111
|
def test_portal_usage_empty_explains_session_model():
|
|
69
112
|
cw = types.SimpleNamespace(
|
|
70
113
|
get_metric_data=lambda **_: {"MetricDataResults": []} # no data
|
|
@@ -156,6 +156,13 @@ class SecureBrowserPortalDetails(BaseModel):
|
|
|
156
156
|
network: dict[str, object] = Field(default_factory=dict)
|
|
157
157
|
has_browser_policy: bool = False
|
|
158
158
|
has_data_protection: bool = False
|
|
159
|
+
data_protection: dict[str, object] = Field(
|
|
160
|
+
default_factory=dict,
|
|
161
|
+
description=(
|
|
162
|
+
"Resolved inline-redaction configuration when data protection is attached: redacted "
|
|
163
|
+
"patterns (built-in + custom), global confidence level, and enforced/exempt URLs."
|
|
164
|
+
),
|
|
165
|
+
)
|
|
159
166
|
errors: list[ServiceError] = Field(default_factory=list)
|
|
160
167
|
|
|
161
168
|
|
|
@@ -279,9 +279,22 @@ def _diagnose_directory_into(
|
|
|
279
279
|
)
|
|
280
280
|
dirs = (reg or {}).get("Directories", [])
|
|
281
281
|
if dirs:
|
|
282
|
-
|
|
282
|
+
d0 = dirs[0]
|
|
283
|
+
reg_state = d0.get("State", "UNKNOWN")
|
|
283
284
|
if signals is not None:
|
|
284
285
|
signals[f"{signals_prefix}registration_state"] = reg_state
|
|
286
|
+
signals[f"{signals_prefix}directory_type"] = d0.get("DirectoryType")
|
|
287
|
+
signals[f"{signals_prefix}workspace_type"] = d0.get("WorkspaceType")
|
|
288
|
+
# Registration-level WorkspaceCreationProperties — notably the target OU. Only AD-backed
|
|
289
|
+
# directories carry a DefaultOu; WorkSpaces-managed (Entra/internal) ones return None.
|
|
290
|
+
wcp = d0.get("WorkspaceCreationProperties") or {}
|
|
291
|
+
signals[f"{signals_prefix}default_ou"] = wcp.get("DefaultOu")
|
|
292
|
+
signals[f"{signals_prefix}custom_security_group_id"] = wcp.get("CustomSecurityGroupId")
|
|
293
|
+
signals[f"{signals_prefix}user_enabled_as_local_administrator"] = wcp.get(
|
|
294
|
+
"UserEnabledAsLocalAdministrator"
|
|
295
|
+
)
|
|
296
|
+
signals[f"{signals_prefix}enable_internet_access"] = wcp.get("EnableInternetAccess")
|
|
297
|
+
signals[f"{signals_prefix}enable_maintenance_mode"] = wcp.get("EnableMaintenanceMode")
|
|
285
298
|
if reg_state != "REGISTERED":
|
|
286
299
|
findings.append(
|
|
287
300
|
Finding(
|
|
@@ -78,6 +78,19 @@ def get_secure_browser_portal_details_core(
|
|
|
78
78
|
if key in (ns or {}):
|
|
79
79
|
network[key] = ns[key]
|
|
80
80
|
|
|
81
|
+
data_protection: dict[str, object] = {}
|
|
82
|
+
if portal.get("dataProtectionSettingsArn"):
|
|
83
|
+
dps = try_call(
|
|
84
|
+
errors,
|
|
85
|
+
consts.PRODUCT_SECURE_BROWSER,
|
|
86
|
+
"GetDataProtectionSettings",
|
|
87
|
+
lambda: web.get_data_protection_settings(
|
|
88
|
+
dataProtectionSettingsArn=portal["dataProtectionSettingsArn"]
|
|
89
|
+
).get("dataProtectionSettings", {}),
|
|
90
|
+
default={},
|
|
91
|
+
)
|
|
92
|
+
data_protection = _summarize_data_protection(dps or {})
|
|
93
|
+
|
|
81
94
|
return SecureBrowserPortalDetails(
|
|
82
95
|
portal_arn=portal_arn,
|
|
83
96
|
display_name=portal.get("displayName"),
|
|
@@ -87,10 +100,40 @@ def get_secure_browser_portal_details_core(
|
|
|
87
100
|
network=network,
|
|
88
101
|
has_browser_policy=bool(portal.get("browserSettingsArn")),
|
|
89
102
|
has_data_protection=bool(portal.get("dataProtectionSettingsArn")),
|
|
103
|
+
data_protection=data_protection,
|
|
90
104
|
errors=errors,
|
|
91
105
|
)
|
|
92
106
|
|
|
93
107
|
|
|
108
|
+
def _summarize_data_protection(dps: dict[str, Any]) -> dict[str, Any]:
|
|
109
|
+
"""Reduce a data-protection settings object to the policy-relevant redaction configuration."""
|
|
110
|
+
inline: dict[str, Any] = dps.get("inlineRedactionConfiguration") or {}
|
|
111
|
+
patterns: list[dict[str, Any]] = inline.get("inlineRedactionPatterns") or []
|
|
112
|
+
builtin: list[str] = []
|
|
113
|
+
custom: list[dict[str, Any]] = []
|
|
114
|
+
for p in patterns:
|
|
115
|
+
if p.get("builtInPatternId"):
|
|
116
|
+
builtin.append(p["builtInPatternId"])
|
|
117
|
+
elif p.get("customPattern"):
|
|
118
|
+
cp = p["customPattern"]
|
|
119
|
+
custom.append(
|
|
120
|
+
{
|
|
121
|
+
"name": cp.get("patternName"),
|
|
122
|
+
"description": cp.get("patternDescription"),
|
|
123
|
+
"keyword_regex": cp.get("keywordRegex"),
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
return {
|
|
127
|
+
"display_name": dps.get("displayName"),
|
|
128
|
+
"redacted_pattern_count": len(patterns),
|
|
129
|
+
"builtin_patterns": builtin,
|
|
130
|
+
"custom_patterns": custom,
|
|
131
|
+
"global_confidence_level": inline.get("globalConfidenceLevel"),
|
|
132
|
+
"global_enforced_urls": inline.get("globalEnforcedUrls"),
|
|
133
|
+
"global_exempt_urls": inline.get("globalExemptUrls"),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
94
137
|
def _portal_id(portal: str) -> str:
|
|
95
138
|
"""Accept a portal ARN or id; the CloudWatch dimension uses the id (last ARN segment)."""
|
|
96
139
|
return portal.rsplit("/", 1)[-1] if "/" in portal else portal
|
|
@@ -147,8 +190,10 @@ def register(mcp: Any, factory: ClientFactory) -> None:
|
|
|
147
190
|
"""Resolve a WorkSpaces Secure Browser portal's settings (security-relevant).
|
|
148
191
|
|
|
149
192
|
Returns the portal's user settings (clipboard copy/paste, file download/upload, print
|
|
150
|
-
controls + timeouts), network (VPC/subnets/security groups),
|
|
151
|
-
and data
|
|
193
|
+
controls + timeouts), network (VPC/subnets/security groups), whether a browser policy is
|
|
194
|
+
attached, and — when data protection is configured — the resolved inline-redaction config
|
|
195
|
+
(which built-in/custom patterns are redacted, global confidence, enforced/exempt URLs).
|
|
196
|
+
Read-only.
|
|
152
197
|
|
|
153
198
|
Args:
|
|
154
199
|
portal_arn: The portal ARN (from get_euc_inventory_summary / generate_inventory_report).
|
|
File without changes
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/ci.yml
RENAMED
|
File without changes
|
|
File without changes
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/.github/workflows/publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/scripts/smoke_readonly.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_destructive.py
RENAMED
|
File without changes
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_governance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{workspaces_euc_mcp_server-0.1.5 → workspaces_euc_mcp_server-0.1.6}/tests/test_performance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|