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,280 @@
1
+ """
2
+ DynamoDB table encryption compliance test.
3
+
4
+ Checks that all DynamoDB tables have server-side encryption enabled.
5
+
6
+ ISO 27001 Control: A.8.24 - Use of cryptography
7
+ Requirement: All data at rest must be encrypted
8
+
9
+ Example:
10
+ >>> from complio.connectors.aws.client import AWSConnector
11
+ >>> from complio.tests_library.storage.dynamodb_encryption import DynamoDBEncryptionTest
12
+ >>>
13
+ >>> connector = AWSConnector("production", "us-east-1")
14
+ >>> connector.connect()
15
+ >>>
16
+ >>> test = DynamoDBEncryptionTest(connector)
17
+ >>> result = test.run()
18
+ >>> print(f"Passed: {result.passed}, Score: {result.score}")
19
+ """
20
+
21
+ from typing import Any, Dict
22
+
23
+ from botocore.exceptions import ClientError
24
+
25
+ from complio.connectors.aws.client import AWSConnector
26
+ from complio.tests_library.base import (
27
+ ComplianceTest,
28
+ Severity,
29
+ TestResult,
30
+ TestStatus,
31
+ )
32
+
33
+
34
+ class DynamoDBEncryptionTest(ComplianceTest):
35
+ """Test for DynamoDB table encryption compliance.
36
+
37
+ Verifies that all DynamoDB tables have server-side encryption enabled.
38
+
39
+ Compliance Requirements:
40
+ - All DynamoDB tables must have SSEDescription.Status=ENABLED
41
+ - Tables can use AWS managed or customer managed KMS keys
42
+ - Tables without SSE are non-compliant
43
+
44
+ Scoring:
45
+ - 100% if all tables are encrypted
46
+ - Proportional score based on encrypted/total ratio
47
+ - 0% if no tables are encrypted
48
+
49
+ Example:
50
+ >>> test = DynamoDBEncryptionTest(connector)
51
+ >>> result = test.execute()
52
+ >>> for finding in result.findings:
53
+ ... print(f"{finding.resource_id}: {finding.title}")
54
+ """
55
+
56
+ def __init__(self, connector: AWSConnector) -> None:
57
+ """Initialize DynamoDB encryption test.
58
+
59
+ Args:
60
+ connector: AWS connector instance
61
+ """
62
+ super().__init__(
63
+ test_id="dynamodb_encryption",
64
+ test_name="DynamoDB Table Encryption Check",
65
+ description="Verify all DynamoDB tables have server-side encryption enabled",
66
+ control_id="A.8.24",
67
+ connector=connector,
68
+ scope="regional",
69
+ )
70
+
71
+ def execute(self) -> TestResult:
72
+ """Execute DynamoDB encryption compliance test.
73
+
74
+ Returns:
75
+ TestResult with findings for non-encrypted tables
76
+
77
+ Example:
78
+ >>> test = DynamoDBEncryptionTest(connector)
79
+ >>> result = test.execute()
80
+ >>> print(result.score)
81
+ 100.0
82
+ """
83
+ result = TestResult(
84
+ test_id=self.test_id,
85
+ test_name=self.test_name,
86
+ status=TestStatus.PASSED,
87
+ passed=True,
88
+ score=100.0,
89
+ )
90
+
91
+ try:
92
+ # Get DynamoDB client
93
+ dynamodb_client = self.connector.get_client("dynamodb")
94
+
95
+ # List all tables
96
+ self.logger.info("listing_dynamodb_tables")
97
+ table_names = []
98
+
99
+ # Handle pagination for list_tables
100
+ paginator = dynamodb_client.get_paginator("list_tables")
101
+ for page in paginator.paginate():
102
+ table_names.extend(page.get("TableNames", []))
103
+
104
+ if not table_names:
105
+ self.logger.info("no_dynamodb_tables_found")
106
+ result.metadata["message"] = "No DynamoDB tables found in region"
107
+ return result
108
+
109
+ self.logger.info("dynamodb_tables_found", count=len(table_names))
110
+
111
+ # Check each table for encryption
112
+ encrypted_count = 0
113
+ total_count = len(table_names)
114
+
115
+ for table_name in table_names:
116
+ result.resources_scanned += 1
117
+
118
+ # Describe table to get encryption details
119
+ try:
120
+ table_response = dynamodb_client.describe_table(TableName=table_name)
121
+ table = table_response.get("Table", {})
122
+
123
+ # Check SSEDescription
124
+ sse_description = table.get("SSEDescription", {})
125
+ sse_status = sse_description.get("Status", "DISABLED")
126
+ sse_type = sse_description.get("SSEType", "None")
127
+ kms_key_arn = sse_description.get("KMSMasterKeyArn")
128
+
129
+ # Encryption is enabled if Status=ENABLED or ENABLING
130
+ encrypted = sse_status in ["ENABLED", "ENABLING"]
131
+
132
+ # Get table details
133
+ table_status = table.get("TableStatus", "unknown")
134
+ item_count = table.get("ItemCount", 0)
135
+ table_size_bytes = table.get("TableSizeBytes", 0)
136
+ billing_mode = table.get("BillingModeSummary", {}).get("BillingMode", "PROVISIONED")
137
+
138
+ # Create evidence
139
+ evidence = self.create_evidence(
140
+ resource_id=table_name,
141
+ resource_type="dynamodb_table",
142
+ data={
143
+ "table_name": table_name,
144
+ "encrypted": encrypted,
145
+ "sse_status": sse_status,
146
+ "sse_type": sse_type,
147
+ "kms_key_arn": kms_key_arn,
148
+ "table_status": table_status,
149
+ "item_count": item_count,
150
+ "table_size_bytes": table_size_bytes,
151
+ "billing_mode": billing_mode,
152
+ }
153
+ )
154
+ result.add_evidence(evidence)
155
+
156
+ if encrypted:
157
+ encrypted_count += 1
158
+ self.logger.debug(
159
+ "dynamodb_table_encrypted",
160
+ table_name=table_name,
161
+ sse_type=sse_type,
162
+ sse_status=sse_status
163
+ )
164
+ else:
165
+ # Create finding for non-encrypted table
166
+ size_mb = table_size_bytes / (1024 * 1024) if table_size_bytes > 0 else 0
167
+ finding = self.create_finding(
168
+ resource_id=table_name,
169
+ resource_type="dynamodb_table",
170
+ severity=Severity.HIGH,
171
+ title="DynamoDB table encryption not enabled",
172
+ description=f"DynamoDB table '{table_name}' with {item_count} items ({size_mb:.2f}MB) "
173
+ f"does not have server-side encryption enabled (SSE Status: {sse_status}). "
174
+ "Table data is stored unencrypted. "
175
+ "This violates ISO 27001 A.8.24 requirement for data-at-rest encryption.",
176
+ remediation=(
177
+ f"Enable server-side encryption for table '{table_name}':\n\n"
178
+ "Using AWS managed key (easier):\n"
179
+ f"aws dynamodb update-table --table-name {table_name} \\\n"
180
+ " --sse-specification Enabled=true\n\n"
181
+ "Or using customer managed KMS key (more control):\n"
182
+ f"aws dynamodb update-table --table-name {table_name} \\\n"
183
+ " --sse-specification Enabled=true,SSEType=KMS,KMSMasterKeyId=<your-kms-key-id>\n\n"
184
+ "Or use AWS Console:\n"
185
+ "1. Go to DynamoDB → Tables\n"
186
+ f"2. Select table '{table_name}'\n"
187
+ "3. Go to 'Additional settings' tab\n"
188
+ "4. Under 'Encryption at rest', click 'Manage encryption'\n"
189
+ "5. Select 'AWS owned key' or 'AWS managed key' or 'Customer managed key'\n"
190
+ "6. Click 'Save changes'\n\n"
191
+ "Note: Encryption can be enabled on existing tables without downtime."
192
+ ),
193
+ evidence=evidence
194
+ )
195
+ result.add_finding(finding)
196
+
197
+ self.logger.warning(
198
+ "dynamodb_table_not_encrypted",
199
+ table_name=table_name,
200
+ sse_status=sse_status
201
+ )
202
+
203
+ except ClientError as e:
204
+ error_code = e.response.get("Error", {}).get("Code")
205
+ self.logger.error(
206
+ "dynamodb_table_describe_error",
207
+ table_name=table_name,
208
+ error_code=error_code
209
+ )
210
+ # Skip this table and continue
211
+ continue
212
+
213
+ # Calculate compliance score
214
+ if total_count > 0:
215
+ result.score = (encrypted_count / total_count) * 100
216
+
217
+ # Determine pass/fail
218
+ result.passed = encrypted_count == total_count
219
+ result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
220
+
221
+ # Add metadata
222
+ result.metadata = {
223
+ "total_tables": total_count,
224
+ "encrypted_tables": encrypted_count,
225
+ "non_encrypted_tables": total_count - encrypted_count,
226
+ "compliance_percentage": result.score,
227
+ "region": self.connector.region,
228
+ }
229
+
230
+ self.logger.info(
231
+ "dynamodb_encryption_test_completed",
232
+ total=total_count,
233
+ encrypted=encrypted_count,
234
+ score=result.score,
235
+ passed=result.passed
236
+ )
237
+
238
+ except ClientError as e:
239
+ error_code = e.response.get("Error", {}).get("Code")
240
+ self.logger.error("dynamodb_encryption_test_error", error_code=error_code, error=str(e))
241
+ result.status = TestStatus.ERROR
242
+ result.passed = False
243
+ result.score = 0.0
244
+ result.error_message = f"AWS API Error: {error_code} - {str(e)}"
245
+
246
+ except Exception as e:
247
+ self.logger.error("dynamodb_encryption_test_error", error=str(e))
248
+ result.status = TestStatus.ERROR
249
+ result.passed = False
250
+ result.score = 0.0
251
+ result.error_message = str(e)
252
+
253
+ return result
254
+
255
+
256
+ # ============================================================================
257
+ # CONVENIENCE FUNCTION
258
+ # ============================================================================
259
+
260
+
261
+ def run_dynamodb_encryption_test(connector: AWSConnector) -> TestResult:
262
+ """Run DynamoDB encryption compliance test.
263
+
264
+ Convenience function for running the test.
265
+
266
+ Args:
267
+ connector: AWS connector
268
+
269
+ Returns:
270
+ TestResult
271
+
272
+ Example:
273
+ >>> from complio.connectors.aws.client import AWSConnector
274
+ >>> connector = AWSConnector("production", "us-east-1")
275
+ >>> connector.connect()
276
+ >>> result = run_dynamodb_encryption_test(connector)
277
+ >>> print(f"Score: {result.score}%")
278
+ """
279
+ test = DynamoDBEncryptionTest(connector)
280
+ return test.execute()
@@ -0,0 +1,257 @@
1
+ """
2
+ EFS (Elastic File System) encryption compliance test.
3
+
4
+ Checks that all EFS file systems have encryption enabled.
5
+
6
+ ISO 27001 Control: A.8.24 - Use of cryptography
7
+ Requirement: All data at rest must be encrypted
8
+
9
+ Example:
10
+ >>> from complio.connectors.aws.client import AWSConnector
11
+ >>> from complio.tests_library.storage.efs_encryption import EFSEncryptionTest
12
+ >>>
13
+ >>> connector = AWSConnector("production", "us-east-1")
14
+ >>> connector.connect()
15
+ >>>
16
+ >>> test = EFSEncryptionTest(connector)
17
+ >>> result = test.run()
18
+ >>> print(f"Passed: {result.passed}, Score: {result.score}")
19
+ """
20
+
21
+ from typing import Any, Dict
22
+
23
+ from botocore.exceptions import ClientError
24
+
25
+ from complio.connectors.aws.client import AWSConnector
26
+ from complio.tests_library.base import (
27
+ ComplianceTest,
28
+ Severity,
29
+ TestResult,
30
+ TestStatus,
31
+ )
32
+
33
+
34
+ class EFSEncryptionTest(ComplianceTest):
35
+ """Test for EFS file system encryption compliance.
36
+
37
+ Verifies that all EFS file systems have encryption enabled.
38
+
39
+ Compliance Requirements:
40
+ - All EFS file systems must have Encrypted=True
41
+ - Unencrypted file systems expose shared data
42
+ - File systems without encryption are non-compliant
43
+
44
+ Scoring:
45
+ - 100% if all file systems are encrypted
46
+ - Proportional score based on encrypted/total ratio
47
+ - 0% if no file systems are encrypted
48
+
49
+ Example:
50
+ >>> test = EFSEncryptionTest(connector)
51
+ >>> result = test.execute()
52
+ >>> for finding in result.findings:
53
+ ... print(f"{finding.resource_id}: {finding.title}")
54
+ """
55
+
56
+ def __init__(self, connector: AWSConnector) -> None:
57
+ """Initialize EFS encryption test.
58
+
59
+ Args:
60
+ connector: AWS connector instance
61
+ """
62
+ super().__init__(
63
+ test_id="efs_encryption",
64
+ test_name="EFS File System Encryption Check",
65
+ description="Verify all EFS file systems have encryption enabled",
66
+ control_id="A.8.24",
67
+ connector=connector,
68
+ scope="regional",
69
+ )
70
+
71
+ def execute(self) -> TestResult:
72
+ """Execute EFS encryption compliance test.
73
+
74
+ Returns:
75
+ TestResult with findings for non-encrypted file systems
76
+
77
+ Example:
78
+ >>> test = EFSEncryptionTest(connector)
79
+ >>> result = test.execute()
80
+ >>> print(result.score)
81
+ 100.0
82
+ """
83
+ result = TestResult(
84
+ test_id=self.test_id,
85
+ test_name=self.test_name,
86
+ status=TestStatus.PASSED,
87
+ passed=True,
88
+ score=100.0,
89
+ )
90
+
91
+ try:
92
+ # Get EFS client
93
+ efs_client = self.connector.get_client("efs")
94
+
95
+ # List all file systems
96
+ self.logger.info("listing_efs_file_systems")
97
+ response = efs_client.describe_file_systems()
98
+ file_systems = response.get("FileSystems", [])
99
+
100
+ if not file_systems:
101
+ self.logger.info("no_efs_file_systems_found")
102
+ result.metadata["message"] = "No EFS file systems found in region"
103
+ return result
104
+
105
+ self.logger.info("efs_file_systems_found", count=len(file_systems))
106
+
107
+ # Check each file system for encryption
108
+ encrypted_count = 0
109
+ total_count = len(file_systems)
110
+
111
+ for fs in file_systems:
112
+ fs_id = fs["FileSystemId"]
113
+ encrypted = fs.get("Encrypted", False)
114
+ result.resources_scanned += 1
115
+
116
+ # Get file system details
117
+ name = fs.get("Name", "unnamed")
118
+ life_cycle_state = fs.get("LifeCycleState", "unknown")
119
+ size_in_bytes = fs.get("SizeInBytes", {}).get("Value", 0)
120
+ number_of_mount_targets = fs.get("NumberOfMountTargets", 0)
121
+ performance_mode = fs.get("PerformanceMode", "unknown")
122
+
123
+ # Create evidence
124
+ evidence = self.create_evidence(
125
+ resource_id=fs_id,
126
+ resource_type="efs_file_system",
127
+ data={
128
+ "file_system_id": fs_id,
129
+ "name": name,
130
+ "encrypted": encrypted,
131
+ "life_cycle_state": life_cycle_state,
132
+ "size_bytes": size_in_bytes,
133
+ "number_of_mount_targets": number_of_mount_targets,
134
+ "performance_mode": performance_mode,
135
+ "kms_key_id": fs.get("KmsKeyId"),
136
+ "creation_token": fs.get("CreationToken"),
137
+ }
138
+ )
139
+ result.add_evidence(evidence)
140
+
141
+ if encrypted:
142
+ encrypted_count += 1
143
+ self.logger.debug(
144
+ "efs_file_system_encrypted",
145
+ fs_id=fs_id,
146
+ name=name,
147
+ kms_key_id=fs.get("KmsKeyId")
148
+ )
149
+ else:
150
+ # Create finding for non-encrypted file system
151
+ size_mb = size_in_bytes / (1024 * 1024) if size_in_bytes > 0 else 0
152
+ finding = self.create_finding(
153
+ resource_id=fs_id,
154
+ resource_type="efs_file_system",
155
+ severity=Severity.HIGH,
156
+ title="EFS file system encryption not enabled",
157
+ description=f"EFS file system '{name}' ({fs_id}) with {number_of_mount_targets} mount targets "
158
+ f"and {size_mb:.2f}MB of data does not have encryption enabled. "
159
+ "Shared file system data is stored unencrypted. "
160
+ "This violates ISO 27001 A.8.24 requirement for data-at-rest encryption.",
161
+ remediation=(
162
+ "EFS file systems cannot be encrypted after creation. To remediate:\n"
163
+ "1. Create a new EFS file system with encryption enabled:\n"
164
+ " aws efs create-file-system --encrypted \\\n"
165
+ " --kms-key-id <your-kms-key-id> \\\n"
166
+ f" --performance-mode {performance_mode} \\\n"
167
+ " --tags Key=Name,Value=encrypted-replacement\n"
168
+ "2. Create mount targets in the same subnets:\n"
169
+ " aws efs create-mount-target --file-system-id <new-fs-id> \\\n"
170
+ " --subnet-id <subnet-id> --security-groups <sg-id>\n"
171
+ "3. Use AWS DataSync or rsync to migrate data:\n"
172
+ " # Mount both file systems, then:\n"
173
+ " rsync -av /mnt/old-efs/ /mnt/new-efs/\n"
174
+ "4. Update application mount points\n"
175
+ f"5. Delete the old unencrypted file system '{fs_id}'\n\n"
176
+ "Note: This will cause temporary downtime during data migration.\n"
177
+ "For future file systems, enable encryption at creation time."
178
+ ),
179
+ evidence=evidence
180
+ )
181
+ result.add_finding(finding)
182
+
183
+ self.logger.warning(
184
+ "efs_file_system_not_encrypted",
185
+ fs_id=fs_id,
186
+ name=name,
187
+ size_mb=size_mb
188
+ )
189
+
190
+ # Calculate compliance score
191
+ if total_count > 0:
192
+ result.score = (encrypted_count / total_count) * 100
193
+
194
+ # Determine pass/fail
195
+ result.passed = encrypted_count == total_count
196
+ result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
197
+
198
+ # Add metadata
199
+ result.metadata = {
200
+ "total_file_systems": total_count,
201
+ "encrypted_file_systems": encrypted_count,
202
+ "non_encrypted_file_systems": total_count - encrypted_count,
203
+ "compliance_percentage": result.score,
204
+ "region": self.connector.region,
205
+ }
206
+
207
+ self.logger.info(
208
+ "efs_encryption_test_completed",
209
+ total=total_count,
210
+ encrypted=encrypted_count,
211
+ score=result.score,
212
+ passed=result.passed
213
+ )
214
+
215
+ except ClientError as e:
216
+ error_code = e.response.get("Error", {}).get("Code")
217
+ self.logger.error("efs_encryption_test_error", error_code=error_code, error=str(e))
218
+ result.status = TestStatus.ERROR
219
+ result.passed = False
220
+ result.score = 0.0
221
+ result.error_message = f"AWS API Error: {error_code} - {str(e)}"
222
+
223
+ except Exception as e:
224
+ self.logger.error("efs_encryption_test_error", error=str(e))
225
+ result.status = TestStatus.ERROR
226
+ result.passed = False
227
+ result.score = 0.0
228
+ result.error_message = str(e)
229
+
230
+ return result
231
+
232
+
233
+ # ============================================================================
234
+ # CONVENIENCE FUNCTION
235
+ # ============================================================================
236
+
237
+
238
+ def run_efs_encryption_test(connector: AWSConnector) -> TestResult:
239
+ """Run EFS encryption compliance test.
240
+
241
+ Convenience function for running the test.
242
+
243
+ Args:
244
+ connector: AWS connector
245
+
246
+ Returns:
247
+ TestResult
248
+
249
+ Example:
250
+ >>> from complio.connectors.aws.client import AWSConnector
251
+ >>> connector = AWSConnector("production", "us-east-1")
252
+ >>> connector.connect()
253
+ >>> result = run_efs_encryption_test(connector)
254
+ >>> print(f"Score: {result.score}%")
255
+ """
256
+ test = EFSEncryptionTest(connector)
257
+ return test.execute()