Path: blob/develop/tests/unit/customizations/cloudtrail/test_subscribe.py
1569 views
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License"). You3# may not use this file except in compliance with the License. A copy of4# the License is located at5#6# http://aws.amazon.com/apache2.0/7#8# or in the "license" file accompanying this file. This file is9# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF10# ANY KIND, either express or implied. See the License for the specific11# language governing permissions and limitations under the License.12import json1314from botocore.client import ClientError15from botocore.session import Session1617from tests.unit.test_clidriver import FakeSession18from awscli.customizations.cloudtrail.subscribe import CloudTrailError, CloudTrailSubscribe19from awscli.compat import BytesIO20from awscli.testutils import BaseAWSCommandParamsTest21from awscli.testutils import mock, unittest, temporary_file222324class TestCreateSubscription(BaseAWSCommandParamsTest):25def test_create_subscription_has_zero_rc(self):26command = (27'cloudtrail create-subscription --s3-use-bucket foo --name bar')28stdout = self.run_cmd(command, expected_rc=0)[0]29# We don't want to overspecify here, but we'll do a quick check to make30# sure it says log delivery is happening.31self.assertIn('Logs will be delivered to foo', stdout)3233@mock.patch.object(Session, 'create_client')34def test_policy_from_paramfile(self, create_client_mock):35client = mock.Mock()36# S3 mock calls37client.get_caller_identity.return_value = {'Account': ''}38client.head_bucket.side_effect = ClientError(39{'Error': {'Code': 404, 'Message': ''}}, 'HeadBucket')40# CloudTrail mock call41client.describe_trails.return_value = {}42create_client_mock.return_value = client4344policy = '{"Statement": []}'4546with temporary_file('w') as f:47f.write(policy)48f.flush()49command = (50'cloudtrail create-subscription --s3-new-bucket foo '51'--name bar --s3-custom-policy file://{0}'.format(f.name))52self.run_cmd(command, expected_rc=0)5354# Ensure that the *contents* of the file are sent as the policy55# parameter to S3.56client.put_bucket_policy.assert_called_with(57Bucket='foo', Policy=policy)585960class TestCloudTrailCommand(unittest.TestCase):61def setUp(self):62self.session = FakeSession({'config_file': 'myconfigfile'})63self.subscribe = CloudTrailSubscribe(self.session)64self.subscribe.region_name = 'us-east-1'6566self.subscribe.sts = mock.Mock()67self.subscribe.sts.get_caller_identity = mock.Mock(68return_value={'Account': '123456'})6970self.subscribe.s3 = mock.Mock()71self.subscribe.s3.meta.region_name = 'us-east-1'72policy_template = BytesIO(u'{"Statement": []}'.encode('latin-1'))73self.subscribe.s3.get_object = mock.Mock(74return_value={'Body': policy_template})75self.subscribe.s3.head_bucket.return_value = {}7677self.subscribe.sns = mock.Mock()78self.subscribe.sns.meta.region_name = 'us-east-1'79self.subscribe.sns.list_topics = mock.Mock(80return_value={'Topics': [{'TopicArn': ':test2'}]})81self.subscribe.sns.create_topic = mock.Mock(82return_value={'TopicArn': 'foo'})83self.subscribe.sns.get_topic_attributes = mock.Mock(84return_value={'Attributes': {'Policy': '{"Statement": []}'}})8586def test_clients_all_from_same_session(self):87session = mock.Mock()88subscribe_command = CloudTrailSubscribe(session)89parsed_globals = mock.Mock(region=None, verify_ssl=None,90endpoint_url=None)91subscribe_command.setup_services(None, parsed_globals)92create_client_calls = session.create_client.call_args_list93self.assertEqual(94create_client_calls, [95mock.call('sts', verify=None, region_name=None),96mock.call('s3', verify=None, region_name=None),97mock.call('sns', verify=None, region_name=None),98mock.call('cloudtrail', verify=None, region_name=None),99]100)101102def test_endpoint_url_is_only_used_for_cloudtrail(self):103endpoint_url = 'https://mycloudtrail.awsamazon.com/'104session = mock.Mock()105subscribe_command = CloudTrailSubscribe(session)106parsed_globals = mock.Mock(region=None, verify_ssl=None,107endpoint_url=endpoint_url)108subscribe_command.setup_services(None, parsed_globals)109create_client_calls = session.create_client.call_args_list110self.assertEqual(111create_client_calls, [112mock.call('sts', verify=None, region_name=None),113mock.call('s3', verify=None, region_name=None),114mock.call('sns', verify=None, region_name=None),115# Here we should inject the endpoint_url only for cloudtrail.116mock.call('cloudtrail', verify=None, region_name=None,117endpoint_url=endpoint_url),118]119)120121def test_s3_create(self):122sts = self.subscribe.sts123s3 = self.subscribe.s3124s3.head_bucket.side_effect = ClientError(125{'Error': {'Code': '404', 'Message': ''}}, 'HeadBucket')126127self.subscribe.setup_new_bucket('test', 'logs')128129sts.get_caller_identity.assert_called_with()130131s3.get_object.assert_called_with(132Bucket='awscloudtrail-policy-us-east-1',133Key='policy/S3/AWSCloudTrail-S3BucketPolicy-2014-12-17.json',134)135s3.create_bucket.assert_called_with(Bucket='test')136s3.put_bucket_policy.assert_called_with(137Bucket='test', Policy=u'{"Statement": []}'138)139140self.assertFalse(s3.delete_bucket.called)141142args, kwargs = s3.create_bucket.call_args143self.assertNotIn('create_bucket_configuration', kwargs)144145def test_s3_uses_regionalized_policy(self):146s3 = self.subscribe.s3147s3.head_bucket.side_effect = ClientError(148{'Error': {'Code': '404', 'Message': ''}}, 'HeadBucket')149150self.subscribe.setup_new_bucket('test', 'logs')151152s3.get_object.assert_called_with(153Bucket='awscloudtrail-policy-us-east-1', Key=mock.ANY)154155def test_s3_create_non_us_east_1(self):156# Because this is outside of us-east-1, it should create157# a bucket configuration with a location constraint.158s3 = self.subscribe.s3159self.subscribe.region_name = 'us-west-2'160s3.head_bucket.side_effect = ClientError(161{'Error': {'Code': '404', 'Message': ''}}, 'HeadBucket')162163self.subscribe.setup_new_bucket('test', 'logs')164165args, kwargs = s3.create_bucket.call_args166self.assertIn('CreateBucketConfiguration', kwargs)167168bucket_config = kwargs['CreateBucketConfiguration']169self.assertEqual(bucket_config['LocationConstraint'],170'us-west-2')171172def test_s3_create_already_exists(self):173with self.assertRaises(Exception):174self.subscribe.setup_new_bucket('test2', 'logs')175176def test_s3_custom_policy(self):177s3 = self.subscribe.s3178s3.head_bucket.side_effect = ClientError(179{'Error': {'Code': '404', 'Message': ''}}, 'HeadBucket')180181self.subscribe.setup_new_bucket('test', 'logs', custom_policy='{}')182183s3.get_object.assert_not_called()184s3.put_bucket_policy.assert_called_with(Bucket='test', Policy='{}')185186def test_s3_create_set_policy_fail(self):187s3 = self.subscribe.s3188orig = s3.put_bucket_policy189s3.put_bucket_policy = mock.Mock(side_effect=Exception('Error!'))190191with self.assertRaises(Exception):192self.subscribe.setup_new_bucket('test', 'logs')193194def test_s3_get_policy_fail(self):195self.subscribe.s3.get_object = mock.Mock(side_effect=Exception('Foo!'))196197with self.assertRaises(CloudTrailError) as cm:198self.subscribe.setup_new_bucket('test', 'logs')199200# Exception should contain its custom message, the region201# where there is an issue, and the original exception message.202self.assertIn('us-east-1', str(cm.exception))203self.assertIn('Foo!', str(cm.exception))204205def test_get_policy_read_timeout(self):206response = {207'Body': mock.Mock()208}209response['Body'].read.side_effect = Exception('Error!')210self.subscribe.s3.get_object.return_value = response211212with self.assertRaises(CloudTrailError):213self.subscribe.setup_new_bucket('test', 'logs')214215def test_sns_get_policy_fail(self):216self.subscribe.s3.get_object = mock.Mock(side_effect=Exception('Error!'))217218with self.assertRaises(Exception):219self.subscribe.setup_new_bucket('test', 'logs')220221def test_sns_create(self):222s3 = self.subscribe.s3223sns = self.subscribe.sns224225self.subscribe.setup_new_topic('test')226227s3.get_object.assert_called_with(228Bucket='awscloudtrail-policy-us-east-1',229Key='policy/SNS/AWSCloudTrail-SnsTopicPolicy-2014-12-17.json',230)231sns.list_topics.assert_called_with()232sns.create_topic.assert_called_with(Name='test')233sns.set_topic_attributes.assert_called_with(234AttributeName='Policy',235AttributeValue='{"Statement": []}',236TopicArn='foo',237)238239self.assertFalse(sns.delete_topic.called)240241def test_sns_uses_regionalized_policy(self):242s3 = self.subscribe.s3243244self.subscribe.setup_new_topic('test')245246s3.get_object.assert_called_with(247Bucket='awscloudtrail-policy-us-east-1', Key=mock.ANY)248249def test_sns_custom_policy(self):250s3 = self.subscribe.s3251sns = self.subscribe.sns252sns.get_topic_attributes.return_value = {253'Attributes': {254'Policy': '{"Statement": []}'255}256}257258policy = '{"Statement": []}'259260self.subscribe.setup_new_topic('test', custom_policy=policy)261262s3.get_object.assert_not_called()263sns.set_topic_attributes.assert_called_with(264TopicArn=mock.ANY, AttributeName='Policy', AttributeValue=policy)265266def test_sns_create_already_exists(self):267with self.assertRaises(Exception):268self.subscribe.setup_new_topic('test2')269270def test_cloudtrail_new_call_format(self):271self.subscribe.cloudtrail = mock.Mock()272self.subscribe.cloudtrail.create_trail = mock.Mock(return_value={})273self.subscribe.cloudtrail.describe_trail = mock.Mock(return_value={})274275self.subscribe.upsert_cloudtrail_config('test', 'bucket', 'prefix',276'topic', True)277278self.subscribe.cloudtrail.create_trail.assert_called_with(279Name='test',280S3BucketName='bucket',281S3KeyPrefix='prefix',282SnsTopicName='topic',283IncludeGlobalServiceEvents=True284)285286def test_sns_policy_merge(self):287left = '''288{289"Version":"2008-10-17",290"Id":"us-east-1/698519295917/test__default_policy_ID",291"Statement":[292{293"Effect":"Allow",294"Sid":"us-east-1/698519295917/test__default_statement_ID",295"Principal":{296"AWS":"*"297},298"Action":[299"SNS:GetTopicAttributes",300"SNS:SetTopicAttributes",301"SNS:AddPermission",302"SNS:RemovePermission",303"SNS:DeleteTopic",304"SNS:Subscribe",305"SNS:ListSubscriptionsByTopic",306"SNS:Publish",307"SNS:Receive"308],309"Resource":"arn:aws:sns:us-east-1:698519295917:test",310"Condition":{311"StringLike":{312"AWS:SourceArn":"arn:aws:*:*:698519295917:*"313}314}315}316]317}'''318right = '''319{320"Version":"2008-10-17",321"Id":"us-east-1/698519295917/test_foo",322"Statement":[323{324"Effect":"Allow",325"Sid":"us-east-1/698519295917/test_foo_ID",326"Principal":{327"AWS":"*"328},329"Action":[330"SNS:GetTopicAttributes",331"SNS:SetTopicAttributes",332"SNS:AddPermission",333"SNS:RemovePermission",334"SNS:DeleteTopic",335"SNS:Subscribe",336"SNS:ListSubscriptionsByTopic",337"SNS:Publish",338"SNS:Receive"339],340"Resource":"arn:aws:sns:us-east-1:698519295917:test",341"Condition":{342"StringLike":{343"AWS:SourceArn":"arn:aws:*:*:698519295917:*"344}345}346}347]348}'''349expected = '''350{351"Version":"2008-10-17",352"Id":"us-east-1/698519295917/test__default_policy_ID",353"Statement":[354{355"Effect":"Allow",356"Sid":"us-east-1/698519295917/test__default_statement_ID",357"Principal":{358"AWS":"*"359},360"Action":[361"SNS:GetTopicAttributes",362"SNS:SetTopicAttributes",363"SNS:AddPermission",364"SNS:RemovePermission",365"SNS:DeleteTopic",366"SNS:Subscribe",367"SNS:ListSubscriptionsByTopic",368"SNS:Publish",369"SNS:Receive"370],371"Resource":"arn:aws:sns:us-east-1:698519295917:test",372"Condition":{373"StringLike":{374"AWS:SourceArn":"arn:aws:*:*:698519295917:*"375}376}377},378{379"Effect":"Allow",380"Sid":"us-east-1/698519295917/test_foo_ID",381"Principal":{382"AWS":"*"383},384"Action":[385"SNS:GetTopicAttributes",386"SNS:SetTopicAttributes",387"SNS:AddPermission",388"SNS:RemovePermission",389"SNS:DeleteTopic",390"SNS:Subscribe",391"SNS:ListSubscriptionsByTopic",392"SNS:Publish",393"SNS:Receive"394],395"Resource":"arn:aws:sns:us-east-1:698519295917:test",396"Condition":{397"StringLike":{398"AWS:SourceArn":"arn:aws:*:*:698519295917:*"399}400}401}402]403}'''404405merged = self.subscribe.merge_sns_policy(left, right)406407self.assertEqual(json.loads(expected), json.loads(merged))408409410