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.
@@ -0,0 +1,1174 @@
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
+ """AWS Well-Architected Security Assessment Tool MCP Server"""
16
+
17
+ import argparse
18
+ import datetime
19
+ import os
20
+ import sys
21
+ from typing import Dict, List, Optional
22
+
23
+ import boto3
24
+ from botocore.config import Config
25
+ from loguru import logger
26
+ from mcp.server.fastmcp import Context, FastMCP
27
+ from pydantic import Field
28
+
29
+ from awslabs.well_architected_security_mcp_server import __version__
30
+ from awslabs.well_architected_security_mcp_server.consts import INSTRUCTIONS
31
+ from awslabs.well_architected_security_mcp_server.util.network_security import (
32
+ check_network_security,
33
+ )
34
+ from awslabs.well_architected_security_mcp_server.util.resource_utils import (
35
+ list_services_in_region,
36
+ )
37
+ from awslabs.well_architected_security_mcp_server.util.security_services import (
38
+ check_access_analyzer,
39
+ check_guard_duty,
40
+ check_inspector,
41
+ check_macie,
42
+ check_security_hub,
43
+ check_trusted_advisor,
44
+ get_access_analyzer_findings,
45
+ get_guardduty_findings,
46
+ get_inspector_findings,
47
+ get_macie_findings,
48
+ get_securityhub_findings,
49
+ get_trusted_advisor_findings,
50
+ )
51
+ from awslabs.well_architected_security_mcp_server.util.storage_security import (
52
+ check_storage_encryption,
53
+ )
54
+
55
+ # User agent configuration for AWS API calls
56
+ USER_AGENT_CONFIG = Config(
57
+ user_agent_extra=f"awslabs/mcp/well-architected-security-mcp-server/{__version__}"
58
+ )
59
+
60
+ # Set up AWS region and profile from environment variables
61
+ AWS_REGION = os.environ.get("AWS_REGION", "us-east-1")
62
+ AWS_PROFILE = os.environ.get("AWS_PROFILE", "default")
63
+
64
+ # Remove default logger and add custom configuration
65
+ logger.remove()
66
+ logger.add(sys.stderr, level=os.getenv("FASTMCP_LOG_LEVEL", "DEBUG"))
67
+
68
+ # Initialize MCP Server
69
+ mcp = FastMCP(
70
+ "well-architected-security-mcp-server",
71
+ instructions=INSTRUCTIONS,
72
+ dependencies=[
73
+ "boto3",
74
+ "requests",
75
+ "beautifulsoup4",
76
+ "pydantic",
77
+ "loguru",
78
+ ],
79
+ )
80
+
81
+ # Global shared components
82
+ security_pattern_catalog = None
83
+ rule_catalog = None
84
+
85
+ # Define Field singleton variables for parameter defaults
86
+ FIELD_AWS_REGION = Field(
87
+ AWS_REGION, description="AWS region to check for security services status"
88
+ )
89
+ FIELD_AWS_PROFILE = Field(
90
+ AWS_PROFILE,
91
+ description="Optional AWS profile to use (defaults to AWS_PROFILE environment variable or 'default')",
92
+ )
93
+ FIELD_STORE_IN_CONTEXT_TRUE = Field(
94
+ True, description="Whether to store results in context for access by other tools"
95
+ )
96
+ FIELD_DEBUG_TRUE = Field(
97
+ True, description="Whether to include detailed debug information in the response"
98
+ )
99
+ FIELD_SECURITY_SERVICES = Field(
100
+ ["guardduty", "inspector", "accessanalyzer", "securityhub", "trustedadvisor", "macie"],
101
+ description="List of security services to check. Options: guardduty, inspector, accessanalyzer, securityhub, trustedadvisor, macie",
102
+ )
103
+ FIELD_ACCOUNT_ID = Field(
104
+ None, description="Optional AWS account ID (defaults to caller's account)"
105
+ )
106
+ FIELD_MAX_FINDINGS = Field(100, description="Maximum number of findings to retrieve")
107
+ FIELD_SEVERITY_FILTER = Field(
108
+ None,
109
+ description="Optional severity filter (e.g., 'HIGH', 'CRITICAL', or for Trusted Advisor: 'ERROR', 'WARNING')",
110
+ )
111
+ FIELD_CHECK_ENABLED = Field(
112
+ True, description="Whether to check if service is enabled before retrieving findings"
113
+ )
114
+ FIELD_DETAILED_FALSE = Field(
115
+ False, description="Whether to return the full details of the stored security services data"
116
+ )
117
+ FIELD_STORAGE_SERVICES = Field(
118
+ ["s3", "ebs", "rds", "dynamodb", "efs", "elasticache"],
119
+ description="List of storage services to check. Options: s3, ebs, rds, dynamodb, efs, elasticache",
120
+ )
121
+ FIELD_INCLUDE_UNENCRYPTED_ONLY = Field(
122
+ False, description="Whether to include only unencrypted resources in the results"
123
+ )
124
+ FIELD_SERVICE_FILTER = Field(
125
+ None, description="Optional filter to limit results to a specific service (e.g., 's3', 'ec2')"
126
+ )
127
+ FIELD_NETWORK_SERVICES = Field(
128
+ ["elb", "vpc", "apigateway", "cloudfront"],
129
+ description="List of network services to check. Options: elb, vpc, apigateway, cloudfront",
130
+ )
131
+ FIELD_INCLUDE_NON_COMPLIANT_ONLY = Field(
132
+ False, description="Whether to include only non-compliant resources in the results"
133
+ )
134
+
135
+ # Global context storage for sharing data between tool calls
136
+ context_storage = {}
137
+
138
+
139
+ @mcp.tool(name="CheckSecurityServices")
140
+ async def check_security_services(
141
+ ctx: Context,
142
+ region: str = FIELD_AWS_REGION,
143
+ services: List[str] = FIELD_SECURITY_SERVICES,
144
+ account_id: Optional[str] = FIELD_ACCOUNT_ID,
145
+ aws_profile: Optional[str] = FIELD_AWS_PROFILE,
146
+ store_in_context: bool = FIELD_STORE_IN_CONTEXT_TRUE,
147
+ debug: bool = FIELD_DEBUG_TRUE,
148
+ ) -> Dict:
149
+ """Verify if selected AWS security services are enabled in the specified region and account.
150
+
151
+ This consolidated tool checks the status of multiple AWS security services in a single call,
152
+ providing a comprehensive overview of your security posture.
153
+
154
+ ## Response format
155
+ Returns a dictionary with:
156
+ - region: The region that was checked
157
+ - services_checked: List of services that were checked
158
+ - all_enabled: Boolean indicating if all specified services are enabled
159
+ - service_statuses: Dictionary with detailed status for each service
160
+ - summary: Summary of security recommendations
161
+
162
+ ## AWS permissions required
163
+ - guardduty:ListDetectors, guardduty:GetDetector (if checking GuardDuty)
164
+ - inspector2:GetStatus (if checking Inspector)
165
+ - accessanalyzer:ListAnalyzers (if checking Access Analyzer)
166
+ - securityhub:DescribeHub (if checking Security Hub)
167
+ - support:DescribeTrustedAdvisorChecks (if checking Trusted Advisor)
168
+ """
169
+ try:
170
+ # Start timestamp for measuring execution time
171
+ start_time = datetime.datetime.now()
172
+
173
+ if debug:
174
+ print(
175
+ f"[DEBUG:CheckSecurityServices] Starting security services check for region: {region}"
176
+ )
177
+ print(f"[DEBUG:CheckSecurityServices] Services to check: {', '.join(services)}")
178
+ print(f"[DEBUG:CheckSecurityServices] Using AWS profile: {aws_profile or 'default'}")
179
+
180
+ # Use the provided AWS profile or default to 'default'
181
+ profile_name = aws_profile or "default"
182
+
183
+ # Create a session using the specified profile
184
+ session = boto3.Session(profile_name=profile_name)
185
+
186
+ # Initialize results
187
+ results = {
188
+ "region": region,
189
+ "services_checked": services,
190
+ "all_enabled": True,
191
+ "service_statuses": {},
192
+ }
193
+
194
+ if debug:
195
+ # Add debug info to the results
196
+ results["debug_info"] = {
197
+ "start_time": start_time.isoformat(),
198
+ "aws_profile": profile_name,
199
+ "service_details": {},
200
+ }
201
+
202
+ # Check each requested service
203
+ for service_name in services:
204
+ # Process status update
205
+ service_start_time = datetime.datetime.now()
206
+ print(f"Checking {service_name} status in {region}...")
207
+ if debug:
208
+ print(f"[DEBUG:CheckSecurityServices] Starting check for {service_name}")
209
+
210
+ service_result = None
211
+
212
+ # Call the appropriate check function based on service name
213
+ if service_name.lower() == "guardduty":
214
+ service_result = await check_guard_duty(region, session, ctx)
215
+ elif service_name.lower() == "inspector":
216
+ service_result = await check_inspector(region, session, ctx)
217
+ elif service_name.lower() == "accessanalyzer":
218
+ # Call the access analyzer check with additional debugging
219
+ print(
220
+ f"[DEBUG:CheckSecurityServices] Calling check_access_analyzer for region {region}"
221
+ )
222
+ service_result = await check_access_analyzer(region, session, ctx)
223
+ print(
224
+ f"[DEBUG:CheckSecurityServices] check_access_analyzer returned: enabled={service_result.get('enabled', False)}"
225
+ )
226
+
227
+ # If service_result says not enabled but analyzers are present, override the enabled flag
228
+ analyzers = service_result.get("analyzers", [])
229
+ if not service_result.get("enabled", False) and analyzers and len(analyzers) > 0:
230
+ print(
231
+ "[DEBUG:CheckSecurityServices] OVERRIDING: Access Analyzer has analyzers but reported as disabled. Setting enabled=True"
232
+ )
233
+ service_result["enabled"] = True
234
+ service_result["message"] = (
235
+ f"IAM Access Analyzer is enabled with {len(analyzers)} analyzer(s)."
236
+ )
237
+
238
+ # Always log the analyzers we found
239
+ analyzers = service_result.get("analyzers", [])
240
+ if analyzers:
241
+ print(
242
+ f"[DEBUG:CheckSecurityServices] Access Analyzer check found {len(analyzers)} analyzers:"
243
+ )
244
+ for idx, analyzer in enumerate(analyzers):
245
+ print(
246
+ f"[DEBUG:CheckSecurityServices] Analyzer {idx + 1}: name={analyzer.get('name')}, status={analyzer.get('status')}"
247
+ )
248
+ else:
249
+ print("[DEBUG:CheckSecurityServices] Access Analyzer check found no analyzers")
250
+
251
+ elif service_name.lower() == "securityhub":
252
+ service_result = await check_security_hub(region, session, ctx)
253
+ elif service_name.lower() == "trustedadvisor":
254
+ service_result = await check_trusted_advisor(region, session, ctx)
255
+ elif service_name.lower() == "macie":
256
+ service_result = await check_macie(region, session, ctx)
257
+ else:
258
+ # Log warning
259
+ print(f"WARNING: Unknown service: {service_name}. Skipping.")
260
+ continue
261
+
262
+ # Add service result to the output
263
+ results["service_statuses"][service_name] = service_result
264
+
265
+ # Update all_enabled flag
266
+ if service_result and not service_result.get("enabled", False):
267
+ results["all_enabled"] = False
268
+
269
+ # Add debug info for this service if debug is enabled
270
+ if debug:
271
+ service_end_time = datetime.datetime.now()
272
+ service_duration = (service_end_time - service_start_time).total_seconds()
273
+
274
+ if "debug_info" in results and "service_details" in results["debug_info"]:
275
+ results["debug_info"]["service_details"][service_name] = {
276
+ "duration_seconds": service_duration,
277
+ "enabled": service_result.get("enabled", False)
278
+ if service_result
279
+ else False,
280
+ "timestamp": service_end_time.isoformat(),
281
+ "status": "success" if service_result else "error",
282
+ }
283
+
284
+ print(
285
+ f"[DEBUG:CheckSecurityServices] {service_name} check completed in {service_duration:.2f} seconds"
286
+ )
287
+
288
+ # Generate summary based on results
289
+ enabled_services = [
290
+ name
291
+ for name, status in results["service_statuses"].items()
292
+ if status.get("enabled", False)
293
+ ]
294
+ disabled_services = [
295
+ name
296
+ for name, status in results["service_statuses"].items()
297
+ if not status.get("enabled", False)
298
+ ]
299
+
300
+ summary = []
301
+ if enabled_services:
302
+ summary.append(f"Enabled services: {', '.join(enabled_services)}")
303
+
304
+ if disabled_services:
305
+ summary.append(f"Disabled services: {', '.join(disabled_services)}")
306
+ summary.append("Consider enabling these services to improve your security posture.")
307
+
308
+ results["summary"] = " ".join(summary)
309
+
310
+ # Store results in context if requested
311
+ if store_in_context:
312
+ context_key = f"security_services_{region}"
313
+ context_storage[context_key] = results
314
+ print(f"Stored security services results in context with key: {context_key}")
315
+
316
+ return results
317
+
318
+ except Exception as e:
319
+ # Log error
320
+ print(f"ERROR: Error checking security services: {e}")
321
+ return {
322
+ "region": region,
323
+ "services_checked": services,
324
+ "all_enabled": False,
325
+ "error": str(e),
326
+ "message": "Error checking security services status.",
327
+ }
328
+
329
+
330
+ @mcp.tool(name="GetSecurityFindings")
331
+ async def get_security_findings(
332
+ ctx: Context,
333
+ region: str = FIELD_AWS_REGION,
334
+ service: str = Field(
335
+ ...,
336
+ description="Security service to retrieve findings from ('guardduty', 'securityhub', 'inspector', 'accessanalyzer', 'trustedadvisor', 'macie')",
337
+ ),
338
+ max_findings: int = FIELD_MAX_FINDINGS,
339
+ severity_filter: Optional[str] = FIELD_SEVERITY_FILTER,
340
+ aws_profile: Optional[str] = FIELD_AWS_PROFILE,
341
+ check_enabled: bool = FIELD_CHECK_ENABLED,
342
+ ) -> Dict:
343
+ """Retrieve security findings from AWS security services.
344
+
345
+ This tool provides a consolidated interface to retrieve findings from various AWS security
346
+ services, including GuardDuty, Security Hub, Inspector, IAM Access Analyzer, and Trusted Advisor.
347
+
348
+ It first checks if the specified security service is enabled in the region (using data from
349
+ a previous CheckSecurityServices call) and only retrieves findings if the service is enabled.
350
+
351
+ ## Response format
352
+ Returns a dictionary with:
353
+ - service: The security service findings were retrieved from
354
+ - enabled: Whether the service is enabled in the specified region
355
+ - findings: List of findings from the service (if service is enabled)
356
+ - summary: Summary statistics about the findings (if service is enabled)
357
+ - message: Status message or error information
358
+
359
+ ## AWS permissions required
360
+ - Read permissions for the specified security service
361
+
362
+ ## Note
363
+ For optimal performance, run CheckSecurityServices with store_in_context=True
364
+ before using this tool. Otherwise, it will need to check if the service is enabled first.
365
+ """
366
+ try:
367
+ # Normalize service name
368
+ service_name = service.lower()
369
+
370
+ # Check if service is supported
371
+ if service_name not in [
372
+ "guardduty",
373
+ "securityhub",
374
+ "inspector",
375
+ "accessanalyzer",
376
+ "trustedadvisor",
377
+ "macie",
378
+ ]:
379
+ raise ValueError(
380
+ f"Unsupported security service: {service}. "
381
+ + "Supported services are: guardduty, securityhub, inspector, accessanalyzer, trustedadvisor, macie"
382
+ )
383
+
384
+ # Get context key for security services data
385
+ context_key = f"security_services_{region}"
386
+ service_status = None
387
+
388
+ # First check if we need to verify service is enabled
389
+ if check_enabled:
390
+ # Check if security services data is available in context
391
+ if context_key in context_storage:
392
+ print(f"Using stored security services data for region: {region}")
393
+ security_data = context_storage[context_key]
394
+
395
+ # Check if the requested service is in the stored data
396
+ service_statuses = security_data.get("service_statuses", {})
397
+ if service_name in service_statuses:
398
+ service_status = service_statuses[service_name]
399
+
400
+ # Check if service is enabled
401
+ if not service_status.get("enabled", False):
402
+ return {
403
+ "service": service_name,
404
+ "enabled": False,
405
+ "message": f"{service_name} is not enabled in region {region}. Please enable it before retrieving findings.",
406
+ "setup_instructions": service_status.get(
407
+ "setup_instructions", "No setup instructions available."
408
+ ),
409
+ }
410
+ else:
411
+ print(
412
+ f"Service {service_name} not found in stored security services data. Will check directly."
413
+ )
414
+ else:
415
+ print(
416
+ f"No stored security services data found for region: {region}. Will check service status directly."
417
+ )
418
+
419
+ # Use the provided AWS profile or default to 'default'
420
+ profile_name = aws_profile or "default"
421
+
422
+ # Create a session using the specified profile
423
+ session = boto3.Session(profile_name=profile_name)
424
+
425
+ # Prepare filter criteria based on severity
426
+ filter_criteria = None
427
+ if severity_filter:
428
+ if service_name == "guardduty":
429
+ # GuardDuty uses numeric severity levels
430
+ severity_mapping = {
431
+ "LOW": ["1", "2", "3"],
432
+ "MEDIUM": ["4", "5", "6"],
433
+ "HIGH": ["7", "8"],
434
+ "CRITICAL": ["8"],
435
+ }
436
+ if severity_filter.upper() in severity_mapping:
437
+ filter_criteria = {
438
+ "Criterion": {
439
+ "severity": {"Eq": severity_mapping[severity_filter.upper()]}
440
+ }
441
+ }
442
+ elif service_name == "securityhub":
443
+ filter_criteria = {
444
+ "SeverityLabel": [{"Comparison": "EQUALS", "Value": severity_filter.upper()}]
445
+ }
446
+ elif service_name == "inspector":
447
+ filter_criteria = {
448
+ "severities": [{"comparison": "EQUALS", "value": severity_filter.upper()}]
449
+ }
450
+ elif service_name == "trustedadvisor":
451
+ # For Trusted Advisor, severity maps to status (error, warning, ok)
452
+ status_filter = [severity_filter.lower()]
453
+
454
+ # Initialize result with default values
455
+ result = {
456
+ "service": service_name,
457
+ "enabled": False,
458
+ "message": f"Error retrieving {service_name} findings",
459
+ "findings": [],
460
+ }
461
+
462
+ # Call appropriate service function based on service parameter
463
+ if service_name == "guardduty":
464
+ print(f"Retrieving GuardDuty findings from {region}...")
465
+ result = await get_guardduty_findings(
466
+ region, session, ctx, max_findings, filter_criteria
467
+ )
468
+ elif service_name == "securityhub":
469
+ print(f"Retrieving Security Hub findings from {region}...")
470
+ result = await get_securityhub_findings(
471
+ region, session, ctx, max_findings, filter_criteria
472
+ )
473
+ elif service_name == "inspector":
474
+ print(f"Retrieving Inspector findings from {region}...")
475
+ result = await get_inspector_findings(
476
+ region, session, ctx, max_findings, filter_criteria
477
+ )
478
+ elif service_name == "accessanalyzer":
479
+ print(f"Retrieving IAM Access Analyzer findings from {region}...")
480
+ result = await get_access_analyzer_findings(region, session, ctx)
481
+ elif service_name == "trustedadvisor":
482
+ print("Retrieving Trusted Advisor security checks with Error/Warning status...")
483
+ # For Trusted Advisor, we'll focus on security category checks
484
+ # Use the severity filter if provided, otherwise default to error and warning
485
+ if severity_filter:
486
+ status_filter = [severity_filter.lower()]
487
+ print(f"Filtering Trusted Advisor checks by status: {status_filter}")
488
+ else:
489
+ status_filter = ["error", "warning"]
490
+ print(f"Using default status filter for Trusted Advisor: {status_filter}")
491
+ result = await get_trusted_advisor_findings(
492
+ region,
493
+ session,
494
+ ctx,
495
+ max_findings=max_findings,
496
+ status_filter=status_filter,
497
+ category_filter="security",
498
+ )
499
+ elif service_name == "macie":
500
+ print(f"Retrieving Macie findings from {region}...")
501
+ result = await get_macie_findings(region, session, ctx, max_findings, filter_criteria)
502
+
503
+ # Add service info to result
504
+ result["service"] = service_name
505
+
506
+ # If the result indicates the service isn't enabled, store this information
507
+ if not result.get("enabled", True) and context_key in context_storage:
508
+ security_data = context_storage[context_key]
509
+ service_statuses = security_data.get("service_statuses", {})
510
+ if service_name not in service_statuses:
511
+ service_statuses[service_name] = {"enabled": False}
512
+ print(f"Updated context with status for {service_name}: not enabled")
513
+
514
+ return result
515
+
516
+ except Exception as e:
517
+ # Log error
518
+ print(f"ERROR: Error retrieving {service} findings: {e}")
519
+ raise e
520
+
521
+
522
+ @mcp.tool(name="GetStoredSecurityContext")
523
+ async def get_stored_security_context(
524
+ ctx: Context,
525
+ region: str = FIELD_AWS_REGION,
526
+ detailed: bool = FIELD_DETAILED_FALSE,
527
+ ) -> Dict:
528
+ """Retrieve security services data that was stored in context from a previous CheckSecurityServices call.
529
+
530
+ This tool allows you to access security service status data stored by the CheckSecurityServices tool
531
+ without making additional AWS API calls. This is useful for workflows where you need to reference
532
+ the security services status in subsequent steps.
533
+
534
+ ## Response format
535
+ Returns a dictionary with:
536
+ - region: The region the data was stored for
537
+ - available: Boolean indicating if data is available for the requested region
538
+ - data: The stored security services data (if available and detailed=True)
539
+ - summary: A summary of the stored data (if available)
540
+ - timestamp: When the data was stored (if available)
541
+
542
+ ## Note
543
+ This tool requires that CheckSecurityServices was previously called with store_in_context=True
544
+ for the requested region.
545
+ """
546
+ context_key = f"security_services_{region}"
547
+
548
+ if context_key not in context_storage:
549
+ print(f"No stored security services data found for region: {region}")
550
+ return {
551
+ "region": region,
552
+ "available": False,
553
+ "message": f"No security services data has been stored for region {region}. Call CheckSecurityServices with store_in_context=True first.",
554
+ }
555
+
556
+ stored_data = context_storage[context_key]
557
+
558
+ # Prepare response
559
+ response = {
560
+ "region": region,
561
+ "available": True,
562
+ "summary": stored_data.get("summary", "No summary available"),
563
+ "all_enabled": stored_data.get("all_enabled", False),
564
+ "services_checked": stored_data.get("services_checked", []),
565
+ }
566
+
567
+ # Include full data if requested
568
+ if detailed:
569
+ response["data"] = stored_data
570
+
571
+ print(f"Retrieved stored security services data for region: {region}")
572
+ return response
573
+
574
+
575
+ @mcp.tool(name="CheckStorageEncryption")
576
+ async def check_storage_encryption_tool(
577
+ ctx: Context,
578
+ region: str = FIELD_AWS_REGION,
579
+ services: List[str] = FIELD_STORAGE_SERVICES,
580
+ include_unencrypted_only: bool = FIELD_INCLUDE_UNENCRYPTED_ONLY,
581
+ aws_profile: Optional[str] = FIELD_AWS_PROFILE,
582
+ store_in_context: bool = FIELD_STORE_IN_CONTEXT_TRUE,
583
+ ) -> Dict:
584
+ """Check if AWS storage resources have encryption enabled.
585
+
586
+ This tool identifies storage resources using Resource Explorer and checks if they
587
+ are properly configured for data protection at rest according to AWS Well-Architected
588
+ Framework Security Pillar best practices.
589
+
590
+ ## Response format
591
+ Returns a dictionary with:
592
+ - region: The region that was checked
593
+ - resources_checked: Total number of storage resources checked
594
+ - compliant_resources: Number of resources with proper encryption
595
+ - non_compliant_resources: Number of resources without proper encryption
596
+ - compliance_by_service: Breakdown of compliance by service type
597
+ - resource_details: Details about each resource checked
598
+ - recommendations: Recommendations for improving data protection at rest
599
+
600
+ ## AWS permissions required
601
+ - resource-explorer-2:ListResources
602
+ - Read permissions for each storage service being analyzed (s3:GetEncryptionConfiguration, etc.)
603
+ """
604
+ try:
605
+ print(f"Starting storage encryption check for region: {region}")
606
+ print(f"Services to check: {', '.join(services)}")
607
+ print(f"Using AWS profile: {aws_profile or 'default'}")
608
+
609
+ # Use the provided AWS profile or default to 'default'
610
+ profile_name = aws_profile or "default"
611
+
612
+ # Create a session using the specified profile
613
+ session = boto3.Session(profile_name=profile_name)
614
+
615
+ # Call the storage security utility function
616
+ results = await check_storage_encryption(
617
+ region, services, session, ctx, include_unencrypted_only
618
+ )
619
+
620
+ # Store results in context if requested
621
+ if store_in_context:
622
+ context_key = f"storage_encryption_{region}"
623
+ context_storage[context_key] = results
624
+ return results
625
+
626
+ except Exception as e:
627
+ # Log error
628
+ print(f"ERROR: Error checking storage encryption: {e}")
629
+ return {
630
+ "region": region,
631
+ "services_checked": services,
632
+ "error": str(e),
633
+ "message": "Error checking storage encryption status.",
634
+ }
635
+
636
+
637
+ @mcp.tool(name="ListServicesInRegion")
638
+ async def list_services_in_region_tool(
639
+ ctx: Context,
640
+ region: str = FIELD_AWS_REGION,
641
+ aws_profile: Optional[str] = FIELD_AWS_PROFILE,
642
+ store_in_context: bool = FIELD_STORE_IN_CONTEXT_TRUE,
643
+ ) -> Dict:
644
+ """List all AWS services being used in a specific region.
645
+
646
+ This tool identifies which AWS services are actively being used in the specified region
647
+ by discovering resources through AWS Resource Explorer or direct API calls.
648
+
649
+ ## Response format
650
+ Returns a dictionary with:
651
+ - region: The region that was checked
652
+ - services: List of AWS services being used in the region
653
+ - service_counts: Dictionary mapping service names to resource counts
654
+ - total_resources: Total number of resources found across all services
655
+
656
+ ## AWS permissions required
657
+ - resource-explorer-2:Search (if Resource Explorer is set up)
658
+ - Read permissions for various AWS services
659
+ """
660
+ print(f"Starting service discovery for region: {region}")
661
+ print(f"Using AWS profile: {aws_profile or 'default'}")
662
+
663
+ # Use the provided AWS profile or default to 'default'
664
+ profile_name = aws_profile or "default"
665
+
666
+ # Create a session using the specified profile
667
+ session = boto3.Session(profile_name=profile_name)
668
+
669
+ # Initialize results with default values
670
+ results = {"region": region, "services": [], "service_counts": {}, "total_resources": 0}
671
+
672
+ try:
673
+ # First try using Resource Explorer method
674
+ print(f"Attempting to discover services using Resource Explorer in {region}...")
675
+ results = await list_services_in_region(region, session, ctx)
676
+
677
+ except Exception as e:
678
+ # If Resource Explorer method fails, log the error and try alternative method
679
+ print(f"Resource Explorer method failed: {e}")
680
+ print("Falling back to alternative service discovery method...")
681
+
682
+ return {
683
+ "region": region,
684
+ "error": f"Discovery methods failed. Primary error: {str(e)}.",
685
+ "message": f"Error listing services in region {region}.",
686
+ "services": [],
687
+ "service_counts": {},
688
+ "total_resources": 0,
689
+ }
690
+
691
+ # Store results in context if requested
692
+ if store_in_context:
693
+ context_key = f"services_in_region_{region}"
694
+ context_storage[context_key] = results
695
+ return results
696
+
697
+
698
+ @mcp.tool(name="CheckNetworkSecurity")
699
+ async def check_network_security_tool(
700
+ ctx: Context,
701
+ region: str = FIELD_AWS_REGION,
702
+ services: List[str] = FIELD_NETWORK_SERVICES,
703
+ include_non_compliant_only: bool = FIELD_INCLUDE_NON_COMPLIANT_ONLY,
704
+ aws_profile: Optional[str] = FIELD_AWS_PROFILE,
705
+ store_in_context: bool = FIELD_STORE_IN_CONTEXT_TRUE,
706
+ ) -> Dict:
707
+ """Check if AWS network resources are configured for secure data-in-transit.
708
+
709
+ This tool identifies network resources using Resource Explorer and checks if they
710
+ are properly configured for data protection in transit according to AWS Well-Architected
711
+ Framework Security Pillar best practices.
712
+
713
+ ## Response format
714
+ Returns a dictionary with:
715
+ - region: The region that was checked
716
+ - resources_checked: Total number of network resources checked
717
+ - compliant_resources: Number of resources with proper in-transit protection
718
+ - non_compliant_resources: Number of resources without proper in-transit protection
719
+ - compliance_by_service: Breakdown of compliance by service type
720
+ - resource_details: Details about each resource checked
721
+ - recommendations: Recommendations for improving data protection in transit
722
+
723
+ ## AWS permissions required
724
+ - resource-explorer-2:ListResources
725
+ - Read permissions for each network service being analyzed (elb:DescribeLoadBalancers, etc.)
726
+ """
727
+ try:
728
+ print(f"Starting network security check for region: {region}")
729
+ print(f"Services to check: {', '.join(services)}")
730
+ print(f"Using AWS profile: {aws_profile or 'default'}")
731
+
732
+ # Use the provided AWS profile or default to 'default'
733
+ profile_name = aws_profile or "default"
734
+
735
+ # Create a session using the specified profile
736
+ session = boto3.Session(profile_name=profile_name)
737
+
738
+ # Call the network security utility function
739
+ results = await check_network_security(
740
+ region, services, session, ctx, include_non_compliant_only
741
+ )
742
+
743
+ # Store results in context if requested
744
+ if store_in_context:
745
+ context_key = f"network_security_{region}"
746
+ context_storage[context_key] = results
747
+ return results
748
+
749
+ except Exception as e:
750
+ # Log error
751
+ print(f"ERROR: Error checking network security: {e}")
752
+ return {
753
+ "region": region,
754
+ "services_checked": services,
755
+ "error": str(e),
756
+ "message": "Error checking network security status.",
757
+ }
758
+
759
+
760
+ @mcp.prompt(name="wa-sec-check-findings")
761
+ async def security_assessment_precheck(ctx: Context) -> str:
762
+ """Provides guidance on using CheckSecurityServices and GetSecurityFindings tools in sequence
763
+ for a comprehensive AWS security assessment.
764
+
765
+ This prompt explains the recommended workflow for assessing AWS security services and findings:
766
+ 1. First, check which security services are enabled using CheckSecurityServices
767
+ 2. Then, retrieve findings from the enabled services using GetSecurityFindings
768
+
769
+ Following this sequence ensures efficient API usage and provides a structured approach to security assessment.
770
+ """
771
+ return """
772
+ # AWS Security Assessment Workflow Guide
773
+
774
+ This guide will help you assess your AWS security posture by checking which security services are enabled and retrieving findings from those services.
775
+
776
+ ## Step 1: Check Security Services Status
777
+
778
+ First, use the `CheckSecurityServices` tool to determine which AWS security services are enabled in your account:
779
+
780
+ ```python
781
+ result = await use_mcp_tool(
782
+ server_name="well-architected-security-mcp-server",
783
+ tool_name="CheckSecurityServices",
784
+ arguments={
785
+ "region": "us-east-1", # Specify your AWS region
786
+ "services": ["guardduty", "inspector", "accessanalyzer", "securityhub", "trustedadvisor"],
787
+ "aws_profile": "default", # Optional: specify your AWS profile
788
+ "store_in_context": True # Important: store results for later use
789
+ }
790
+ )
791
+ ```
792
+
793
+ This will check the status of each security service and store the results in context for later use.
794
+
795
+ ## Step 2: Analyze the Results
796
+
797
+ Review the results to see which services are enabled:
798
+
799
+ ```python
800
+ enabled_services = []
801
+ for service, status in result['service_statuses'].items():
802
+ if status.get('enabled', False):
803
+ enabled_services.append(service)
804
+ print(f"✅ {service} is enabled")
805
+ else:
806
+ print(f"❌ {service} is not enabled")
807
+ ```
808
+
809
+ ## Step 3: Retrieve Findings from Enabled Services
810
+
811
+ For each enabled service, use the `GetSecurityFindings` tool to retrieve findings:
812
+
813
+ ```python
814
+ for service in enabled_services:
815
+ findings = await use_mcp_tool(
816
+ server_name="well-architected-security-mcp-server",
817
+ tool_name="GetSecurityFindings",
818
+ arguments={
819
+ "region": "us-east-1", # Use the same region as in Step 1
820
+ "service": service,
821
+ "max_findings": 100, # Adjust as needed
822
+ "severity_filter": "HIGH", # Optional: filter by severity
823
+ "check_enabled": True # Verify service is enabled before retrieving findings
824
+ }
825
+ )
826
+
827
+ # Process the findings
828
+ if findings.get('findings'):
829
+ print(f"Found {len(findings['findings'])} {service} findings")
830
+ # Analyze findings here
831
+ ```
832
+
833
+ ## Step 4: Summarize Security Posture
834
+
835
+ After retrieving findings from all enabled services, summarize the security posture:
836
+
837
+ ```python
838
+ total_findings = 0
839
+ findings_by_service = {}
840
+
841
+ for service in enabled_services:
842
+ # Get findings count for each service
843
+ # Implement your summary logic here
844
+ ```
845
+
846
+ ## Best Practices
847
+
848
+ 1. Always run `CheckSecurityServices` first with `store_in_context=True`
849
+ 2. Use `GetSecurityFindings` only for services that are enabled
850
+ 3. Consider filtering findings by severity to focus on high-risk issues first
851
+ 4. For large environments, process findings in batches
852
+
853
+ By following this workflow, you'll efficiently assess your AWS security posture and identify potential security issues.
854
+ """
855
+
856
+
857
+ @mcp.prompt(name="wa-sec-check-storage")
858
+ async def check_storage_security_prompt(ctx: Context) -> str:
859
+ """Provides guidance on checking AWS storage resources for proper encryption and security configuration.
860
+
861
+ This prompt explains the recommended workflow for assessing storage security:
862
+ 1. First, identify available storage services in the target region
863
+ 2. Then, check if these storage resources have encryption enabled
864
+ 3. Finally, analyze the results and implement recommended remediation steps
865
+
866
+ This approach helps ensure data protection at rest according to AWS Well-Architected Framework
867
+ Security Pillar best practices.
868
+ """
869
+ return """
870
+ # AWS Storage Security Assessment Guide
871
+
872
+ This guide will help you assess the security of your AWS storage resources by checking for proper encryption and security configurations.
873
+
874
+ ## Step 1: Identify Available Storage Services
875
+
876
+ First, determine which storage services are available in your target region:
877
+
878
+ ```python
879
+ # Option 1: List all services in the region
880
+ services_result = await use_mcp_tool(
881
+ server_name="well-architected-security-mcp-server",
882
+ tool_name="ListServicesInRegion",
883
+ arguments={
884
+ "region": "us-east-1", # Specify your AWS region
885
+ "aws_profile": "default", # Optional: specify your AWS profile
886
+ "store_in_context": True # Store results for later use
887
+ }
888
+ )
889
+
890
+ # Option 2: List resource types (alternative approach)
891
+ resource_types = await use_mcp_tool(
892
+ server_name="well-architected-security-mcp-server",
893
+ tool_name="ListResourceTypes",
894
+ arguments={
895
+ "region": "us-east-1", # Specify your AWS region
896
+ "aws_profile": "default", # Optional: specify your AWS profile
897
+ "store_in_context": True # Store results for later use
898
+ }
899
+ )
900
+ ```
901
+
902
+ ## Step 2: Filter for Storage Services
903
+
904
+ Next, filter the results to focus on storage services:
905
+
906
+ ```python
907
+ # Define storage services to check
908
+ storage_services = ['s3', 'ebs', 'rds', 'dynamodb', 'efs', 'elasticache']
909
+
910
+ # Filter available services to include only storage services
911
+ available_storage_services = []
912
+
913
+ # If using ListServicesInRegion result
914
+ if 'services' in services_result:
915
+ available_storage_services = [s for s in services_result['services'] if s in storage_services]
916
+
917
+ # If using ListResourceTypes result
918
+ if 'storage_services' in resource_types:
919
+ available_storage_services = resource_types['storage_services']
920
+
921
+ print(f"Available storage services: {', '.join(available_storage_services)}")
922
+ ```
923
+
924
+ ## Step 3: Check Storage Encryption
925
+
926
+ Now, check if your storage resources have encryption enabled:
927
+
928
+ ```python
929
+ encryption_result = await use_mcp_tool(
930
+ server_name="well-architected-security-mcp-server",
931
+ tool_name="CheckStorageEncryption",
932
+ arguments={
933
+ "region": "us-east-1", # Specify your AWS region
934
+ "services": available_storage_services, # Use the filtered list from Step 2
935
+ "include_unencrypted_only": False, # Set to True to focus only on unencrypted resources
936
+ "aws_profile": "default", # Optional: specify your AWS profile
937
+ "store_in_context": True # Store results for later use
938
+ }
939
+ )
940
+ ```
941
+
942
+ ## Step 4: Analyze the Results
943
+
944
+ Review the encryption check results:
945
+
946
+ ```python
947
+ # Get overall compliance statistics
948
+ total_resources = encryption_result['resources_checked']
949
+ compliant_resources = encryption_result['compliant_resources']
950
+ non_compliant_resources = encryption_result['non_compliant_resources']
951
+
952
+ print(f"Total resources checked: {total_resources}")
953
+ print(f"Compliant resources: {compliant_resources} ({(compliant_resources/total_resources)*100:.1f}% if total_resources > 0 else 0}%)")
954
+ print(f"Non-compliant resources: {non_compliant_resources} ({(non_compliant_resources/total_resources)*100:.1f}% if total_resources > 0 else 0}%)")
955
+
956
+ # Review compliance by service
957
+ for service, stats in encryption_result['compliance_by_service'].items():
958
+ service_total = stats['resources_checked']
959
+ service_compliant = stats['compliant_resources']
960
+ service_non_compliant = stats['non_compliant_resources']
961
+
962
+ if service_total > 0:
963
+ compliance_rate = (service_compliant / service_total) * 100
964
+ print(f"{service}: {compliance_rate:.1f}% compliant ({service_compliant}/{service_total})")
965
+ ```
966
+
967
+ ## Step 5: Review Non-Compliant Resources
968
+
969
+ Examine the details of non-compliant resources:
970
+
971
+ ```python
972
+ # List all non-compliant resources
973
+ print("\\nNon-compliant resources:")
974
+ for resource in encryption_result['resource_details']:
975
+ if not resource.get('compliant', True):
976
+ print(f"- {resource['type']}: {resource['name']}")
977
+ print(f" Issues: {', '.join(resource['issues'])}")
978
+ print(f" Remediation: {', '.join(resource['remediation'])}")
979
+ ```
980
+
981
+ ## Step 6: Implement Recommendations
982
+
983
+ Review and implement the recommended remediation steps:
984
+
985
+ ```python
986
+ print("\\nRecommendations:")
987
+ for recommendation in encryption_result['recommendations']:
988
+ print(f"- {recommendation}")
989
+ ```
990
+
991
+ ## Best Practices for Storage Security
992
+
993
+ 1. **Enable encryption by default** for all storage services
994
+ 2. **Use customer-managed KMS keys** for sensitive data rather than AWS-managed keys
995
+ 3. **Implement key rotation policies** for all customer-managed KMS keys
996
+ 4. **Block public access** for S3 buckets at the account level
997
+ 5. **Enable bucket key** for S3 buckets to reduce KMS API calls and costs
998
+ 6. **Audit encryption settings regularly** to ensure continued compliance
999
+
1000
+ By following this workflow, you'll efficiently assess your AWS storage security posture and identify resources that need encryption or security improvements.
1001
+ """
1002
+
1003
+
1004
+ @mcp.prompt(name="wa-sec-check-network")
1005
+ async def check_network_security_prompt(ctx: Context) -> str:
1006
+ """Provides guidance on checking AWS network resources for proper in-transit security configuration.
1007
+
1008
+ This prompt explains the recommended workflow for assessing network security:
1009
+ 1. First, identify available network services in the target region
1010
+ 2. Then, check if these network resources have proper in-transit security measures
1011
+ 3. Finally, analyze the results and implement recommended remediation steps
1012
+
1013
+ This approach helps ensure data protection in transit according to AWS Well-Architected Framework
1014
+ Security Pillar best practices.
1015
+ """
1016
+ return """
1017
+ # AWS Network Security Assessment Guide
1018
+
1019
+ This guide will help you assess the security of your AWS network resources by checking for proper in-transit security configurations.
1020
+
1021
+ ## Step 1: Identify Available Network Services
1022
+
1023
+ First, determine which network services are available in your target region:
1024
+
1025
+ ```python
1026
+ # Option 1: List all services in the region
1027
+ services_result = await use_mcp_tool(
1028
+ server_name="well-architected-security-mcp-server",
1029
+ tool_name="ListServicesInRegion",
1030
+ arguments={
1031
+ "region": "us-east-1", # Specify your AWS region
1032
+ "aws_profile": "default", # Optional: specify your AWS profile
1033
+ "store_in_context": True # Store results for later use
1034
+ }
1035
+ )
1036
+
1037
+ # Option 2: List resource types (alternative approach)
1038
+ resource_types = await use_mcp_tool(
1039
+ server_name="well-architected-security-mcp-server",
1040
+ tool_name="ListResourceTypes",
1041
+ arguments={
1042
+ "region": "us-east-1", # Specify your AWS region
1043
+ "aws_profile": "default", # Optional: specify your AWS profile
1044
+ "store_in_context": True # Store results for later use
1045
+ }
1046
+ )
1047
+ ```
1048
+
1049
+ ## Step 2: Filter for Network Services
1050
+
1051
+ Next, filter the results to focus on network services:
1052
+
1053
+ ```python
1054
+ # Define network services to check
1055
+ network_services = ['elb', 'vpc', 'apigateway', 'cloudfront']
1056
+
1057
+ # Filter available services to include only network services
1058
+ available_network_services = []
1059
+
1060
+ # If using ListServicesInRegion result
1061
+ if 'services' in services_result:
1062
+ available_network_services = [s for s in services_result['services'] if s in network_services]
1063
+
1064
+ # If using ListResourceTypes result
1065
+ if 'network_services' in resource_types:
1066
+ available_network_services = resource_types['network_services']
1067
+
1068
+ print(f"Available network services: {', '.join(available_network_services)}")
1069
+ ```
1070
+
1071
+ ## Step 3: Check Network Security
1072
+
1073
+ Now, check if your network resources have proper in-transit security measures:
1074
+
1075
+ ```python
1076
+ network_result = await use_mcp_tool(
1077
+ server_name="well-architected-security-mcp-server",
1078
+ tool_name="CheckNetworkSecurity",
1079
+ arguments={
1080
+ "region": "us-east-1", # Specify your AWS region
1081
+ "services": available_network_services, # Use the filtered list from Step 2
1082
+ "include_non_compliant_only": False, # Set to True to focus only on non-compliant resources
1083
+ "aws_profile": "default", # Optional: specify your AWS profile
1084
+ "store_in_context": True # Store results for later use
1085
+ }
1086
+ )
1087
+ ```
1088
+
1089
+ ## Step 4: Analyze the Results
1090
+
1091
+ Review the network security check results:
1092
+
1093
+ ```python
1094
+ # Get overall compliance statistics
1095
+ total_resources = network_result['resources_checked']
1096
+ compliant_resources = network_result['compliant_resources']
1097
+ non_compliant_resources = network_result['non_compliant_resources']
1098
+
1099
+ print(f"Total resources checked: {total_resources}")
1100
+ print(f"Compliant resources: {compliant_resources} ({(compliant_resources/total_resources)*100:.1f}% if total_resources > 0 else 0}%)")
1101
+ print(f"Non-compliant resources: {non_compliant_resources} ({(non_compliant_resources/total_resources)*100:.1f}% if total_resources > 0 else 0}%)")
1102
+
1103
+ # Review compliance by service
1104
+ for service, stats in network_result['compliance_by_service'].items():
1105
+ service_total = stats['resources_checked']
1106
+ service_compliant = stats['compliant_resources']
1107
+ service_non_compliant = stats['non_compliant_resources']
1108
+
1109
+ if service_total > 0:
1110
+ compliance_rate = (service_compliant / service_total) * 100
1111
+ print(f"{service}: {compliance_rate:.1f}% compliant ({service_compliant}/{service_total})")
1112
+ ```
1113
+
1114
+ ## Step 5: Review Non-Compliant Resources
1115
+
1116
+ Examine the details of non-compliant resources:
1117
+
1118
+ ```python
1119
+ # List all non-compliant resources
1120
+ print("\\nNon-compliant resources:")
1121
+ for resource in network_result['resource_details']:
1122
+ if not resource.get('compliant', True):
1123
+ print(f"- {resource['type']}: {resource['name']}")
1124
+ print(f" Issues: {', '.join(resource['issues'])}")
1125
+ print(f" Remediation: {', '.join(resource['remediation'])}")
1126
+ ```
1127
+
1128
+ ## Step 6: Implement Recommendations
1129
+
1130
+ Review and implement the recommended remediation steps:
1131
+
1132
+ ```python
1133
+ print("\\nRecommendations:")
1134
+ for recommendation in network_result['recommendations']:
1135
+ print(f"- {recommendation}")
1136
+ ```
1137
+
1138
+ ## Best Practices for Network Security
1139
+
1140
+ 1. **Use HTTPS/TLS** for all public-facing endpoints
1141
+ 2. **Configure security policies** to use modern TLS versions (TLS 1.2 or later)
1142
+ 3. **Implement strict security headers** for web applications
1143
+ 4. **Use AWS Certificate Manager (ACM)** for managing SSL/TLS certificates
1144
+ 5. **Enable VPC Flow Logs** to monitor network traffic
1145
+ 6. **Implement network segmentation** using security groups and NACLs
1146
+ 7. **Use AWS WAF** to protect web applications from common exploits
1147
+ 8. **Regularly audit network security configurations** to ensure continued compliance
1148
+
1149
+ By following this workflow, you'll efficiently assess your AWS network security posture and identify resources that need security improvements for data in transit.
1150
+ """
1151
+
1152
+
1153
+ def main():
1154
+ """Run the MCP server with CLI argument support."""
1155
+ parser = argparse.ArgumentParser(description="AWS Security Pillar MCP Server")
1156
+ parser.add_argument("--sse", action="store_true", help="Use SSE transport")
1157
+ parser.add_argument("--port", type=int, default=8888, help="Port to run the server on")
1158
+
1159
+ args = parser.parse_args()
1160
+
1161
+ logger.info("Starting AWS Security Pillar MCP Server")
1162
+
1163
+ # Run server with appropriate transport
1164
+ if args.sse:
1165
+ logger.info(f"Running MCP server with SSE transport on port {args.port}")
1166
+ mcp.settings.port = args.port
1167
+ mcp.run(transport="sse")
1168
+ else:
1169
+ logger.info("Running MCP server with default transport")
1170
+ mcp.run()
1171
+
1172
+
1173
+ if __name__ == "__main__":
1174
+ main()