def test_create_fails_when_account_not_verified(self): """Test that an account is not saved if verification fails.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() validated_data = { 'account_arn': arn, } mock_request = Mock() mock_request.user = util_helper.generate_test_user() context = {'request': mock_request} failed_actions = ['foo', 'bar'] with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws.sts, 'boto3') as mock_boto3: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = False, failed_actions serializer = AwsAccountSerializer(context=context) with self.assertRaises(serializers.ValidationError) as cm: serializer.create(validated_data) exception = cm.exception self.assertIn('account_arn', exception.detail) for index in range(len(failed_actions)): self.assertIn(failed_actions[index], exception.detail['account_arn'][index + 1])
def test_update_from_sources_kafka_message_new_aws_account_id( self, mock_enable, mock_get_auth, mock_notify_sources, mock_get_app): """ Assert the new cloud account created for new aws_account_id. A new CloudAccount will get created with the new arn. And the old CloudAccount will be removed. """ message, headers = util_helper.generate_authentication_create_message_value( self.account_number, self.username, self.authentication_id) new_account_id = util_helper.generate_dummy_aws_account_id() new_arn = util_helper.generate_dummy_arn(account_id=new_account_id) self.auth_return_value["username"] = new_arn mock_get_auth.return_value = self.auth_return_value mock_get_app.return_value = self.app_return_value with patch.object(sts, "boto3") as mock_boto3, patch.object( aws_models, "_delete_cloudtrail"), patch( "api.clouds.aws.util.verify_permissions"): mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = util_helper.generate_dummy_role() tasks.update_from_source_kafka_message(message, headers) mock_enable.assert_called()
def setUp(self): """Set up test models.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(account_id=aws_account_id) self.role = util_helper.generate_dummy_role() self.account = helper.generate_cloud_account( arn=arn, aws_account_id=aws_account_id, name="test")
def test_get_running_instances(self): """Assert we get expected instances in a dict keyed by regions.""" mock_arn = helper.generate_dummy_arn() mock_regions = [f'region-{uuid.uuid4()}'] mock_role = helper.generate_dummy_role() mock_running_instance = helper.generate_dummy_describe_instance( state=aws.InstanceState.running) mock_stopped_instance = helper.generate_dummy_describe_instance( state=aws.InstanceState.stopped) mock_described = { 'Reservations': [ { 'Instances': [ mock_running_instance, mock_stopped_instance, ], }, ], } expected_found = {mock_regions[0]: [mock_running_instance]} with patch.object(aws, 'get_regions') as mock_get_regions, \ patch.object(aws, 'boto3') as mock_boto3: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = mock_role mock_get_regions.return_value = mock_regions mock_client = mock_boto3.Session.return_value.client.return_value mock_client.describe_instances.return_value = mock_described actual_found = aws.get_running_instances(aws.get_session(mock_arn)) self.assertDictEqual(expected_found, actual_found)
def test_create_fails_when_cloudtrail_fails(self): """Test that an account is not saved if cloudtrails errors.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() validated_data = { 'account_arn': arn, } client_error = ClientError( error_response={'Error': { 'Code': 'AccessDeniedException' }}, operation_name=Mock(), ) mock_request = Mock() mock_request.user = util_helper.generate_test_user() context = {'request': mock_request} with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws.sts, 'boto3') as mock_boto3, \ patch.object(aws, 'configure_cloudtrail') as mock_cloudtrail: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = True, [] mock_cloudtrail.side_effect = client_error serializer = AwsAccountSerializer(context=context) with self.assertRaises(ValidationError) as cm: serializer.create(validated_data) raised_exception = cm.exception self.assertIn('account_arn', raised_exception.detail) self.assertIn(arn, raised_exception.detail['account_arn'][0])
def test_verify_account_access_success(self): """Assert that account access via a IAM role is verified.""" mock_arn = helper.generate_dummy_arn() mock_role = helper.generate_dummy_role() mock_dry_run_exception = { 'Error': { 'Code': 'DryRunOperation', 'Message': 'Request would have succeeded, ' 'but DryRun flag is set.' } } with patch.object(aws, 'boto3') as mock_boto3: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = mock_role mock_client = mock_boto3.Session.return_value.client.return_value mock_describe_images = mock_client.describe_images mock_describe_instances = mock_client.describe_instances mock_describe_snapshot_attribute = \ mock_client.describe_snapshot_attribute mock_describe_snapshots = mock_client.describe_snapshots mock_modify_snapshot_attribute = \ mock_client.modify_snapshot_attribute mock_modify_image_attribute = mock_client.modify_image_attribute mock_describe_images.side_effect = ClientError( mock_dry_run_exception, 'DescribeImages') mock_describe_instances.side_effect = ClientError( mock_dry_run_exception, 'DescribeInstances') mock_describe_snapshot_attribute.side_effect = ClientError( mock_dry_run_exception, 'DescribeSnapshotAttribute') mock_describe_snapshots.side_effect = ClientError( mock_dry_run_exception, 'DescribeSnapshots') mock_modify_snapshot_attribute.side_effect = ClientError( mock_dry_run_exception, 'ModifySnapshotAttribute') mock_modify_image_attribute.side_effect = ClientError( mock_dry_run_exception, 'ModifyImageAttribute') actual_verified = aws.verify_account_access( aws.get_session(mock_arn)) mock_describe_images.assert_called_with(DryRun=True) mock_describe_instances.assert_called_with(DryRun=True) mock_describe_snapshot_attribute.assert_called_with( DryRun=True, SnapshotId='string', Attribute='productCodes') mock_describe_snapshots.assert_called_with(DryRun=True) mock_modify_snapshot_attribute.assert_called_with( SnapshotId='string', DryRun=True, Attribute='productCodes', GroupNames=[ 'string', ]) mock_modify_image_attribute.assert_called_with( Attribute='description', ImageId='string', DryRun=True) self.assertTrue(actual_verified)
def test_create_succeeds_when_account_verified(self): """Test saving and processing of a test ARN.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() region = random.choice(util_helper.SOME_AWS_REGIONS) running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( state=aws.InstanceState.stopping) ] } validated_data = { 'account_arn': arn, } mock_request = Mock() mock_request.user = util_helper.generate_test_user() context = {'request': mock_request} with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws.sts, 'boto3') as mock_boto3, \ patch.object(aws, 'get_running_instances') as mock_get_run, \ patch.object(account_serializers, 'copy_ami_snapshot') as mock_copy_snapshot: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = True, [] mock_get_run.return_value = running_instances mock_copy_snapshot.return_value = None serializer = AwsAccountSerializer(context=context) result = serializer.create(validated_data) self.assertIsInstance(result, AwsAccount) account = AwsAccount.objects.get(aws_account_id=aws_account_id) self.assertEqual(aws_account_id, account.aws_account_id) self.assertEqual(arn, account.account_arn) instances = AwsInstance.objects.filter(account=account).all() self.assertEqual(len(running_instances[region]), len(instances)) for region, mock_instances_list in running_instances.items(): for mock_instance in mock_instances_list: instance_id = mock_instance['InstanceId'] instance = AwsInstance.objects.get(ec2_instance_id=instance_id) self.assertIsInstance(instance, AwsInstance) self.assertEqual(region, instance.region) event = InstanceEvent.objects.get(instance=instance) self.assertIsInstance(event, InstanceEvent) self.assertEqual(InstanceEvent.TYPE.power_on, event.event_type) amis = AwsMachineImage.objects.filter(account=account).all() self.assertEqual(len(running_instances[region]), len(amis))
def setUp(self): """Set up shared test data.""" self.aws_account_id = util_helper.generate_dummy_aws_account_id() self.arn = util_helper.generate_dummy_arn(self.aws_account_id) self.role = util_helper.generate_dummy_role() self.validated_data = util_helper.generate_dummy_aws_cloud_account_post_data( ) self.validated_data.update({ "account_arn": self.arn, "name": "account_name" })
def setUp(self): """Set up basic aws account.""" self.created_at = util_helper.utc_dt(2019, 1, 1, 0, 0, 0) aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(account_id=aws_account_id) self.role = util_helper.generate_dummy_role() self.account = helper.generate_cloud_account( arn=arn, aws_account_id=aws_account_id, name="test", created_at=self.created_at, generate_verify_task=False, )
def test_create_fails_when_another_arn_has_same_aws_account_id(self): """Test that an account is not saved if ARN reuses an AWS account.""" user = util_helper.generate_test_user() aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() region = random.choice(util_helper.SOME_AWS_REGIONS) running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( state=aws.InstanceState.stopping) ] } validated_data = { 'account_arn': arn, } # Create one with the same AWS account ID but a different ARN. account_helper.generate_aws_account( aws_account_id=aws_account_id, user=user, ) mock_request = Mock() mock_request.user = util_helper.generate_test_user() context = {'request': mock_request} with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws.sts, 'boto3') as mock_boto3, \ patch.object(aws, 'get_running_instances') as mock_get_run: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = True, [] mock_get_run.return_value = running_instances serializer = AwsAccountSerializer(context=context) with self.assertRaises(ValidationError) as cm: serializer.create(validated_data) raised_exception = cm.exception self.assertIn('account_arn', raised_exception.detail) self.assertIn(aws_account_id, raised_exception.detail['account_arn'][0])
def test_create_succeeds_when_account_verified(self): """Test saving and processing of a test ARN.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() region = f'region-{uuid.uuid4()}' running_instances = { region: [ util_helper.generate_dummy_describe_instance( state=aws.InstanceState.running), util_helper.generate_dummy_describe_instance( state=aws.InstanceState.stopping) ] } validated_data = { 'account_arn': arn, } with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws, 'boto3') as mock_boto3, \ patch.object(aws, 'get_running_instances') as mock_get_running: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = True mock_get_running.return_value = running_instances serializer = AwsAccountSerializer() result = serializer.create(validated_data) self.assertIsInstance(result, AwsAccount) account = AwsAccount.objects.get(aws_account_id=aws_account_id) self.assertEqual(aws_account_id, account.aws_account_id) self.assertEqual(arn, account.account_arn) instances = AwsInstance.objects.filter(account=account).all() self.assertEqual(len(running_instances[region]), len(instances)) for region, mock_instances_list in running_instances.items(): for mock_instance in mock_instances_list: instance_id = mock_instance['InstanceId'] instance = AwsInstance.objects.get(ec2_instance_id=instance_id) self.assertIsInstance(instance, AwsInstance) self.assertEqual(region, instance.region) event = InstanceEvent.objects.get(instance=instance) self.assertIsInstance(event, InstanceEvent) self.assertEqual(InstanceEvent.TYPE.power_on, event.event_type)
def test_get_session(self, mock_client): """Assert get_session returns session object.""" mock_arn = AwsArn(helper.generate_dummy_arn(generate_account_id=True)) mock_account_id = mock_arn.account_id mock_role = helper.generate_dummy_role() mock_assume_role = mock_client.return_value.assume_role mock_assume_role.return_value = mock_role session = sts.get_session(str(mock_arn)) creds = session.get_credentials().get_frozen_credentials() mock_client.assert_called_with('sts') mock_assume_role.assert_called_with( Policy=json.dumps(sts.cloudigrade_policy), RoleArn='{0}'.format(mock_arn), RoleSessionName='cloudigrade-{0}'.format(mock_account_id)) self.assertEqual(creds[0], mock_role['Credentials']['AccessKeyId']) self.assertEqual(creds[1], mock_role['Credentials']['SecretAccessKey']) self.assertEqual(creds[2], mock_role['Credentials']['SessionToken'])
def test_delete_from_sources_kafka_message_application_authentication_success( self, mock_notify_sources): """Assert removing CloudAccount via ApplicationAuthentication.destroy.""" self.assertEqual(CloudAccount.objects.count(), 1) self.assertEqual(aws_models.AwsCloudAccount.objects.count(), 1) account_number = str(self.user.username) ( message, headers, ) = util_helper.generate_applicationauthentication_create_message_value( account_number, platform_id=self.application_authentication_id, application_id=self.application_id, authentication_id=self.authentication_id, ) expected_logger_infos = [ "INFO:api.tasks:delete_from_sources_kafka_message for account_number " f"{account_number}, platform_id {self.application_authentication_id}", "INFO:api.tasks:Deleting CloudAccounts using filter " f"(AND: ('platform_application_id', {self.application_id}), " f"('platform_authentication_id', {self.authentication_id}))", ] with patch.object(sts, "boto3") as mock_boto3, patch.object( aws_models, "_delete_cloudtrail"), self.assertLogs( "api.tasks", level="INFO") as logging_watcher: role = util_helper.generate_dummy_role() mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role tasks.delete_from_sources_kafka_message(message, headers) for index, expected_logger_info in enumerate( expected_logger_infos): self.assertEqual(expected_logger_info, logging_watcher.output[index]) self.assertEqual(CloudAccount.objects.count(), 0) self.assertEqual(aws_models.AwsCloudAccount.objects.count(), 0) mock_notify_sources.delay.assert_called()
def test_get_session(self, mock_client): """Assert get_session returns session object.""" mock_arn = AwsArn(helper.generate_dummy_arn()) mock_account_id = mock_arn.account_id mock_role = helper.generate_dummy_role() mock_assume_role = mock_client.return_value.assume_role mock_assume_role.return_value = mock_role session = sts.get_session(str(mock_arn)) creds = session.get_credentials().get_frozen_credentials() mock_client.assert_called_with("sts") mock_assume_role.assert_called_with( Policy=json.dumps(sts.cloudigrade_policy), RoleArn="{0}".format(mock_arn), RoleSessionName="cloudigrade-{0}".format(mock_account_id), ) self.assertEqual(creds[0], mock_role["Credentials"]["AccessKeyId"]) self.assertEqual(creds[1], mock_role["Credentials"]["SecretAccessKey"]) self.assertEqual(creds[2], mock_role["Credentials"]["SessionToken"])
def test_get_session(self): """Assert get_session returns session object.""" mock_arn = helper.generate_dummy_arn() mock_account_id = extract_account_id_from_arn(mock_arn) mock_role = helper.generate_dummy_role() with patch.object(aws.boto3, 'client') as mock_client: mock_assume_role = mock_client.return_value.assume_role mock_assume_role.return_value = mock_role session = aws.get_session(mock_arn) creds = session.get_credentials().get_frozen_credentials() mock_client.assert_called_with('sts') mock_assume_role.assert_called_with( Policy=json.dumps(aws.cloudigrade_policy), RoleArn=mock_arn, RoleSessionName=f'cloudigrade-{mock_account_id}') self.assertEqual(creds[0], mock_role['Credentials']['AccessKeyId']) self.assertEqual(creds[1], mock_role['Credentials']['SecretAccessKey']) self.assertEqual(creds[2], mock_role['Credentials']['SessionToken'])
def test_create_fails_when_account_not_verified(self): """Test that an account is not saved if verification fails.""" aws_account_id = util_helper.generate_dummy_aws_account_id() arn = util_helper.generate_dummy_arn(aws_account_id) role = util_helper.generate_dummy_role() validated_data = { 'account_arn': arn, } expected_detail = _( 'AwsAccount verification failed. ARN Info Not Stored') with patch.object(aws, 'verify_account_access') as mock_verify, \ patch.object(aws, 'boto3') as mock_boto3: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = role mock_verify.return_value = False serializer = AwsAccountSerializer() with self.assertRaises(serializers.ValidationError) as cm: serializer.create(validated_data) the_exception = cm.exception self.assertEqual(the_exception.detail, [expected_detail])
def test_get_running_instances(self): """ Assert we get expected instances in a dict keyed by regions. The setup here is a little complicated, and it's important to understand what's going into it. The mock response from the client's `describe_instances` includes a Reservations list of **three** elements with different Instances in each. It is unclear to us how AWS divides Instances into Reservations; so, we must ensure in our tests that we are checking for Instances in potentially multiple Reservations. """ mock_regions = [f'region-{uuid.uuid4()}'] mock_role = helper.generate_dummy_role() mock_session = Mock() mock_assume_role = mock_session.client.return_value.assume_role mock_assume_role.return_value = mock_role mock_running_instance_1 = helper.generate_dummy_describe_instance( state=ec2.InstanceState.running) mock_running_instance_2 = helper.generate_dummy_describe_instance( state=ec2.InstanceState.running) mock_stopped_instance_1 = helper.generate_dummy_describe_instance( state=ec2.InstanceState.stopped) mock_stopped_instance_2 = helper.generate_dummy_describe_instance( state=ec2.InstanceState.stopped) mock_described = { 'Reservations': [ { 'Instances': [ mock_running_instance_1, mock_stopped_instance_1, ], }, { 'Instances': [ mock_running_instance_2, ], }, { 'Instances': [ mock_stopped_instance_2, ], }, ], } mock_client = mock_session.client.return_value mock_client.describe_instances.return_value = mock_described expected_found = { mock_regions[0]: [ mock_running_instance_1, mock_running_instance_2, ] } with patch.object(ec2, 'get_regions') as mock_get_regions: mock_get_regions.return_value = mock_regions actual_found = ec2.get_running_instances(mock_session) self.assertDictEqual(expected_found, actual_found)
def test_verify_account_access_failure(self): """Assert that account access via a IAM role is not verified.""" mock_arn = helper.generate_dummy_arn() mock_role = helper.generate_dummy_role() mock_unauthorized_exception = { 'Error': { 'Code': 'UnauthorizedOperation', 'Message': 'You are not authorized ' 'to perform this operation.' } } mock_garbage_exception = { 'Error': { 'Code': 'GarbageOperation', 'Message': 'You are not authorized ' 'to perform this garbage operation.' } } bad_policy = { 'Version': '2012-10-17', 'Statement': [{ 'Sid': 'CloudigradeGarbagePolicy', 'Effect': 'Allow', 'Action': ['ec2:DescribeGarbage'], 'Resource': '*' }] } with patch.object(aws, 'boto3') as mock_boto3: mock_assume_role = mock_boto3.client.return_value.assume_role mock_assume_role.return_value = mock_role mock_client = mock_boto3.Session.return_value.client.return_value mock_describe_images = mock_client.describe_images mock_describe_instances = mock_client.describe_instances mock_describe_snapshot_attribute = \ mock_client.describe_snapshot_attribute mock_describe_snapshots = mock_client.describe_snapshots mock_modify_snapshot_attribute = \ mock_client.modify_snapshot_attribute mock_modify_image_attribute = mock_client.modify_image_attribute mock_describe_images.side_effect = ClientError( mock_unauthorized_exception, 'DescribeImages') mock_describe_instances.side_effect = ClientError( mock_unauthorized_exception, 'DescribeInstances') mock_describe_snapshot_attribute.side_effect = ClientError( mock_unauthorized_exception, 'DescribeSnapshotAttribute') mock_describe_snapshots.side_effect = ClientError( mock_unauthorized_exception, 'DescribeSnapshots') mock_modify_snapshot_attribute.side_effect = ClientError( mock_unauthorized_exception, 'ModifySnapshotAttribute') mock_modify_image_attribute.side_effect = ClientError( mock_unauthorized_exception, 'ModifyImageAttribute') session = aws.get_session(mock_arn) actual_verified = aws.verify_account_access(session) mock_describe_images.side_effect = ClientError( mock_garbage_exception, 'DescribeImages') with self.assertRaises(ClientError) as e: aws.verify_account_access(session) self.assertEqual(e.exception.response['Error']['Code'], mock_garbage_exception['Error']['Code']) self.assertEqual(e.exception.response['Error']['Message'], mock_garbage_exception['Error']['Message']) with patch.dict(aws.cloudigrade_policy, bad_policy): aws.verify_account_access(session) mock_describe_images.assert_called_with(DryRun=True) mock_describe_instances.assert_called_with(DryRun=True) mock_describe_snapshot_attribute.assert_called_with( DryRun=True, SnapshotId='string', Attribute='productCodes') mock_describe_snapshots.assert_called_with(DryRun=True) mock_modify_snapshot_attribute.assert_called_with( SnapshotId='string', DryRun=True, Attribute='productCodes', GroupNames=[ 'string', ]) mock_modify_image_attribute.assert_called_with( Attribute='description', ImageId='string', DryRun=True) self.assertFalse(actual_verified)