regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/utils/app_utils.py +11 -2
- regscale/dev/cli.py +26 -0
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +15 -1
- regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/amazon/common.py +204 -0
- regscale/integrations/commercial/amazon/common.py +48 -58
- regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
- regscale/integrations/commercial/aws/cli.py +3093 -55
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +851 -206
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/scanner_integration.py +399 -84
- regscale/models/integration_models/cisa_kev_data.json +34 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/issue.py +2 -5
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/test_aws.py +55 -56
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"""Unit tests for AWS IAM collector."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from botocore.exceptions import ClientError
|
|
8
|
+
|
|
9
|
+
from regscale.integrations.commercial.aws.inventory.resources.iam import IAMCollector
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestIAMCollector(unittest.TestCase):
|
|
13
|
+
"""Test cases for IAMCollector."""
|
|
14
|
+
|
|
15
|
+
def setUp(self):
|
|
16
|
+
"""Set up test fixtures."""
|
|
17
|
+
self.mock_session = MagicMock()
|
|
18
|
+
self.region = "us-east-1"
|
|
19
|
+
self.account_id = "123456789012"
|
|
20
|
+
self.collector = IAMCollector(self.mock_session, self.region, self.account_id)
|
|
21
|
+
|
|
22
|
+
def test_init(self):
|
|
23
|
+
"""Test IAMCollector initialization."""
|
|
24
|
+
assert self.collector.session == self.mock_session
|
|
25
|
+
assert self.collector.region == self.region
|
|
26
|
+
assert self.collector.account_id == self.account_id
|
|
27
|
+
|
|
28
|
+
def test_init_without_account_id(self):
|
|
29
|
+
"""Test IAMCollector initialization without account ID."""
|
|
30
|
+
collector = IAMCollector(self.mock_session, self.region)
|
|
31
|
+
assert collector.account_id is None
|
|
32
|
+
|
|
33
|
+
@patch("regscale.integrations.commercial.aws.inventory.resources.iam.logger")
|
|
34
|
+
def test_collect_success(self, mock_logger):
|
|
35
|
+
"""Test successful collection of IAM resources."""
|
|
36
|
+
mock_client = MagicMock()
|
|
37
|
+
self.mock_session.client.return_value = mock_client
|
|
38
|
+
|
|
39
|
+
# Mock account summary
|
|
40
|
+
mock_client.get_account_summary.return_value = {"SummaryMap": {"Users": 5, "Roles": 10}}
|
|
41
|
+
|
|
42
|
+
# Mock password policy
|
|
43
|
+
mock_client.get_account_password_policy.return_value = {
|
|
44
|
+
"PasswordPolicy": {"MinimumPasswordLength": 14, "RequireSymbols": True}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Mock users
|
|
48
|
+
user_arn = f"arn:aws:iam::{self.account_id}:user/test-user"
|
|
49
|
+
mock_client.get_paginator.return_value.paginate.return_value = [
|
|
50
|
+
{"Users": [{"UserName": "test-user", "UserId": "AIDAI123", "Arn": user_arn, "CreateDate": "2024-01-01"}]}
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Mock access keys and MFA devices
|
|
54
|
+
mock_client.list_access_keys.return_value = {
|
|
55
|
+
"AccessKeyMetadata": [{"AccessKeyId": "AKIAI123", "Status": "Active", "CreateDate": "2024-01-01"}]
|
|
56
|
+
}
|
|
57
|
+
mock_client.list_mfa_devices.return_value = {
|
|
58
|
+
"MFADevices": [{"SerialNumber": "arn:aws:iam::123:mfa/device", "EnableDate": "2024-01-01"}]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
result = self.collector.collect()
|
|
62
|
+
|
|
63
|
+
assert "Users" in result
|
|
64
|
+
assert "Roles" in result
|
|
65
|
+
assert "Groups" in result
|
|
66
|
+
assert "Policies" in result
|
|
67
|
+
assert "AccessKeys" in result
|
|
68
|
+
assert "MFADevices" in result
|
|
69
|
+
assert "AccountSummary" in result
|
|
70
|
+
assert "PasswordPolicy" in result
|
|
71
|
+
|
|
72
|
+
@patch("regscale.integrations.commercial.aws.inventory.resources.iam.logger")
|
|
73
|
+
def test_collect_handles_client_error(self, mock_logger):
|
|
74
|
+
"""Test collection handles ClientError."""
|
|
75
|
+
mock_client = MagicMock()
|
|
76
|
+
self.mock_session.client.return_value = mock_client
|
|
77
|
+
|
|
78
|
+
error_response = {"Error": {"Code": "InternalError", "Message": "Internal error"}}
|
|
79
|
+
mock_client.get_account_summary.side_effect = ClientError(error_response, "get_account_summary")
|
|
80
|
+
|
|
81
|
+
result = self.collector.collect()
|
|
82
|
+
|
|
83
|
+
assert result["AccountSummary"] == {}
|
|
84
|
+
|
|
85
|
+
@patch("regscale.integrations.commercial.aws.inventory.resources.iam.logger")
|
|
86
|
+
def test_collect_handles_unexpected_error(self, mock_logger):
|
|
87
|
+
"""Test collection handles unexpected errors."""
|
|
88
|
+
mock_client = MagicMock()
|
|
89
|
+
self.mock_session.client.return_value = mock_client
|
|
90
|
+
|
|
91
|
+
mock_client.get_account_summary.side_effect = Exception("Unexpected error")
|
|
92
|
+
|
|
93
|
+
self.collector.collect()
|
|
94
|
+
|
|
95
|
+
mock_logger.error.assert_called()
|
|
96
|
+
|
|
97
|
+
def test_get_account_summary_success(self):
|
|
98
|
+
"""Test successful account summary retrieval."""
|
|
99
|
+
mock_client = MagicMock()
|
|
100
|
+
mock_client.get_account_summary.return_value = {"SummaryMap": {"Users": 5, "Roles": 10}}
|
|
101
|
+
|
|
102
|
+
result = self.collector._get_account_summary(mock_client)
|
|
103
|
+
|
|
104
|
+
assert result["Users"] == 5
|
|
105
|
+
assert result["Roles"] == 10
|
|
106
|
+
assert result["Region"] == self.region
|
|
107
|
+
|
|
108
|
+
def test_get_account_summary_access_denied(self):
|
|
109
|
+
"""Test account summary with access denied."""
|
|
110
|
+
mock_client = MagicMock()
|
|
111
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
112
|
+
mock_client.get_account_summary.side_effect = ClientError(error_response, "get_account_summary")
|
|
113
|
+
|
|
114
|
+
result = self.collector._get_account_summary(mock_client)
|
|
115
|
+
|
|
116
|
+
assert result == {}
|
|
117
|
+
|
|
118
|
+
def test_get_password_policy_success(self):
|
|
119
|
+
"""Test successful password policy retrieval."""
|
|
120
|
+
mock_client = MagicMock()
|
|
121
|
+
mock_client.get_account_password_policy.return_value = {
|
|
122
|
+
"PasswordPolicy": {"MinimumPasswordLength": 14, "RequireSymbols": True}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
result = self.collector._get_password_policy(mock_client)
|
|
126
|
+
|
|
127
|
+
assert result["MinimumPasswordLength"] == 14
|
|
128
|
+
assert result["RequireSymbols"] is True
|
|
129
|
+
assert result["Region"] == self.region
|
|
130
|
+
|
|
131
|
+
def test_get_password_policy_no_policy(self):
|
|
132
|
+
"""Test password policy when no policy exists."""
|
|
133
|
+
mock_client = MagicMock()
|
|
134
|
+
error_response = {"Error": {"Code": "NoSuchEntity", "Message": "No policy"}}
|
|
135
|
+
mock_client.get_account_password_policy.side_effect = ClientError(error_response, "get_account_password_policy")
|
|
136
|
+
|
|
137
|
+
result = self.collector._get_password_policy(mock_client)
|
|
138
|
+
|
|
139
|
+
assert result == {}
|
|
140
|
+
|
|
141
|
+
def test_list_users_success(self):
|
|
142
|
+
"""Test successful users listing."""
|
|
143
|
+
mock_client = MagicMock()
|
|
144
|
+
user_arn = f"arn:aws:iam::{self.account_id}:user/test-user"
|
|
145
|
+
mock_paginator = MagicMock()
|
|
146
|
+
mock_paginator.paginate.return_value = [
|
|
147
|
+
{"Users": [{"UserName": "test-user", "UserId": "AIDAI123", "Arn": user_arn, "CreateDate": "2024-01-01"}]}
|
|
148
|
+
]
|
|
149
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
150
|
+
|
|
151
|
+
result = self.collector._list_users(mock_client)
|
|
152
|
+
|
|
153
|
+
assert len(result) == 1
|
|
154
|
+
assert result[0]["UserName"] == "test-user"
|
|
155
|
+
assert result[0]["Region"] == self.region
|
|
156
|
+
|
|
157
|
+
def test_list_users_filters_by_account_id(self):
|
|
158
|
+
"""Test users listing filters by account ID."""
|
|
159
|
+
mock_client = MagicMock()
|
|
160
|
+
user_arn_match = f"arn:aws:iam::{self.account_id}:user/test-user"
|
|
161
|
+
user_arn_no_match = "arn:aws:iam::999999999999:user/other-user"
|
|
162
|
+
|
|
163
|
+
mock_paginator = MagicMock()
|
|
164
|
+
mock_paginator.paginate.return_value = [
|
|
165
|
+
{
|
|
166
|
+
"Users": [
|
|
167
|
+
{"UserName": "test-user", "UserId": "AIDAI123", "Arn": user_arn_match, "CreateDate": "2024-01-01"},
|
|
168
|
+
{
|
|
169
|
+
"UserName": "other-user",
|
|
170
|
+
"UserId": "AIDAI456",
|
|
171
|
+
"Arn": user_arn_no_match,
|
|
172
|
+
"CreateDate": "2024-01-01",
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
178
|
+
|
|
179
|
+
result = self.collector._list_users(mock_client)
|
|
180
|
+
|
|
181
|
+
assert len(result) == 1
|
|
182
|
+
assert result[0]["UserName"] == "test-user"
|
|
183
|
+
|
|
184
|
+
def test_list_users_access_denied(self):
|
|
185
|
+
"""Test users listing with access denied."""
|
|
186
|
+
mock_client = MagicMock()
|
|
187
|
+
mock_paginator = MagicMock()
|
|
188
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
189
|
+
mock_paginator.paginate.side_effect = ClientError(error_response, "list_users")
|
|
190
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
191
|
+
|
|
192
|
+
result = self.collector._list_users(mock_client)
|
|
193
|
+
|
|
194
|
+
assert result == []
|
|
195
|
+
|
|
196
|
+
def test_list_roles_success(self):
|
|
197
|
+
"""Test successful roles listing."""
|
|
198
|
+
mock_client = MagicMock()
|
|
199
|
+
role_arn = f"arn:aws:iam::{self.account_id}:role/test-role"
|
|
200
|
+
mock_paginator = MagicMock()
|
|
201
|
+
mock_paginator.paginate.return_value = [
|
|
202
|
+
{"Roles": [{"RoleName": "test-role", "RoleId": "AIDAI123", "Arn": role_arn, "CreateDate": "2024-01-01"}]}
|
|
203
|
+
]
|
|
204
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
205
|
+
|
|
206
|
+
result = self.collector._list_roles(mock_client)
|
|
207
|
+
|
|
208
|
+
assert len(result) == 1
|
|
209
|
+
assert result[0]["RoleName"] == "test-role"
|
|
210
|
+
assert result[0]["Region"] == self.region
|
|
211
|
+
|
|
212
|
+
def test_list_roles_filters_by_account_id(self):
|
|
213
|
+
"""Test roles listing filters by account ID."""
|
|
214
|
+
mock_client = MagicMock()
|
|
215
|
+
role_arn_match = f"arn:aws:iam::{self.account_id}:role/test-role"
|
|
216
|
+
role_arn_no_match = "arn:aws:iam::999999999999:role/other-role"
|
|
217
|
+
|
|
218
|
+
mock_paginator = MagicMock()
|
|
219
|
+
mock_paginator.paginate.return_value = [
|
|
220
|
+
{
|
|
221
|
+
"Roles": [
|
|
222
|
+
{"RoleName": "test-role", "RoleId": "AIDAI123", "Arn": role_arn_match, "CreateDate": "2024-01-01"},
|
|
223
|
+
{
|
|
224
|
+
"RoleName": "other-role",
|
|
225
|
+
"RoleId": "AIDAI456",
|
|
226
|
+
"Arn": role_arn_no_match,
|
|
227
|
+
"CreateDate": "2024-01-01",
|
|
228
|
+
},
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
233
|
+
|
|
234
|
+
result = self.collector._list_roles(mock_client)
|
|
235
|
+
|
|
236
|
+
assert len(result) == 1
|
|
237
|
+
assert result[0]["RoleName"] == "test-role"
|
|
238
|
+
|
|
239
|
+
def test_list_roles_access_denied(self):
|
|
240
|
+
"""Test roles listing with access denied."""
|
|
241
|
+
mock_client = MagicMock()
|
|
242
|
+
mock_paginator = MagicMock()
|
|
243
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
244
|
+
mock_paginator.paginate.side_effect = ClientError(error_response, "list_roles")
|
|
245
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
246
|
+
|
|
247
|
+
result = self.collector._list_roles(mock_client)
|
|
248
|
+
|
|
249
|
+
assert result == []
|
|
250
|
+
|
|
251
|
+
def test_list_groups_success(self):
|
|
252
|
+
"""Test successful groups listing."""
|
|
253
|
+
mock_client = MagicMock()
|
|
254
|
+
group_arn = f"arn:aws:iam::{self.account_id}:group/test-group"
|
|
255
|
+
mock_paginator = MagicMock()
|
|
256
|
+
mock_paginator.paginate.return_value = [
|
|
257
|
+
{
|
|
258
|
+
"Groups": [
|
|
259
|
+
{"GroupName": "test-group", "GroupId": "AIDAI123", "Arn": group_arn, "CreateDate": "2024-01-01"}
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
264
|
+
|
|
265
|
+
result = self.collector._list_groups(mock_client)
|
|
266
|
+
|
|
267
|
+
assert len(result) == 1
|
|
268
|
+
assert result[0]["GroupName"] == "test-group"
|
|
269
|
+
assert result[0]["Region"] == self.region
|
|
270
|
+
|
|
271
|
+
def test_list_groups_filters_by_account_id(self):
|
|
272
|
+
"""Test groups listing filters by account ID."""
|
|
273
|
+
mock_client = MagicMock()
|
|
274
|
+
group_arn_match = f"arn:aws:iam::{self.account_id}:group/test-group"
|
|
275
|
+
group_arn_no_match = "arn:aws:iam::999999999999:group/other-group"
|
|
276
|
+
|
|
277
|
+
mock_paginator = MagicMock()
|
|
278
|
+
mock_paginator.paginate.return_value = [
|
|
279
|
+
{
|
|
280
|
+
"Groups": [
|
|
281
|
+
{
|
|
282
|
+
"GroupName": "test-group",
|
|
283
|
+
"GroupId": "AIDAI123",
|
|
284
|
+
"Arn": group_arn_match,
|
|
285
|
+
"CreateDate": "2024-01-01",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"GroupName": "other-group",
|
|
289
|
+
"GroupId": "AIDAI456",
|
|
290
|
+
"Arn": group_arn_no_match,
|
|
291
|
+
"CreateDate": "2024-01-01",
|
|
292
|
+
},
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
297
|
+
|
|
298
|
+
result = self.collector._list_groups(mock_client)
|
|
299
|
+
|
|
300
|
+
assert len(result) == 1
|
|
301
|
+
assert result[0]["GroupName"] == "test-group"
|
|
302
|
+
|
|
303
|
+
def test_list_groups_access_denied(self):
|
|
304
|
+
"""Test groups listing with access denied."""
|
|
305
|
+
mock_client = MagicMock()
|
|
306
|
+
mock_paginator = MagicMock()
|
|
307
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
308
|
+
mock_paginator.paginate.side_effect = ClientError(error_response, "list_groups")
|
|
309
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
310
|
+
|
|
311
|
+
result = self.collector._list_groups(mock_client)
|
|
312
|
+
|
|
313
|
+
assert result == []
|
|
314
|
+
|
|
315
|
+
def test_list_policies_success(self):
|
|
316
|
+
"""Test successful policies listing."""
|
|
317
|
+
mock_client = MagicMock()
|
|
318
|
+
policy_arn = f"arn:aws:iam::{self.account_id}:policy/test-policy"
|
|
319
|
+
mock_paginator = MagicMock()
|
|
320
|
+
mock_paginator.paginate.return_value = [
|
|
321
|
+
{
|
|
322
|
+
"Policies": [
|
|
323
|
+
{
|
|
324
|
+
"PolicyName": "test-policy",
|
|
325
|
+
"PolicyId": "ANPAI123",
|
|
326
|
+
"Arn": policy_arn,
|
|
327
|
+
"CreateDate": "2024-01-01",
|
|
328
|
+
"UpdateDate": "2024-01-01",
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
334
|
+
|
|
335
|
+
result = self.collector._list_policies(mock_client)
|
|
336
|
+
|
|
337
|
+
assert len(result) == 1
|
|
338
|
+
assert result[0]["PolicyName"] == "test-policy"
|
|
339
|
+
assert result[0]["Region"] == self.region
|
|
340
|
+
|
|
341
|
+
def test_list_policies_filters_by_account_id(self):
|
|
342
|
+
"""Test policies listing filters by account ID."""
|
|
343
|
+
mock_client = MagicMock()
|
|
344
|
+
policy_arn_match = f"arn:aws:iam::{self.account_id}:policy/test-policy"
|
|
345
|
+
policy_arn_no_match = "arn:aws:iam::999999999999:policy/other-policy"
|
|
346
|
+
|
|
347
|
+
mock_paginator = MagicMock()
|
|
348
|
+
mock_paginator.paginate.return_value = [
|
|
349
|
+
{
|
|
350
|
+
"Policies": [
|
|
351
|
+
{
|
|
352
|
+
"PolicyName": "test-policy",
|
|
353
|
+
"PolicyId": "ANPAI123",
|
|
354
|
+
"Arn": policy_arn_match,
|
|
355
|
+
"CreateDate": "2024-01-01",
|
|
356
|
+
"UpdateDate": "2024-01-01",
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"PolicyName": "other-policy",
|
|
360
|
+
"PolicyId": "ANPAI456",
|
|
361
|
+
"Arn": policy_arn_no_match,
|
|
362
|
+
"CreateDate": "2024-01-01",
|
|
363
|
+
"UpdateDate": "2024-01-01",
|
|
364
|
+
},
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
]
|
|
368
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
369
|
+
|
|
370
|
+
result = self.collector._list_policies(mock_client)
|
|
371
|
+
|
|
372
|
+
assert len(result) == 1
|
|
373
|
+
assert result[0]["PolicyName"] == "test-policy"
|
|
374
|
+
|
|
375
|
+
def test_list_policies_access_denied(self):
|
|
376
|
+
"""Test policies listing with access denied."""
|
|
377
|
+
mock_client = MagicMock()
|
|
378
|
+
mock_paginator = MagicMock()
|
|
379
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
380
|
+
mock_paginator.paginate.side_effect = ClientError(error_response, "list_policies")
|
|
381
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
382
|
+
|
|
383
|
+
result = self.collector._list_policies(mock_client)
|
|
384
|
+
|
|
385
|
+
assert result == []
|
|
386
|
+
|
|
387
|
+
def test_list_access_keys_success(self):
|
|
388
|
+
"""Test successful access keys listing."""
|
|
389
|
+
mock_client = MagicMock()
|
|
390
|
+
mock_client.list_access_keys.return_value = {
|
|
391
|
+
"AccessKeyMetadata": [{"AccessKeyId": "AKIAI123", "Status": "Active", "CreateDate": "2024-01-01"}]
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
result = self.collector._list_access_keys(mock_client, "test-user")
|
|
395
|
+
|
|
396
|
+
assert len(result) == 1
|
|
397
|
+
assert result[0]["AccessKeyId"] == "AKIAI123"
|
|
398
|
+
assert result[0]["UserName"] == "test-user"
|
|
399
|
+
assert result[0]["Region"] == self.region
|
|
400
|
+
|
|
401
|
+
def test_list_access_keys_access_denied(self):
|
|
402
|
+
"""Test access keys listing with access denied."""
|
|
403
|
+
mock_client = MagicMock()
|
|
404
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
405
|
+
mock_client.list_access_keys.side_effect = ClientError(error_response, "list_access_keys")
|
|
406
|
+
|
|
407
|
+
result = self.collector._list_access_keys(mock_client, "test-user")
|
|
408
|
+
|
|
409
|
+
assert result == []
|
|
410
|
+
|
|
411
|
+
def test_list_mfa_devices_success(self):
|
|
412
|
+
"""Test successful MFA devices listing."""
|
|
413
|
+
mock_client = MagicMock()
|
|
414
|
+
mock_client.list_mfa_devices.return_value = {
|
|
415
|
+
"MFADevices": [{"SerialNumber": "arn:aws:iam::123:mfa/device", "EnableDate": "2024-01-01"}]
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
result = self.collector._list_mfa_devices(mock_client, "test-user")
|
|
419
|
+
|
|
420
|
+
assert len(result) == 1
|
|
421
|
+
assert result[0]["SerialNumber"] == "arn:aws:iam::123:mfa/device"
|
|
422
|
+
assert result[0]["UserName"] == "test-user"
|
|
423
|
+
assert result[0]["Region"] == self.region
|
|
424
|
+
|
|
425
|
+
def test_list_mfa_devices_access_denied(self):
|
|
426
|
+
"""Test MFA devices listing with access denied."""
|
|
427
|
+
mock_client = MagicMock()
|
|
428
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
429
|
+
mock_client.list_mfa_devices.side_effect = ClientError(error_response, "list_mfa_devices")
|
|
430
|
+
|
|
431
|
+
result = self.collector._list_mfa_devices(mock_client, "test-user")
|
|
432
|
+
|
|
433
|
+
assert result == []
|
|
434
|
+
|
|
435
|
+
def test_matches_account_id_with_matching_arn(self):
|
|
436
|
+
"""Test account ID matching with matching ARN."""
|
|
437
|
+
arn = f"arn:aws:iam::{self.account_id}:user/test-user"
|
|
438
|
+
assert self.collector._matches_account_id(arn) is True
|
|
439
|
+
|
|
440
|
+
def test_matches_account_id_with_non_matching_arn(self):
|
|
441
|
+
"""Test account ID matching with non-matching ARN."""
|
|
442
|
+
arn = "arn:aws:iam::999999999999:user/test-user"
|
|
443
|
+
assert self.collector._matches_account_id(arn) is False
|
|
444
|
+
|
|
445
|
+
def test_matches_account_id_with_invalid_arn(self):
|
|
446
|
+
"""Test account ID matching with invalid ARN."""
|
|
447
|
+
arn = "invalid-arn"
|
|
448
|
+
assert self.collector._matches_account_id(arn) is False
|
|
449
|
+
|
|
450
|
+
def test_matches_account_id_without_filter(self):
|
|
451
|
+
"""Test account ID matching without account filter."""
|
|
452
|
+
collector = IAMCollector(self.mock_session, self.region)
|
|
453
|
+
arn = "arn:aws:iam::999999999999:user/test-user"
|
|
454
|
+
assert collector._matches_account_id(arn) is True
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
if __name__ == "__main__":
|
|
458
|
+
pytest.main([__file__, "-v"])
|