awslabs.terraform-mcp-server 0.0.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.

Potentially problematic release.


This version of awslabs.terraform-mcp-server might be problematic. Click here for more details.

Files changed (32) hide show
  1. awslabs/__init__.py +2 -0
  2. awslabs/terraform_mcp_server/__init__.py +3 -0
  3. awslabs/terraform_mcp_server/impl/resources/__init__.py +11 -0
  4. awslabs/terraform_mcp_server/impl/resources/terraform_aws_provider_resources_listing.py +52 -0
  5. awslabs/terraform_mcp_server/impl/resources/terraform_awscc_provider_resources_listing.py +55 -0
  6. awslabs/terraform_mcp_server/impl/tools/__init__.py +15 -0
  7. awslabs/terraform_mcp_server/impl/tools/execute_terraform_command.py +206 -0
  8. awslabs/terraform_mcp_server/impl/tools/run_checkov_scan.py +359 -0
  9. awslabs/terraform_mcp_server/impl/tools/search_aws_provider_docs.py +677 -0
  10. awslabs/terraform_mcp_server/impl/tools/search_awscc_provider_docs.py +627 -0
  11. awslabs/terraform_mcp_server/impl/tools/search_specific_aws_ia_modules.py +444 -0
  12. awslabs/terraform_mcp_server/impl/tools/utils.py +558 -0
  13. awslabs/terraform_mcp_server/models/__init__.py +27 -0
  14. awslabs/terraform_mcp_server/models/models.py +260 -0
  15. awslabs/terraform_mcp_server/scripts/generate_aws_provider_resources.py +1224 -0
  16. awslabs/terraform_mcp_server/scripts/generate_awscc_provider_resources.py +1020 -0
  17. awslabs/terraform_mcp_server/scripts/scrape_aws_terraform_best_practices.py +129 -0
  18. awslabs/terraform_mcp_server/server.py +329 -0
  19. awslabs/terraform_mcp_server/static/AWSCC_PROVIDER_RESOURCES.md +3125 -0
  20. awslabs/terraform_mcp_server/static/AWS_PROVIDER_RESOURCES.md +3833 -0
  21. awslabs/terraform_mcp_server/static/AWS_TERRAFORM_BEST_PRACTICES.md +2523 -0
  22. awslabs/terraform_mcp_server/static/MCP_INSTRUCTIONS.md +126 -0
  23. awslabs/terraform_mcp_server/static/TERRAFORM_WORKFLOW_GUIDE.md +198 -0
  24. awslabs/terraform_mcp_server/static/__init__.py +22 -0
  25. awslabs/terraform_mcp_server/tests/__init__.py +1 -0
  26. awslabs/terraform_mcp_server/tests/run_tests.sh +35 -0
  27. awslabs/terraform_mcp_server/tests/test_parameter_annotations.py +207 -0
  28. awslabs/terraform_mcp_server/tests/test_tool_implementations.py +309 -0
  29. awslabs_terraform_mcp_server-0.0.1.dist-info/METADATA +97 -0
  30. awslabs_terraform_mcp_server-0.0.1.dist-info/RECORD +32 -0
  31. awslabs_terraform_mcp_server-0.0.1.dist-info/WHEEL +4 -0
  32. awslabs_terraform_mcp_server-0.0.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,359 @@
1
+ """Implementation of Checkov scan tools."""
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import subprocess
7
+ from awslabs.terraform_mcp_server.impl.tools.utils import get_dangerous_patterns
8
+ from awslabs.terraform_mcp_server.models import (
9
+ CheckovScanRequest,
10
+ CheckovScanResult,
11
+ CheckovVulnerability,
12
+ )
13
+ from loguru import logger
14
+ from typing import Any, Dict, List, Tuple
15
+
16
+
17
+ def _clean_output_text(text: str) -> str:
18
+ """Clean output text by removing or replacing problematic Unicode characters.
19
+
20
+ Args:
21
+ text: The text to clean
22
+
23
+ Returns:
24
+ Cleaned text with ASCII-friendly replacements
25
+ """
26
+ if not text:
27
+ return text
28
+
29
+ # First remove ANSI escape sequences (color codes, cursor movement)
30
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
31
+ text = ansi_escape.sub('', text)
32
+
33
+ # Remove C0 and C1 control characters (except common whitespace)
34
+ control_chars = re.compile(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]')
35
+ text = control_chars.sub('', text)
36
+
37
+ # Replace HTML entities
38
+ html_entities = {
39
+ '->': '->', # Replace HTML arrow
40
+ '&lt;': '<', # Less than
41
+ '&gt;': '>', # Greater than
42
+ '&amp;': '&', # Ampersand
43
+ }
44
+ for entity, replacement in html_entities.items():
45
+ text = text.replace(entity, replacement)
46
+
47
+ # Replace box-drawing and other special Unicode characters with ASCII equivalents
48
+ unicode_chars = {
49
+ '\u2500': '-', # Horizontal line
50
+ '\u2502': '|', # Vertical line
51
+ '\u2514': '+', # Up and right
52
+ '\u2518': '+', # Up and left
53
+ '\u2551': '|', # Double vertical
54
+ '\u2550': '-', # Double horizontal
55
+ '\u2554': '+', # Double down and right
56
+ '\u2557': '+', # Double down and left
57
+ '\u255a': '+', # Double up and right
58
+ '\u255d': '+', # Double up and left
59
+ '\u256c': '+', # Double cross
60
+ '\u2588': '#', # Full block
61
+ '\u25cf': '*', # Black circle
62
+ '\u2574': '-', # Left box drawing
63
+ '\u2576': '-', # Right box drawing
64
+ '\u2577': '|', # Down box drawing
65
+ '\u2575': '|', # Up box drawing
66
+ }
67
+ for char, replacement in unicode_chars.items():
68
+ text = text.replace(char, replacement)
69
+
70
+ return text
71
+
72
+
73
+ def _ensure_checkov_installed() -> bool:
74
+ """Ensure Checkov is installed, and install it if not.
75
+
76
+ Returns:
77
+ True if Checkov is installed or was successfully installed, False otherwise
78
+ """
79
+ try:
80
+ # Check if Checkov is already installed
81
+ subprocess.run(
82
+ ['checkov', '--version'],
83
+ capture_output=True,
84
+ text=True,
85
+ check=False,
86
+ )
87
+ logger.info('Checkov is already installed')
88
+ return True
89
+ except FileNotFoundError:
90
+ logger.warning('Checkov not found, attempting to install')
91
+ try:
92
+ # Install Checkov using pip
93
+ subprocess.run(
94
+ ['pip', 'install', 'checkov'],
95
+ capture_output=True,
96
+ text=True,
97
+ check=True,
98
+ )
99
+ logger.info('Successfully installed Checkov')
100
+ return True
101
+ except subprocess.CalledProcessError as e:
102
+ logger.error(f'Failed to install Checkov: {e}')
103
+ return False
104
+
105
+
106
+ def _parse_checkov_json_output(output: str) -> Tuple[List[CheckovVulnerability], Dict[str, Any]]:
107
+ """Parse Checkov JSON output into structured vulnerability data.
108
+
109
+ Args:
110
+ output: JSON output from Checkov scan
111
+
112
+ Returns:
113
+ Tuple of (list of vulnerabilities, summary dictionary)
114
+ """
115
+ try:
116
+ data = json.loads(output)
117
+ vulnerabilities = []
118
+ summary = {
119
+ 'passed': 0,
120
+ 'failed': 0,
121
+ 'skipped': 0,
122
+ 'parsing_errors': 0,
123
+ 'resource_count': 0,
124
+ }
125
+
126
+ # Extract summary information
127
+ if 'summary' in data:
128
+ summary = data['summary']
129
+
130
+ # Process check results
131
+ if 'results' in data and 'failed_checks' in data['results']:
132
+ for check in data['results']['failed_checks']:
133
+ vuln = CheckovVulnerability(
134
+ id=check.get('check_id', 'UNKNOWN'),
135
+ type=check.get('check_type', 'terraform'),
136
+ resource=check.get('resource', 'UNKNOWN'),
137
+ file_path=check.get('file_path', 'UNKNOWN'),
138
+ line=check.get('file_line_range', [0, 0])[0],
139
+ description=check.get('check_name', 'UNKNOWN'),
140
+ guideline=check.get('guideline', None),
141
+ severity=(check.get('severity', 'MEDIUM') or 'MEDIUM').upper(),
142
+ fixed=False,
143
+ fix_details=None,
144
+ )
145
+ vulnerabilities.append(vuln)
146
+
147
+ return vulnerabilities, summary
148
+ except json.JSONDecodeError as e:
149
+ logger.error(f'Failed to parse Checkov JSON output: {e}')
150
+ return [], {'error': 'Failed to parse JSON output'}
151
+
152
+
153
+ async def run_checkov_scan_impl(request: CheckovScanRequest) -> CheckovScanResult:
154
+ """Run Checkov scan on Terraform code.
155
+
156
+ Args:
157
+ request: Details about the Checkov scan to execute
158
+
159
+ Returns:
160
+ A CheckovScanResult object containing scan results and vulnerabilities
161
+ """
162
+ logger.info(f'Running Checkov scan in {request.working_directory}')
163
+
164
+ # Ensure Checkov is installed
165
+ if not _ensure_checkov_installed():
166
+ return CheckovScanResult(
167
+ status='error',
168
+ working_directory=request.working_directory,
169
+ error_message='Failed to install Checkov. Please install it manually with: pip install checkov',
170
+ vulnerabilities=[],
171
+ summary={},
172
+ raw_output=None,
173
+ )
174
+
175
+ # Security checks for parameters
176
+
177
+ # Check framework parameter for allowed values
178
+ allowed_frameworks = ['terraform', 'cloudformation', 'kubernetes', 'dockerfile', 'arm', 'all']
179
+ if request.framework not in allowed_frameworks:
180
+ logger.error(f'Security violation: Invalid framework: {request.framework}')
181
+ return CheckovScanResult(
182
+ status='error',
183
+ working_directory=request.working_directory,
184
+ error_message=f"Security violation: Invalid framework '{request.framework}'. Allowed frameworks are: {', '.join(allowed_frameworks)}",
185
+ vulnerabilities=[],
186
+ summary={},
187
+ raw_output=None,
188
+ )
189
+
190
+ # Check output_format parameter for allowed values
191
+ allowed_output_formats = [
192
+ 'cli',
193
+ 'csv',
194
+ 'cyclonedx',
195
+ 'cyclonedx_json',
196
+ 'spdx',
197
+ 'json',
198
+ 'junitxml',
199
+ 'github_failed_only',
200
+ 'gitlab_sast',
201
+ 'sarif',
202
+ ]
203
+ if request.output_format not in allowed_output_formats:
204
+ logger.error(f'Security violation: Invalid output format: {request.output_format}')
205
+ return CheckovScanResult(
206
+ status='error',
207
+ working_directory=request.working_directory,
208
+ error_message=f"Security violation: Invalid output format '{request.output_format}'. Allowed formats are: {', '.join(allowed_output_formats)}",
209
+ vulnerabilities=[],
210
+ summary={},
211
+ raw_output=None,
212
+ )
213
+
214
+ # Check for command injection patterns in check_ids and skip_check_ids
215
+ dangerous_patterns = get_dangerous_patterns()
216
+ logger.debug(f'Checking for {len(dangerous_patterns)} dangerous patterns')
217
+
218
+ if request.check_ids:
219
+ for check_id in request.check_ids:
220
+ for pattern in dangerous_patterns:
221
+ if pattern in check_id:
222
+ logger.error(
223
+ f"Security violation: Potentially dangerous pattern '{pattern}' in check_id: {check_id}"
224
+ )
225
+ return CheckovScanResult(
226
+ status='error',
227
+ working_directory=request.working_directory,
228
+ error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in check_id",
229
+ vulnerabilities=[],
230
+ summary={},
231
+ raw_output=None,
232
+ )
233
+
234
+ if request.skip_check_ids:
235
+ for skip_id in request.skip_check_ids:
236
+ for pattern in dangerous_patterns:
237
+ if pattern in skip_id:
238
+ logger.error(
239
+ f"Security violation: Potentially dangerous pattern '{pattern}' in skip_check_id: {skip_id}"
240
+ )
241
+ return CheckovScanResult(
242
+ status='error',
243
+ working_directory=request.working_directory,
244
+ error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in skip_check_id",
245
+ vulnerabilities=[],
246
+ summary={},
247
+ raw_output=None,
248
+ )
249
+
250
+ # Build the command
251
+ # Convert working_directory to absolute path if it's not already
252
+ working_dir = request.working_directory
253
+ if not os.path.isabs(working_dir):
254
+ # Get the current working directory of the MCP server
255
+ current_dir = os.getcwd()
256
+ # Go up to the project root directory (assuming we're in src/terraform-mcp-server/awslabs/terraform_mcp_server)
257
+ project_root = os.path.abspath(os.path.join(current_dir, '..', '..', '..', '..'))
258
+ # Join with the requested working directory
259
+ working_dir = os.path.abspath(os.path.join(project_root, working_dir))
260
+
261
+ logger.info(f'Using absolute working directory: {working_dir}')
262
+ cmd = ['checkov', '--quiet', '-d', working_dir]
263
+
264
+ # Add framework if specified
265
+ if request.framework:
266
+ cmd.extend(['--framework', request.framework])
267
+
268
+ # Add specific check IDs if provided
269
+ if request.check_ids:
270
+ cmd.extend(['--check', ','.join(request.check_ids)])
271
+
272
+ # Add skip check IDs if provided
273
+ if request.skip_check_ids:
274
+ cmd.extend(['--skip-check', ','.join(request.skip_check_ids)])
275
+
276
+ # Set output format
277
+ cmd.extend(['--output', request.output_format])
278
+
279
+ # Execute command
280
+ try:
281
+ logger.info(f'Executing command: {" ".join(cmd)}')
282
+ process = subprocess.run(
283
+ cmd,
284
+ capture_output=True,
285
+ text=True,
286
+ )
287
+
288
+ # Clean output text
289
+ stdout = _clean_output_text(process.stdout)
290
+ stderr = _clean_output_text(process.stderr)
291
+
292
+ # Debug logging
293
+ logger.info(f'Checkov return code: {process.returncode}')
294
+ logger.info(f'Checkov stdout: {stdout}')
295
+ logger.info(f'Checkov stderr: {stderr}')
296
+
297
+ # Parse results if JSON output was requested
298
+ vulnerabilities = []
299
+ summary = {}
300
+ if request.output_format == 'json' and stdout:
301
+ vulnerabilities, summary = _parse_checkov_json_output(stdout)
302
+
303
+ # For non-JSON output, try to parse vulnerabilities from the text output
304
+ elif stdout and process.returncode == 1: # Return code 1 means vulnerabilities were found
305
+ # Simple regex to extract failed checks from CLI output
306
+ failed_checks = re.findall(
307
+ r'Check: (CKV\w*_\d+).*?FAILED for resource: ([\w\.]+).*?File: ([\w\/\.-]+):(\d+)',
308
+ stdout,
309
+ re.DOTALL,
310
+ )
311
+ for check_id, resource, file_path, line in failed_checks:
312
+ vuln = CheckovVulnerability(
313
+ id=check_id,
314
+ type='terraform',
315
+ resource=resource,
316
+ file_path=file_path,
317
+ line=int(line),
318
+ description=f'Failed check: {check_id}',
319
+ guideline=None,
320
+ severity='MEDIUM',
321
+ fixed=False,
322
+ fix_details=None,
323
+ )
324
+ vulnerabilities.append(vuln)
325
+
326
+ # Extract summary counts
327
+ passed_match = re.search(r'Passed checks: (\d+)', stdout)
328
+ failed_match = re.search(r'Failed checks: (\d+)', stdout)
329
+ skipped_match = re.search(r'Skipped checks: (\d+)', stdout)
330
+
331
+ summary = {
332
+ 'passed': int(passed_match.group(1)) if passed_match else 0,
333
+ 'failed': int(failed_match.group(1)) if failed_match else 0,
334
+ 'skipped': int(skipped_match.group(1)) if skipped_match else 0,
335
+ }
336
+
337
+ # Prepare the result - consider it a success even if vulnerabilities were found
338
+ # A return code of 1 from Checkov means vulnerabilities were found, not an error
339
+ is_error = process.returncode not in [0, 1]
340
+ result = CheckovScanResult(
341
+ status='error' if is_error else 'success',
342
+ return_code=process.returncode,
343
+ working_directory=request.working_directory,
344
+ vulnerabilities=vulnerabilities,
345
+ summary=summary,
346
+ raw_output=stdout,
347
+ )
348
+
349
+ return result
350
+ except Exception as e:
351
+ logger.error(f'Error running Checkov scan: {e}')
352
+ return CheckovScanResult(
353
+ status='error',
354
+ working_directory=request.working_directory,
355
+ error_message=str(e),
356
+ vulnerabilities=[],
357
+ summary={},
358
+ raw_output=None,
359
+ )