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.
- CHANGELOG.md +208 -0
- README.md +343 -0
- complio/__init__.py +48 -0
- complio/cli/__init__.py +0 -0
- complio/cli/banner.py +87 -0
- complio/cli/commands/__init__.py +0 -0
- complio/cli/commands/history.py +439 -0
- complio/cli/commands/scan.py +700 -0
- complio/cli/main.py +115 -0
- complio/cli/output.py +338 -0
- complio/config/__init__.py +17 -0
- complio/config/settings.py +333 -0
- complio/connectors/__init__.py +9 -0
- complio/connectors/aws/__init__.py +0 -0
- complio/connectors/aws/client.py +342 -0
- complio/connectors/base.py +135 -0
- complio/core/__init__.py +10 -0
- complio/core/registry.py +228 -0
- complio/core/runner.py +351 -0
- complio/py.typed +0 -0
- complio/reporters/__init__.py +7 -0
- complio/reporters/generator.py +417 -0
- complio/tests_library/__init__.py +0 -0
- complio/tests_library/base.py +492 -0
- complio/tests_library/identity/__init__.py +0 -0
- complio/tests_library/identity/access_key_rotation.py +302 -0
- complio/tests_library/identity/mfa_enforcement.py +327 -0
- complio/tests_library/identity/root_account_protection.py +470 -0
- complio/tests_library/infrastructure/__init__.py +0 -0
- complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
- complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
- complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
- complio/tests_library/infrastructure/ebs_encryption.py +244 -0
- complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
- complio/tests_library/infrastructure/iam_password_policy.py +460 -0
- complio/tests_library/infrastructure/nacl_security.py +356 -0
- complio/tests_library/infrastructure/rds_encryption.py +252 -0
- complio/tests_library/infrastructure/s3_encryption.py +301 -0
- complio/tests_library/infrastructure/s3_public_access.py +369 -0
- complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
- complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
- complio/tests_library/logging/__init__.py +0 -0
- complio/tests_library/logging/cloudwatch_alarms.py +354 -0
- complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
- complio/tests_library/logging/cloudwatch_retention.py +252 -0
- complio/tests_library/logging/config_enabled.py +393 -0
- complio/tests_library/logging/eventbridge_rules.py +460 -0
- complio/tests_library/logging/guardduty_enabled.py +436 -0
- complio/tests_library/logging/security_hub_enabled.py +416 -0
- complio/tests_library/logging/sns_encryption.py +273 -0
- complio/tests_library/network/__init__.py +0 -0
- complio/tests_library/network/alb_nlb_security.py +421 -0
- complio/tests_library/network/api_gateway_security.py +452 -0
- complio/tests_library/network/cloudfront_https.py +332 -0
- complio/tests_library/network/direct_connect_security.py +343 -0
- complio/tests_library/network/nacl_configuration.py +367 -0
- complio/tests_library/network/network_firewall.py +355 -0
- complio/tests_library/network/transit_gateway_security.py +318 -0
- complio/tests_library/network/vpc_endpoints_security.py +339 -0
- complio/tests_library/network/vpn_security.py +333 -0
- complio/tests_library/network/waf_configuration.py +428 -0
- complio/tests_library/security/__init__.py +0 -0
- complio/tests_library/security/kms_key_rotation.py +314 -0
- complio/tests_library/storage/__init__.py +0 -0
- complio/tests_library/storage/backup_encryption.py +288 -0
- complio/tests_library/storage/dynamodb_encryption.py +280 -0
- complio/tests_library/storage/efs_encryption.py +257 -0
- complio/tests_library/storage/elasticache_encryption.py +370 -0
- complio/tests_library/storage/redshift_encryption.py +252 -0
- complio/tests_library/storage/s3_versioning.py +264 -0
- complio/utils/__init__.py +26 -0
- complio/utils/errors.py +179 -0
- complio/utils/exceptions.py +151 -0
- complio/utils/history.py +243 -0
- complio/utils/logger.py +391 -0
- complio-0.1.1.dist-info/METADATA +385 -0
- complio-0.1.1.dist-info/RECORD +79 -0
- complio-0.1.1.dist-info/WHEEL +4 -0
- complio-0.1.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Network ACL configuration compliance test.
|
|
3
|
+
|
|
4
|
+
Checks that Network ACLs follow security best practices for inbound/outbound rules.
|
|
5
|
+
|
|
6
|
+
ISO 27001 Control: A.8.20 - Networks security
|
|
7
|
+
Requirement: Network ACLs must have proper rule configuration and deny rules
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
11
|
+
>>> from complio.tests_library.network.nacl_configuration import NACLConfigurationTest
|
|
12
|
+
>>>
|
|
13
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
14
|
+
>>> connector.connect()
|
|
15
|
+
>>>
|
|
16
|
+
>>> test = NACLConfigurationTest(connector)
|
|
17
|
+
>>> result = test.run()
|
|
18
|
+
>>> print(f"Passed: {result.passed}, Score: {result.score}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict, List
|
|
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 NACLConfigurationTest(ComplianceTest):
|
|
35
|
+
"""Test for Network ACL configuration compliance.
|
|
36
|
+
|
|
37
|
+
Verifies that Network ACLs follow security best practices including:
|
|
38
|
+
- Not using default allow-all rules only
|
|
39
|
+
- Having explicit deny rules for security
|
|
40
|
+
- Proper rule numbering (not all 100, 200, etc.)
|
|
41
|
+
- No overly permissive rules (0.0.0.0/0 on all ports)
|
|
42
|
+
|
|
43
|
+
Compliance Requirements:
|
|
44
|
+
- NACLs should have custom rules beyond defaults
|
|
45
|
+
- Should include explicit deny rules
|
|
46
|
+
- Rule numbers should be properly spaced for maintainability
|
|
47
|
+
- Should not allow all traffic from anywhere
|
|
48
|
+
|
|
49
|
+
Scoring:
|
|
50
|
+
- 100% if all NACLs follow best practices
|
|
51
|
+
- Proportional score based on compliant/total ratio
|
|
52
|
+
- 100% if only default NACLs exist (no custom NACLs)
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> test = NACLConfigurationTest(connector)
|
|
56
|
+
>>> result = test.execute()
|
|
57
|
+
>>> for finding in result.findings:
|
|
58
|
+
... print(f"{finding.resource_id}: {finding.title}")
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, connector: AWSConnector) -> None:
|
|
62
|
+
"""Initialize Network ACL configuration test.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
connector: AWS connector instance
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(
|
|
68
|
+
test_id="nacl_configuration",
|
|
69
|
+
test_name="Network ACL Configuration Check",
|
|
70
|
+
description="Verify Network ACLs follow security best practices",
|
|
71
|
+
control_id="A.8.20",
|
|
72
|
+
connector=connector,
|
|
73
|
+
scope="regional",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def _check_nacl_rules(self, entries: List[Dict]) -> tuple:
|
|
77
|
+
"""Check NACL rules for security issues.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
entries: List of NACL entries
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Tuple of (has_issues, issues_list)
|
|
84
|
+
"""
|
|
85
|
+
issues = []
|
|
86
|
+
|
|
87
|
+
# Check if only default rules exist (32767 is default rule number)
|
|
88
|
+
non_default_rules = [e for e in entries if e.get("RuleNumber", 32767) != 32767]
|
|
89
|
+
if len(non_default_rules) == 0:
|
|
90
|
+
issues.append("Only default rules present (no custom rules)")
|
|
91
|
+
|
|
92
|
+
# Check for overly permissive rules
|
|
93
|
+
for entry in entries:
|
|
94
|
+
rule_number = entry.get("RuleNumber")
|
|
95
|
+
rule_action = entry.get("RuleAction")
|
|
96
|
+
cidr_block = entry.get("CidrBlock", "")
|
|
97
|
+
protocol = entry.get("Protocol", "-1")
|
|
98
|
+
|
|
99
|
+
# Skip default deny-all rule
|
|
100
|
+
if rule_number == 32767:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Check for allow-all rules (0.0.0.0/0 with protocol -1 or all ports)
|
|
104
|
+
if rule_action == "allow" and cidr_block == "0.0.0.0/0":
|
|
105
|
+
# Protocol -1 means all protocols
|
|
106
|
+
if protocol == "-1":
|
|
107
|
+
issues.append(f"Rule {rule_number} allows all traffic from anywhere")
|
|
108
|
+
else:
|
|
109
|
+
# Check if port range is too wide
|
|
110
|
+
port_range = entry.get("PortRange", {})
|
|
111
|
+
if port_range:
|
|
112
|
+
from_port = port_range.get("From", 0)
|
|
113
|
+
to_port = port_range.get("To", 0)
|
|
114
|
+
if from_port == 0 and to_port == 65535:
|
|
115
|
+
issues.append(f"Rule {rule_number} allows all ports from anywhere")
|
|
116
|
+
|
|
117
|
+
# Check if there are any explicit deny rules (security best practice)
|
|
118
|
+
deny_rules = [e for e in entries if e.get("RuleAction") == "deny" and e.get("RuleNumber") != 32767]
|
|
119
|
+
if len(deny_rules) == 0 and len(non_default_rules) > 0:
|
|
120
|
+
issues.append("No explicit deny rules (only relying on default deny)")
|
|
121
|
+
|
|
122
|
+
# Check rule numbering (should have spacing for future rules)
|
|
123
|
+
rule_numbers = sorted([e.get("RuleNumber") for e in non_default_rules])
|
|
124
|
+
if len(rule_numbers) > 1:
|
|
125
|
+
consecutive_rules = 0
|
|
126
|
+
for i in range(len(rule_numbers) - 1):
|
|
127
|
+
if rule_numbers[i+1] - rule_numbers[i] == 1:
|
|
128
|
+
consecutive_rules += 1
|
|
129
|
+
|
|
130
|
+
if consecutive_rules > 0:
|
|
131
|
+
issues.append("Consecutive rule numbers detected (should use spacing like 100, 200, 300)")
|
|
132
|
+
|
|
133
|
+
return len(issues) > 0, issues
|
|
134
|
+
|
|
135
|
+
def execute(self) -> TestResult:
|
|
136
|
+
"""Execute Network ACL configuration compliance test.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
TestResult with findings for misconfigured NACLs
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
>>> test = NACLConfigurationTest(connector)
|
|
143
|
+
>>> result = test.execute()
|
|
144
|
+
>>> print(result.score)
|
|
145
|
+
85.0
|
|
146
|
+
"""
|
|
147
|
+
result = TestResult(
|
|
148
|
+
test_id=self.test_id,
|
|
149
|
+
test_name=self.test_name,
|
|
150
|
+
status=TestStatus.PASSED,
|
|
151
|
+
passed=True,
|
|
152
|
+
score=100.0,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# Get EC2 client
|
|
157
|
+
ec2_client = self.connector.get_client("ec2")
|
|
158
|
+
|
|
159
|
+
# List all Network ACLs
|
|
160
|
+
self.logger.info("listing_network_acls")
|
|
161
|
+
nacls_response = ec2_client.describe_network_acls()
|
|
162
|
+
nacls = nacls_response.get("NetworkAcls", [])
|
|
163
|
+
|
|
164
|
+
if not nacls:
|
|
165
|
+
self.logger.info("no_network_acls_found")
|
|
166
|
+
result.metadata["message"] = "No Network ACLs found in region"
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
self.logger.info("network_acls_found", count=len(nacls))
|
|
170
|
+
|
|
171
|
+
# Check configuration for each NACL
|
|
172
|
+
properly_configured_count = 0
|
|
173
|
+
default_nacls_count = 0
|
|
174
|
+
|
|
175
|
+
for nacl in nacls:
|
|
176
|
+
nacl_id = nacl["NetworkAclId"]
|
|
177
|
+
is_default = nacl.get("IsDefault", False)
|
|
178
|
+
vpc_id = nacl.get("VpcId")
|
|
179
|
+
entries = nacl.get("Entries", [])
|
|
180
|
+
|
|
181
|
+
result.resources_scanned += 1
|
|
182
|
+
|
|
183
|
+
# Skip default NACLs (they are expected to have default rules)
|
|
184
|
+
if is_default:
|
|
185
|
+
default_nacls_count += 1
|
|
186
|
+
self.logger.debug("skipping_default_nacl", nacl_id=nacl_id)
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
# Check rules for security issues
|
|
190
|
+
has_issues, issues_list = self._check_nacl_rules(entries)
|
|
191
|
+
|
|
192
|
+
# Create evidence
|
|
193
|
+
evidence = self.create_evidence(
|
|
194
|
+
resource_id=nacl_id,
|
|
195
|
+
resource_type="network_acl",
|
|
196
|
+
data={
|
|
197
|
+
"nacl_id": nacl_id,
|
|
198
|
+
"vpc_id": vpc_id,
|
|
199
|
+
"is_default": is_default,
|
|
200
|
+
"entry_count": len(entries),
|
|
201
|
+
"has_issues": has_issues,
|
|
202
|
+
"issues": issues_list,
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
result.add_evidence(evidence)
|
|
206
|
+
|
|
207
|
+
if not has_issues:
|
|
208
|
+
properly_configured_count += 1
|
|
209
|
+
self.logger.debug(
|
|
210
|
+
"nacl_properly_configured",
|
|
211
|
+
nacl_id=nacl_id
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
# Determine severity based on issues
|
|
215
|
+
severity = Severity.HIGH if any("all traffic" in issue or "all ports" in issue for issue in issues_list) else Severity.MEDIUM
|
|
216
|
+
|
|
217
|
+
# Create finding for misconfigured NACL
|
|
218
|
+
finding = self.create_finding(
|
|
219
|
+
resource_id=nacl_id,
|
|
220
|
+
resource_type="network_acl",
|
|
221
|
+
severity=severity,
|
|
222
|
+
title="Network ACL has configuration issues",
|
|
223
|
+
description=f"Network ACL '{nacl_id}' in VPC '{vpc_id}' has security configuration "
|
|
224
|
+
f"issues: {'; '.join(issues_list)}. Network ACLs should follow security "
|
|
225
|
+
"best practices including proper rule numbering, explicit deny rules, and "
|
|
226
|
+
"avoiding overly permissive allow rules. ISO 27001 A.8.20 requires proper "
|
|
227
|
+
"network security controls.",
|
|
228
|
+
remediation=(
|
|
229
|
+
f"Improve Network ACL '{nacl_id}' configuration:\n\n"
|
|
230
|
+
"Best practices:\n"
|
|
231
|
+
"1. Use rule number spacing (100, 200, 300) for future flexibility\n"
|
|
232
|
+
"2. Add explicit deny rules for known bad traffic patterns\n"
|
|
233
|
+
"3. Avoid allowing all traffic from 0.0.0.0/0 on all ports\n"
|
|
234
|
+
"4. Use specific port ranges instead of all ports\n"
|
|
235
|
+
"5. Document each rule's purpose using tags\n\n"
|
|
236
|
+
"Example of good NACL configuration:\n"
|
|
237
|
+
"Inbound Rules:\n"
|
|
238
|
+
"- Rule 100: Allow HTTP (80) from 0.0.0.0/0\n"
|
|
239
|
+
"- Rule 110: Allow HTTPS (443) from 0.0.0.0/0\n"
|
|
240
|
+
"- Rule 120: Allow SSH (22) from 10.0.0.0/8 only\n"
|
|
241
|
+
"- Rule 900: Deny all from known bad IP ranges\n"
|
|
242
|
+
"- Rule 32767: Deny all (default)\n\n"
|
|
243
|
+
"Outbound Rules:\n"
|
|
244
|
+
"- Rule 100: Allow HTTP (80) to 0.0.0.0/0\n"
|
|
245
|
+
"- Rule 110: Allow HTTPS (443) to 0.0.0.0/0\n"
|
|
246
|
+
"- Rule 120: Allow ephemeral ports (1024-65535)\n"
|
|
247
|
+
"- Rule 32767: Deny all (default)\n\n"
|
|
248
|
+
"To modify NACL rules using AWS CLI:\n"
|
|
249
|
+
f"# Create a new allow rule\n"
|
|
250
|
+
f"aws ec2 create-network-acl-entry \\\n"
|
|
251
|
+
f" --network-acl-id {nacl_id} \\\n"
|
|
252
|
+
" --rule-number 100 \\\n"
|
|
253
|
+
" --protocol tcp \\\n"
|
|
254
|
+
" --port-range From=443,To=443 \\\n"
|
|
255
|
+
" --cidr-block 0.0.0.0/0 \\\n"
|
|
256
|
+
" --rule-action allow \\\n"
|
|
257
|
+
" --ingress\n\n"
|
|
258
|
+
"# Create a deny rule for bad traffic\n"
|
|
259
|
+
f"aws ec2 create-network-acl-entry \\\n"
|
|
260
|
+
f" --network-acl-id {nacl_id} \\\n"
|
|
261
|
+
" --rule-number 900 \\\n"
|
|
262
|
+
" --protocol -1 \\\n"
|
|
263
|
+
" --cidr-block <BAD-IP-RANGE> \\\n"
|
|
264
|
+
" --rule-action deny \\\n"
|
|
265
|
+
" --ingress\n\n"
|
|
266
|
+
"Or use AWS Console:\n"
|
|
267
|
+
"1. Go to VPC console → Network ACLs\n"
|
|
268
|
+
f"2. Select NACL '{nacl_id}'\n"
|
|
269
|
+
"3. Edit inbound/outbound rules\n"
|
|
270
|
+
"4. Add specific allow rules with proper spacing\n"
|
|
271
|
+
"5. Add explicit deny rules for known threats\n"
|
|
272
|
+
"6. Remove overly permissive rules\n\n"
|
|
273
|
+
"Security considerations:\n"
|
|
274
|
+
"- NACLs are stateless (need both inbound and outbound rules)\n"
|
|
275
|
+
"- Lower rule numbers are evaluated first\n"
|
|
276
|
+
"- Use Security Groups for instance-level filtering\n"
|
|
277
|
+
"- Use NACLs for subnet-level defense in depth\n"
|
|
278
|
+
"- Regularly review and audit NACL rules\n"
|
|
279
|
+
"- Use VPC Flow Logs to monitor traffic patterns"
|
|
280
|
+
),
|
|
281
|
+
evidence=evidence
|
|
282
|
+
)
|
|
283
|
+
result.add_finding(finding)
|
|
284
|
+
|
|
285
|
+
self.logger.warning(
|
|
286
|
+
"nacl_configuration_issues",
|
|
287
|
+
nacl_id=nacl_id,
|
|
288
|
+
issues=issues_list
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Calculate score based on custom NACLs only
|
|
292
|
+
custom_nacls_count = len(nacls) - default_nacls_count
|
|
293
|
+
|
|
294
|
+
if custom_nacls_count == 0:
|
|
295
|
+
# No custom NACLs, only defaults - this is acceptable
|
|
296
|
+
result.metadata["message"] = f"Only default NACLs found ({default_nacls_count} default NACLs)"
|
|
297
|
+
result.score = 100.0
|
|
298
|
+
result.passed = True
|
|
299
|
+
else:
|
|
300
|
+
# Calculate compliance score
|
|
301
|
+
result.score = (properly_configured_count / custom_nacls_count) * 100
|
|
302
|
+
result.passed = properly_configured_count == custom_nacls_count
|
|
303
|
+
|
|
304
|
+
result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
|
|
305
|
+
|
|
306
|
+
# Add metadata
|
|
307
|
+
result.metadata = {
|
|
308
|
+
"total_nacls": len(nacls),
|
|
309
|
+
"default_nacls": default_nacls_count,
|
|
310
|
+
"custom_nacls": custom_nacls_count,
|
|
311
|
+
"properly_configured": properly_configured_count,
|
|
312
|
+
"misconfigured": custom_nacls_count - properly_configured_count,
|
|
313
|
+
"compliance_percentage": result.score,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
self.logger.info(
|
|
317
|
+
"nacl_configuration_test_completed",
|
|
318
|
+
total_nacls=len(nacls),
|
|
319
|
+
custom_nacls=custom_nacls_count,
|
|
320
|
+
properly_configured=properly_configured_count,
|
|
321
|
+
score=result.score,
|
|
322
|
+
passed=result.passed
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
except ClientError as e:
|
|
326
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
327
|
+
self.logger.error("nacl_configuration_test_error", error_code=error_code, error=str(e))
|
|
328
|
+
result.status = TestStatus.ERROR
|
|
329
|
+
result.passed = False
|
|
330
|
+
result.score = 0.0
|
|
331
|
+
result.error_message = f"AWS API Error: {error_code} - {str(e)}"
|
|
332
|
+
|
|
333
|
+
except Exception as e:
|
|
334
|
+
self.logger.error("nacl_configuration_test_error", error=str(e))
|
|
335
|
+
result.status = TestStatus.ERROR
|
|
336
|
+
result.passed = False
|
|
337
|
+
result.score = 0.0
|
|
338
|
+
result.error_message = str(e)
|
|
339
|
+
|
|
340
|
+
return result
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# ============================================================================
|
|
344
|
+
# CONVENIENCE FUNCTION
|
|
345
|
+
# ============================================================================
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def run_nacl_configuration_test(connector: AWSConnector) -> TestResult:
|
|
349
|
+
"""Run Network ACL configuration compliance test.
|
|
350
|
+
|
|
351
|
+
Convenience function for running the test.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
connector: AWS connector
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
TestResult
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
>>> from complio.connectors.aws.client import AWSConnector
|
|
361
|
+
>>> connector = AWSConnector("production", "us-east-1")
|
|
362
|
+
>>> connector.connect()
|
|
363
|
+
>>> result = run_nacl_configuration_test(connector)
|
|
364
|
+
>>> print(f"Score: {result.score}%")
|
|
365
|
+
"""
|
|
366
|
+
test = NACLConfigurationTest(connector)
|
|
367
|
+
return test.execute()
|