def test_role_get_permissions_for_policy_version_no_policies( mock_get_permissions_in_policy, role_dict): r = Role(**role_dict) r.policies = {} r.get_permissions_for_policy_version() mock_get_permissions_in_policy.assert_not_called()
def test_calculate_repo_scores(self, mock_get_repoable_permissions, mock_get_role_permissions): roles = [Role(ROLES[0]), Role(ROLES[1]), Role(ROLES[2])] roles[0].disqualified_by = [] roles[0].aa_data = 'some_aa_data' # disqualified by a filter roles[1].policies = [{'Policy': ROLE_POLICIES['unused_ec2']}] roles[1].disqualified_by = ['some_filter'] roles[1].aa_data = 'some_aa_data' # no AA data roles[2].policies = [{'Policy': ROLE_POLICIES['all_services_used']}] roles[2].disqualified_by = [] roles[2].aa_data = None mock_get_role_permissions.side_effect = [[ 'iam:AddRoleToInstanceProfile', 'iam:AttachRolePolicy', 'ec2:AllocateHosts', 'ec2:AssociateAddress' ], ['iam:AddRoleToInstanceProfile', 'iam:AttachRolePolicy' ], ['iam:AddRoleToInstanceProfile', 'iam:AttachRolePolicy']] mock_get_repoable_permissions.side_effect = [ set(['iam:AddRoleToInstanceProfile', 'iam:AttachRolePolicy']) ] minimum_age = 90 repokid.utils.roledata._calculate_repo_scores(roles, minimum_age) assert roles[0].repoable_permissions == 2 assert roles[0].repoable_services == ['iam'] assert roles[1].repoable_permissions == 0 assert roles[1].repoable_services == [] assert roles[2].repoable_permissions == 0 assert roles[2].repoable_services == []
def test_role_is_eligible_for_repo_stale_aa_data(mock_stale_aa_services, role_dict): mock_stale_aa_services.return_value = ["service1", "service2"] r = Role(**role_dict) eligible, reason = r.is_eligible_for_repo() mock_stale_aa_services.assert_called_once() assert not eligible assert reason == "stale Access Advisor data for service1, service2"
def test_role_fetch_aa_data_no_arn(role_dict): role_data = copy.deepcopy(role_dict) role_data.pop("arn") role_data.pop("account") r = Role(**role_data) with pytest.raises(ModelError): r.fetch_aa_data()
def test_role_is_eligible_for_repo_no_aa_data(mock_stale_aa_services, role_dict): r = Role(**role_dict) r.aa_data = [] eligible, reason = r.is_eligible_for_repo() mock_stale_aa_services.assert_not_called() assert not eligible assert reason == "no Access Advisor data available"
def cancel_scheduled_repo(account_number, dynamo_table, role_name=None, is_all=None): """ Cancel scheduled repo for a role in an account """ if not is_all and not role_name: LOGGER.error('Either a specific role to cancel or all must be provided') return if is_all: roles = Roles([Role(get_role_data(dynamo_table, roleID)) for roleID in role_ids_for_account(dynamo_table, account_number)]) # filter to show only roles that are scheduled roles = [role for role in roles if (role.repo_scheduled)] for role in roles: set_role_data(dynamo_table, role.role_id, {'RepoScheduled': 0, 'ScheduledPerms': []}) LOGGER.info('Canceled scheduled repo for roles: {}'.format(', '.join([role.role_name for role in roles]))) return role_id = find_role_in_cache(dynamo_table, account_number, role_name) if not role_id: LOGGER.warn('Could not find role with name {} in account {}'.format(role_name, account_number)) return role = Role(get_role_data(dynamo_table, role_id)) if not role.repo_scheduled: LOGGER.warn('Repo was not scheduled for role {} in account {}'.format(role.role_name, account_number)) return set_role_data(dynamo_table, role.role_id, {'RepoScheduled': 0, 'ScheduledPerms': []}) LOGGER.info('Successfully cancelled scheduled repo for role {} in account {}'.format(role.role_name, role.account))
def list_role_rollbacks(message: Message) -> ResponderReturn: role_id = find_role_in_cache(message.role_name, message.account) if not role_id: return ResponderReturn( successful=False, return_message="Unable to find role {} in account {}".format( message.role_name, message.account ), ) role = Role(role_id=role_id) role.fetch(fields=["Policies"]) return_val = "Restorable versions for role {} in account {}\n".format( message.role_name, message.account ) for index, policy_version in enumerate(role.policies): total_permissions, _ = get_permissions_in_policy(policy_version["Policy"]) return_val += "({:>3}): {:<5} {:<15} {}\n".format( index, len(total_permissions), policy_version["Discovered"], policy_version["Source"], ) return ResponderReturn(successful=True, return_message=return_val)
def test_role_update_opt_out_future(role_dict): r = Role(**role_dict) future_dt = datetime.datetime.now() + datetime.timedelta(days=1) r.opt_out = {"expire": future_dt.timestamp()} r._update_opt_out() # opt out should not have been touched since it is not expired assert r.opt_out == {"expire": future_dt.timestamp()}
def test_role_mark_inactive(mock_store, role_dict): r = Role(**role_dict) r.active = True r.mark_inactive() assert not r.active mock_store.assert_called_once() assert mock_store.call_args[1]["fields"] == ["Active"]
def test_role_is_eligible_for_repo_disqualified(mock_stale_aa_services, role_dict): r = Role(**role_dict) r.disqualified_by = ["filter1", "filter2"] eligible, reason = r.is_eligible_for_repo() mock_stale_aa_services.assert_not_called() assert not eligible assert reason == "disqualified by filter1, filter2"
def test_role_stale_aa_services(role_dict): r = Role(**role_dict) r.config["repo_requirements"] = {"oldest_aa_data_days": 5} recent_dt = datetime.datetime.now() - datetime.timedelta(days=1) older_dt = datetime.datetime.now() - datetime.timedelta(days=14) r.aa_data = [ { "serviceName": "service1", "lastUpdated": recent_dt.isoformat() }, { "serviceName": "service2", "lastUpdated": recent_dt.isoformat() }, { "serviceName": "service3", "lastUpdated": older_dt.isoformat() }, { "serviceName": "service4", "lastUpdated": older_dt.isoformat() }, ] stale = r._stale_aa_services() assert "service1" not in stale assert "service2" not in stale assert "service3" in stale assert "service4" in stale
def test_role_is_eligible_for_repo(mock_stale_aa_services, role_dict): mock_stale_aa_services.return_value = [] r = Role(**role_dict) eligible, reason = r.is_eligible_for_repo() mock_stale_aa_services.assert_called_once() assert eligible assert not reason
def _repo_role( account_number: str, role_name: str, config: RepokidConfig, hooks: RepokidHooks, commit: bool = False, scheduled: bool = False, ) -> List[str]: """ Calculate what repoing can be done for a role and then actually do it if commit is set 1) Check that a role exists, it isn't being disqualified by a filter, and that is has fresh AA data 2) Get the role's current permissions, repoable permissions, and the new policy if it will change 3) Make the changes if commit is set Args: account_number (string) role_name (string) commit (bool) Returns: None """ role_id = find_role_in_cache(role_name, account_number) # only load partial data that we need to determine if we should keep going role = Role(role_id=role_id, config=config) role.fetch() return role.repo(hooks, commit=commit, scheduled=scheduled)
def list_repoable_services(message: Message) -> ResponderReturn: role_id = find_role_in_cache(message.role_name, message.account) if not role_id: return ResponderReturn( successful=False, return_message="Unable to find role {} in account {}".format( message.role_name, message.account), ) else: role = Role(role_id=role_id) role.fetch(fields=["RepoableServices"]) ( repoable_permissions, repoable_services, ) = get_services_and_permissions_from_repoable(role.repoable_services) return ResponderReturn( successful=True, return_message= ("Role {} in account {} has:\n Repoable Services: \n{}\n\n Repoable Permissions: \n{}" .format( message.role_name, message.account, "\n".join([service for service in repoable_services]), "\n".join([perm for perm in repoable_permissions]), )), )
def test_role_calculate_repo_scores( mock_get_permissions_for_policy_version, mock_get_repoable_permissions, mock_convert_repoable_perms_to_perms_and_services, role_dict, ): mock_get_permissions_for_policy_version.return_value = ( { "service1:action1", "service1:action2", "service2", "service3:action3" }, {"service1:action2", "service2", "service3:action3"}, ) mock_get_repoable_permissions.return_value = { "service1:action2", "service2" } mock_convert_repoable_perms_to_perms_and_services.return_value = ( {"service1:action2"}, {"service2"}, ) r = Role(**role_dict) r.calculate_repo_scores(0, {}) mock_get_permissions_for_policy_version.assert_called_once() mock_get_repoable_permissions.assert_called_once() mock_convert_repoable_perms_to_perms_and_services.assert_called_once() assert r.total_permissions == 4 assert r.repoable_services == ["service1:action2", "service2"] assert r.repoable_permissions == 2
def test_log_during_repoable_calculation_batch_hooks(self): hooks = { "DURING_REPOABLE_CALCULATION_BATCH": [log_during_repoable_calculation_batch_hooks] } input_dict = { "role_batch": [Role.parse_obj(ROLES[0]), "def"], "potentially_repoable_permissions": [], "minimum_age": 1, } with pytest.raises(repokid.hooks.MissingHookParamaeter): # role_batch', 'potentially_repoable_permissions', 'minimum_age' repokid.hooks.call_hooks(hooks, "DURING_REPOABLE_CALCULATION_BATCH", input_dict) input_dict["role_batch"] = [ Role.parse_obj(ROLES[0]), Role.parse_obj(ROLES[1]), Role.parse_obj(ROLES[2]), ] assert input_dict == repokid.hooks.call_hooks( hooks, "DURING_REPOABLE_CALCULATION_BATCH", input_dict)
def test_get_role_permissions(self, mock_all_permissions, mock_get_actions_from_statement, mock_expand_policy): test_role = Role(ROLES[0]) all_permissions = [ "ec2:associateaddress", "ec2:attachvolume", "ec2:createsnapshot", "s3:createbucket", "s3:getobject", ] # empty policy to make sure we get the latest test_role.policies = [ { "Policy": ROLE_POLICIES["all_services_used"] }, { "Policy": ROLE_POLICIES["unused_ec2"] }, ] mock_all_permissions.return_value = all_permissions mock_get_actions_from_statement.return_value = ROLE_POLICIES[ "unused_ec2"]["ec2_perms"] mock_expand_policy.return_value = ROLE_POLICIES["unused_ec2"][ "ec2_perms"] total_permissions, eligible_permissions = repokid.utils.roledata._get_role_permissions( test_role) assert total_permissions == set( ROLE_POLICIES["unused_ec2"]["ec2_perms"]) assert eligible_permissions == set( ROLE_POLICIES["unused_ec2"]["ec2_perms"])
def test_get_role_permissions(self, mock_all_permissions, mock_get_actions_from_statement, mock_expand_policy): test_role = Role(ROLES[0]) all_permissions = [ 'ec2:associateaddress', 'ec2:attachvolume', 'ec2:createsnapshot', 's3:createbucket', 's3:getobject' ] # empty policy to make sure we get the latest test_role.policies = [{ 'Policy': ROLE_POLICIES['all_services_used'] }, { 'Policy': ROLE_POLICIES['unused_ec2'] }] mock_all_permissions.return_value = all_permissions mock_get_actions_from_statement.return_value = ROLE_POLICIES[ 'unused_ec2']['ec2_perms'] mock_expand_policy.return_value = ROLE_POLICIES['unused_ec2'][ 'ec2_perms'] permissions = repokid.utils.roledata._get_role_permissions(test_role) assert permissions == set(ROLE_POLICIES['unused_ec2']['ec2_perms'])
def test_role_fetch(mock_get_role_by_id, role_dict): stored_role_data = copy.deepcopy(role_dict) stored_role_data["repoable_permissions"] = 20 mock_get_role_by_id.return_value = stored_role_data r = Role(**role_dict) assert r.repoable_permissions == 5 r.fetch() assert r.repoable_permissions == 20
def test_role_is_eligible_for_repo_no_repoable_permissions( mock_stale_aa_services, role_dict): r = Role(**role_dict) r.repoable_permissions = [] eligible, reason = r.is_eligible_for_repo() mock_stale_aa_services.assert_not_called() assert not eligible assert reason == "no repoable permissions"
def test_role_fetch_not_found(role_dict): local_role_data = copy.deepcopy(role_dict) local_role_data.pop("role_id") local_role_data.pop("role_name") local_role_data.pop("account") r = Role(**local_role_data) with pytest.raises(ModelError): r.fetch()
def test_role_get_permissions_for_policy_version( mock_get_permissions_in_policy, role_dict): r = Role(**role_dict) r.get_permissions_for_policy_version() mock_get_permissions_in_policy.assert_called_once() assert mock_get_permissions_in_policy.call_args[0][0] == vars.policies[-1][ "Policy"] assert not mock_get_permissions_in_policy.call_args[1]["warn_unknown_perms"]
def test_role_fetch_not_found(mock_get_role_by_arn, role_dict): mock_get_role_by_arn.side_effect = RoleNotFoundError local_role_data = copy.deepcopy(role_dict) local_role_data.pop("role_id") local_role_data.pop("role_name") local_role_data.pop("account") r = Role(**local_role_data) with pytest.raises(RoleNotFoundError): r.fetch()
def test_role_get_repoed_policy_no_repoable_services( mock_get_services_and_permissions_from_repoable, mock_get_repoed_policy, role_dict): r = Role(**role_dict) r.repoable_services = [] with pytest.raises(MissingRepoableServices): r.get_repoed_policy() mock_get_repoed_policy.assert_not_called() mock_get_services_and_permissions_from_repoable.assert_not_called()
def test_role_fetch_no_id(mock_get_role_by_name, role_dict): stored_role_data = copy.deepcopy(role_dict) stored_role_data["repoable_permissions"] = 20 mock_get_role_by_name.return_value = stored_role_data local_role_data = copy.deepcopy(role_dict) local_role_data.pop("role_id") r = Role(**local_role_data) assert r.repoable_permissions == 5 r.fetch() assert r.repoable_permissions == 20
def test_calculate_repo_scores(self, mock_call_hooks, mock_get_repoable_permissions, mock_get_role_permissions): roles = [Role(ROLES[0]), Role(ROLES[1]), Role(ROLES[2])] roles[0].disqualified_by = [] roles[0].aa_data = "some_aa_data" # disqualified by a filter roles[1].policies = [{"Policy": ROLE_POLICIES["unused_ec2"]}] roles[1].disqualified_by = ["some_filter"] roles[1].aa_data = "some_aa_data" # no AA data roles[2].policies = [{"Policy": ROLE_POLICIES["all_services_used"]}] roles[2].disqualified_by = [] roles[2].aa_data = None hooks = {} mock_get_role_permissions.side_effect = [ ( [ "iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy", "ec2:AllocateHosts", "ec2:AssociateAddress", ], ["iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy"], ), ( ["iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy"], ["iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy"], ), ( ["iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy"], ["iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy"], ), ] mock_call_hooks.return_value = set( ["iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy"]) mock_get_repoable_permissions.side_effect = [ set(["iam:AddRoleToInstanceProfile", "iam:AttachRolePolicy"]) ] minimum_age = 90 repokid.utils.roledata._calculate_repo_scores(roles, minimum_age, hooks) assert roles[0].repoable_permissions == 2 assert roles[0].repoable_services == ["iam"] assert roles[1].repoable_permissions == 0 assert roles[1].repoable_services == [] assert roles[2].repoable_permissions == 0 assert roles[2].repoable_services == []
def test_role_get_repoed_policy( mock_get_services_and_permissions_from_repoable, mock_get_repoed_policy, role_dict ): mock_get_repoed_policy.return_value = ({"repoed": "woohoo"}, ["old_policy_name"]) r = Role(**role_dict) repoed_policies, deleted_policy_names = r.get_repoed_policy(scheduled=False) mock_get_repoed_policy.assert_called_once() mock_get_services_and_permissions_from_repoable.assert_not_called() assert mock_get_repoed_policy.call_args[0][0] == vars.policies[-1]["Policy"] assert mock_get_repoed_policy.call_args[0][1] == set(vars.repoable_services) assert repoed_policies == {"repoed": "woohoo"} assert deleted_policy_names == ["old_policy_name"]
def test_role_add_policy_version_duplicate(mock_store, mock_calculate_no_repo_permissions, role_dict): r = Role(**role_dict) source = "Fixture" fake_policy = vars.policies[0]["Policy"] assert len(r.policies) == 1 r.add_policy_version(fake_policy, source=source, store=True) assert len(r.policies) == 1 assert r.policies[0]["Policy"] == fake_policy mock_calculate_no_repo_permissions.assert_not_called() mock_store.assert_not_called()
def test_role_store_create(mock_create_dynamodb_entry, mock_get_role_by_id, role_dict, role_dict_with_aliases): expected = copy.deepcopy(role_dict_with_aliases) mock_get_role_by_id.side_effect = RoleNotFoundError r = Role(**role_dict) r.store() mock_create_dynamodb_entry.assert_called_once() # Remove LastUpdated from the fn call and expected dict so we can compare the rest mock_create_dynamodb_entry.call_args[0][0].pop("LastUpdated") expected.pop("LastUpdated") assert mock_create_dynamodb_entry.call_args[0][0] == expected
def test_role_store_remote_updated(mock_get_role_by_id, role_dict, role_dict_with_aliases): expected = copy.deepcopy(role_dict_with_aliases) expected.pop("RoleId") expected.pop("RoleName") expected.pop("Account") # simulate the record having been updated in DynamoDB since we last fetched it last_updated = (vars.last_updated + datetime.timedelta(hours=2)).strftime("%Y-%m-%d %H:%M") mock_get_role_by_id.return_value = {"LastUpdated": last_updated} r = Role(**role_dict) with pytest.raises(IntegrityError): r.store()