|
1 | 1 | import json |
| 2 | +from datetime import datetime, timezone |
2 | 3 | from unittest.mock import Mock, patch |
3 | 4 |
|
4 | 5 | import pytest |
@@ -349,3 +350,126 @@ def test_arn_parsing_different_session_formats(self, mock_boto3): |
349 | 350 | for arn, expected_username in test_cases: |
350 | 351 | session_name = arn.split('/')[-1] |
351 | 352 | assert session_name == expected_username, f"Failed to parse {arn}" |
| 353 | + |
| 354 | + |
| 355 | +class TestCloudTrailOperationsRosaTagging: |
| 356 | + """Tests for ROSA cluster ownership resolution added in PR #1007.""" |
| 357 | + |
| 358 | + LAUNCH_TIME = datetime(2026, 6, 30, 14, 6, 46, tzinfo=timezone.utc) |
| 359 | + IAM_USERS = ['pragchau', 'cloud-governance-delete-user'] |
| 360 | + |
| 361 | + @patch('cloud_governance.common.clouds.aws.cloudtrail.cloudtrail_operations.boto3') |
| 362 | + def test_get_username_from_resource_events_skips_excluded_users(self, mock_boto3): |
| 363 | + cloudtrail_ops = CloudTrailOperations(region_name='us-east-1') |
| 364 | + cloudtrail_ops.get_full_responses = Mock(return_value=[ |
| 365 | + { |
| 366 | + 'EventName': 'CreateTags', |
| 367 | + 'Username': 'cloud-governance-delete-user', |
| 368 | + 'CloudTrailEvent': '{}', |
| 369 | + }, |
| 370 | + { |
| 371 | + 'EventName': 'CreateTags', |
| 372 | + 'Username': 'pragchau', |
| 373 | + 'CloudTrailEvent': '{}', |
| 374 | + }, |
| 375 | + ]) |
| 376 | + result = cloudtrail_ops.get_username_from_resource_events( |
| 377 | + resource_id='i-0123456789abcdef0', |
| 378 | + iam_users=self.IAM_USERS, |
| 379 | + start_time=self.LAUNCH_TIME, |
| 380 | + exclude_users={'cloud-governance-delete-user'}) |
| 381 | + assert result == 'pragchau' |
| 382 | + |
| 383 | + @patch('cloud_governance.common.clouds.aws.cloudtrail.cloudtrail_operations.boto3') |
| 384 | + def test_get_username_from_cluster_role_direct_match(self, mock_boto3): |
| 385 | + mock_iam = Mock() |
| 386 | + mock_paginator = Mock() |
| 387 | + mock_paginator.paginate.return_value = [{ |
| 388 | + 'Roles': [{ |
| 389 | + 'RoleName': 'z2r7p1f3o6m2h3y-t5bx8-master-role', |
| 390 | + 'Arn': 'arn:aws:iam::123456789012:role/z2r7p1f3o6m2h3y-t5bx8-master-role', |
| 391 | + 'CreateDate': self.LAUNCH_TIME, |
| 392 | + }] |
| 393 | + }] |
| 394 | + mock_iam.get_paginator.return_value = mock_paginator |
| 395 | + mock_boto3.client.side_effect = lambda service, **kwargs: { |
| 396 | + 'cloudtrail': Mock(), |
| 397 | + 'iam': mock_iam, |
| 398 | + }[service] |
| 399 | + |
| 400 | + cloudtrail_ops = CloudTrailOperations(region_name='us-east-1') |
| 401 | + cloudtrail_ops._CloudTrailOperations__get_username_from_role_cloudtrail = Mock( |
| 402 | + return_value='pragchau') |
| 403 | + |
| 404 | + result = cloudtrail_ops.get_username_from_cluster_role( |
| 405 | + cluster_id='z2r7p1f3o6m2h3y-t5bx8', |
| 406 | + iam_users=self.IAM_USERS, |
| 407 | + launch_time=self.LAUNCH_TIME) |
| 408 | + assert result == 'pragchau' |
| 409 | + |
| 410 | + @patch('cloud_governance.common.clouds.aws.cloudtrail.cloudtrail_operations.boto3') |
| 411 | + def test_get_username_from_cluster_role_ambiguous_temporal_match_returns_empty( |
| 412 | + self, mock_boto3): |
| 413 | + role_create = datetime(2026, 6, 30, 13, 0, 0, tzinfo=timezone.utc) |
| 414 | + mock_iam = Mock() |
| 415 | + mock_paginator = Mock() |
| 416 | + mock_paginator.paginate.return_value = [{ |
| 417 | + 'Roles': [ |
| 418 | + { |
| 419 | + 'RoleName': 'cluster-a-openshift-machine-api', |
| 420 | + 'Arn': 'arn:aws:iam::123456789012:role/cluster-a-openshift-machine-api', |
| 421 | + 'CreateDate': role_create, |
| 422 | + }, |
| 423 | + { |
| 424 | + 'RoleName': 'cluster-b-openshift-machine-api', |
| 425 | + 'Arn': 'arn:aws:iam::123456789012:role/cluster-b-openshift-machine-api', |
| 426 | + 'CreateDate': role_create, |
| 427 | + }, |
| 428 | + ] |
| 429 | + }] |
| 430 | + mock_iam.get_paginator.return_value = mock_paginator |
| 431 | + mock_boto3.client.side_effect = lambda service, **kwargs: { |
| 432 | + 'cloudtrail': Mock(), |
| 433 | + 'iam': mock_iam, |
| 434 | + }[service] |
| 435 | + |
| 436 | + cloudtrail_ops = CloudTrailOperations(region_name='us-east-1') |
| 437 | + cloudtrail_ops._CloudTrailOperations__get_username_from_role_cloudtrail = Mock( |
| 438 | + side_effect=['pragchau', 'otheruser']) |
| 439 | + cloudtrail_ops._CloudTrailOperations__get_username_from_create_role_events = Mock( |
| 440 | + return_value='') |
| 441 | + |
| 442 | + result = cloudtrail_ops.get_username_from_cluster_role( |
| 443 | + cluster_id='unknown-infra-id', |
| 444 | + iam_users=self.IAM_USERS + ['otheruser'], |
| 445 | + launch_time=self.LAUNCH_TIME) |
| 446 | + assert result == '' |
| 447 | + cloudtrail_ops._CloudTrailOperations__get_username_from_create_role_events.assert_called_once() |
| 448 | + |
| 449 | + @patch('cloud_governance.common.clouds.aws.cloudtrail.cloudtrail_operations.boto3') |
| 450 | + def test_get_username_from_create_role_events_paginates_global_cloudtrail( |
| 451 | + self, mock_boto3): |
| 452 | + mock_global_ct = Mock() |
| 453 | + mock_global_ct.lookup_events.side_effect = [ |
| 454 | + {'Events': [], 'NextToken': 'page2'}, |
| 455 | + {'Events': [{ |
| 456 | + 'EventName': 'CreateRole', |
| 457 | + 'Username': 'pragchau', |
| 458 | + 'Resources': [{ |
| 459 | + 'ResourceType': 'AWS::IAM::Role', |
| 460 | + 'ResourceName': 'rosa-test-openshift-machine-api', |
| 461 | + }], |
| 462 | + 'CloudTrailEvent': '{}', |
| 463 | + }]}, |
| 464 | + ] |
| 465 | + mock_boto3.client.return_value = Mock() |
| 466 | + |
| 467 | + cloudtrail_ops = CloudTrailOperations(region_name='us-west-2') |
| 468 | + cloudtrail_ops._CloudTrailOperations__global_cloudtrail = mock_global_ct |
| 469 | + |
| 470 | + start = datetime(2026, 6, 30, 8, 0, 0, tzinfo=timezone.utc) |
| 471 | + end = datetime(2026, 6, 30, 14, 0, 0, tzinfo=timezone.utc) |
| 472 | + result = cloudtrail_ops._CloudTrailOperations__get_username_from_create_role_events( |
| 473 | + start_time=start, end_time=end, iam_users=self.IAM_USERS) |
| 474 | + assert result == 'pragchau' |
| 475 | + assert mock_global_ct.lookup_events.call_count == 2 |
0 commit comments