awslabs.well-architected-security-mcp-server 0.1.1__py3-none-any.whl
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.
- awslabs/well_architected_security_mcp_server/__init__.py +17 -0
- awslabs/well_architected_security_mcp_server/consts.py +113 -0
- awslabs/well_architected_security_mcp_server/server.py +1174 -0
- awslabs/well_architected_security_mcp_server/util/__init__.py +42 -0
- awslabs/well_architected_security_mcp_server/util/network_security.py +1251 -0
- awslabs/well_architected_security_mcp_server/util/prompt_utils.py +173 -0
- awslabs/well_architected_security_mcp_server/util/resource_utils.py +109 -0
- awslabs/well_architected_security_mcp_server/util/security_services.py +1618 -0
- awslabs/well_architected_security_mcp_server/util/storage_security.py +1126 -0
- awslabs_well_architected_security_mcp_server-0.1.1.dist-info/METADATA +258 -0
- awslabs_well_architected_security_mcp_server-0.1.1.dist-info/RECORD +13 -0
- awslabs_well_architected_security_mcp_server-0.1.1.dist-info/WHEEL +4 -0
- awslabs_well_architected_security_mcp_server-0.1.1.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Utilities for working with prompt templates."""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
from loguru import logger
|
|
22
|
+
|
|
23
|
+
# Cache for prompt templates
|
|
24
|
+
_prompt_templates = {}
|
|
25
|
+
_is_initialized = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_prompt_templates(file_path: str = "PROMPT_TEMPLATE.md") -> Dict[str, Dict[str, Any]]:
|
|
29
|
+
"""Load prompt templates from a markdown file.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
file_path: Path to the markdown file containing prompt templates
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dictionary mapping template names to template content and metadata
|
|
36
|
+
"""
|
|
37
|
+
global _prompt_templates, _is_initialized
|
|
38
|
+
|
|
39
|
+
if _is_initialized:
|
|
40
|
+
return _prompt_templates
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Check if file exists
|
|
44
|
+
if not os.path.exists(file_path):
|
|
45
|
+
logger.error(f"Prompt template file not found: {file_path}")
|
|
46
|
+
return {}
|
|
47
|
+
|
|
48
|
+
# Read the file content
|
|
49
|
+
with open(file_path, "r") as f:
|
|
50
|
+
content = f.read()
|
|
51
|
+
|
|
52
|
+
# Parse the markdown content to extract templates
|
|
53
|
+
# First, split by level 2 headers (## )
|
|
54
|
+
sections = re.split(r"(?m)^## ", content)
|
|
55
|
+
|
|
56
|
+
# The first section is the introduction, skip it
|
|
57
|
+
if sections and not sections[0].startswith("## "):
|
|
58
|
+
sections = sections[1:]
|
|
59
|
+
|
|
60
|
+
# Process each section
|
|
61
|
+
for section in sections:
|
|
62
|
+
if not section.strip():
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Extract the section title (template name)
|
|
66
|
+
lines = section.split("\n")
|
|
67
|
+
title = lines[0].strip()
|
|
68
|
+
|
|
69
|
+
# Convert title to a template name (lowercase, underscores)
|
|
70
|
+
template_name = title.lower().replace(" ", "_")
|
|
71
|
+
|
|
72
|
+
# Extract the template content (between triple backticks)
|
|
73
|
+
template_content = ""
|
|
74
|
+
description = ""
|
|
75
|
+
in_code_block = False
|
|
76
|
+
desc_lines = []
|
|
77
|
+
|
|
78
|
+
for line in lines[1:]:
|
|
79
|
+
if line.strip() == "```":
|
|
80
|
+
in_code_block = not in_code_block
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
if in_code_block:
|
|
84
|
+
template_content += line + "\n"
|
|
85
|
+
elif line.strip() and not in_code_block:
|
|
86
|
+
desc_lines.append(line.strip())
|
|
87
|
+
|
|
88
|
+
# Join description lines
|
|
89
|
+
if desc_lines:
|
|
90
|
+
description = " ".join(desc_lines)
|
|
91
|
+
|
|
92
|
+
# Store the template
|
|
93
|
+
_prompt_templates[template_name] = {
|
|
94
|
+
"name": template_name,
|
|
95
|
+
"title": title,
|
|
96
|
+
"content": template_content.strip(),
|
|
97
|
+
"description": description,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Special handling for the workflow example section
|
|
101
|
+
workflow_section = None
|
|
102
|
+
for section in content.split("## "):
|
|
103
|
+
if section.startswith("Example Workflow"):
|
|
104
|
+
workflow_section = section
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
if workflow_section:
|
|
108
|
+
_prompt_templates["workflow_example"] = {
|
|
109
|
+
"name": "workflow_example",
|
|
110
|
+
"title": "Example Workflow",
|
|
111
|
+
"content": workflow_section.split("Example Workflow")[1].strip(),
|
|
112
|
+
"description": "Recommended workflow for a comprehensive security assessment",
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_is_initialized = True
|
|
116
|
+
logger.info(f"Loaded {len(_prompt_templates)} prompt templates from {file_path}")
|
|
117
|
+
return _prompt_templates
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Error loading prompt templates: {e}")
|
|
121
|
+
return {}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_prompt_template(template_name: str) -> Optional[Dict[str, Any]]:
|
|
125
|
+
"""Get a specific prompt template by name.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
template_name: Name of the template to retrieve
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Template dictionary or None if not found
|
|
132
|
+
"""
|
|
133
|
+
global _prompt_templates, _is_initialized
|
|
134
|
+
|
|
135
|
+
if not _is_initialized:
|
|
136
|
+
load_prompt_templates()
|
|
137
|
+
|
|
138
|
+
return _prompt_templates.get(template_name)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_all_template_names() -> List[str]:
|
|
142
|
+
"""Get a list of all available template names.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List of template names
|
|
146
|
+
"""
|
|
147
|
+
global _prompt_templates, _is_initialized
|
|
148
|
+
|
|
149
|
+
if not _is_initialized:
|
|
150
|
+
load_prompt_templates()
|
|
151
|
+
|
|
152
|
+
return list(_prompt_templates.keys())
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_template_metadata() -> List[Dict[str, str]]:
|
|
156
|
+
"""Get metadata for all available templates.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
List of dictionaries with template metadata
|
|
160
|
+
"""
|
|
161
|
+
global _prompt_templates, _is_initialized
|
|
162
|
+
|
|
163
|
+
if not _is_initialized:
|
|
164
|
+
load_prompt_templates()
|
|
165
|
+
|
|
166
|
+
return [
|
|
167
|
+
{
|
|
168
|
+
"name": template["name"],
|
|
169
|
+
"title": template["title"],
|
|
170
|
+
"description": template["description"],
|
|
171
|
+
}
|
|
172
|
+
for template in _prompt_templates.values()
|
|
173
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""General utility functions for AWS resource operations."""
|
|
16
|
+
|
|
17
|
+
from typing import Any, Dict
|
|
18
|
+
|
|
19
|
+
import boto3
|
|
20
|
+
from botocore.config import Config
|
|
21
|
+
from mcp.server.fastmcp import Context
|
|
22
|
+
|
|
23
|
+
from awslabs.well_architected_security_mcp_server import __version__
|
|
24
|
+
|
|
25
|
+
# User agent configuration for AWS API calls
|
|
26
|
+
USER_AGENT_CONFIG = Config(
|
|
27
|
+
user_agent_extra=f"awslabs/mcp/well-architected-security-mcp-server/{__version__}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def list_services_in_region(
|
|
32
|
+
region: str, session: boto3.Session, ctx: Context
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
"""List all AWS services being used in a specific region.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
region: AWS region to list services for
|
|
38
|
+
session: boto3 Session for AWS API calls
|
|
39
|
+
ctx: MCP context for error reporting
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Dictionary with services information and counts
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
# Initialize the result dictionary
|
|
46
|
+
result = {"region": region, "services": [], "service_counts": {}, "total_resources": 0}
|
|
47
|
+
|
|
48
|
+
# Use Resource Explorer to efficiently discover resources
|
|
49
|
+
try:
|
|
50
|
+
resource_explorer = session.client(
|
|
51
|
+
"resource-explorer-2", region_name=region, config=USER_AGENT_CONFIG
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Check if Resource Explorer is available in this region
|
|
55
|
+
try:
|
|
56
|
+
# Try to search with Resource Explorer
|
|
57
|
+
resource_explorer.search(
|
|
58
|
+
QueryString="*",
|
|
59
|
+
MaxResults=1, # Just checking if it works
|
|
60
|
+
)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
if "Resource Explorer has not been set up" in str(e):
|
|
63
|
+
await ctx.warning(
|
|
64
|
+
f"Resource Explorer not set up in {region}. Using alternative method."
|
|
65
|
+
)
|
|
66
|
+
return {"region": region, "services": [], "error": str(e)}
|
|
67
|
+
else:
|
|
68
|
+
raise e
|
|
69
|
+
|
|
70
|
+
# Resource Explorer is available, use it to get all resources
|
|
71
|
+
paginator = resource_explorer.get_paginator("search")
|
|
72
|
+
page_iterator = paginator.paginate(QueryString="*", MaxResults=1000)
|
|
73
|
+
|
|
74
|
+
# Track unique services
|
|
75
|
+
services_set = set()
|
|
76
|
+
service_resource_counts = {}
|
|
77
|
+
|
|
78
|
+
# Process each page of results
|
|
79
|
+
for page in page_iterator:
|
|
80
|
+
for resource in page.get("Resources", []):
|
|
81
|
+
# Extract service from ARN
|
|
82
|
+
arn = resource.get("Arn", "")
|
|
83
|
+
if arn:
|
|
84
|
+
arn_parts = arn.split(":")
|
|
85
|
+
if len(arn_parts) >= 3:
|
|
86
|
+
service = arn_parts[2]
|
|
87
|
+
services_set.add(service)
|
|
88
|
+
|
|
89
|
+
# Update count for this service
|
|
90
|
+
if service in service_resource_counts:
|
|
91
|
+
service_resource_counts[service] += 1
|
|
92
|
+
else:
|
|
93
|
+
service_resource_counts[service] = 1
|
|
94
|
+
|
|
95
|
+
# Update result with discovered services
|
|
96
|
+
result["services"] = sorted(list(services_set))
|
|
97
|
+
result["service_counts"] = service_resource_counts
|
|
98
|
+
result["total_resources"] = sum(service_resource_counts.values())
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
await ctx.warning(f"Error using Resource Explorer in {region}: {e}")
|
|
102
|
+
# Fall back to alternative method
|
|
103
|
+
return {"region": region, "services": [], "error": str(e)}
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
await ctx.error(f"Error listing services in region {region}: {e}")
|
|
109
|
+
return {"region": region, "services": [], "error": str(e)}
|