complio 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.
Files changed (79) hide show
  1. CHANGELOG.md +208 -0
  2. README.md +343 -0
  3. complio/__init__.py +48 -0
  4. complio/cli/__init__.py +0 -0
  5. complio/cli/banner.py +87 -0
  6. complio/cli/commands/__init__.py +0 -0
  7. complio/cli/commands/history.py +439 -0
  8. complio/cli/commands/scan.py +700 -0
  9. complio/cli/main.py +115 -0
  10. complio/cli/output.py +338 -0
  11. complio/config/__init__.py +17 -0
  12. complio/config/settings.py +333 -0
  13. complio/connectors/__init__.py +9 -0
  14. complio/connectors/aws/__init__.py +0 -0
  15. complio/connectors/aws/client.py +342 -0
  16. complio/connectors/base.py +135 -0
  17. complio/core/__init__.py +10 -0
  18. complio/core/registry.py +228 -0
  19. complio/core/runner.py +351 -0
  20. complio/py.typed +0 -0
  21. complio/reporters/__init__.py +7 -0
  22. complio/reporters/generator.py +417 -0
  23. complio/tests_library/__init__.py +0 -0
  24. complio/tests_library/base.py +492 -0
  25. complio/tests_library/identity/__init__.py +0 -0
  26. complio/tests_library/identity/access_key_rotation.py +302 -0
  27. complio/tests_library/identity/mfa_enforcement.py +327 -0
  28. complio/tests_library/identity/root_account_protection.py +470 -0
  29. complio/tests_library/infrastructure/__init__.py +0 -0
  30. complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
  31. complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
  32. complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
  33. complio/tests_library/infrastructure/ebs_encryption.py +244 -0
  34. complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
  35. complio/tests_library/infrastructure/iam_password_policy.py +460 -0
  36. complio/tests_library/infrastructure/nacl_security.py +356 -0
  37. complio/tests_library/infrastructure/rds_encryption.py +252 -0
  38. complio/tests_library/infrastructure/s3_encryption.py +301 -0
  39. complio/tests_library/infrastructure/s3_public_access.py +369 -0
  40. complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
  41. complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
  42. complio/tests_library/logging/__init__.py +0 -0
  43. complio/tests_library/logging/cloudwatch_alarms.py +354 -0
  44. complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
  45. complio/tests_library/logging/cloudwatch_retention.py +252 -0
  46. complio/tests_library/logging/config_enabled.py +393 -0
  47. complio/tests_library/logging/eventbridge_rules.py +460 -0
  48. complio/tests_library/logging/guardduty_enabled.py +436 -0
  49. complio/tests_library/logging/security_hub_enabled.py +416 -0
  50. complio/tests_library/logging/sns_encryption.py +273 -0
  51. complio/tests_library/network/__init__.py +0 -0
  52. complio/tests_library/network/alb_nlb_security.py +421 -0
  53. complio/tests_library/network/api_gateway_security.py +452 -0
  54. complio/tests_library/network/cloudfront_https.py +332 -0
  55. complio/tests_library/network/direct_connect_security.py +343 -0
  56. complio/tests_library/network/nacl_configuration.py +367 -0
  57. complio/tests_library/network/network_firewall.py +355 -0
  58. complio/tests_library/network/transit_gateway_security.py +318 -0
  59. complio/tests_library/network/vpc_endpoints_security.py +339 -0
  60. complio/tests_library/network/vpn_security.py +333 -0
  61. complio/tests_library/network/waf_configuration.py +428 -0
  62. complio/tests_library/security/__init__.py +0 -0
  63. complio/tests_library/security/kms_key_rotation.py +314 -0
  64. complio/tests_library/storage/__init__.py +0 -0
  65. complio/tests_library/storage/backup_encryption.py +288 -0
  66. complio/tests_library/storage/dynamodb_encryption.py +280 -0
  67. complio/tests_library/storage/efs_encryption.py +257 -0
  68. complio/tests_library/storage/elasticache_encryption.py +370 -0
  69. complio/tests_library/storage/redshift_encryption.py +252 -0
  70. complio/tests_library/storage/s3_versioning.py +264 -0
  71. complio/utils/__init__.py +26 -0
  72. complio/utils/errors.py +179 -0
  73. complio/utils/exceptions.py +151 -0
  74. complio/utils/history.py +243 -0
  75. complio/utils/logger.py +391 -0
  76. complio-0.1.1.dist-info/METADATA +385 -0
  77. complio-0.1.1.dist-info/RECORD +79 -0
  78. complio-0.1.1.dist-info/WHEEL +4 -0
  79. complio-0.1.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,333 @@
1
+ """
2
+ Configuration management for Complio.
3
+
4
+ This module provides centralized configuration using Pydantic Settings.
5
+ Supports environment variables, defaults, and validation.
6
+
7
+ Configuration Sources (priority order):
8
+ 1. Environment variables (COMPLIO_*)
9
+ 2. Configuration file (~/.complio/config.yaml)
10
+ 3. Default values
11
+
12
+ Example:
13
+ >>> from complio.config.settings import get_settings
14
+ >>> settings = get_settings()
15
+ >>> print(settings.default_region)
16
+ 'us-east-1'
17
+ """
18
+
19
+ from pathlib import Path
20
+ from typing import List, Optional
21
+
22
+ from pydantic import Field, field_validator
23
+ from pydantic_settings import BaseSettings, SettingsConfigDict
24
+
25
+ # ============================================================================
26
+ # CONSTANTS
27
+ # ============================================================================
28
+
29
+ # Application version
30
+ VERSION = "0.1.0"
31
+
32
+ # Default configuration directory
33
+ DEFAULT_CONFIG_DIR = Path.home() / ".complio"
34
+ DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.yaml"
35
+ DEFAULT_CREDENTIALS_FILE = DEFAULT_CONFIG_DIR / "credentials.enc"
36
+ DEFAULT_LOG_DIR = DEFAULT_CONFIG_DIR / "logs"
37
+ DEFAULT_REPORT_DIR = DEFAULT_CONFIG_DIR / "reports"
38
+
39
+ # Valid AWS regions (as of 2024)
40
+ VALID_AWS_REGIONS: List[str] = [
41
+ "us-east-1",
42
+ "us-east-2",
43
+ "us-west-1",
44
+ "us-west-2",
45
+ "af-south-1",
46
+ "ap-east-1",
47
+ "ap-south-1",
48
+ "ap-southeast-1",
49
+ "ap-southeast-2",
50
+ "ap-southeast-3",
51
+ "ap-northeast-1",
52
+ "ap-northeast-2",
53
+ "ap-northeast-3",
54
+ "ca-central-1",
55
+ "eu-central-1",
56
+ "eu-west-1",
57
+ "eu-west-2",
58
+ "eu-west-3",
59
+ "eu-south-1",
60
+ "eu-north-1",
61
+ "me-south-1",
62
+ "sa-east-1",
63
+ ]
64
+
65
+ # Valid output formats
66
+ VALID_OUTPUT_FORMATS: List[str] = ["json", "markdown", "pdf", "html"]
67
+
68
+ # Valid log levels
69
+ VALID_LOG_LEVELS: List[str] = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
70
+
71
+
72
+ # ============================================================================
73
+ # SETTINGS MODEL
74
+ # ============================================================================
75
+
76
+
77
+ class ComplioSettings(BaseSettings):
78
+ """Application settings with validation and defaults.
79
+
80
+ Settings can be configured via:
81
+ - Environment variables (COMPLIO_DEFAULT_REGION, etc.)
82
+ - Configuration file (~/.complio/config.yaml)
83
+ - Default values (defined here)
84
+
85
+ Attributes:
86
+ default_region: Default AWS region for scans
87
+ default_profile: Default credential profile name
88
+ default_output_format: Default report format
89
+ log_level: Logging level
90
+ log_to_file: Enable file logging
91
+ log_dir: Directory for log files
92
+ log_max_bytes: Max log file size before rotation
93
+ log_backup_count: Number of backup log files to keep
94
+ config_dir: Configuration directory path
95
+ credentials_file: Path to encrypted credentials
96
+ report_dir: Directory for generated reports
97
+ aws_timeout: Timeout for AWS API calls (seconds)
98
+ max_concurrent_tests: Maximum concurrent compliance tests
99
+ enable_colors: Enable colored terminal output
100
+ verbose: Enable verbose output
101
+
102
+ Example:
103
+ >>> settings = ComplioSettings()
104
+ >>> print(settings.default_region)
105
+ 'us-east-1'
106
+
107
+ >>> # Override with environment variable
108
+ >>> import os
109
+ >>> os.environ['COMPLIO_DEFAULT_REGION'] = 'eu-west-1'
110
+ >>> settings = ComplioSettings()
111
+ >>> print(settings.default_region)
112
+ 'eu-west-1'
113
+ """
114
+
115
+ model_config = SettingsConfigDict(
116
+ env_prefix="COMPLIO_",
117
+ env_file=".env",
118
+ env_file_encoding="utf-8",
119
+ case_sensitive=False,
120
+ )
121
+
122
+ # Application Version
123
+ VERSION: str = Field(
124
+ default=VERSION,
125
+ description="Application version",
126
+ )
127
+
128
+ # AWS Configuration
129
+ default_region: str = Field(
130
+ default="us-east-1",
131
+ description="Default AWS region for compliance scans",
132
+ )
133
+ default_profile: str = Field(
134
+ default="default",
135
+ description="Default credential profile name",
136
+ )
137
+ aws_timeout: int = Field(
138
+ default=30,
139
+ ge=1,
140
+ le=300,
141
+ description="Timeout for AWS API calls in seconds",
142
+ )
143
+
144
+ # Output Configuration
145
+ default_output_format: str = Field(
146
+ default="json",
147
+ description="Default format for compliance reports",
148
+ )
149
+ enable_colors: bool = Field(
150
+ default=True,
151
+ description="Enable colored terminal output",
152
+ )
153
+ verbose: bool = Field(
154
+ default=False,
155
+ description="Enable verbose output",
156
+ )
157
+
158
+ # Logging Configuration
159
+ log_level: str = Field(
160
+ default="INFO",
161
+ description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
162
+ )
163
+ log_to_file: bool = Field(
164
+ default=True,
165
+ description="Enable logging to file",
166
+ )
167
+ log_dir: Path = Field(
168
+ default=DEFAULT_LOG_DIR,
169
+ description="Directory for log files",
170
+ )
171
+ log_max_bytes: int = Field(
172
+ default=10_000_000, # 10 MB
173
+ ge=1_000_000, # Min 1 MB
174
+ le=100_000_000, # Max 100 MB
175
+ description="Maximum log file size before rotation",
176
+ )
177
+ log_backup_count: int = Field(
178
+ default=5,
179
+ ge=1,
180
+ le=20,
181
+ description="Number of backup log files to keep",
182
+ )
183
+
184
+ # Directory Configuration
185
+ config_dir: Path = Field(
186
+ default=DEFAULT_CONFIG_DIR,
187
+ description="Configuration directory path",
188
+ )
189
+ credentials_file: Path = Field(
190
+ default=DEFAULT_CREDENTIALS_FILE,
191
+ description="Path to encrypted credentials file",
192
+ )
193
+ report_dir: Path = Field(
194
+ default=DEFAULT_REPORT_DIR,
195
+ description="Directory for generated reports",
196
+ )
197
+
198
+ # Test Execution Configuration
199
+ max_concurrent_tests: int = Field(
200
+ default=10,
201
+ ge=1,
202
+ le=50,
203
+ description="Maximum number of concurrent compliance tests",
204
+ )
205
+
206
+ # Validation
207
+ @field_validator("default_region")
208
+ @classmethod
209
+ def validate_region(cls, v: str) -> str:
210
+ """Validate AWS region.
211
+
212
+ Args:
213
+ v: Region string to validate
214
+
215
+ Returns:
216
+ Validated region string
217
+
218
+ Raises:
219
+ ValueError: If region is invalid
220
+ """
221
+ if v not in VALID_AWS_REGIONS:
222
+ raise ValueError(
223
+ f"Invalid AWS region: {v}. Must be one of: {', '.join(VALID_AWS_REGIONS)}"
224
+ )
225
+ return v
226
+
227
+ @field_validator("default_output_format")
228
+ @classmethod
229
+ def validate_output_format(cls, v: str) -> str:
230
+ """Validate output format.
231
+
232
+ Args:
233
+ v: Format string to validate
234
+
235
+ Returns:
236
+ Validated format string (lowercase)
237
+
238
+ Raises:
239
+ ValueError: If format is invalid
240
+ """
241
+ v_lower = v.lower()
242
+ if v_lower not in VALID_OUTPUT_FORMATS:
243
+ raise ValueError(
244
+ f"Invalid output format: {v}. Must be one of: {', '.join(VALID_OUTPUT_FORMATS)}"
245
+ )
246
+ return v_lower
247
+
248
+ @field_validator("log_level")
249
+ @classmethod
250
+ def validate_log_level(cls, v: str) -> str:
251
+ """Validate log level.
252
+
253
+ Args:
254
+ v: Log level string to validate
255
+
256
+ Returns:
257
+ Validated log level string (uppercase)
258
+
259
+ Raises:
260
+ ValueError: If log level is invalid
261
+ """
262
+ v_upper = v.upper()
263
+ if v_upper not in VALID_LOG_LEVELS:
264
+ raise ValueError(
265
+ f"Invalid log level: {v}. Must be one of: {', '.join(VALID_LOG_LEVELS)}"
266
+ )
267
+ return v_upper
268
+
269
+ def ensure_directories(self) -> None:
270
+ """Create required directories if they don't exist.
271
+
272
+ Creates:
273
+ - Configuration directory
274
+ - Log directory
275
+ - Report directory
276
+
277
+ Sets appropriate permissions (700 for directories).
278
+
279
+ Example:
280
+ >>> settings = ComplioSettings()
281
+ >>> settings.ensure_directories()
282
+ >>> assert settings.log_dir.exists()
283
+ """
284
+ for directory in [self.config_dir, self.log_dir, self.report_dir]:
285
+ directory.mkdir(parents=True, exist_ok=True)
286
+ # Set directory permissions to 700 (rwx------)
287
+ directory.chmod(0o700)
288
+
289
+
290
+ # ============================================================================
291
+ # SINGLETON SETTINGS INSTANCE
292
+ # ============================================================================
293
+
294
+ _settings: Optional[ComplioSettings] = None
295
+
296
+
297
+ def get_settings() -> ComplioSettings:
298
+ """Get application settings singleton.
299
+
300
+ Returns cached settings instance if available, otherwise creates new one.
301
+ This ensures consistent settings across the application.
302
+
303
+ Returns:
304
+ ComplioSettings instance
305
+
306
+ Example:
307
+ >>> settings = get_settings()
308
+ >>> print(settings.default_region)
309
+ 'us-east-1'
310
+
311
+ >>> # Subsequent calls return same instance
312
+ >>> settings2 = get_settings()
313
+ >>> assert settings is settings2
314
+ """
315
+ global _settings
316
+ if _settings is None:
317
+ _settings = ComplioSettings()
318
+ # Ensure required directories exist
319
+ _settings.ensure_directories()
320
+ return _settings
321
+
322
+
323
+ def reset_settings() -> None:
324
+ """Reset settings singleton.
325
+
326
+ Useful for testing to force re-initialization of settings.
327
+
328
+ Example:
329
+ >>> reset_settings()
330
+ >>> settings = get_settings() # Creates new instance
331
+ """
332
+ global _settings
333
+ _settings = None
@@ -0,0 +1,9 @@
1
+ """Cloud connector modules for Complio."""
2
+
3
+ from complio.connectors.base import CloudConnector
4
+ from complio.connectors.aws.client import AWSConnector
5
+
6
+ __all__ = [
7
+ "CloudConnector",
8
+ "AWSConnector",
9
+ ]
File without changes
@@ -0,0 +1,342 @@
1
+ """
2
+ AWS connector using boto3.
3
+
4
+ This module provides AWS connectivity using standard AWS credentials (from
5
+ ~/.aws/credentials, environment variables, or IAM roles) via boto3 SDK.
6
+
7
+ Security Features:
8
+ - Uses boto3's standard credential chain
9
+ - Reads from ~/.aws/credentials (same as AWS CLI)
10
+ - Supports environment variables and IAM roles
11
+ - Optional encrypted credential storage (legacy mode)
12
+ - STS validation before use
13
+ - Configurable timeouts
14
+ - Automatic retry with exponential backoff
15
+ - No credential logging
16
+
17
+ Example:
18
+ >>> from complio.connectors.aws.client import AWSConnector
19
+ >>> # Uses credentials from ~/.aws/credentials (no password needed)
20
+ >>> connector = AWSConnector(profile_name="default", region="us-east-1")
21
+ >>> connector.connect()
22
+ >>> # Validate credentials
23
+ >>> result = connector.validate_credentials()
24
+ >>> print(result["account_id"])
25
+ '123456789012'
26
+ >>> # Get AWS clients
27
+ >>> s3 = connector.get_client("s3")
28
+ >>> ec2 = connector.get_client("ec2")
29
+ """
30
+
31
+ from typing import Any, Dict, Optional
32
+
33
+ import boto3
34
+ from boto3.session import Session
35
+ from botocore.config import Config
36
+ from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError
37
+
38
+ from complio.config.settings import get_settings
39
+ from complio.connectors.base import CloudConnector
40
+ from complio.utils.exceptions import AWSConnectionError, AWSCredentialsError
41
+ from complio.utils.logger import get_logger, log_aws_api_call
42
+
43
+ logger = get_logger(__name__)
44
+
45
+
46
+ class AWSConnector(CloudConnector):
47
+ """AWS cloud connector using boto3.
48
+
49
+ Manages AWS connections using standard AWS credentials (from ~/.aws/credentials,
50
+ environment variables, or IAM roles). Optionally supports encrypted credential
51
+ storage for legacy compatibility.
52
+
53
+ Attributes:
54
+ profile_name: Credential profile name (from ~/.aws/credentials)
55
+ region: AWS region
56
+ password: Optional password for encrypted credentials (legacy mode)
57
+ session: Boto3 session (created on connect)
58
+ connected: Connection status
59
+
60
+ Example:
61
+ >>> # Standard usage (reads from ~/.aws/credentials)
62
+ >>> connector = AWSConnector("default", "us-east-1")
63
+ >>> connector.connect()
64
+ >>> s3_client = connector.get_client("s3")
65
+ >>> buckets = s3_client.list_buckets()
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ profile_name: str,
71
+ region: Optional[str] = None,
72
+ ) -> None:
73
+ """Initialize AWS connector.
74
+
75
+ Args:
76
+ profile_name: Name of credential profile (from ~/.aws/credentials)
77
+ region: AWS region (defaults to settings.default_region)
78
+
79
+ Example:
80
+ >>> connector = AWSConnector("default", "us-east-1")
81
+ >>> connector.connect()
82
+ """
83
+ settings = get_settings()
84
+ region = region or settings.default_region
85
+
86
+ super().__init__(profile_name=profile_name, region=region)
87
+
88
+ self.session: Optional[Session] = None
89
+ self._clients: Dict[str, Any] = {}
90
+ self._account_id: Optional[str] = None
91
+ self._user_arn: Optional[str] = None
92
+
93
+ def connect(self) -> bool:
94
+ """Connect to AWS using standard AWS credentials.
95
+
96
+ Uses boto3's default credential chain (reads from ~/.aws/credentials, environment
97
+ variables, EC2 instance metadata, etc). Falls back to encrypted credentials if
98
+ password is explicitly provided.
99
+
100
+ Returns:
101
+ True if connection successful
102
+
103
+ Raises:
104
+ AWSCredentialsError: If credentials cannot be loaded or are invalid
105
+ AWSConnectionError: If connection fails
106
+
107
+ Example:
108
+ >>> connector.connect()
109
+ True
110
+ """
111
+ logger.info("connecting_to_aws", profile=self.profile_name, region=self.region)
112
+
113
+ try:
114
+ # Use standard AWS credential chain
115
+ # This reads from:
116
+ # 1. ~/.aws/credentials (AWS config file)
117
+ # 2. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
118
+ # 3. IAM role (for EC2 instances)
119
+ # 4. Other boto3 credential providers
120
+ logger.debug("using_standard_aws_credentials", profile=self.profile_name)
121
+
122
+ # Create boto3 session using profile from ~/.aws/credentials
123
+ self.session = boto3.Session(
124
+ profile_name=self.profile_name if self.profile_name != "default" else None,
125
+ region_name=self.region
126
+ )
127
+
128
+ self.connected = True
129
+ logger.info("aws_connection_established", profile=self.profile_name, region=self.region)
130
+
131
+ return True
132
+
133
+ except AWSCredentialsError:
134
+ raise
135
+ except Exception as e:
136
+ logger.error("aws_connection_failed", error=str(e), profile=self.profile_name)
137
+ raise AWSConnectionError(
138
+ f"Failed to connect to AWS: {str(e)}",
139
+ details={"profile": self.profile_name, "region": self.region}
140
+ ) from e
141
+
142
+ def disconnect(self) -> None:
143
+ """Disconnect from AWS.
144
+
145
+ Clears session and cached clients.
146
+
147
+ Example:
148
+ >>> connector.disconnect()
149
+ """
150
+ self.session = None
151
+ self._clients = {}
152
+ self.connected = False
153
+ logger.info("aws_disconnected", profile=self.profile_name)
154
+
155
+ def get_client(self, service_name: str, region: Optional[str] = None) -> Any:
156
+ """Get boto3 client for AWS service.
157
+
158
+ Creates and caches boto3 clients with proper configuration.
159
+
160
+ Args:
161
+ service_name: AWS service name (e.g., 's3', 'ec2', 'iam')
162
+ region: AWS region (uses connector region if not specified)
163
+
164
+ Returns:
165
+ Boto3 client for the specified service
166
+
167
+ Raises:
168
+ AWSConnectionError: If not connected
169
+
170
+ Example:
171
+ >>> s3 = connector.get_client("s3")
172
+ >>> buckets = s3.list_buckets()
173
+ """
174
+ if not self.connected or not self.session:
175
+ raise AWSConnectionError(
176
+ "Not connected to AWS. Call connect() first.",
177
+ details={"profile": self.profile_name}
178
+ )
179
+
180
+ region = region or self.region
181
+ cache_key = f"{service_name}:{region}"
182
+
183
+ # Return cached client if available
184
+ if cache_key in self._clients:
185
+ return self._clients[cache_key]
186
+
187
+ # Create new client with configuration
188
+ settings = get_settings()
189
+
190
+ client_config = Config(
191
+ region_name=region,
192
+ retries={
193
+ "max_attempts": 3,
194
+ "mode": "adaptive"
195
+ },
196
+ connect_timeout=settings.aws_timeout,
197
+ read_timeout=settings.aws_timeout,
198
+ )
199
+
200
+ try:
201
+ client = self.session.client(service_name, config=client_config)
202
+ self._clients[cache_key] = client
203
+
204
+ logger.debug("aws_client_created", service=service_name, region=region)
205
+
206
+ return client
207
+
208
+ except Exception as e:
209
+ logger.error("aws_client_creation_failed", service=service_name, error=str(e))
210
+ raise AWSConnectionError(
211
+ f"Failed to create {service_name} client: {str(e)}",
212
+ details={"service": service_name, "region": region}
213
+ ) from e
214
+
215
+ def validate_credentials(self) -> Dict[str, Any]:
216
+ """Validate AWS credentials using STS GetCallerIdentity.
217
+
218
+ Returns:
219
+ Dictionary with validation result:
220
+ {
221
+ "valid": True,
222
+ "account_id": "123456789012",
223
+ "user_arn": "arn:aws:iam::123456789012:user/admin",
224
+ "user_id": "AIDAI..."
225
+ }
226
+
227
+ Raises:
228
+ AWSCredentialsError: If credentials are invalid
229
+ AWSConnectionError: If not connected
230
+
231
+ Example:
232
+ >>> result = connector.validate_credentials()
233
+ >>> print(f"Connected to account: {result['account_id']}")
234
+ """
235
+ if not self.connected:
236
+ raise AWSConnectionError(
237
+ "Not connected to AWS. Call connect() first.",
238
+ details={"profile": self.profile_name}
239
+ )
240
+
241
+ logger.info("validating_aws_credentials", profile=self.profile_name)
242
+
243
+ try:
244
+ sts = self.get_client("sts")
245
+ log_aws_api_call(logger, "sts", "get_caller_identity", self.region)
246
+
247
+ response = sts.get_caller_identity()
248
+
249
+ result = {
250
+ "valid": True,
251
+ "account_id": response["Account"],
252
+ "user_arn": response["Arn"],
253
+ "user_id": response["UserId"],
254
+ }
255
+
256
+ # Cache for future use
257
+ self._account_id = result["account_id"]
258
+ self._user_arn = result["user_arn"]
259
+
260
+ logger.info(
261
+ "aws_credentials_valid",
262
+ account_id=result["account_id"],
263
+ user_id=result["user_id"]
264
+ )
265
+
266
+ return result
267
+
268
+ except (ClientError, NoCredentialsError, PartialCredentialsError) as e:
269
+ logger.error("aws_credential_validation_failed", error=str(e))
270
+ raise AWSCredentialsError(
271
+ f"AWS credential validation failed: {str(e)}",
272
+ details={"profile": self.profile_name}
273
+ ) from e
274
+
275
+ def test_connection(self) -> bool:
276
+ """Test if AWS connection is working.
277
+
278
+ Performs a simple STS call to verify connectivity.
279
+
280
+ Returns:
281
+ True if connection is healthy, False otherwise
282
+
283
+ Example:
284
+ >>> if connector.test_connection():
285
+ ... print("AWS connection is healthy")
286
+ """
287
+ try:
288
+ self.validate_credentials()
289
+ return True
290
+ except Exception as e:
291
+ logger.warning("aws_connection_test_failed", error=str(e))
292
+ return False
293
+
294
+ def get_account_id(self) -> str:
295
+ """Get AWS account ID.
296
+
297
+ Returns:
298
+ AWS account ID
299
+
300
+ Raises:
301
+ AWSConnectionError: If credentials haven't been validated
302
+
303
+ Example:
304
+ >>> account_id = connector.get_account_id()
305
+ >>> print(account_id)
306
+ '123456789012'
307
+ """
308
+ if not self._account_id:
309
+ # Validate to get account ID
310
+ self.validate_credentials()
311
+
312
+ if not self._account_id:
313
+ raise AWSConnectionError(
314
+ "Account ID not available. Validate credentials first.",
315
+ details={"profile": self.profile_name}
316
+ )
317
+
318
+ return self._account_id
319
+
320
+ def list_regions(self, service: str = "ec2") -> list[str]:
321
+ """List available AWS regions for a service.
322
+
323
+ Args:
324
+ service: AWS service name (default: ec2)
325
+
326
+ Returns:
327
+ List of region names
328
+
329
+ Example:
330
+ >>> regions = connector.list_regions()
331
+ >>> print(regions)
332
+ ['us-east-1', 'us-west-2', 'eu-west-1', ...]
333
+ """
334
+ try:
335
+ client = self.get_client(service)
336
+ regions = client.describe_regions()["Regions"]
337
+ return [region["RegionName"] for region in regions]
338
+ except Exception as e:
339
+ logger.warning("failed_to_list_regions", error=str(e))
340
+ # Return default list if API call fails
341
+ from complio.config.settings import VALID_AWS_REGIONS
342
+ return VALID_AWS_REGIONS