regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.1.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/commercial/synqly/ticketing.py +27 -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 +65 -5
- 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.1.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/RECORD +113 -34
- 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.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Unit tests for AWS Session Token Manager."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from unittest.mock import MagicMock, Mock, patch, mock_open
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from regscale.integrations.commercial.aws.session_manager import AWSSessionManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestAWSSessionManager:
|
|
17
|
+
"""Test suite for AWS Session Manager."""
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def temp_cache_dir(self, tmp_path):
|
|
21
|
+
"""Create a temporary cache directory for testing."""
|
|
22
|
+
cache_dir = tmp_path / "aws_sessions"
|
|
23
|
+
cache_dir.mkdir()
|
|
24
|
+
return str(cache_dir)
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def session_manager(self, temp_cache_dir):
|
|
28
|
+
"""Create a session manager with temporary cache directory."""
|
|
29
|
+
return AWSSessionManager(cache_dir=temp_cache_dir)
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def mock_credentials(self):
|
|
33
|
+
"""Create mock AWS credentials response."""
|
|
34
|
+
expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
35
|
+
return {
|
|
36
|
+
"aws_access_key_id": "ASIAIOSFODNN7EXAMPLE",
|
|
37
|
+
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
38
|
+
"aws_session_token": "FwoGZXIvYXdzEBYaD...",
|
|
39
|
+
"expiration": expiration.isoformat(),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def test_init_creates_cache_directory(self, tmp_path):
|
|
43
|
+
"""Test that __init__ creates cache directory if it doesn't exist."""
|
|
44
|
+
cache_dir = tmp_path / "test_cache"
|
|
45
|
+
assert not cache_dir.exists()
|
|
46
|
+
|
|
47
|
+
manager = AWSSessionManager(cache_dir=str(cache_dir))
|
|
48
|
+
|
|
49
|
+
assert cache_dir.exists()
|
|
50
|
+
assert manager.cache_dir == cache_dir
|
|
51
|
+
|
|
52
|
+
def test_init_default_cache_directory(self):
|
|
53
|
+
"""Test that __init__ uses default cache directory."""
|
|
54
|
+
manager = AWSSessionManager()
|
|
55
|
+
|
|
56
|
+
expected_path = Path.home() / ".regscale" / "aws_sessions"
|
|
57
|
+
assert manager.cache_dir == expected_path
|
|
58
|
+
|
|
59
|
+
@patch("os.chmod")
|
|
60
|
+
@patch("os.name", "posix")
|
|
61
|
+
def test_init_sets_directory_permissions_unix(self, mock_chmod, temp_cache_dir):
|
|
62
|
+
"""Test that __init__ sets restrictive permissions on Unix systems."""
|
|
63
|
+
manager = AWSSessionManager(cache_dir=temp_cache_dir)
|
|
64
|
+
|
|
65
|
+
# Verify chmod was called with owner-only permissions (0o700)
|
|
66
|
+
mock_chmod.assert_called_with(manager.cache_dir, 0o700)
|
|
67
|
+
|
|
68
|
+
@patch("os.name", "nt")
|
|
69
|
+
def test_init_skips_permissions_windows(self, temp_cache_dir):
|
|
70
|
+
"""Test that __init__ skips chmod on Windows."""
|
|
71
|
+
# Should not raise an error on Windows
|
|
72
|
+
manager = AWSSessionManager(cache_dir=temp_cache_dir)
|
|
73
|
+
assert manager.cache_dir == Path(temp_cache_dir)
|
|
74
|
+
|
|
75
|
+
def test_get_session_token_with_profile(self, session_manager):
|
|
76
|
+
"""Test getting session token using AWS profile."""
|
|
77
|
+
expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
78
|
+
mock_response = {
|
|
79
|
+
"Credentials": {
|
|
80
|
+
"AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
|
|
81
|
+
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
82
|
+
"SessionToken": "FwoGZXIvYXdzEBYaD...",
|
|
83
|
+
"Expiration": expiration,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
with patch("boto3.Session") as mock_session_class:
|
|
88
|
+
mock_session = MagicMock()
|
|
89
|
+
mock_sts_client = MagicMock()
|
|
90
|
+
mock_sts_client.get_session_token.return_value = mock_response
|
|
91
|
+
mock_session.client.return_value = mock_sts_client
|
|
92
|
+
mock_session_class.return_value = mock_session
|
|
93
|
+
|
|
94
|
+
credentials = session_manager.get_session_token(profile="test-profile", duration_seconds=3600)
|
|
95
|
+
|
|
96
|
+
# Verify boto3 Session was created with profile
|
|
97
|
+
mock_session_class.assert_called_once_with(profile_name="test-profile")
|
|
98
|
+
|
|
99
|
+
# Verify credentials were returned correctly
|
|
100
|
+
assert credentials["aws_access_key_id"] == "ASIAIOSFODNN7EXAMPLE"
|
|
101
|
+
assert credentials["aws_secret_access_key"] == "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
102
|
+
assert credentials["aws_session_token"] == "FwoGZXIvYXdzEBYaD..."
|
|
103
|
+
assert "expiration" in credentials
|
|
104
|
+
|
|
105
|
+
def test_get_session_token_with_explicit_credentials(self, session_manager):
|
|
106
|
+
"""Test getting session token using explicit AWS credentials."""
|
|
107
|
+
expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
108
|
+
mock_response = {
|
|
109
|
+
"Credentials": {
|
|
110
|
+
"AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
|
|
111
|
+
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
112
|
+
"SessionToken": "FwoGZXIvYXdzEBYaD...",
|
|
113
|
+
"Expiration": expiration,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
with patch("boto3.Session") as mock_session_class:
|
|
118
|
+
mock_session = MagicMock()
|
|
119
|
+
mock_sts_client = MagicMock()
|
|
120
|
+
mock_sts_client.get_session_token.return_value = mock_response
|
|
121
|
+
mock_session.client.return_value = mock_sts_client
|
|
122
|
+
mock_session_class.return_value = mock_session
|
|
123
|
+
|
|
124
|
+
credentials = session_manager.get_session_token(
|
|
125
|
+
aws_access_key_id="AKIAIOSFODNN7EXAMPLE",
|
|
126
|
+
aws_secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
127
|
+
duration_seconds=7200,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Verify boto3 Session was created with explicit credentials
|
|
131
|
+
mock_session_class.assert_called_once_with(
|
|
132
|
+
aws_access_key_id="AKIAIOSFODNN7EXAMPLE",
|
|
133
|
+
aws_secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Verify get_session_token was called with correct duration
|
|
137
|
+
mock_sts_client.get_session_token.assert_called_once_with(DurationSeconds=7200)
|
|
138
|
+
|
|
139
|
+
assert credentials["aws_access_key_id"] == "ASIAIOSFODNN7EXAMPLE"
|
|
140
|
+
|
|
141
|
+
def test_get_session_token_with_mfa(self, session_manager):
|
|
142
|
+
"""Test getting session token with MFA."""
|
|
143
|
+
expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
144
|
+
mock_response = {
|
|
145
|
+
"Credentials": {
|
|
146
|
+
"AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
|
|
147
|
+
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
148
|
+
"SessionToken": "FwoGZXIvYXdzEBYaD...",
|
|
149
|
+
"Expiration": expiration,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
with patch("boto3.Session") as mock_session_class:
|
|
154
|
+
mock_session = MagicMock()
|
|
155
|
+
mock_sts_client = MagicMock()
|
|
156
|
+
mock_sts_client.get_session_token.return_value = mock_response
|
|
157
|
+
mock_session.client.return_value = mock_sts_client
|
|
158
|
+
mock_session_class.return_value = mock_session
|
|
159
|
+
|
|
160
|
+
credentials = session_manager.get_session_token(
|
|
161
|
+
profile="test-profile",
|
|
162
|
+
mfa_serial="arn:aws:iam::123456789012:mfa/user",
|
|
163
|
+
mfa_code="123456",
|
|
164
|
+
duration_seconds=3600,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Verify get_session_token was called with MFA parameters
|
|
168
|
+
mock_sts_client.get_session_token.assert_called_once_with(
|
|
169
|
+
SerialNumber="arn:aws:iam::123456789012:mfa/user", TokenCode="123456", DurationSeconds=3600
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
assert credentials["aws_access_key_id"] == "ASIAIOSFODNN7EXAMPLE"
|
|
173
|
+
|
|
174
|
+
def test_get_session_token_assume_role(self, session_manager):
|
|
175
|
+
"""Test assuming a role to get session token."""
|
|
176
|
+
expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
177
|
+
mock_response = {
|
|
178
|
+
"Credentials": {
|
|
179
|
+
"AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
|
|
180
|
+
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
181
|
+
"SessionToken": "FwoGZXIvYXdzEBYaD...",
|
|
182
|
+
"Expiration": expiration,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
with patch("boto3.Session") as mock_session_class:
|
|
187
|
+
mock_session = MagicMock()
|
|
188
|
+
mock_sts_client = MagicMock()
|
|
189
|
+
mock_sts_client.assume_role.return_value = mock_response
|
|
190
|
+
mock_session.client.return_value = mock_sts_client
|
|
191
|
+
mock_session_class.return_value = mock_session
|
|
192
|
+
|
|
193
|
+
credentials = session_manager.get_session_token(
|
|
194
|
+
profile="test-profile",
|
|
195
|
+
role_arn="arn:aws:iam::987654321098:role/TestRole",
|
|
196
|
+
role_session_name="test-session",
|
|
197
|
+
duration_seconds=3600,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Verify assume_role was called instead of get_session_token
|
|
201
|
+
mock_sts_client.assume_role.assert_called_once()
|
|
202
|
+
call_args = mock_sts_client.assume_role.call_args[1]
|
|
203
|
+
assert call_args["RoleArn"] == "arn:aws:iam::987654321098:role/TestRole"
|
|
204
|
+
assert call_args["RoleSessionName"] == "test-session"
|
|
205
|
+
assert call_args["DurationSeconds"] == 3600
|
|
206
|
+
|
|
207
|
+
assert credentials["aws_access_key_id"] == "ASIAIOSFODNN7EXAMPLE"
|
|
208
|
+
|
|
209
|
+
def test_get_session_token_assume_role_with_mfa(self, session_manager):
|
|
210
|
+
"""Test assuming a role with MFA."""
|
|
211
|
+
expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
212
|
+
mock_response = {
|
|
213
|
+
"Credentials": {
|
|
214
|
+
"AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
|
|
215
|
+
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
216
|
+
"SessionToken": "FwoGZXIvYXdzEBYaD...",
|
|
217
|
+
"Expiration": expiration,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
with patch("boto3.Session") as mock_session_class:
|
|
222
|
+
mock_session = MagicMock()
|
|
223
|
+
mock_sts_client = MagicMock()
|
|
224
|
+
mock_sts_client.assume_role.return_value = mock_response
|
|
225
|
+
mock_session.client.return_value = mock_sts_client
|
|
226
|
+
mock_session_class.return_value = mock_session
|
|
227
|
+
|
|
228
|
+
credentials = session_manager.get_session_token(
|
|
229
|
+
profile="test-profile",
|
|
230
|
+
role_arn="arn:aws:iam::987654321098:role/TestRole",
|
|
231
|
+
role_session_name="test-session",
|
|
232
|
+
mfa_serial="arn:aws:iam::123456789012:mfa/user",
|
|
233
|
+
mfa_code="123456",
|
|
234
|
+
duration_seconds=3600,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Verify assume_role was called with MFA parameters
|
|
238
|
+
call_args = mock_sts_client.assume_role.call_args[1]
|
|
239
|
+
assert call_args["SerialNumber"] == "arn:aws:iam::123456789012:mfa/user"
|
|
240
|
+
assert call_args["TokenCode"] == "123456"
|
|
241
|
+
|
|
242
|
+
assert credentials["aws_access_key_id"] == "ASIAIOSFODNN7EXAMPLE"
|
|
243
|
+
|
|
244
|
+
def test_get_session_token_error_handling(self, session_manager):
|
|
245
|
+
"""Test error handling when getting session token fails."""
|
|
246
|
+
with patch("boto3.Session") as mock_session_class:
|
|
247
|
+
mock_session = MagicMock()
|
|
248
|
+
mock_sts_client = MagicMock()
|
|
249
|
+
mock_sts_client.get_session_token.side_effect = Exception("AWS API Error")
|
|
250
|
+
mock_session.client.return_value = mock_sts_client
|
|
251
|
+
mock_session_class.return_value = mock_session
|
|
252
|
+
|
|
253
|
+
with pytest.raises(Exception) as exc_info:
|
|
254
|
+
session_manager.get_session_token(profile="test-profile")
|
|
255
|
+
|
|
256
|
+
assert "AWS API Error" in str(exc_info.value)
|
|
257
|
+
|
|
258
|
+
def test_cache_session(self, session_manager, mock_credentials, temp_cache_dir):
|
|
259
|
+
"""Test caching session credentials to file."""
|
|
260
|
+
session_manager.cache_session("test-session", mock_credentials, region="us-east-1")
|
|
261
|
+
|
|
262
|
+
cache_file = Path(temp_cache_dir) / "test-session.json"
|
|
263
|
+
assert cache_file.exists()
|
|
264
|
+
|
|
265
|
+
with open(cache_file, "r") as f:
|
|
266
|
+
cached_data = json.load(f)
|
|
267
|
+
|
|
268
|
+
assert cached_data["session_name"] == "test-session"
|
|
269
|
+
assert cached_data["region"] == "us-east-1"
|
|
270
|
+
assert cached_data["credentials"] == mock_credentials
|
|
271
|
+
assert "cached_at" in cached_data
|
|
272
|
+
|
|
273
|
+
@patch("os.chmod")
|
|
274
|
+
@patch("os.name", "posix")
|
|
275
|
+
def test_cache_session_sets_file_permissions_unix(
|
|
276
|
+
self, mock_chmod, session_manager, mock_credentials, temp_cache_dir
|
|
277
|
+
):
|
|
278
|
+
"""Test that cache_session sets restrictive permissions on Unix."""
|
|
279
|
+
session_manager.cache_session("test-session", mock_credentials)
|
|
280
|
+
|
|
281
|
+
# Verify chmod was called with owner-only permissions (0o600)
|
|
282
|
+
cache_file = Path(temp_cache_dir) / "test-session.json"
|
|
283
|
+
mock_chmod.assert_called_with(cache_file, 0o600)
|
|
284
|
+
|
|
285
|
+
def test_get_cached_session_valid(self, session_manager, mock_credentials, temp_cache_dir):
|
|
286
|
+
"""Test retrieving a valid cached session."""
|
|
287
|
+
# Cache a session first
|
|
288
|
+
session_manager.cache_session("test-session", mock_credentials, region="us-east-1")
|
|
289
|
+
|
|
290
|
+
# Retrieve it
|
|
291
|
+
cached_data = session_manager.get_cached_session("test-session")
|
|
292
|
+
|
|
293
|
+
assert cached_data is not None
|
|
294
|
+
assert cached_data["session_name"] == "test-session"
|
|
295
|
+
assert cached_data["region"] == "us-east-1"
|
|
296
|
+
assert cached_data["credentials"] == mock_credentials
|
|
297
|
+
|
|
298
|
+
def test_get_cached_session_not_found(self, session_manager):
|
|
299
|
+
"""Test retrieving a non-existent cached session."""
|
|
300
|
+
cached_data = session_manager.get_cached_session("nonexistent")
|
|
301
|
+
|
|
302
|
+
assert cached_data is None
|
|
303
|
+
|
|
304
|
+
def test_get_cached_session_expired(self, session_manager, temp_cache_dir):
|
|
305
|
+
"""Test retrieving an expired cached session."""
|
|
306
|
+
# Create expired credentials (expired 1 hour ago)
|
|
307
|
+
expiration = datetime.now(timezone.utc) - timedelta(hours=1)
|
|
308
|
+
expired_credentials = {
|
|
309
|
+
"aws_access_key_id": "ASIAIOSFODNN7EXAMPLE",
|
|
310
|
+
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
311
|
+
"aws_session_token": "FwoGZXIvYXdzEBYaD...",
|
|
312
|
+
"expiration": expiration.isoformat(),
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
session_manager.cache_session("expired-session", expired_credentials)
|
|
316
|
+
|
|
317
|
+
# Try to retrieve it
|
|
318
|
+
cached_data = session_manager.get_cached_session("expired-session")
|
|
319
|
+
|
|
320
|
+
assert cached_data is None
|
|
321
|
+
|
|
322
|
+
# Verify cache file was deleted
|
|
323
|
+
cache_file = Path(temp_cache_dir) / "expired-session.json"
|
|
324
|
+
assert not cache_file.exists()
|
|
325
|
+
|
|
326
|
+
def test_get_cached_session_expiring_soon(self, session_manager, temp_cache_dir):
|
|
327
|
+
"""Test retrieving a session that expires in less than 5 minutes."""
|
|
328
|
+
# Create credentials that expire in 2 minutes
|
|
329
|
+
expiration = datetime.now(timezone.utc) + timedelta(minutes=2)
|
|
330
|
+
expiring_credentials = {
|
|
331
|
+
"aws_access_key_id": "ASIAIOSFODNN7EXAMPLE",
|
|
332
|
+
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
333
|
+
"aws_session_token": "FwoGZXIvYXdzEBYaD...",
|
|
334
|
+
"expiration": expiration.isoformat(),
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
session_manager.cache_session("expiring-session", expiring_credentials)
|
|
338
|
+
|
|
339
|
+
# Try to retrieve it
|
|
340
|
+
cached_data = session_manager.get_cached_session("expiring-session")
|
|
341
|
+
|
|
342
|
+
# Should be treated as expired (5 minute buffer)
|
|
343
|
+
assert cached_data is None
|
|
344
|
+
|
|
345
|
+
def test_get_cached_session_corrupted_file(self, session_manager, temp_cache_dir):
|
|
346
|
+
"""Test retrieving a session with corrupted cache file."""
|
|
347
|
+
# Create a corrupted cache file
|
|
348
|
+
cache_file = Path(temp_cache_dir) / "corrupted-session.json"
|
|
349
|
+
with open(cache_file, "w") as f:
|
|
350
|
+
f.write("not valid json{{{")
|
|
351
|
+
|
|
352
|
+
cached_data = session_manager.get_cached_session("corrupted-session")
|
|
353
|
+
|
|
354
|
+
assert cached_data is None
|
|
355
|
+
|
|
356
|
+
def test_clear_session_exists(self, session_manager, mock_credentials, temp_cache_dir):
|
|
357
|
+
"""Test clearing an existing cached session."""
|
|
358
|
+
session_manager.cache_session("test-session", mock_credentials)
|
|
359
|
+
|
|
360
|
+
result = session_manager.clear_session("test-session")
|
|
361
|
+
|
|
362
|
+
assert result is True
|
|
363
|
+
|
|
364
|
+
cache_file = Path(temp_cache_dir) / "test-session.json"
|
|
365
|
+
assert not cache_file.exists()
|
|
366
|
+
|
|
367
|
+
def test_clear_session_not_found(self, session_manager):
|
|
368
|
+
"""Test clearing a non-existent cached session."""
|
|
369
|
+
result = session_manager.clear_session("nonexistent")
|
|
370
|
+
|
|
371
|
+
assert result is False
|
|
372
|
+
|
|
373
|
+
def test_clear_all_sessions(self, session_manager, mock_credentials, temp_cache_dir):
|
|
374
|
+
"""Test clearing all cached sessions."""
|
|
375
|
+
# Create multiple sessions
|
|
376
|
+
session_manager.cache_session("session-1", mock_credentials)
|
|
377
|
+
session_manager.cache_session("session-2", mock_credentials)
|
|
378
|
+
session_manager.cache_session("session-3", mock_credentials)
|
|
379
|
+
|
|
380
|
+
count = session_manager.clear_all_sessions()
|
|
381
|
+
|
|
382
|
+
assert count == 3
|
|
383
|
+
|
|
384
|
+
# Verify all cache files are deleted
|
|
385
|
+
cache_dir = Path(temp_cache_dir)
|
|
386
|
+
assert len(list(cache_dir.glob("*.json"))) == 0
|
|
387
|
+
|
|
388
|
+
def test_clear_all_sessions_empty(self, session_manager):
|
|
389
|
+
"""Test clearing all sessions when none exist."""
|
|
390
|
+
count = session_manager.clear_all_sessions()
|
|
391
|
+
|
|
392
|
+
assert count == 0
|
|
393
|
+
|
|
394
|
+
def test_list_sessions(self, session_manager, mock_credentials, temp_cache_dir):
|
|
395
|
+
"""Test listing all cached sessions."""
|
|
396
|
+
# Create multiple sessions
|
|
397
|
+
session_manager.cache_session("session-1", mock_credentials, region="us-east-1")
|
|
398
|
+
session_manager.cache_session("session-2", mock_credentials, region="us-west-2")
|
|
399
|
+
|
|
400
|
+
sessions = session_manager.list_sessions()
|
|
401
|
+
|
|
402
|
+
assert len(sessions) == 2
|
|
403
|
+
|
|
404
|
+
# Verify session data
|
|
405
|
+
session_names = {s["name"] for s in sessions}
|
|
406
|
+
assert "session-1" in session_names
|
|
407
|
+
assert "session-2" in session_names
|
|
408
|
+
|
|
409
|
+
# Verify each session has required fields
|
|
410
|
+
for session in sessions:
|
|
411
|
+
assert "name" in session
|
|
412
|
+
assert "region" in session
|
|
413
|
+
assert "expiration" in session
|
|
414
|
+
assert "expired" in session
|
|
415
|
+
assert "cached_at" in session
|
|
416
|
+
assert isinstance(session["expired"], bool)
|
|
417
|
+
|
|
418
|
+
def test_list_sessions_with_expired(self, session_manager, temp_cache_dir):
|
|
419
|
+
"""Test listing sessions includes expired status."""
|
|
420
|
+
# Create one valid and one expired session
|
|
421
|
+
valid_expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
422
|
+
valid_credentials = {
|
|
423
|
+
"aws_access_key_id": "ASIAIOSFODNN7EXAMPLE",
|
|
424
|
+
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
425
|
+
"aws_session_token": "FwoGZXIvYXdzEBYaD...",
|
|
426
|
+
"expiration": valid_expiration.isoformat(),
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
expired_expiration = datetime.now(timezone.utc) - timedelta(hours=1)
|
|
430
|
+
expired_credentials = {
|
|
431
|
+
"aws_access_key_id": "ASIAIOSFODNN7EXAMPLE",
|
|
432
|
+
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
433
|
+
"aws_session_token": "FwoGZXIvYXdzEBYaD...",
|
|
434
|
+
"expiration": expired_expiration.isoformat(),
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
session_manager.cache_session("valid-session", valid_credentials)
|
|
438
|
+
session_manager.cache_session("expired-session", expired_credentials)
|
|
439
|
+
|
|
440
|
+
sessions = session_manager.list_sessions()
|
|
441
|
+
|
|
442
|
+
assert len(sessions) == 2
|
|
443
|
+
|
|
444
|
+
# Find the sessions and check their expired status
|
|
445
|
+
valid_session = next(s for s in sessions if s["name"] == "valid-session")
|
|
446
|
+
expired_session = next(s for s in sessions if s["name"] == "expired-session")
|
|
447
|
+
|
|
448
|
+
assert valid_session["expired"] is False
|
|
449
|
+
assert expired_session["expired"] is True
|
|
450
|
+
|
|
451
|
+
def test_list_sessions_empty(self, session_manager):
|
|
452
|
+
"""Test listing sessions when none exist."""
|
|
453
|
+
sessions = session_manager.list_sessions()
|
|
454
|
+
|
|
455
|
+
assert sessions == []
|
|
456
|
+
|
|
457
|
+
def test_list_sessions_corrupted_file(self, session_manager, temp_cache_dir):
|
|
458
|
+
"""Test listing sessions skips corrupted files."""
|
|
459
|
+
# Create one valid and one corrupted session
|
|
460
|
+
valid_expiration = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
461
|
+
valid_credentials = {
|
|
462
|
+
"aws_access_key_id": "ASIAIOSFODNN7EXAMPLE",
|
|
463
|
+
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
464
|
+
"aws_session_token": "FwoGZXIvYXdzEBYaD...",
|
|
465
|
+
"expiration": valid_expiration.isoformat(),
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
session_manager.cache_session("valid-session", valid_credentials)
|
|
469
|
+
|
|
470
|
+
# Create corrupted file
|
|
471
|
+
corrupted_file = Path(temp_cache_dir) / "corrupted.json"
|
|
472
|
+
with open(corrupted_file, "w") as f:
|
|
473
|
+
f.write("not valid json{{{")
|
|
474
|
+
|
|
475
|
+
sessions = session_manager.list_sessions()
|
|
476
|
+
|
|
477
|
+
# Should only return the valid session
|
|
478
|
+
assert len(sessions) == 1
|
|
479
|
+
assert sessions[0]["name"] == "valid-session"
|
|
480
|
+
|
|
481
|
+
def test_get_credentials_for_session(self, session_manager, mock_credentials):
|
|
482
|
+
"""Test getting credentials tuple for use in CLI commands."""
|
|
483
|
+
session_manager.cache_session("test-session", mock_credentials, region="us-east-1")
|
|
484
|
+
|
|
485
|
+
credentials_tuple = session_manager.get_credentials_for_session("test-session")
|
|
486
|
+
|
|
487
|
+
assert credentials_tuple is not None
|
|
488
|
+
access_key_id, secret_access_key, session_token, region = credentials_tuple
|
|
489
|
+
|
|
490
|
+
assert access_key_id == mock_credentials["aws_access_key_id"]
|
|
491
|
+
assert secret_access_key == mock_credentials["aws_secret_access_key"]
|
|
492
|
+
assert session_token == mock_credentials["aws_session_token"]
|
|
493
|
+
assert region == "us-east-1"
|
|
494
|
+
|
|
495
|
+
def test_get_credentials_for_session_not_found(self, session_manager):
|
|
496
|
+
"""Test getting credentials for non-existent session."""
|
|
497
|
+
credentials_tuple = session_manager.get_credentials_for_session("nonexistent")
|
|
498
|
+
|
|
499
|
+
assert credentials_tuple is None
|
|
500
|
+
|
|
501
|
+
def test_get_credentials_for_session_expired(self, session_manager):
|
|
502
|
+
"""Test getting credentials for expired session."""
|
|
503
|
+
# Create expired credentials
|
|
504
|
+
expiration = datetime.now(timezone.utc) - timedelta(hours=1)
|
|
505
|
+
expired_credentials = {
|
|
506
|
+
"aws_access_key_id": "ASIAIOSFODNN7EXAMPLE",
|
|
507
|
+
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
508
|
+
"aws_session_token": "FwoGZXIvYXdzEBYaD...",
|
|
509
|
+
"expiration": expiration.isoformat(),
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
session_manager.cache_session("expired-session", expired_credentials)
|
|
513
|
+
|
|
514
|
+
credentials_tuple = session_manager.get_credentials_for_session("expired-session")
|
|
515
|
+
|
|
516
|
+
assert credentials_tuple is None
|