@raghulm/aegis-mcp 1.0.5 → 1.0.9

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.
package/server/main.py CHANGED
@@ -1,212 +1,224 @@
1
- from __future__ import annotations
2
-
3
- import os
4
-
5
- from mcp.server.fastmcp import FastMCP
6
-
7
- from audit.audit_logger import audit_tool_call
8
- from server.auth import authorize_tool, decode_bearer_token
9
- from server.config import load_role_policies, load_scope_policies, load_settings
10
- from tools.aws.ec2 import list_ec2_instances
11
- from tools.aws.s3 import check_s3_public_access
12
- from tools.cicd.pipeline import pipeline_status
13
- from tools.cicd.jenkins import (
14
- jenkins_list_jobs as _jenkins_list_jobs,
15
- jenkins_get_job_info as _jenkins_get_job_info,
16
- jenkins_create_job as _jenkins_create_job,
17
- jenkins_trigger_build as _jenkins_trigger_build,
18
- jenkins_get_build_info as _jenkins_get_build_info,
19
- jenkins_get_build_log as _jenkins_get_build_log,
20
- jenkins_delete_job as _jenkins_delete_job,
21
- )
22
- from tools.git.repo import get_recent_commits
23
- from tools.kubernetes.pods import list_pods
24
- from tools.kubernetes.audit import k8s_security_audit
25
- from tools.network.headers import check_http_headers
26
- from tools.network.port_scanner import port_scan
27
- from tools.network.ssl_checker import check_ssl_certificate
28
- from tools.security.deps import check_dependencies
29
- from tools.security.secrets import scan_secrets
30
- from tools.security.semgrep import run_semgrep_scan
31
- from tools.security.trivy import run_trivy_scan
32
-
33
- settings = load_settings()
34
- role_policies = load_role_policies(settings)
35
- scope_policies = load_scope_policies(settings)
36
-
37
- mcp = FastMCP(settings.service_name, json_response=True)
38
-
39
- AUTH_DISABLED = os.getenv("MCP_AUTH_DISABLED", "false").lower() == "true"
40
-
41
-
42
- def _authorize(token: str, tool_name: str) -> None:
43
- if AUTH_DISABLED or not token:
44
- return
45
- principal = decode_bearer_token(token, settings)
46
- authorize_tool(principal, tool_name, role_policies, scope_policies)
47
-
48
-
49
- @mcp.tool()
50
- @audit_tool_call("aws_list_ec2_instances")
51
- def aws_list_ec2_instances(region: str, token: str = "") -> list[dict]:
52
- _authorize(token, "aws_list_ec2_instances")
53
- return list_ec2_instances(region)
54
-
55
-
56
- @mcp.tool()
57
- @audit_tool_call("k8s_list_pods")
58
- def k8s_list_pods(namespace: str = "default", token: str = "") -> list[dict]:
59
- _authorize(token, "k8s_list_pods")
60
- return list_pods(namespace)
61
-
62
-
63
- @mcp.tool()
64
- @audit_tool_call("k8s_security_audit")
65
- def k8s_security_audit_tool(namespace: str = "", token: str = "") -> list[dict]:
66
- """Audit Kubernetes clusters for misconfigurations and security risks (privileged containers, hostNetwork, exposed NodePorts, cluster-admin service accounts)."""
67
- _authorize(token, "k8s_security_audit")
68
- return k8s_security_audit(namespace)
69
-
70
-
71
- @mcp.tool()
72
- @audit_tool_call("security_run_trivy_scan")
73
- def security_run_trivy_scan(image: str, token: str = "") -> dict:
74
- _authorize(token, "security_run_trivy_scan")
75
- return run_trivy_scan(image)
76
-
77
-
78
- @mcp.tool()
79
- @audit_tool_call("git_recent_commits")
80
- def git_recent_commits(limit: int = 10, token: str = "") -> list[dict[str, str]]:
81
- _authorize(token, "git_recent_commits")
82
- return get_recent_commits(limit)
83
-
84
-
85
- @mcp.tool()
86
- @audit_tool_call("cicd_pipeline_status")
87
- def cicd_pipeline_status(base_url: str, pipeline_id: str, api_token: str, token: str = "") -> dict:
88
- """Fetch the status of a CI/CD pipeline."""
89
- _authorize(token, "cicd_pipeline_status")
90
- return pipeline_status(base_url, pipeline_id, api_token)
91
-
92
-
93
- # ── New zero-install tools ─────────────────────────────────────────
94
-
95
-
96
- @mcp.tool()
97
- @audit_tool_call("security_scan_secrets")
98
- def security_scan_secrets(path: str, token: str = "") -> list[dict]:
99
- """Scan local files or directories for exposed secrets (API keys, tokens, passwords). This tool runs locally and has full access to the user's local filesystem (e.g. C:\\... paths)."""
100
- _authorize(token, "security_scan_secrets")
101
- return scan_secrets(path)
102
-
103
-
104
- @mcp.tool()
105
- @audit_tool_call("security_check_ssl_certificate")
106
- def security_check_ssl_certificate(hostname: str, port: int = 443, token: str = "") -> dict:
107
- """Check SSL/TLS certificate details and expiry for a domain."""
108
- _authorize(token, "security_check_ssl_certificate")
109
- return check_ssl_certificate(hostname, port)
110
-
111
-
112
- @mcp.tool()
113
- @audit_tool_call("security_check_dependencies")
114
- def security_check_dependencies(file_path: str, token: str = "") -> list[dict]:
115
- """Scan dependency files (requirements.txt, package.json) for known vulnerabilities via OSV.dev."""
116
- _authorize(token, "security_check_dependencies")
117
- return check_dependencies(file_path)
118
-
119
-
120
- @mcp.tool()
121
- @audit_tool_call("security_check_http_headers")
122
- def security_check_http_headers(url: str, token: str = "") -> dict:
123
- """Audit HTTP security headers (HSTS, CSP, X-Frame-Options, etc.) for a URL."""
124
- _authorize(token, "security_check_http_headers")
125
- return check_http_headers(url)
126
-
127
-
128
- @mcp.tool()
129
- @audit_tool_call("aws_check_s3_public_access")
130
- def aws_check_s3_public_access(region: str = "us-east-1", token: str = "") -> list[dict]:
131
- """Audit S3 buckets for public access settings."""
132
- _authorize(token, "aws_check_s3_public_access")
133
- return check_s3_public_access(region)
134
-
135
-
136
- @mcp.tool()
137
- @audit_tool_call("network_port_scan")
138
- def network_port_scan(host: str, ports: str = "", token: str = "") -> list[dict]:
139
- """Perform a TCP port scan on common service ports for a host."""
140
- _authorize(token, "network_port_scan")
141
- return port_scan(host, ports)
142
-
143
-
144
- @mcp.tool()
145
- @audit_tool_call("security_semgrep_scan")
146
- def security_semgrep_scan(path: str, config: str = "auto", token: str = "") -> dict:
147
- """Run a Semgrep SAST scan on a local directory or file. This tool runs locally and has full access to the user's local filesystem (e.g. C:\\... paths). Config can be 'auto', 'p/python', 'p/javascript', etc."""
148
- _authorize(token, "security_semgrep_scan")
149
- return run_semgrep_scan(path, config)
150
-
151
-
152
- # ── Jenkins CI/CD tools ────────────────────────────────────────────
153
-
154
-
155
- @mcp.tool()
156
- @audit_tool_call("jenkins_list_jobs")
157
- def jenkins_list_jobs(url: str, username: str, api_token: str, token: str = "") -> list[dict]:
158
- """List all jobs on a Jenkins server. Provide the Jenkins URL, username, and API token."""
159
- _authorize(token, "jenkins_list_jobs")
160
- return _jenkins_list_jobs(url, username, api_token)
161
-
162
-
163
- @mcp.tool()
164
- @audit_tool_call("jenkins_get_job_info")
165
- def jenkins_get_job_info(url: str, username: str, api_token: str, job_name: str, token: str = "") -> dict:
166
- """Get detailed information about a specific Jenkins job including build history and health."""
167
- _authorize(token, "jenkins_get_job_info")
168
- return _jenkins_get_job_info(url, username, api_token, job_name)
169
-
170
-
171
- @mcp.tool()
172
- @audit_tool_call("jenkins_create_job")
173
- def jenkins_create_job(url: str, username: str, api_token: str, job_name: str, config_xml: str = "", token: str = "") -> dict:
174
- """Create a new Jenkins job. Optionally provide config_xml for the job definition; otherwise a minimal freestyle project is created."""
175
- _authorize(token, "jenkins_create_job")
176
- return _jenkins_create_job(url, username, api_token, job_name, config_xml)
177
-
178
-
179
- @mcp.tool()
180
- @audit_tool_call("jenkins_trigger_build")
181
- def jenkins_trigger_build(url: str, username: str, api_token: str, job_name: str, parameters: str = "", token: str = "") -> dict:
182
- """Trigger a build for an existing Jenkins job. Optionally pass parameters as a JSON string, e.g. '{"BRANCH": "main"}'."""
183
- _authorize(token, "jenkins_trigger_build")
184
- return _jenkins_trigger_build(url, username, api_token, job_name, parameters)
185
-
186
-
187
- @mcp.tool()
188
- @audit_tool_call("jenkins_get_build_info")
189
- def jenkins_get_build_info(url: str, username: str, api_token: str, job_name: str, build_number: int, token: str = "") -> dict:
190
- """Get information about a specific Jenkins build including result, duration, and status."""
191
- _authorize(token, "jenkins_get_build_info")
192
- return _jenkins_get_build_info(url, username, api_token, job_name, build_number)
193
-
194
-
195
- @mcp.tool()
196
- @audit_tool_call("jenkins_get_build_log")
197
- def jenkins_get_build_log(url: str, username: str, api_token: str, job_name: str, build_number: int, token: str = "") -> dict:
198
- """Fetch the console output of a Jenkins build."""
199
- _authorize(token, "jenkins_get_build_log")
200
- return _jenkins_get_build_log(url, username, api_token, job_name, build_number)
201
-
202
-
203
- @mcp.tool()
204
- @audit_tool_call("jenkins_delete_job")
205
- def jenkins_delete_job(url: str, username: str, api_token: str, job_name: str, token: str = "") -> dict:
206
- """Delete a Jenkins job from the server."""
207
- _authorize(token, "jenkins_delete_job")
208
- return _jenkins_delete_job(url, username, api_token, job_name)
209
-
210
-
211
- if __name__ == "__main__":
212
- mcp.run(transport="streamable-http")
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from audit.audit_logger import audit_tool_call
8
+ from server.auth import authorize_tool, decode_bearer_token
9
+ from server.config import load_role_policies, load_scope_policies, load_settings
10
+ from tools.aws.ec2 import list_ec2_instances
11
+ from tools.aws.s3 import check_s3_public_access
12
+ from tools.cicd.pipeline import pipeline_status
13
+ from tools.cicd.jenkins import (
14
+ jenkins_list_jobs as _jenkins_list_jobs,
15
+ jenkins_get_job_info as _jenkins_get_job_info,
16
+ jenkins_create_job as _jenkins_create_job,
17
+ jenkins_trigger_build as _jenkins_trigger_build,
18
+ jenkins_get_build_info as _jenkins_get_build_info,
19
+ jenkins_get_build_log as _jenkins_get_build_log,
20
+ jenkins_delete_job as _jenkins_delete_job,
21
+ )
22
+ from tools.git.repo import get_recent_commits
23
+ from tools.kubernetes.pods import list_pods
24
+ from tools.kubernetes.audit import k8s_security_audit
25
+ from tools.network.headers import check_http_headers
26
+ from tools.network.port_scanner import port_scan
27
+ from tools.network.ssl_checker import check_ssl_certificate
28
+ from tools.security.deps import check_dependencies
29
+ from tools.security.secrets import scan_secrets
30
+ from tools.security.semgrep import run_semgrep_scan
31
+ from tools.security.terraform import scan_terraform
32
+ from tools.security.trivy import run_trivy_scan
33
+
34
+ settings = load_settings()
35
+ role_policies = load_role_policies(settings)
36
+ scope_policies = load_scope_policies(settings)
37
+
38
+ mcp = FastMCP(settings.service_name, json_response=True)
39
+
40
+ AUTH_DISABLED = os.getenv("MCP_AUTH_DISABLED", "false").lower() == "true"
41
+
42
+
43
+ def _authorize(token: str, tool_name: str) -> None:
44
+ if AUTH_DISABLED or not token:
45
+ return
46
+ principal = decode_bearer_token(token, settings)
47
+ authorize_tool(principal, tool_name, role_policies, scope_policies)
48
+
49
+
50
+ @mcp.tool()
51
+ @audit_tool_call("aws_list_ec2_instances")
52
+ def aws_list_ec2_instances(region: str, token: str = "") -> list[dict]:
53
+ _authorize(token, "aws_list_ec2_instances")
54
+ return list_ec2_instances(region)
55
+
56
+
57
+ @mcp.tool()
58
+ @audit_tool_call("k8s_list_pods")
59
+ def k8s_list_pods(namespace: str = "default", token: str = "") -> list[dict]:
60
+ _authorize(token, "k8s_list_pods")
61
+ return list_pods(namespace)
62
+
63
+
64
+ @mcp.tool()
65
+ @audit_tool_call("k8s_security_audit")
66
+ def k8s_security_audit_tool(namespace: str = "", token: str = "") -> list[dict]:
67
+ """Audit Kubernetes clusters for misconfigurations and security risks (privileged containers, hostNetwork, exposed NodePorts, cluster-admin service accounts)."""
68
+ _authorize(token, "k8s_security_audit")
69
+ return k8s_security_audit(namespace)
70
+
71
+
72
+ @mcp.tool()
73
+ @audit_tool_call("security_run_trivy_scan")
74
+ def security_run_trivy_scan(image: str, token: str = "") -> dict:
75
+ _authorize(token, "security_run_trivy_scan")
76
+ return run_trivy_scan(image)
77
+
78
+
79
+ @mcp.tool()
80
+ @audit_tool_call("git_recent_commits")
81
+ def git_recent_commits(limit: int = 10, token: str = "") -> list[dict[str, str]]:
82
+ _authorize(token, "git_recent_commits")
83
+ return get_recent_commits(limit)
84
+
85
+
86
+ @mcp.tool()
87
+ @audit_tool_call("cicd_pipeline_status")
88
+ def cicd_pipeline_status(base_url: str, pipeline_id: str, api_token: str, token: str = "") -> dict:
89
+ """Fetch the status of a CI/CD pipeline."""
90
+ _authorize(token, "cicd_pipeline_status")
91
+ return pipeline_status(base_url, pipeline_id, api_token)
92
+
93
+
94
+ # ── New zero-install tools ─────────────────────────────────────────
95
+
96
+
97
+ @mcp.tool()
98
+ @audit_tool_call("security_scan_secrets")
99
+ def security_scan_secrets(path: str, token: str = "") -> list[dict]:
100
+ """Scan local files or directories for exposed secrets (API keys, tokens, passwords). This tool runs locally and has full access to the user's local filesystem (e.g. C:\\... paths)."""
101
+ _authorize(token, "security_scan_secrets")
102
+ return scan_secrets(path)
103
+
104
+
105
+ @mcp.tool()
106
+ @audit_tool_call("security_check_ssl_certificate")
107
+ def security_check_ssl_certificate(hostname: str, port: int = 443, token: str = "") -> dict:
108
+ """Check SSL/TLS certificate details and expiry for a domain."""
109
+ _authorize(token, "security_check_ssl_certificate")
110
+ return check_ssl_certificate(hostname, port)
111
+
112
+
113
+ @mcp.tool()
114
+ @audit_tool_call("security_check_dependencies")
115
+ def security_check_dependencies(file_path: str, token: str = "") -> list[dict]:
116
+ """Scan dependency files (requirements.txt, package.json) for known vulnerabilities via OSV.dev."""
117
+ _authorize(token, "security_check_dependencies")
118
+ return check_dependencies(file_path)
119
+
120
+
121
+ @mcp.tool()
122
+ @audit_tool_call("security_check_http_headers")
123
+ def security_check_http_headers(url: str, token: str = "") -> dict:
124
+ """Audit HTTP security headers (HSTS, CSP, X-Frame-Options, etc.) for a URL."""
125
+ _authorize(token, "security_check_http_headers")
126
+ return check_http_headers(url)
127
+
128
+
129
+ @mcp.tool()
130
+ @audit_tool_call("aws_check_s3_public_access")
131
+ def aws_check_s3_public_access(region: str = "us-east-1", token: str = "") -> list[dict]:
132
+ """Audit S3 buckets for public access settings."""
133
+ _authorize(token, "aws_check_s3_public_access")
134
+ return check_s3_public_access(region)
135
+
136
+
137
+ @mcp.tool()
138
+ @audit_tool_call("network_port_scan")
139
+ def network_port_scan(host: str, ports: str = "", token: str = "") -> list[dict]:
140
+ """Perform a TCP port scan on common service ports for a host."""
141
+ _authorize(token, "network_port_scan")
142
+ return port_scan(host, ports)
143
+
144
+
145
+ @mcp.tool()
146
+ @audit_tool_call("security_semgrep_scan")
147
+ def security_semgrep_scan(path: str, config: str = "auto", token: str = "") -> dict:
148
+ """Run a Semgrep SAST scan on a local directory or file. This tool runs locally and has full access to the user's local filesystem (e.g. C:\\... paths). Config can be 'auto', 'p/python', 'p/javascript', etc."""
149
+ _authorize(token, "security_semgrep_scan")
150
+ return run_semgrep_scan(path, config)
151
+
152
+
153
+ # ── Terraform security scanner ─────────────────────────────────────
154
+
155
+
156
+ @mcp.tool()
157
+ @audit_tool_call("security_scan_terraform")
158
+ def security_scan_terraform(path: str, severity: str = "", token: str = "") -> dict:
159
+ """Scan Terraform (.tf) files for security misconfigurations (S3, IAM, RDS, EC2, networking, encryption, credentials). Optionally filter by severity (CRITICAL, HIGH, MEDIUM, LOW). This tool runs locally and has full access to the user's local filesystem."""
160
+ _authorize(token, "security_scan_terraform")
161
+ return scan_terraform(path, severity)
162
+
163
+
164
+ # ── Jenkins CI/CD tools ────────────────────────────────────────────
165
+
166
+
167
+ @mcp.tool()
168
+ @audit_tool_call("jenkins_list_jobs")
169
+ def jenkins_list_jobs(url: str, username: str, api_token: str, token: str = "") -> list[dict]:
170
+ """List all jobs on a Jenkins server. Provide the Jenkins URL, username, and API token."""
171
+ _authorize(token, "jenkins_list_jobs")
172
+ return _jenkins_list_jobs(url, username, api_token)
173
+
174
+
175
+ @mcp.tool()
176
+ @audit_tool_call("jenkins_get_job_info")
177
+ def jenkins_get_job_info(url: str, username: str, api_token: str, job_name: str, token: str = "") -> dict:
178
+ """Get detailed information about a specific Jenkins job including build history and health."""
179
+ _authorize(token, "jenkins_get_job_info")
180
+ return _jenkins_get_job_info(url, username, api_token, job_name)
181
+
182
+
183
+ @mcp.tool()
184
+ @audit_tool_call("jenkins_create_job")
185
+ def jenkins_create_job(url: str, username: str, api_token: str, job_name: str, config_xml: str = "", token: str = "") -> dict:
186
+ """Create a new Jenkins job. Optionally provide config_xml for the job definition; otherwise a minimal freestyle project is created."""
187
+ _authorize(token, "jenkins_create_job")
188
+ return _jenkins_create_job(url, username, api_token, job_name, config_xml)
189
+
190
+
191
+ @mcp.tool()
192
+ @audit_tool_call("jenkins_trigger_build")
193
+ def jenkins_trigger_build(url: str, username: str, api_token: str, job_name: str, parameters: str = "", token: str = "") -> dict:
194
+ """Trigger a build for an existing Jenkins job. Optionally pass parameters as a JSON string, e.g. '{"BRANCH": "main"}'."""
195
+ _authorize(token, "jenkins_trigger_build")
196
+ return _jenkins_trigger_build(url, username, api_token, job_name, parameters)
197
+
198
+
199
+ @mcp.tool()
200
+ @audit_tool_call("jenkins_get_build_info")
201
+ def jenkins_get_build_info(url: str, username: str, api_token: str, job_name: str, build_number: int, token: str = "") -> dict:
202
+ """Get information about a specific Jenkins build including result, duration, and status."""
203
+ _authorize(token, "jenkins_get_build_info")
204
+ return _jenkins_get_build_info(url, username, api_token, job_name, build_number)
205
+
206
+
207
+ @mcp.tool()
208
+ @audit_tool_call("jenkins_get_build_log")
209
+ def jenkins_get_build_log(url: str, username: str, api_token: str, job_name: str, build_number: int, token: str = "") -> dict:
210
+ """Fetch the console output of a Jenkins build."""
211
+ _authorize(token, "jenkins_get_build_log")
212
+ return _jenkins_get_build_log(url, username, api_token, job_name, build_number)
213
+
214
+
215
+ @mcp.tool()
216
+ @audit_tool_call("jenkins_delete_job")
217
+ def jenkins_delete_job(url: str, username: str, api_token: str, job_name: str, token: str = "") -> dict:
218
+ """Delete a Jenkins job from the server."""
219
+ _authorize(token, "jenkins_delete_job")
220
+ return _jenkins_delete_job(url, username, api_token, job_name)
221
+
222
+
223
+ if __name__ == "__main__":
224
+ mcp.run(transport="streamable-http")
package/server/stdio.py CHANGED
@@ -1,7 +1,7 @@
1
- """Stdio entry-point for Claude Desktop and other MCP clients that launch the server as a subprocess."""
2
- from __future__ import annotations
3
-
4
- from server.main import mcp
5
-
6
- if __name__ == "__main__":
7
- mcp.run(transport="stdio")
1
+ """Stdio entry-point for Claude Desktop and other MCP clients that launch the server as a subprocess."""
2
+ from __future__ import annotations
3
+
4
+ from server.main import mcp
5
+
6
+ if __name__ == "__main__":
7
+ mcp.run(transport="stdio")
package/tools/aws/ec2.py CHANGED
@@ -1,26 +1,26 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
-
6
- def list_ec2_instances(region: str) -> list[dict[str, Any]]:
7
- import boto3
8
- from botocore.exceptions import BotoCoreError, ClientError
9
-
10
- try:
11
- client = boto3.client("ec2", region_name=region)
12
- reservations = client.describe_instances().get("Reservations", [])
13
- except (BotoCoreError, ClientError) as exc:
14
- raise RuntimeError(f"AWS EC2 error in region '{region}': {exc}") from exc
15
-
16
- output: list[dict[str, Any]] = []
17
- for reservation in reservations:
18
- for instance in reservation.get("Instances", []):
19
- output.append(
20
- {
21
- "instance_id": instance.get("InstanceId"),
22
- "state": instance.get("State", {}).get("Name"),
23
- "type": instance.get("InstanceType"),
24
- }
25
- )
26
- return output
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ def list_ec2_instances(region: str) -> list[dict[str, Any]]:
7
+ import boto3
8
+ from botocore.exceptions import BotoCoreError, ClientError
9
+
10
+ try:
11
+ client = boto3.client("ec2", region_name=region)
12
+ reservations = client.describe_instances().get("Reservations", [])
13
+ except (BotoCoreError, ClientError) as exc:
14
+ raise RuntimeError(f"AWS EC2 error in region '{region}': {exc}") from exc
15
+
16
+ output: list[dict[str, Any]] = []
17
+ for reservation in reservations:
18
+ for instance in reservation.get("Instances", []):
19
+ output.append(
20
+ {
21
+ "instance_id": instance.get("InstanceId"),
22
+ "state": instance.get("State", {}).get("Name"),
23
+ "type": instance.get("InstanceType"),
24
+ }
25
+ )
26
+ return output
package/tools/aws/s3.py CHANGED
@@ -1,54 +1,54 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
-
6
- def check_s3_public_access(region: str = "us-east-1") -> list[dict[str, Any]]:
7
- """Audit S3 buckets for public access settings.
8
-
9
- Args:
10
- region: AWS region for the S3 client (default us-east-1).
11
-
12
- Returns:
13
- List of buckets with their public access block configuration.
14
- """
15
- import boto3
16
- from botocore.exceptions import BotoCoreError, ClientError
17
-
18
- try:
19
- s3 = boto3.client("s3", region_name=region)
20
- buckets_resp = s3.list_buckets()
21
- except (BotoCoreError, ClientError) as exc:
22
- raise RuntimeError(f"AWS S3 error: {exc}") from exc
23
-
24
- results: list[dict[str, Any]] = []
25
- for bucket in buckets_resp.get("Buckets", []):
26
- name = bucket["Name"]
27
- entry: dict[str, Any] = {"bucket": name, "public_access_block": None, "is_potentially_public": False}
28
-
29
- try:
30
- pab = s3.get_public_access_block(Bucket=name)
31
- config = pab.get("PublicAccessBlockConfiguration", {})
32
- entry["public_access_block"] = {
33
- "block_public_acls": config.get("BlockPublicAcls", False),
34
- "ignore_public_acls": config.get("IgnorePublicAcls", False),
35
- "block_public_policy": config.get("BlockPublicPolicy", False),
36
- "restrict_public_buckets": config.get("RestrictPublicBuckets", False),
37
- }
38
- all_blocked = all(entry["public_access_block"].values())
39
- entry["is_potentially_public"] = not all_blocked
40
- except ClientError as exc:
41
- error_code = exc.response.get("Error", {}).get("Code", "")
42
- if error_code == "NoSuchPublicAccessBlockConfiguration":
43
- entry["public_access_block"] = "not_configured"
44
- entry["is_potentially_public"] = True
45
- elif error_code == "AccessDenied":
46
- entry["public_access_block"] = "access_denied"
47
- entry["is_potentially_public"] = "unknown"
48
- else:
49
- entry["public_access_block"] = f"error: {error_code}"
50
- entry["is_potentially_public"] = "unknown"
51
-
52
- results.append(entry)
53
-
54
- return results
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ def check_s3_public_access(region: str = "us-east-1") -> list[dict[str, Any]]:
7
+ """Audit S3 buckets for public access settings.
8
+
9
+ Args:
10
+ region: AWS region for the S3 client (default us-east-1).
11
+
12
+ Returns:
13
+ List of buckets with their public access block configuration.
14
+ """
15
+ import boto3
16
+ from botocore.exceptions import BotoCoreError, ClientError
17
+
18
+ try:
19
+ s3 = boto3.client("s3", region_name=region)
20
+ buckets_resp = s3.list_buckets()
21
+ except (BotoCoreError, ClientError) as exc:
22
+ raise RuntimeError(f"AWS S3 error: {exc}") from exc
23
+
24
+ results: list[dict[str, Any]] = []
25
+ for bucket in buckets_resp.get("Buckets", []):
26
+ name = bucket["Name"]
27
+ entry: dict[str, Any] = {"bucket": name, "public_access_block": None, "is_potentially_public": False}
28
+
29
+ try:
30
+ pab = s3.get_public_access_block(Bucket=name)
31
+ config = pab.get("PublicAccessBlockConfiguration", {})
32
+ entry["public_access_block"] = {
33
+ "block_public_acls": config.get("BlockPublicAcls", False),
34
+ "ignore_public_acls": config.get("IgnorePublicAcls", False),
35
+ "block_public_policy": config.get("BlockPublicPolicy", False),
36
+ "restrict_public_buckets": config.get("RestrictPublicBuckets", False),
37
+ }
38
+ all_blocked = all(entry["public_access_block"].values())
39
+ entry["is_potentially_public"] = not all_blocked
40
+ except ClientError as exc:
41
+ error_code = exc.response.get("Error", {}).get("Code", "")
42
+ if error_code == "NoSuchPublicAccessBlockConfiguration":
43
+ entry["public_access_block"] = "not_configured"
44
+ entry["is_potentially_public"] = True
45
+ elif error_code == "AccessDenied":
46
+ entry["public_access_block"] = "access_denied"
47
+ entry["is_potentially_public"] = "unknown"
48
+ else:
49
+ entry["public_access_block"] = f"error: {error_code}"
50
+ entry["is_potentially_public"] = "unknown"
51
+
52
+ results.append(entry)
53
+
54
+ return results