awslabs.cdk-mcp-server 0.0.10417__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/__init__.py +2 -0
- awslabs/cdk_mcp_server/__init__.py +8 -0
- awslabs/cdk_mcp_server/core/__init__.py +1 -0
- awslabs/cdk_mcp_server/core/resources.py +271 -0
- awslabs/cdk_mcp_server/core/search_utils.py +182 -0
- awslabs/cdk_mcp_server/core/server.py +74 -0
- awslabs/cdk_mcp_server/core/tools.py +324 -0
- awslabs/cdk_mcp_server/data/__init__.py +1 -0
- awslabs/cdk_mcp_server/data/cdk_nag_parser.py +331 -0
- awslabs/cdk_mcp_server/data/construct_descriptions.py +32 -0
- awslabs/cdk_mcp_server/data/genai_cdk_loader.py +423 -0
- awslabs/cdk_mcp_server/data/lambda_powertools_loader.py +48 -0
- awslabs/cdk_mcp_server/data/schema_generator.py +666 -0
- awslabs/cdk_mcp_server/data/solutions_constructs_parser.py +782 -0
- awslabs/cdk_mcp_server/server.py +7 -0
- awslabs/cdk_mcp_server/static/CDK_GENERAL_GUIDANCE.md +232 -0
- awslabs/cdk_mcp_server/static/CDK_NAG_GUIDANCE.md +192 -0
- awslabs/cdk_mcp_server/static/__init__.py +5 -0
- awslabs/cdk_mcp_server/static/bedrock/agent/actiongroups.md +137 -0
- awslabs/cdk_mcp_server/static/bedrock/agent/alias.md +39 -0
- awslabs/cdk_mcp_server/static/bedrock/agent/collaboration.md +91 -0
- awslabs/cdk_mcp_server/static/bedrock/agent/creation.md +149 -0
- awslabs/cdk_mcp_server/static/bedrock/agent/custom_orchestration.md +74 -0
- awslabs/cdk_mcp_server/static/bedrock/agent/overview.md +78 -0
- awslabs/cdk_mcp_server/static/bedrock/agent/prompt_override.md +70 -0
- awslabs/cdk_mcp_server/static/bedrock/bedrockguardrails.md +188 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/chunking.md +137 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/datasources.md +225 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/kendra.md +81 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/overview.md +116 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/parsing.md +36 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/transformation.md +30 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/aurora.md +185 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/creation.md +80 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/opensearch.md +56 -0
- awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/pinecone.md +66 -0
- awslabs/cdk_mcp_server/static/bedrock/profiles.md +153 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/actiongroups.md +137 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/alias.md +39 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/collaboration.md +91 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/creation.md +149 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/custom_orchestration.md +74 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/overview.md +78 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/prompt_override.md +70 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/bedrockguardrails.md +188 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/chunking.md +137 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/datasources.md +225 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/kendra.md +81 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/overview.md +116 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/parsing.md +36 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/transformation.md +30 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/aurora.md +185 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/creation.md +80 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/opensearch.md +56 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/pinecone.md +66 -0
- awslabs/cdk_mcp_server/static/genai_cdk/bedrock/profiles.md +153 -0
- awslabs/cdk_mcp_server/static/genai_cdk/opensearch-vectorindex/overview.md +135 -0
- awslabs/cdk_mcp_server/static/genai_cdk/opensearchserverless/overview.md +17 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/bedrock.md +127 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/cdk.md +99 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/dependencies.md +45 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/index.md +36 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/insights.md +95 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/logging.md +43 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/metrics.md +93 -0
- awslabs/cdk_mcp_server/static/lambda_powertools/tracing.md +63 -0
- awslabs/cdk_mcp_server/static/opensearch-vectorindex/overview.md +135 -0
- awslabs/cdk_mcp_server/static/opensearchserverless/overview.md +17 -0
- awslabs_cdk_mcp_server-0.0.10417.dist-info/METADATA +14 -0
- awslabs_cdk_mcp_server-0.0.10417.dist-info/RECORD +72 -0
- awslabs_cdk_mcp_server-0.0.10417.dist-info/WHEEL +4 -0
- awslabs_cdk_mcp_server-0.0.10417.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""AWS CDK MCP tool handlers."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from awslabs.cdk_mcp_server.core import search_utils
|
|
7
|
+
from awslabs.cdk_mcp_server.data.cdk_nag_parser import (
|
|
8
|
+
check_cdk_nag_suppressions,
|
|
9
|
+
get_rule,
|
|
10
|
+
)
|
|
11
|
+
from awslabs.cdk_mcp_server.data.genai_cdk_loader import (
|
|
12
|
+
list_available_constructs,
|
|
13
|
+
)
|
|
14
|
+
from awslabs.cdk_mcp_server.data.schema_generator import generate_bedrock_schema_from_file
|
|
15
|
+
from awslabs.cdk_mcp_server.data.solutions_constructs_parser import (
|
|
16
|
+
fetch_pattern_list,
|
|
17
|
+
get_pattern_info,
|
|
18
|
+
search_patterns,
|
|
19
|
+
)
|
|
20
|
+
from awslabs.cdk_mcp_server.static import (
|
|
21
|
+
CDK_GENERAL_GUIDANCE,
|
|
22
|
+
)
|
|
23
|
+
from mcp.server.fastmcp import Context
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Set up logging
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def cdk_guidance(
|
|
32
|
+
ctx: Context,
|
|
33
|
+
) -> str:
|
|
34
|
+
"""Use this tool to get prescriptive CDK advice for building applications on AWS.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
ctx: MCP context
|
|
38
|
+
"""
|
|
39
|
+
return CDK_GENERAL_GUIDANCE
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def explain_cdk_nag_rule(
|
|
43
|
+
ctx: Context,
|
|
44
|
+
rule_id: str,
|
|
45
|
+
) -> Dict[str, Any]:
|
|
46
|
+
"""Explain a specific CDK Nag rule with AWS Well-Architected guidance.
|
|
47
|
+
|
|
48
|
+
CDK Nag is a crucial tool for ensuring your CDK applications follow AWS security best practices.
|
|
49
|
+
|
|
50
|
+
Basic implementation:
|
|
51
|
+
```typescript
|
|
52
|
+
import { App } from 'aws-cdk-lib';
|
|
53
|
+
import { AwsSolutionsChecks } from 'cdk-nag';
|
|
54
|
+
|
|
55
|
+
const app = new App();
|
|
56
|
+
// Create your stack
|
|
57
|
+
const stack = new MyStack(app, 'MyStack');
|
|
58
|
+
// Apply CDK Nag
|
|
59
|
+
AwsSolutionsChecks.check(app);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Optional integration patterns:
|
|
63
|
+
|
|
64
|
+
1. Using environment variables:
|
|
65
|
+
```typescript
|
|
66
|
+
if (process.env.ENABLE_CDK_NAG === 'true') {
|
|
67
|
+
AwsSolutionsChecks.check(app);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
2. Using CDK context parameters:
|
|
72
|
+
```typescript
|
|
73
|
+
3. Environment-specific application:
|
|
74
|
+
```typescript
|
|
75
|
+
const environment = app.node.tryGetContext('environment') || 'development';
|
|
76
|
+
if (['production', 'staging'].includes(environment)) {
|
|
77
|
+
AwsSolutionsChecks.check(stack);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For more information on specific rule packs:
|
|
82
|
+
- Use resource `cdk-nag://rules/{rule_pack}` to get all rules for a specific pack
|
|
83
|
+
- Use resource `cdk-nag://warnings/{rule_pack}` to get warnings for a specific pack
|
|
84
|
+
- Use resource `cdk-nag://errors/{rule_pack}` to get errors for a specific pack
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
ctx: MCP context
|
|
88
|
+
rule_id: The CDK Nag rule ID (e.g., 'AwsSolutions-IAM4')
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary with detailed explanation and remediation steps
|
|
92
|
+
"""
|
|
93
|
+
# Use the resource we created to fetch the rule information
|
|
94
|
+
try:
|
|
95
|
+
rule_content = await get_rule(rule_id)
|
|
96
|
+
|
|
97
|
+
# If the rule was found, return a structured response
|
|
98
|
+
if not rule_content.startswith('Rule'):
|
|
99
|
+
return {
|
|
100
|
+
'rule_id': rule_id,
|
|
101
|
+
'content': rule_content,
|
|
102
|
+
'source': 'https://github.com/cdklabs/cdk-nag/blob/main/RULES.md',
|
|
103
|
+
'status': 'success',
|
|
104
|
+
}
|
|
105
|
+
else:
|
|
106
|
+
# Rule not found
|
|
107
|
+
return {
|
|
108
|
+
'rule_id': rule_id,
|
|
109
|
+
'error': f'Rule {rule_id} not found in CDK Nag documentation.',
|
|
110
|
+
'source': 'https://github.com/cdklabs/cdk-nag/blob/main/RULES.md',
|
|
111
|
+
'status': 'not_found',
|
|
112
|
+
}
|
|
113
|
+
except Exception as e:
|
|
114
|
+
# Handle any errors
|
|
115
|
+
return {
|
|
116
|
+
'rule_id': rule_id,
|
|
117
|
+
'error': f'Failed to fetch rule information: {str(e)}',
|
|
118
|
+
'source': 'https://github.com/cdklabs/cdk-nag/blob/main/RULES.md',
|
|
119
|
+
'status': 'error',
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def check_cdk_nag_suppressions_tool(
|
|
124
|
+
ctx: Context,
|
|
125
|
+
code: Optional[str] = None,
|
|
126
|
+
file_path: Optional[str] = None,
|
|
127
|
+
) -> Dict[str, Any]:
|
|
128
|
+
"""Check if CDK code contains Nag suppressions that require human review.
|
|
129
|
+
|
|
130
|
+
Scans TypeScript/JavaScript code for NagSuppressions usage to ensure security
|
|
131
|
+
suppressions receive proper human oversight and justification.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
ctx: MCP context
|
|
135
|
+
code: CDK code to analyze (TypeScript/JavaScript)
|
|
136
|
+
file_path: Path to a file containing CDK code to analyze
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Analysis results with suppression details and security guidance
|
|
140
|
+
"""
|
|
141
|
+
# Use the imported function from cdk_nag_parser.py
|
|
142
|
+
return check_cdk_nag_suppressions(code=code, file_path=file_path)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def bedrock_schema_generator_from_file(
|
|
146
|
+
ctx: Context, lambda_code_path: str, output_path: str
|
|
147
|
+
) -> Dict[str, Any]:
|
|
148
|
+
"""Generate OpenAPI schema for Bedrock Agent Action Groups from a file.
|
|
149
|
+
|
|
150
|
+
This tool converts a Lambda file with BedrockAgentResolver into a Bedrock-compatible
|
|
151
|
+
OpenAPI schema. It uses a progressive approach to handle common issues:
|
|
152
|
+
1. Direct import of the Lambda file
|
|
153
|
+
2. Simplified version with problematic imports commented out
|
|
154
|
+
3. Fallback script generation if needed
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
ctx: MCP context
|
|
158
|
+
lambda_code_path: Path to Python file containing BedrockAgentResolver app
|
|
159
|
+
output_path: Where to save the generated schema
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Dictionary with schema generation results, including status, path to generated schema,
|
|
163
|
+
and diagnostic information if errors occurred
|
|
164
|
+
"""
|
|
165
|
+
# Ensure the output directory exists
|
|
166
|
+
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
|
167
|
+
|
|
168
|
+
# Generate the schema
|
|
169
|
+
result = generate_bedrock_schema_from_file(
|
|
170
|
+
lambda_code_path=lambda_code_path,
|
|
171
|
+
output_path=output_path,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def get_aws_solutions_construct_pattern(
|
|
178
|
+
ctx: Context,
|
|
179
|
+
pattern_name: Optional[str] = None,
|
|
180
|
+
services: Optional[List[str]] = None,
|
|
181
|
+
) -> Dict[str, Any]:
|
|
182
|
+
"""Search and discover AWS Solutions Constructs patterns.
|
|
183
|
+
|
|
184
|
+
AWS Solutions Constructs are vetted architecture patterns that combine multiple
|
|
185
|
+
AWS services to solve common use cases following AWS Well-Architected best practices.
|
|
186
|
+
|
|
187
|
+
Key benefits:
|
|
188
|
+
- Accelerated Development: Implement common patterns without boilerplate code
|
|
189
|
+
- Best Practices Built-in: Security, reliability, and performance best practices
|
|
190
|
+
- Reduced Complexity: Simplified interfaces for multi-service architectures
|
|
191
|
+
- Well-Architected: Patterns follow AWS Well-Architected Framework principles
|
|
192
|
+
|
|
193
|
+
When to use Solutions Constructs:
|
|
194
|
+
- Implementing common architecture patterns (e.g., API + Lambda + DynamoDB)
|
|
195
|
+
- You want secure defaults and best practices applied automatically
|
|
196
|
+
- You need to quickly prototype or build production-ready infrastructure
|
|
197
|
+
|
|
198
|
+
This tool provides metadata about patterns. For complete documentation,
|
|
199
|
+
use the resource URI returned in the 'documentation_uri' field.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
ctx: MCP context
|
|
203
|
+
pattern_name: Optional name of the specific pattern (e.g., 'aws-lambda-dynamodb')
|
|
204
|
+
services: Optional list of AWS services to search for patterns that use them
|
|
205
|
+
(e.g., ['lambda', 'dynamodb'])
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Dictionary with pattern metadata including description, services, and documentation URI
|
|
209
|
+
"""
|
|
210
|
+
if pattern_name:
|
|
211
|
+
result = await get_pattern_info(pattern_name)
|
|
212
|
+
return result
|
|
213
|
+
elif services:
|
|
214
|
+
patterns = await search_patterns(services)
|
|
215
|
+
return {
|
|
216
|
+
'results': patterns,
|
|
217
|
+
'count': len(patterns),
|
|
218
|
+
'status': 'success',
|
|
219
|
+
'metadata': {'services_searched': services},
|
|
220
|
+
}
|
|
221
|
+
else:
|
|
222
|
+
available_patterns = await fetch_pattern_list()
|
|
223
|
+
return {
|
|
224
|
+
'error': 'Either pattern_name or services must be provided',
|
|
225
|
+
'available_patterns': available_patterns,
|
|
226
|
+
'status': 'error',
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def search_genai_cdk_constructs(
|
|
231
|
+
ctx: Context,
|
|
232
|
+
query: Optional[str] = None,
|
|
233
|
+
construct_type: Optional[str] = None,
|
|
234
|
+
) -> Dict[str, Any]:
|
|
235
|
+
"""Search for GenAI CDK constructs by name or type.
|
|
236
|
+
|
|
237
|
+
The search is flexible and will match any of your search terms (OR logic).
|
|
238
|
+
It handles common variations like singular/plural forms and terms with/without spaces.
|
|
239
|
+
|
|
240
|
+
Examples:
|
|
241
|
+
- "bedrock agent" - Returns all agent-related constructs
|
|
242
|
+
- "knowledgebase vector" - Returns knowledge base constructs related to vector stores
|
|
243
|
+
- "agent actiongroups" - Returns action groups for agents
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
ctx: MCP context
|
|
247
|
+
query: Search term(s) to find constructs by name or description
|
|
248
|
+
construct_type: Optional filter by construct type ('bedrock', 'opensearchserverless', etc.)
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Dictionary with matching constructs and resource URIs
|
|
252
|
+
"""
|
|
253
|
+
try:
|
|
254
|
+
# Get list of constructs
|
|
255
|
+
constructs = list_available_constructs(construct_type)
|
|
256
|
+
|
|
257
|
+
# If no query, return all constructs
|
|
258
|
+
if not query:
|
|
259
|
+
results = []
|
|
260
|
+
for construct in constructs:
|
|
261
|
+
results.append(
|
|
262
|
+
{
|
|
263
|
+
'name': construct['name'],
|
|
264
|
+
'type': construct['type'],
|
|
265
|
+
'description': construct['description'],
|
|
266
|
+
'resource_uri': f'genai-cdk-constructs://{construct["type"]}/{construct["name"]}',
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
'results': results,
|
|
272
|
+
'count': len(results),
|
|
273
|
+
'status': 'success',
|
|
274
|
+
'installation_required': {
|
|
275
|
+
'package_name': '@cdklabs/generative-ai-cdk-constructs',
|
|
276
|
+
'message': 'This construct requires the @cdklabs/generative-ai-cdk-constructs package to be installed',
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Define functions to extract searchable text and name parts
|
|
281
|
+
def get_text_fn(construct: Dict[str, Any]) -> str:
|
|
282
|
+
# Create a searchable string from the construct
|
|
283
|
+
name = construct['name'].lower().replace('_', ' ')
|
|
284
|
+
# Split camelCase words (e.g., actionGroups -> action Groups)
|
|
285
|
+
name = re.sub(r'([a-z])([A-Z])', r'\1 \2', name).lower()
|
|
286
|
+
return f'{name} {construct["type"]} {construct["description"]}'.lower()
|
|
287
|
+
|
|
288
|
+
def get_name_parts_fn(construct: Dict[str, Any]) -> List[str]:
|
|
289
|
+
name = construct['name'].lower().replace('_', ' ')
|
|
290
|
+
# Split camelCase words
|
|
291
|
+
name = re.sub(r'([a-z])([A-Z])', r'\1 \2', name).lower()
|
|
292
|
+
return name.split()
|
|
293
|
+
|
|
294
|
+
# Use common search utility
|
|
295
|
+
search_terms = query.lower().split()
|
|
296
|
+
scored_constructs = search_utils.search_items_with_terms(
|
|
297
|
+
constructs, search_terms, get_text_fn, get_name_parts_fn
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Format results with resource URIs and matched keywords
|
|
301
|
+
results = []
|
|
302
|
+
for scored_item in scored_constructs:
|
|
303
|
+
construct = scored_item['item']
|
|
304
|
+
results.append(
|
|
305
|
+
{
|
|
306
|
+
'name': construct['name'],
|
|
307
|
+
'type': construct['type'],
|
|
308
|
+
'description': construct['description'],
|
|
309
|
+
'resource_uri': f'genai-cdk-constructs://{construct["type"]}/{construct["name"]}',
|
|
310
|
+
'matched_keywords': scored_item['matched_terms'],
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
'results': results,
|
|
316
|
+
'count': len(results),
|
|
317
|
+
'status': 'success',
|
|
318
|
+
'installation_required': {
|
|
319
|
+
'package_name': '@cdklabs/generative-ai-cdk-constructs',
|
|
320
|
+
'message': 'This construct requires the @cdklabs/generative-ai-cdk-constructs package to be installed',
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
except Exception as e:
|
|
324
|
+
return {'error': f'Error searching constructs: {str(e)}', 'status': 'error'}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Data modules for the AWS CDK MCP server."""
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""CDK Nag rules parsing utilities."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
import re
|
|
5
|
+
import urllib.parse
|
|
6
|
+
from typing import Any, Dict, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Constants
|
|
10
|
+
CDK_NAG_RULES_URL = 'https://raw.githubusercontent.com/cdklabs/cdk-nag/main/RULES.md'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Helper functions
|
|
14
|
+
async def fetch_cdk_nag_content() -> str:
|
|
15
|
+
"""Fetch the CDK Nag rules content from GitHub.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The raw content of the RULES.md file from the CDK Nag repository.
|
|
19
|
+
"""
|
|
20
|
+
async with httpx.AsyncClient() as client:
|
|
21
|
+
response = await client.get(CDK_NAG_RULES_URL)
|
|
22
|
+
return response.text
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def extract_rule_pack_section(content: str, rule_pack: str) -> str:
|
|
26
|
+
"""Extract a specific rule pack section from the content.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
content: The full content of the RULES.md file.
|
|
30
|
+
rule_pack: The name of the rule pack to extract.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The section of the content for the specified rule pack.
|
|
34
|
+
If the rule pack is not found, returns an error message.
|
|
35
|
+
"""
|
|
36
|
+
# Use a direct string search approach
|
|
37
|
+
start_marker = f'## {rule_pack}'
|
|
38
|
+
start_pos = content.find(start_marker)
|
|
39
|
+
|
|
40
|
+
if start_pos < 0:
|
|
41
|
+
return f"Rule pack '{rule_pack}' not found in CDK Nag documentation."
|
|
42
|
+
|
|
43
|
+
# Find the next section heading
|
|
44
|
+
next_section_pos = content.find('\n## ', start_pos + len(start_marker))
|
|
45
|
+
|
|
46
|
+
if next_section_pos >= 0:
|
|
47
|
+
rule_pack_section = content[start_pos:next_section_pos]
|
|
48
|
+
else:
|
|
49
|
+
# If no next section, take until the end of the content
|
|
50
|
+
rule_pack_section = content[start_pos:]
|
|
51
|
+
|
|
52
|
+
return rule_pack_section
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def extract_section_by_marker(section: str, marker: str) -> Tuple[bool, str]:
|
|
56
|
+
"""Extract a subsection based on a marker (e.g., '### Warnings').
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
section: The section to extract from.
|
|
60
|
+
marker: The marker to look for (e.g., '### Warnings').
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A tuple containing:
|
|
64
|
+
- A boolean indicating whether the marker was found.
|
|
65
|
+
- The extracted subsection if found, or an error message if not found.
|
|
66
|
+
"""
|
|
67
|
+
marker_pos = section.find(marker)
|
|
68
|
+
|
|
69
|
+
if marker_pos < 0:
|
|
70
|
+
return False, f'No {marker.lstrip("#").strip()} found.'
|
|
71
|
+
|
|
72
|
+
# Find the next subsection heading
|
|
73
|
+
next_subsection_pos = section.find('\n### ', marker_pos + len(marker))
|
|
74
|
+
|
|
75
|
+
if next_subsection_pos >= 0:
|
|
76
|
+
subsection = section[marker_pos:next_subsection_pos]
|
|
77
|
+
else:
|
|
78
|
+
# If no next subsection, take until the end of the section
|
|
79
|
+
subsection = section[marker_pos:]
|
|
80
|
+
|
|
81
|
+
return True, subsection
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def extract_rule_info(content: str, rule_id: str) -> Optional[Dict[str, str]]:
|
|
85
|
+
"""Extract information about a specific rule from the content.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
content: The full content of the RULES.md file.
|
|
89
|
+
rule_id: The ID of the rule to extract information for.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
A dictionary containing the rule information, or None if the rule is not found.
|
|
93
|
+
"""
|
|
94
|
+
# Find the rule in the table
|
|
95
|
+
# The table format is: | Rule ID | Cause | Explanation | [Relevant Control ID(s)] |
|
|
96
|
+
pattern = rf'\|\s*{re.escape(rule_id)}\s*\|(.*?)\|(.*?)\|'
|
|
97
|
+
match = re.search(pattern, content, re.DOTALL)
|
|
98
|
+
|
|
99
|
+
if not match:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
result = {
|
|
103
|
+
'rule_id': rule_id,
|
|
104
|
+
'cause': match.group(1).strip(),
|
|
105
|
+
'explanation': match.group(2).strip(),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Check if there's a fourth column (Relevant Control ID(s))
|
|
109
|
+
control_pattern = rf'\|\s*{re.escape(rule_id)}\s*\|(.*?)\|(.*?)\|(.*?)\|'
|
|
110
|
+
control_match = re.search(control_pattern, content, re.DOTALL)
|
|
111
|
+
|
|
112
|
+
if control_match and len(control_match.groups()) >= 3:
|
|
113
|
+
result['control_ids'] = control_match.group(3).strip()
|
|
114
|
+
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def format_rule_info(rule_info: Optional[Dict[str, str]]) -> str:
|
|
119
|
+
"""Format rule information as a markdown string.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
rule_info: A dictionary containing rule information.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
A formatted markdown string.
|
|
126
|
+
"""
|
|
127
|
+
if not rule_info:
|
|
128
|
+
return "Rule information not found."
|
|
129
|
+
|
|
130
|
+
result = f'# {rule_info["rule_id"]}\n\n'
|
|
131
|
+
result += f'## Cause\n\n{rule_info["cause"]}\n\n'
|
|
132
|
+
result += f'## Explanation\n\n{rule_info["explanation"]}\n\n'
|
|
133
|
+
|
|
134
|
+
if 'control_ids' in rule_info:
|
|
135
|
+
result += f'## Relevant Control ID(s)\n\n{rule_info["control_ids"]}\n\n'
|
|
136
|
+
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Main functions
|
|
141
|
+
async def get_rule_pack(rule_pack: str) -> str:
|
|
142
|
+
"""Get the full content for a rule pack.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
rule_pack: The name of the rule pack to get.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The full content for the specified rule pack.
|
|
149
|
+
"""
|
|
150
|
+
# Decode the rule pack name if it's URL-encoded
|
|
151
|
+
rule_pack = urllib.parse.unquote(rule_pack)
|
|
152
|
+
|
|
153
|
+
# Fetch the content
|
|
154
|
+
content = await fetch_cdk_nag_content()
|
|
155
|
+
|
|
156
|
+
# Extract the section for this rule pack
|
|
157
|
+
return extract_rule_pack_section(content, rule_pack)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def get_warnings(rule_pack: str) -> str:
|
|
161
|
+
"""Get only the warnings section for a rule pack.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
rule_pack: The name of the rule pack to get warnings for.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The warnings section for the specified rule pack.
|
|
168
|
+
"""
|
|
169
|
+
# Decode the rule pack name if it's URL-encoded
|
|
170
|
+
rule_pack = urllib.parse.unquote(rule_pack)
|
|
171
|
+
|
|
172
|
+
# Fetch the content
|
|
173
|
+
content = await fetch_cdk_nag_content()
|
|
174
|
+
|
|
175
|
+
# Extract the section for this rule pack
|
|
176
|
+
rule_pack_section = extract_rule_pack_section(content, rule_pack)
|
|
177
|
+
|
|
178
|
+
# Check if we got an error message
|
|
179
|
+
if rule_pack_section.startswith(f"Rule pack '{rule_pack}' not found"):
|
|
180
|
+
return rule_pack_section
|
|
181
|
+
|
|
182
|
+
# Extract the warnings section
|
|
183
|
+
found, warnings_section = extract_section_by_marker(rule_pack_section, '### Warnings')
|
|
184
|
+
|
|
185
|
+
if not found:
|
|
186
|
+
return f"No warnings found for rule pack '{rule_pack}'."
|
|
187
|
+
|
|
188
|
+
return warnings_section
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
async def get_errors(rule_pack: str) -> str:
|
|
192
|
+
"""Get only the errors section for a rule pack.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
rule_pack: The name of the rule pack to get errors for.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
The errors section for the specified rule pack.
|
|
199
|
+
"""
|
|
200
|
+
# Decode the rule pack name if it's URL-encoded
|
|
201
|
+
rule_pack = urllib.parse.unquote(rule_pack)
|
|
202
|
+
|
|
203
|
+
# Fetch the content
|
|
204
|
+
content = await fetch_cdk_nag_content()
|
|
205
|
+
|
|
206
|
+
# Extract the section for this rule pack
|
|
207
|
+
rule_pack_section = extract_rule_pack_section(content, rule_pack)
|
|
208
|
+
|
|
209
|
+
# Check if we got an error message
|
|
210
|
+
if rule_pack_section.startswith(f"Rule pack '{rule_pack}' not found"):
|
|
211
|
+
return rule_pack_section
|
|
212
|
+
|
|
213
|
+
# Extract the errors section
|
|
214
|
+
found, errors_section = extract_section_by_marker(rule_pack_section, '### Errors')
|
|
215
|
+
|
|
216
|
+
if not found:
|
|
217
|
+
return f"No errors found for rule pack '{rule_pack}'."
|
|
218
|
+
|
|
219
|
+
return errors_section
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def get_rule(rule_id: str) -> str:
|
|
223
|
+
"""Get information about a specific rule.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
rule_id: The ID of the rule to get information for.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
A formatted string containing information about the rule.
|
|
230
|
+
"""
|
|
231
|
+
# Fetch the content
|
|
232
|
+
content = await fetch_cdk_nag_content()
|
|
233
|
+
|
|
234
|
+
# Extract the rule information
|
|
235
|
+
rule_info = extract_rule_info(content, rule_id)
|
|
236
|
+
|
|
237
|
+
# Format the rule information
|
|
238
|
+
if rule_info:
|
|
239
|
+
return format_rule_info(rule_info)
|
|
240
|
+
else:
|
|
241
|
+
return f'Rule {rule_id} not found in CDK Nag documentation.'
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def check_cdk_nag_suppressions(
|
|
245
|
+
code: Optional[str] = None,
|
|
246
|
+
file_path: Optional[str] = None,
|
|
247
|
+
) -> Dict[str, Any]:
|
|
248
|
+
"""Check if CDK code contains Nag suppressions that require human review.
|
|
249
|
+
|
|
250
|
+
This function scans TypeScript/JavaScript code for any instances of NagSuppressions being used
|
|
251
|
+
and flags them for human review. It helps ensure that security suppressions are only
|
|
252
|
+
applied with proper human oversight and justification.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
code: CDK code to analyze (TypeScript/JavaScript)
|
|
256
|
+
file_path: Path to a file containing CDK code to analyze
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Dictionary with analysis results including:
|
|
260
|
+
- has_suppressions: Whether suppressions were found
|
|
261
|
+
- suppressions: List of detected suppressions with line numbers and context
|
|
262
|
+
- recommendation: Security guidance for human developers
|
|
263
|
+
"""
|
|
264
|
+
# Validate input parameters
|
|
265
|
+
if code is None and file_path is None:
|
|
266
|
+
return {'error': 'Either code or file_path must be provided', 'status': 'error'}
|
|
267
|
+
|
|
268
|
+
if code is not None and file_path is not None:
|
|
269
|
+
return {'error': 'Only one of code or file_path should be provided', 'status': 'error'}
|
|
270
|
+
|
|
271
|
+
# If file_path is provided, read the file content
|
|
272
|
+
if file_path is not None:
|
|
273
|
+
try:
|
|
274
|
+
with open(file_path, 'r') as f:
|
|
275
|
+
code = f.read()
|
|
276
|
+
except Exception as e:
|
|
277
|
+
return {'error': f'Failed to read file: {str(e)}', 'status': 'error'}
|
|
278
|
+
|
|
279
|
+
# Ensure code is not None at this point
|
|
280
|
+
if code is None:
|
|
281
|
+
code = "" # Default to empty string if somehow still None
|
|
282
|
+
|
|
283
|
+
# Define patterns to look for
|
|
284
|
+
patterns = [
|
|
285
|
+
(
|
|
286
|
+
r'import\s+{\s*.*NagSuppressions.*\s*}\s+from\s+[\'"]cdk-nag[\'"]',
|
|
287
|
+
'NagSuppressions import',
|
|
288
|
+
),
|
|
289
|
+
(r'NagSuppressions\.addStackSuppressions', 'Stack-level suppression'),
|
|
290
|
+
(r'NagSuppressions\.addResourceSuppressions', 'Resource-level suppression'),
|
|
291
|
+
(r'NagSuppressions\.addResourceSuppressionsByPath', 'Path-based suppression'),
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
# Find all matches
|
|
295
|
+
suppressions_found = []
|
|
296
|
+
lines = code.split('\n')
|
|
297
|
+
|
|
298
|
+
for i, line in enumerate(lines):
|
|
299
|
+
for pattern, suppression_type in patterns:
|
|
300
|
+
if re.search(pattern, line):
|
|
301
|
+
# Get context (3 lines before and after)
|
|
302
|
+
start = max(0, i - 3)
|
|
303
|
+
end = min(len(lines), i + 4)
|
|
304
|
+
context = '\n'.join(lines[start:end])
|
|
305
|
+
|
|
306
|
+
suppressions_found.append(
|
|
307
|
+
{
|
|
308
|
+
'line_number': i + 1,
|
|
309
|
+
'line': line.strip(),
|
|
310
|
+
'type': suppression_type,
|
|
311
|
+
'context': context,
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Generate response
|
|
316
|
+
if suppressions_found:
|
|
317
|
+
return {
|
|
318
|
+
'has_suppressions': True,
|
|
319
|
+
'suppressions': suppressions_found,
|
|
320
|
+
'recommendation': '⚠️ SECURITY ALERT: This code contains CDK Nag suppressions that require human review.',
|
|
321
|
+
'action_required': 'Review each suppression and ensure it has proper justification.',
|
|
322
|
+
'security_impact': 'CDK Nag suppressions can bypass important security checks. Each suppression should be carefully reviewed by a human developer and have a documented justification.',
|
|
323
|
+
'best_practice': 'Fix the underlying security issue rather than suppressing the warning whenever possible.',
|
|
324
|
+
'status': 'success',
|
|
325
|
+
}
|
|
326
|
+
else:
|
|
327
|
+
return {
|
|
328
|
+
'has_suppressions': False,
|
|
329
|
+
'message': 'No CDK Nag suppressions detected in the provided code.',
|
|
330
|
+
'status': 'success',
|
|
331
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""GenAI CDK construct descriptions."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_construct_descriptions() -> Dict[str, str]:
|
|
7
|
+
"""Get a dictionary mapping construct names to their descriptions."""
|
|
8
|
+
return {
|
|
9
|
+
# Agent-related constructs
|
|
10
|
+
'Agent_creation': 'Create and configure Bedrock Agents with foundation models, instructions, and optional features',
|
|
11
|
+
'Agent_actiongroups': 'Define custom functions for Bedrock Agents to call via Lambda and OpenAPI schemas',
|
|
12
|
+
'Agent_alias': 'Create versioned aliases for Bedrock Agents to manage deployment and integration',
|
|
13
|
+
'Agent_collaboration': 'Configure multiple Bedrock Agents to work together on complex tasks',
|
|
14
|
+
'Agent_custom_orchestration': 'Override default agent orchestration flow with custom Lambda functions',
|
|
15
|
+
'Agent_prompt_override': 'Customize prompts and LLM configurations for different agent processing steps',
|
|
16
|
+
# Knowledge Base constructs
|
|
17
|
+
'Knowledgebases_kendra': 'Create knowledge bases from Amazon Kendra GenAI indexes for RAG applications',
|
|
18
|
+
'Knowledgebases_datasources': 'Configure data sources for Bedrock Knowledge Bases including S3, web crawlers, and more',
|
|
19
|
+
'Knowledgebases_parsing': 'Define strategies for processing and interpreting document contents in knowledge bases',
|
|
20
|
+
'Knowledgebases_transformation': 'Apply custom processing steps to documents during knowledge base ingestion',
|
|
21
|
+
'Knowledgebases_chunking': 'Configure document chunking strategies for optimal knowledge base performance',
|
|
22
|
+
'Knowledgebases_vector_opensearch': 'Use OpenSearch Serverless as a vector store (vector database) for Bedrock Knowledge Bases',
|
|
23
|
+
'Knowledgebases_vector_aurora': 'Use Amazon RDS Aurora PostgreSQL as a vector store (vector database) for Bedrock Knowledge Bases',
|
|
24
|
+
'Knowledgebases_vector_pinecone': 'Use Pinecone as a vector store (vector database) for Bedrock Knowledge Bases',
|
|
25
|
+
'Knowledgebases_vector_creation': 'Create and configure vector stores (vector databases) for Bedrock Knowledge Bases',
|
|
26
|
+
# Other Bedrock constructs
|
|
27
|
+
'Bedrockguardrails': 'Configure content filtering and safety guardrails for Bedrock foundation models',
|
|
28
|
+
'Profiles': 'Create and manage inference profiles for tracking usage and costs across regions',
|
|
29
|
+
# OpenSearch constructs
|
|
30
|
+
'Opensearchserverless_overview': 'Create and configure Amazon OpenSearch Serverless for vector search applications',
|
|
31
|
+
'Opensearch_vectorindex_overview': 'Configure vector indexes in Amazon OpenSearch for semantic search',
|
|
32
|
+
}
|