different aggregation strategy
This commit is contained in:
parent
d8cea0213d
commit
8d5c70a924
@ -48,6 +48,7 @@ from moto.config.exceptions import (
|
|||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.s3.config import s3_account_public_access_block_query, s3_config_query
|
from moto.s3.config import s3_account_public_access_block_query, s3_config_query
|
||||||
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
|
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
|
||||||
|
|
||||||
from moto.iam.config import role_config_query, policy_config_query
|
from moto.iam.config import role_config_query, policy_config_query
|
||||||
|
|
||||||
POP_STRINGS = [
|
POP_STRINGS = [
|
||||||
@ -68,6 +69,29 @@ RESOURCE_MAP = {
|
|||||||
"AWS::IAM::Policy": policy_config_query,
|
"AWS::IAM::Policy": policy_config_query,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CONFIG_REGIONS = [
|
||||||
|
"af-south-1",
|
||||||
|
"ap-east-1",
|
||||||
|
"ap-northeast-1",
|
||||||
|
"ap-northeast-2",
|
||||||
|
"ap-south-1",
|
||||||
|
"ap-southeast-1",
|
||||||
|
"ap-southeast-2",
|
||||||
|
"ca-central-1",
|
||||||
|
"eu-central-1",
|
||||||
|
"eu-north-1",
|
||||||
|
"eu-south-1",
|
||||||
|
"eu-west-1",
|
||||||
|
"eu-west-2",
|
||||||
|
"eu-west-3",
|
||||||
|
"me-south-1",
|
||||||
|
"sa-east-1",
|
||||||
|
"us-east-1",
|
||||||
|
"us-east-2",
|
||||||
|
"us-west-1",
|
||||||
|
"us-west-2",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def datetime2int(date):
|
def datetime2int(date):
|
||||||
return int(time.mktime(date.timetuple()))
|
return int(time.mktime(date.timetuple()))
|
||||||
@ -979,6 +1003,7 @@ class ConfigBackend(BaseBackend):
|
|||||||
limit,
|
limit,
|
||||||
next_token,
|
next_token,
|
||||||
resource_region=resource_region,
|
resource_region=resource_region,
|
||||||
|
aggregator=self.config_aggregators.get(aggregator_name).__dict__,
|
||||||
)
|
)
|
||||||
|
|
||||||
resource_identifiers = []
|
resource_identifiers = []
|
||||||
@ -989,7 +1014,6 @@ class ConfigBackend(BaseBackend):
|
|||||||
"ResourceType": identifier["type"],
|
"ResourceType": identifier["type"],
|
||||||
"ResourceId": identifier["id"],
|
"ResourceId": identifier["id"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if identifier.get("name"):
|
if identifier.get("name"):
|
||||||
item["ResourceName"] = identifier["name"]
|
item["ResourceName"] = identifier["name"]
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ from .utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
ACCOUNT_ID = os.environ.get("MOTO_ACCOUNT_ID", "123456789012")
|
ACCOUNT_ID = os.environ.get("MOTO_ACCOUNT_ID", "123456789012")
|
||||||
CONFIG_BACKEND_DELIM = "\x1e" # Record Seperator "RS" ASCII Character
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMockAWS(object):
|
class BaseMockAWS(object):
|
||||||
@ -723,6 +722,8 @@ class ConfigQueryModel(object):
|
|||||||
:param backend_region: The region for the backend to pull results from. Set to `None` if this is an aggregated query.
|
:param backend_region: The region for the backend to pull results from. Set to `None` if this is an aggregated query.
|
||||||
:param resource_region: The region for where the resources reside to pull results from. Set to `None` if this is a
|
:param resource_region: The region for where the resources reside to pull results from. Set to `None` if this is a
|
||||||
non-aggregated query.
|
non-aggregated query.
|
||||||
|
:param aggregator: If an aggregated query, this will be the `ConfigAggregator instance from the backend. Set to `None`
|
||||||
|
if a non-aggregated query. Useful if you need special logic based off the aggregator (ie IAM)
|
||||||
:return: This should return a list of Dicts that have the following fields:
|
:return: This should return a list of Dicts that have the following fields:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -766,47 +767,6 @@ class ConfigQueryModel(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def aggregate_regions(self, path, backend_region, resource_region):
|
|
||||||
"""
|
|
||||||
This method is called for both aggregated and non-aggregated calls for config resources.
|
|
||||||
|
|
||||||
It will figure out how to return the full list of resources for a given regional backend and append them to a final list.
|
|
||||||
It produces a list of both the region and the resource name with a delimiter character (CONFIG_BACKEND_DELIM, ASCII Record separator, \x1e).
|
|
||||||
IE: "us-east-1\x1ei-1234567800"
|
|
||||||
|
|
||||||
You should only use this method if you need to aggregate resources over more than one region.
|
|
||||||
If your region is global, just query the global backend directly in the `list_config_service_resources` method
|
|
||||||
|
|
||||||
If you use this method, your config-enabled resource must parse the delimited string in it's `list_config_service_resources` method.
|
|
||||||
...
|
|
||||||
:param path: - A dict accessor string applied to the backend that locates resources inside that backend.
|
|
||||||
For example, if you passed path="keypairs", and you were working with an ec2 moto backend, it would yield the contents from
|
|
||||||
ec2_moto_backend[region].keypairs
|
|
||||||
:param backend_region: - Only used for filtering; A string representing the region IE: us-east-1
|
|
||||||
:param resource_region: - Only used for filtering; A string representing the region IE: us-east-1
|
|
||||||
:return: - Returns a list of "region\x1eresourcename" strings
|
|
||||||
"""
|
|
||||||
|
|
||||||
filter_region = backend_region or resource_region
|
|
||||||
if filter_region:
|
|
||||||
filter_resources = list(self.backends[filter_region].__dict__[path].keys())
|
|
||||||
return list(
|
|
||||||
map(
|
|
||||||
lambda resource: "{}{}{}".format(
|
|
||||||
filter_region, CONFIG_BACKEND_DELIM, resource
|
|
||||||
),
|
|
||||||
filter_resources,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# If we don't have a filter region
|
|
||||||
ret = []
|
|
||||||
for region in self.backends:
|
|
||||||
this_region_resources = list(self.backends[region].__dict__[path].keys())
|
|
||||||
for resource in this_region_resources:
|
|
||||||
ret.append("{}{}{}".format(region, CONFIG_BACKEND_DELIM, resource))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class base_decorator(object):
|
class base_decorator(object):
|
||||||
mock_backend = MockAWS
|
mock_backend = MockAWS
|
||||||
|
@ -4,8 +4,6 @@ from moto.core.exceptions import InvalidNextTokenException
|
|||||||
from moto.core.models import ConfigQueryModel
|
from moto.core.models import ConfigQueryModel
|
||||||
from moto.iam import iam_backends
|
from moto.iam import iam_backends
|
||||||
|
|
||||||
CONFIG_BACKEND_DELIM = "\x1e" # Record Seperator "RS" ASCII Character
|
|
||||||
|
|
||||||
|
|
||||||
class RoleConfigQuery(ConfigQueryModel):
|
class RoleConfigQuery(ConfigQueryModel):
|
||||||
def list_config_service_resources(
|
def list_config_service_resources(
|
||||||
@ -16,6 +14,7 @@ class RoleConfigQuery(ConfigQueryModel):
|
|||||||
next_token,
|
next_token,
|
||||||
backend_region=None,
|
backend_region=None,
|
||||||
resource_region=None,
|
resource_region=None,
|
||||||
|
aggregator=None,
|
||||||
):
|
):
|
||||||
# IAM roles are "global" and aren't assigned into any availability zone
|
# IAM roles are "global" and aren't assigned into any availability zone
|
||||||
# The resource ID is a AWS-assigned random string like "AROA0BSVNSZKXVHS00SBJ"
|
# The resource ID is a AWS-assigned random string like "AROA0BSVNSZKXVHS00SBJ"
|
||||||
@ -31,12 +30,16 @@ class RoleConfigQuery(ConfigQueryModel):
|
|||||||
# Filter by resource name or ids
|
# Filter by resource name or ids
|
||||||
if resource_name or resource_ids:
|
if resource_name or resource_ids:
|
||||||
filtered_roles = []
|
filtered_roles = []
|
||||||
# resource_name takes precendence over resource_ids
|
# resource_name takes precedence over resource_ids
|
||||||
if resource_name:
|
if resource_name:
|
||||||
for role in role_list:
|
for role in role_list:
|
||||||
if role.name == resource_name:
|
if role.name == resource_name:
|
||||||
filtered_roles = [role]
|
filtered_roles = [role]
|
||||||
break
|
break
|
||||||
|
# but if both are passed, it must be a subset
|
||||||
|
if filtered_roles and resource_ids:
|
||||||
|
if filtered_roles[0].id not in resource_ids:
|
||||||
|
return [], None
|
||||||
else:
|
else:
|
||||||
for role in role_list:
|
for role in role_list:
|
||||||
if role.id in resource_ids:
|
if role.id in resource_ids:
|
||||||
@ -45,10 +48,54 @@ class RoleConfigQuery(ConfigQueryModel):
|
|||||||
# Filtered roles are now the subject for the listing
|
# Filtered roles are now the subject for the listing
|
||||||
role_list = filtered_roles
|
role_list = filtered_roles
|
||||||
|
|
||||||
# Pagination logic, sort by role id
|
if aggregator:
|
||||||
sorted_roles = sorted(role_list, key=lambda role: role.id)
|
# IAM is a little special; Roles are created in us-east-1 (which AWS calls the "global" region)
|
||||||
# sorted_role_ids matches indicies of sorted_roles
|
# However, the resource will return in the aggregator (in duplicate) for each region in the aggregator
|
||||||
sorted_role_ids = list(map(lambda role: role.id, sorted_roles))
|
# Therefore, we'll need to find out the regions where the aggregators are running, and then duplicate the resource there
|
||||||
|
|
||||||
|
# In practice, it looks like AWS will only duplicate these resources if you've "used" any roles in the region, but since
|
||||||
|
# we can't really tell if this has happened in moto, we'll just bind this to the regions in your aggregator
|
||||||
|
from moto.config.models import CONFIG_REGIONS
|
||||||
|
|
||||||
|
aggregated_regions = []
|
||||||
|
aggregator_sources = aggregator.get(
|
||||||
|
"account_aggregation_sources"
|
||||||
|
) or aggregator.get("organization_aggregation_source")
|
||||||
|
for source in aggregator_sources:
|
||||||
|
source_dict = source.__dict__
|
||||||
|
if source_dict["all_aws_regions"]:
|
||||||
|
aggregated_regions = CONFIG_REGIONS
|
||||||
|
break
|
||||||
|
for region in source_dict["aws_regions"]:
|
||||||
|
aggregated_regions.append(region)
|
||||||
|
|
||||||
|
duplicate_role_list = []
|
||||||
|
for region in list(set(aggregated_regions)):
|
||||||
|
for role in role_list:
|
||||||
|
duplicate_role_list.append(
|
||||||
|
{
|
||||||
|
"_id": "{}{}".format(
|
||||||
|
role.id, region
|
||||||
|
), # this is only for sorting, isn't returned outside of this functin
|
||||||
|
"type": "AWS::IAM::Role",
|
||||||
|
"id": role.id,
|
||||||
|
"name": role.name,
|
||||||
|
"region": region,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pagination logic, sort by role id
|
||||||
|
sorted_roles = sorted(duplicate_role_list, key=lambda role: role["_id"])
|
||||||
|
|
||||||
|
# sorted_role_ids matches indicies of sorted_roles
|
||||||
|
sorted_role_ids = list(map(lambda role: role["_id"], sorted_roles))
|
||||||
|
else:
|
||||||
|
# Non-aggregated queries are in the else block, and we can treat these like a normal config resource
|
||||||
|
# Pagination logic, sort by role id
|
||||||
|
sorted_roles = sorted(role_list, key=lambda role: role.id)
|
||||||
|
# sorted_role_ids matches indicies of sorted_roles
|
||||||
|
sorted_role_ids = list(map(lambda role: role.id, sorted_roles))
|
||||||
|
|
||||||
new_token = None
|
new_token = None
|
||||||
|
|
||||||
# Get the start:
|
# Get the start:
|
||||||
@ -70,9 +117,9 @@ class RoleConfigQuery(ConfigQueryModel):
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "AWS::IAM::Role",
|
"type": "AWS::IAM::Role",
|
||||||
"id": role.id,
|
"id": role["id"] if aggregator else role.id,
|
||||||
"name": role.name,
|
"name": role["name"] if aggregator else role.name,
|
||||||
"region": "global",
|
"region": role["region"] if aggregator else "global",
|
||||||
}
|
}
|
||||||
for role in role_list
|
for role in role_list
|
||||||
],
|
],
|
||||||
@ -114,6 +161,7 @@ class PolicyConfigQuery(ConfigQueryModel):
|
|||||||
next_token,
|
next_token,
|
||||||
backend_region=None,
|
backend_region=None,
|
||||||
resource_region=None,
|
resource_region=None,
|
||||||
|
aggregator=None,
|
||||||
):
|
):
|
||||||
# IAM policies are "global" and aren't assigned into any availability zone
|
# IAM policies are "global" and aren't assigned into any availability zone
|
||||||
# The resource ID is a AWS-assigned random string like "ANPA0BSVNSZK00SJSPVUJ"
|
# The resource ID is a AWS-assigned random string like "ANPA0BSVNSZK00SJSPVUJ"
|
||||||
@ -126,8 +174,11 @@ class PolicyConfigQuery(ConfigQueryModel):
|
|||||||
# respect the configuration recorder's 'includeGlobalResourceTypes' setting,
|
# respect the configuration recorder's 'includeGlobalResourceTypes' setting,
|
||||||
# but it's default set be default, and moto's config doesn't yet support
|
# but it's default set be default, and moto's config doesn't yet support
|
||||||
# custom configuration recorders, we'll just behave as default.
|
# custom configuration recorders, we'll just behave as default.
|
||||||
policy_list = filter(
|
policy_list = list(
|
||||||
lambda policy: not policy.arn.startswith("arn:aws:iam::aws"), policy_list,
|
filter(
|
||||||
|
lambda policy: not policy.arn.startswith("arn:aws:iam::aws"),
|
||||||
|
policy_list,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not policy_list:
|
if not policy_list:
|
||||||
@ -136,12 +187,17 @@ class PolicyConfigQuery(ConfigQueryModel):
|
|||||||
# Filter by resource name or ids
|
# Filter by resource name or ids
|
||||||
if resource_name or resource_ids:
|
if resource_name or resource_ids:
|
||||||
filtered_policies = []
|
filtered_policies = []
|
||||||
# resource_name takes precendence over resource_ids
|
# resource_name takes precedence over resource_ids
|
||||||
if resource_name:
|
if resource_name:
|
||||||
for policy in policy_list:
|
for policy in policy_list:
|
||||||
if policy.name == resource_name:
|
if policy.name == resource_name:
|
||||||
filtered_policies = [policy]
|
filtered_policies = [policy]
|
||||||
break
|
break
|
||||||
|
# but if both are passed, it must be a subset
|
||||||
|
if filtered_policies and resource_ids:
|
||||||
|
if filtered_policies[0].id not in resource_ids:
|
||||||
|
return [], None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for policy in policy_list:
|
for policy in policy_list:
|
||||||
if policy.id in resource_ids:
|
if policy.id in resource_ids:
|
||||||
@ -150,10 +206,55 @@ class PolicyConfigQuery(ConfigQueryModel):
|
|||||||
# Filtered roles are now the subject for the listing
|
# Filtered roles are now the subject for the listing
|
||||||
policy_list = filtered_policies
|
policy_list = filtered_policies
|
||||||
|
|
||||||
# Pagination logic, sort by role id
|
if aggregator:
|
||||||
sorted_policies = sorted(policy_list, key=lambda role: role.id)
|
# IAM is a little special; Policies are created in us-east-1 (which AWS calls the "global" region)
|
||||||
# sorted_policy_ids matches indicies of sorted_policies
|
# However, the resource will return in the aggregator (in duplicate) for each region in the aggregator
|
||||||
sorted_policy_ids = list(map(lambda policy: policy.id, sorted_policies))
|
# Therefore, we'll need to find out the regions where the aggregators are running, and then duplicate the resource there
|
||||||
|
|
||||||
|
# In practice, it looks like AWS will only duplicate these resources if you've "used" any policies in the region, but since
|
||||||
|
# we can't really tell if this has happened in moto, we'll just bind this to the regions in your aggregator
|
||||||
|
from moto.config.models import CONFIG_REGIONS
|
||||||
|
|
||||||
|
aggregated_regions = []
|
||||||
|
aggregator_sources = aggregator.get(
|
||||||
|
"account_aggregation_sources"
|
||||||
|
) or aggregator.get("organization_aggregation_source")
|
||||||
|
for source in aggregator_sources:
|
||||||
|
source_dict = source.__dict__
|
||||||
|
if source_dict["all_aws_regions"]:
|
||||||
|
aggregated_regions = CONFIG_REGIONS
|
||||||
|
break
|
||||||
|
for region in source_dict["aws_regions"]:
|
||||||
|
aggregated_regions.append(region)
|
||||||
|
|
||||||
|
duplicate_policy_list = []
|
||||||
|
for region in list(set(aggregated_regions)):
|
||||||
|
for policy in policy_list:
|
||||||
|
duplicate_policy_list.append(
|
||||||
|
{
|
||||||
|
"_id": "{}{}".format(
|
||||||
|
policy.id, region
|
||||||
|
), # this is only for sorting, isn't returned outside of this functin
|
||||||
|
"type": "AWS::IAM::Policy",
|
||||||
|
"id": policy.id,
|
||||||
|
"name": policy.name,
|
||||||
|
"region": region,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pagination logic, sort by role id
|
||||||
|
sorted_policies = sorted(
|
||||||
|
duplicate_policy_list, key=lambda policy: policy["_id"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# sorted_policy_ids matches indicies of sorted_policies
|
||||||
|
sorted_policy_ids = list(map(lambda policy: policy["_id"], sorted_policies))
|
||||||
|
else:
|
||||||
|
# Non-aggregated queries are in the else block, and we can treat these like a normal config resource
|
||||||
|
# Pagination logic, sort by role id
|
||||||
|
sorted_policies = sorted(policy_list, key=lambda role: role.id)
|
||||||
|
# sorted_policy_ids matches indicies of sorted_policies
|
||||||
|
sorted_policy_ids = list(map(lambda policy: policy.id, sorted_policies))
|
||||||
|
|
||||||
new_token = None
|
new_token = None
|
||||||
|
|
||||||
@ -176,9 +277,9 @@ class PolicyConfigQuery(ConfigQueryModel):
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "AWS::IAM::Policy",
|
"type": "AWS::IAM::Policy",
|
||||||
"id": policy.id,
|
"id": policy["id"] if aggregator else policy.id,
|
||||||
"name": policy.name,
|
"name": policy["name"] if aggregator else policy.name,
|
||||||
"region": "global",
|
"region": policy["region"] if aggregator else "global",
|
||||||
}
|
}
|
||||||
for policy in policy_list
|
for policy in policy_list
|
||||||
],
|
],
|
||||||
|
@ -19,6 +19,7 @@ class S3ConfigQuery(ConfigQueryModel):
|
|||||||
next_token,
|
next_token,
|
||||||
backend_region=None,
|
backend_region=None,
|
||||||
resource_region=None,
|
resource_region=None,
|
||||||
|
aggregator=None,
|
||||||
):
|
):
|
||||||
# The resource_region only matters for aggregated queries as you can filter on bucket regions for them.
|
# The resource_region only matters for aggregated queries as you can filter on bucket regions for them.
|
||||||
# For other resource types, you would need to iterate appropriately for the backend_region.
|
# For other resource types, you would need to iterate appropriately for the backend_region.
|
||||||
@ -132,6 +133,7 @@ class S3AccountPublicAccessBlockConfigQuery(ConfigQueryModel):
|
|||||||
next_token,
|
next_token,
|
||||||
backend_region=None,
|
backend_region=None,
|
||||||
resource_region=None,
|
resource_region=None,
|
||||||
|
aggregator=None,
|
||||||
):
|
):
|
||||||
# For the Account Public Access Block, they are the same for all regions. The resource ID is the AWS account ID
|
# For the Account Public Access Block, they are the same for all regions. The resource ID is the AWS account ID
|
||||||
# There is no resource name -- it should be a blank string "" if provided.
|
# There is no resource name -- it should be a blank string "" if provided.
|
||||||
|
@ -2905,27 +2905,62 @@ def test_role_list_config_discovered_resources():
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a role
|
# Make 3 roles
|
||||||
role_config_query.backends["global"].create_role(
|
roles = []
|
||||||
role_name="something",
|
num_roles = 3
|
||||||
assume_role_policy_document=None,
|
for ix in range(1, num_roles + 1):
|
||||||
path="/",
|
this_role = role_config_query.backends["global"].create_role(
|
||||||
permissions_boundary=None,
|
role_name="role{}".format(ix),
|
||||||
description="something",
|
assume_role_policy_document=None,
|
||||||
tags=[],
|
path="/",
|
||||||
max_session_duration=3600,
|
permissions_boundary=None,
|
||||||
)
|
description="role{}".format(ix),
|
||||||
|
tags=[{"Key": "foo", "Value": "bar"}],
|
||||||
|
max_session_duration=3600,
|
||||||
|
)
|
||||||
|
roles.append(
|
||||||
|
{"id": this_role.id, "name": this_role.name,}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(roles) == num_roles
|
||||||
|
|
||||||
result = role_config_query.list_config_service_resources(None, None, 100, None)[0]
|
result = role_config_query.list_config_service_resources(None, None, 100, None)[0]
|
||||||
assert len(result) == 1
|
assert len(result) == num_roles
|
||||||
|
|
||||||
# The role gets a random ID, so we have to grab it
|
# The roles gets a random ID, so we can't directly test it
|
||||||
role = result[0]
|
role = result[0]
|
||||||
assert role["type"] == "AWS::IAM::Role"
|
assert role["type"] == "AWS::IAM::Role"
|
||||||
assert len(role["id"]) == len(random_resource_id())
|
assert role["id"] in list(map(lambda p: p["id"], roles))
|
||||||
assert role["name"] == "something"
|
assert role["name"] in list(map(lambda p: p["name"], roles))
|
||||||
assert role["region"] == "global"
|
assert role["region"] == "global"
|
||||||
|
|
||||||
|
# test passing list of resource ids
|
||||||
|
resource_ids = role_config_query.list_config_service_resources(
|
||||||
|
[roles[0]["id"], roles[1]["id"]], None, 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(resource_ids) == 2
|
||||||
|
|
||||||
|
# test passing a single resource name
|
||||||
|
resource_name = role_config_query.list_config_service_resources(
|
||||||
|
None, roles[0]["name"], 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(resource_name) == 1
|
||||||
|
assert resource_name[0]["id"] == roles[0]["id"]
|
||||||
|
assert resource_name[0]["name"] == roles[0]["name"]
|
||||||
|
|
||||||
|
# test passing a single resource name AND some resource id's
|
||||||
|
both_filter_good = role_config_query.list_config_service_resources(
|
||||||
|
[roles[0]["id"], roles[1]["id"]], roles[0]["name"], 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(both_filter_good) == 1
|
||||||
|
assert both_filter_good[0]["id"] == roles[0]["id"]
|
||||||
|
assert both_filter_good[0]["name"] == roles[0]["name"]
|
||||||
|
|
||||||
|
both_filter_bad = role_config_query.list_config_service_resources(
|
||||||
|
[roles[0]["id"], roles[1]["id"]], roles[2]["name"], 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(both_filter_bad) == 0
|
||||||
|
|
||||||
|
|
||||||
@mock_iam
|
@mock_iam
|
||||||
def test_role_config_dict():
|
def test_role_config_dict():
|
||||||
@ -3200,18 +3235,29 @@ def test_role_config_dict():
|
|||||||
def test_role_config_client():
|
def test_role_config_client():
|
||||||
from moto.iam.models import ACCOUNT_ID
|
from moto.iam.models import ACCOUNT_ID
|
||||||
from moto.iam.utils import random_resource_id
|
from moto.iam.utils import random_resource_id
|
||||||
|
from moto.config.models import CONFIG_REGIONS
|
||||||
|
|
||||||
iam_client = boto3.client("iam", region_name="us-west-2")
|
iam_client = boto3.client("iam", region_name="us-west-2")
|
||||||
config_client = boto3.client("config", region_name="us-west-2")
|
config_client = boto3.client("config", region_name="us-west-2")
|
||||||
|
|
||||||
account_aggregation_source = {
|
all_account_aggregation_source = {
|
||||||
"AccountIds": [ACCOUNT_ID],
|
"AccountIds": [ACCOUNT_ID],
|
||||||
"AllAwsRegions": True,
|
"AllAwsRegions": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
two_region_account_aggregation_source = {
|
||||||
|
"AccountIds": [ACCOUNT_ID],
|
||||||
|
"AwsRegions": ["us-east-1", "us-west-2"],
|
||||||
|
}
|
||||||
|
|
||||||
config_client.put_configuration_aggregator(
|
config_client.put_configuration_aggregator(
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
AccountAggregationSources=[account_aggregation_source],
|
AccountAggregationSources=[all_account_aggregation_source],
|
||||||
|
)
|
||||||
|
|
||||||
|
config_client.put_configuration_aggregator(
|
||||||
|
ConfigurationAggregatorName="test_aggregator_two_regions",
|
||||||
|
AccountAggregationSources=[two_region_account_aggregation_source],
|
||||||
)
|
)
|
||||||
|
|
||||||
result = config_client.list_discovered_resources(resourceType="AWS::IAM::Role")
|
result = config_client.list_discovered_resources(resourceType="AWS::IAM::Role")
|
||||||
@ -3251,29 +3297,88 @@ def test_role_config_client():
|
|||||||
)["resourceIdentifiers"][0]["resourceId"]
|
)["resourceIdentifiers"][0]["resourceId"]
|
||||||
) != first_result
|
) != first_result
|
||||||
|
|
||||||
# Test aggregated query: (everything is getting a random id, so we can't test names by ordering)
|
# Test aggregated query - by `Limit=len(CONFIG_REGIONS)`, we should get a single policy duplicated across all regions
|
||||||
agg_result = config_client.list_aggregate_discovered_resources(
|
agg_result = config_client.list_aggregate_discovered_resources(
|
||||||
ResourceType="AWS::IAM::Role",
|
ResourceType="AWS::IAM::Role",
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
Limit=1,
|
Limit=len(CONFIG_REGIONS),
|
||||||
)
|
)
|
||||||
first_agg_result = agg_result["ResourceIdentifiers"][0]["ResourceId"]
|
assert len(agg_result["ResourceIdentifiers"]) == len(CONFIG_REGIONS)
|
||||||
assert agg_result["ResourceIdentifiers"][0]["ResourceType"] == "AWS::IAM::Role"
|
|
||||||
assert len(first_agg_result) == len(random_resource_id())
|
agg_name = None
|
||||||
assert agg_result["ResourceIdentifiers"][0]["SourceAccountId"] == ACCOUNT_ID
|
agg_id = None
|
||||||
assert agg_result["ResourceIdentifiers"][0]["SourceRegion"] == "global"
|
for resource in agg_result["ResourceIdentifiers"]:
|
||||||
|
assert resource["ResourceType"] == "AWS::IAM::Role"
|
||||||
|
assert resource["SourceRegion"] in CONFIG_REGIONS
|
||||||
|
assert resource["SourceAccountId"] == ACCOUNT_ID
|
||||||
|
if agg_id:
|
||||||
|
assert resource["ResourceId"] == agg_id
|
||||||
|
if agg_name:
|
||||||
|
assert resource["ResourceName"] == agg_name
|
||||||
|
agg_name = resource["ResourceName"]
|
||||||
|
agg_id = resource["ResourceId"]
|
||||||
|
|
||||||
# Test aggregated pagination
|
# Test aggregated pagination
|
||||||
|
for resource in config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
|
ResourceType="AWS::IAM::Role",
|
||||||
|
NextToken=agg_result["NextToken"],
|
||||||
|
)["ResourceIdentifiers"]:
|
||||||
|
assert resource["ResourceId"] != agg_id
|
||||||
|
|
||||||
|
# Test non-aggregated resource name/id filter
|
||||||
assert (
|
assert (
|
||||||
config_client.list_aggregate_discovered_resources(
|
config_client.list_discovered_resources(
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
resourceType="AWS::IAM::Role", resourceName=roles[1]["name"], limit=1,
|
||||||
ResourceType="AWS::IAM::Role",
|
)["resourceIdentifiers"][0]["resourceName"]
|
||||||
Limit=1,
|
== roles[1]["name"]
|
||||||
NextToken=agg_result["NextToken"],
|
|
||||||
)["ResourceIdentifiers"][0]["ResourceId"]
|
|
||||||
!= first_agg_result
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
config_client.list_discovered_resources(
|
||||||
|
resourceType="AWS::IAM::Role", resourceIds=[roles[0]["id"]], limit=1,
|
||||||
|
)["resourceIdentifiers"][0]["resourceName"]
|
||||||
|
== roles[0]["name"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test aggregated resource name/id filter
|
||||||
|
agg_name_filter = config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
|
ResourceType="AWS::IAM::Role",
|
||||||
|
Filters={"ResourceName": roles[5]["name"]},
|
||||||
|
)
|
||||||
|
assert len(agg_name_filter["ResourceIdentifiers"]) == len(CONFIG_REGIONS)
|
||||||
|
assert agg_name_filter["ResourceIdentifiers"][0]["ResourceId"] == roles[5]["id"]
|
||||||
|
|
||||||
|
agg_name_filter = config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator_two_regions",
|
||||||
|
ResourceType="AWS::IAM::Role",
|
||||||
|
Filters={"ResourceName": roles[5]["name"]},
|
||||||
|
)
|
||||||
|
assert len(agg_name_filter["ResourceIdentifiers"]) == len(
|
||||||
|
two_region_account_aggregation_source["AwsRegions"]
|
||||||
|
)
|
||||||
|
assert agg_name_filter["ResourceIdentifiers"][0]["ResourceId"] == roles[5]["id"]
|
||||||
|
|
||||||
|
agg_id_filter = config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
|
ResourceType="AWS::IAM::Role",
|
||||||
|
Filters={"ResourceId": roles[4]["id"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agg_id_filter["ResourceIdentifiers"]) == len(CONFIG_REGIONS)
|
||||||
|
assert agg_id_filter["ResourceIdentifiers"][0]["ResourceName"] == roles[4]["name"]
|
||||||
|
|
||||||
|
agg_name_filter = config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator_two_regions",
|
||||||
|
ResourceType="AWS::IAM::Role",
|
||||||
|
Filters={"ResourceId": roles[5]["id"]},
|
||||||
|
)
|
||||||
|
assert len(agg_name_filter["ResourceIdentifiers"]) == len(
|
||||||
|
two_region_account_aggregation_source["AwsRegions"]
|
||||||
|
)
|
||||||
|
assert agg_name_filter["ResourceIdentifiers"][0]["ResourceName"] == roles[5]["name"]
|
||||||
|
|
||||||
# Test non-aggregated resource name/id filter
|
# Test non-aggregated resource name/id filter
|
||||||
assert (
|
assert (
|
||||||
config_client.list_discovered_resources(
|
config_client.list_discovered_resources(
|
||||||
@ -3354,7 +3459,7 @@ def test_role_config_client():
|
|||||||
ResourceIdentifiers=[
|
ResourceIdentifiers=[
|
||||||
{
|
{
|
||||||
"SourceAccountId": ACCOUNT_ID,
|
"SourceAccountId": ACCOUNT_ID,
|
||||||
"SourceRegion": "global",
|
"SourceRegion": "us-east-1",
|
||||||
"ResourceId": roles[1]["id"],
|
"ResourceId": roles[1]["id"],
|
||||||
"ResourceType": "AWS::IAM::Role",
|
"ResourceType": "AWS::IAM::Role",
|
||||||
}
|
}
|
||||||
@ -3382,13 +3487,21 @@ def test_policy_list_config_discovered_resources():
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a policy
|
# Make 3 policies
|
||||||
policy_config_query.backends["global"].create_policy(
|
policies = []
|
||||||
description="mypolicy",
|
num_policies = 3
|
||||||
path="",
|
for ix in range(1, num_policies + 1):
|
||||||
policy_document=json.dumps(basic_policy),
|
this_policy = policy_config_query.backends["global"].create_policy(
|
||||||
policy_name="mypolicy",
|
description="policy{}".format(ix),
|
||||||
)
|
path="",
|
||||||
|
policy_document=json.dumps(basic_policy),
|
||||||
|
policy_name="policy{}".format(ix),
|
||||||
|
)
|
||||||
|
policies.append(
|
||||||
|
{"id": this_policy.id, "name": this_policy.name,}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(policies) == num_policies
|
||||||
|
|
||||||
# We expect the backend to have arns as their keys
|
# We expect the backend to have arns as their keys
|
||||||
for backend_key in list(
|
for backend_key in list(
|
||||||
@ -3397,14 +3510,41 @@ def test_policy_list_config_discovered_resources():
|
|||||||
assert backend_key.startswith("arn:aws:iam::")
|
assert backend_key.startswith("arn:aws:iam::")
|
||||||
|
|
||||||
result = policy_config_query.list_config_service_resources(None, None, 100, None)[0]
|
result = policy_config_query.list_config_service_resources(None, None, 100, None)[0]
|
||||||
assert len(result) == 1
|
assert len(result) == num_policies
|
||||||
|
|
||||||
policy = result[0]
|
policy = result[0]
|
||||||
assert policy["type"] == "AWS::IAM::Policy"
|
assert policy["type"] == "AWS::IAM::Policy"
|
||||||
assert len(policy["id"]) == len(random_policy_id())
|
assert policy["id"] in list(map(lambda p: p["id"], policies))
|
||||||
assert policy["name"] == "mypolicy"
|
assert policy["name"] in list(map(lambda p: p["name"], policies))
|
||||||
assert policy["region"] == "global"
|
assert policy["region"] == "global"
|
||||||
|
|
||||||
|
# test passing list of resource ids
|
||||||
|
resource_ids = policy_config_query.list_config_service_resources(
|
||||||
|
[policies[0]["id"], policies[1]["id"]], None, 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(resource_ids) == 2
|
||||||
|
|
||||||
|
# test passing a single resource name
|
||||||
|
resource_name = policy_config_query.list_config_service_resources(
|
||||||
|
None, policies[0]["name"], 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(resource_name) == 1
|
||||||
|
assert resource_name[0]["id"] == policies[0]["id"]
|
||||||
|
assert resource_name[0]["name"] == policies[0]["name"]
|
||||||
|
|
||||||
|
# test passing a single resource name AND some resource id's
|
||||||
|
both_filter_good = policy_config_query.list_config_service_resources(
|
||||||
|
[policies[0]["id"], policies[1]["id"]], policies[0]["name"], 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(both_filter_good) == 1
|
||||||
|
assert both_filter_good[0]["id"] == policies[0]["id"]
|
||||||
|
assert both_filter_good[0]["name"] == policies[0]["name"]
|
||||||
|
|
||||||
|
both_filter_bad = policy_config_query.list_config_service_resources(
|
||||||
|
[policies[0]["id"], policies[1]["id"]], policies[2]["name"], 100, None
|
||||||
|
)[0]
|
||||||
|
assert len(both_filter_bad) == 0
|
||||||
|
|
||||||
|
|
||||||
@mock_iam
|
@mock_iam
|
||||||
def test_policy_config_dict():
|
def test_policy_config_dict():
|
||||||
@ -3519,6 +3659,7 @@ def test_policy_config_dict():
|
|||||||
def test_policy_config_client():
|
def test_policy_config_client():
|
||||||
from moto.iam.models import ACCOUNT_ID
|
from moto.iam.models import ACCOUNT_ID
|
||||||
from moto.iam.utils import random_policy_id
|
from moto.iam.utils import random_policy_id
|
||||||
|
from moto.config.models import CONFIG_REGIONS
|
||||||
|
|
||||||
basic_policy = {
|
basic_policy = {
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
@ -3528,14 +3669,24 @@ def test_policy_config_client():
|
|||||||
iam_client = boto3.client("iam", region_name="us-west-2")
|
iam_client = boto3.client("iam", region_name="us-west-2")
|
||||||
config_client = boto3.client("config", region_name="us-west-2")
|
config_client = boto3.client("config", region_name="us-west-2")
|
||||||
|
|
||||||
account_aggregation_source = {
|
all_account_aggregation_source = {
|
||||||
"AccountIds": [ACCOUNT_ID],
|
"AccountIds": [ACCOUNT_ID],
|
||||||
"AllAwsRegions": True,
|
"AllAwsRegions": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
two_region_account_aggregation_source = {
|
||||||
|
"AccountIds": [ACCOUNT_ID],
|
||||||
|
"AwsRegions": ["us-east-1", "us-west-2"],
|
||||||
|
}
|
||||||
|
|
||||||
config_client.put_configuration_aggregator(
|
config_client.put_configuration_aggregator(
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
AccountAggregationSources=[account_aggregation_source],
|
AccountAggregationSources=[all_account_aggregation_source],
|
||||||
|
)
|
||||||
|
|
||||||
|
config_client.put_configuration_aggregator(
|
||||||
|
ConfigurationAggregatorName="test_aggregator_two_regions",
|
||||||
|
AccountAggregationSources=[two_region_account_aggregation_source],
|
||||||
)
|
)
|
||||||
|
|
||||||
result = config_client.list_discovered_resources(resourceType="AWS::IAM::Policy")
|
result = config_client.list_discovered_resources(resourceType="AWS::IAM::Policy")
|
||||||
@ -3575,28 +3726,35 @@ def test_policy_config_client():
|
|||||||
)["resourceIdentifiers"][0]["resourceId"]
|
)["resourceIdentifiers"][0]["resourceId"]
|
||||||
) != first_result
|
) != first_result
|
||||||
|
|
||||||
# Test aggregated query: (everything is getting a random id, so we can't test names by ordering)
|
# Test aggregated query - by `Limit=len(CONFIG_REGIONS)`, we should get a single policy duplicated across all regions
|
||||||
agg_result = config_client.list_aggregate_discovered_resources(
|
agg_result = config_client.list_aggregate_discovered_resources(
|
||||||
ResourceType="AWS::IAM::Policy",
|
ResourceType="AWS::IAM::Policy",
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
Limit=1,
|
Limit=len(CONFIG_REGIONS),
|
||||||
)
|
)
|
||||||
first_agg_result = agg_result["ResourceIdentifiers"][0]["ResourceId"]
|
assert len(agg_result["ResourceIdentifiers"]) == len(CONFIG_REGIONS)
|
||||||
assert agg_result["ResourceIdentifiers"][0]["ResourceType"] == "AWS::IAM::Policy"
|
|
||||||
assert len(first_agg_result) == len(random_policy_id())
|
agg_name = None
|
||||||
assert agg_result["ResourceIdentifiers"][0]["SourceAccountId"] == ACCOUNT_ID
|
agg_id = None
|
||||||
assert agg_result["ResourceIdentifiers"][0]["SourceRegion"] == "global"
|
for resource in agg_result["ResourceIdentifiers"]:
|
||||||
|
assert resource["ResourceType"] == "AWS::IAM::Policy"
|
||||||
|
assert resource["SourceRegion"] in CONFIG_REGIONS
|
||||||
|
assert resource["SourceAccountId"] == ACCOUNT_ID
|
||||||
|
if agg_id:
|
||||||
|
assert resource["ResourceId"] == agg_id
|
||||||
|
if agg_name:
|
||||||
|
assert resource["ResourceName"] == agg_name
|
||||||
|
agg_name = resource["ResourceName"]
|
||||||
|
agg_id = resource["ResourceId"]
|
||||||
|
|
||||||
# Test aggregated pagination
|
# Test aggregated pagination
|
||||||
assert (
|
for resource in config_client.list_aggregate_discovered_resources(
|
||||||
config_client.list_aggregate_discovered_resources(
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
ResourceType="AWS::IAM::Policy",
|
||||||
ResourceType="AWS::IAM::Policy",
|
Limit=1,
|
||||||
Limit=1,
|
NextToken=agg_result["NextToken"],
|
||||||
NextToken=agg_result["NextToken"],
|
)["ResourceIdentifiers"]:
|
||||||
)["ResourceIdentifiers"][0]["ResourceId"]
|
assert resource["ResourceId"] != agg_id
|
||||||
!= first_agg_result
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test non-aggregated resource name/id filter
|
# Test non-aggregated resource name/id filter
|
||||||
assert (
|
assert (
|
||||||
@ -3605,6 +3763,7 @@ def test_policy_config_client():
|
|||||||
)["resourceIdentifiers"][0]["resourceName"]
|
)["resourceIdentifiers"][0]["resourceName"]
|
||||||
== policies[1]["name"]
|
== policies[1]["name"]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
config_client.list_discovered_resources(
|
config_client.list_discovered_resources(
|
||||||
resourceType="AWS::IAM::Policy", resourceIds=[policies[0]["id"]], limit=1,
|
resourceType="AWS::IAM::Policy", resourceIds=[policies[0]["id"]], limit=1,
|
||||||
@ -3613,24 +3772,47 @@ def test_policy_config_client():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Test aggregated resource name/id filter
|
# Test aggregated resource name/id filter
|
||||||
|
agg_name_filter = config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
|
ResourceType="AWS::IAM::Policy",
|
||||||
|
Filters={"ResourceName": policies[5]["name"]},
|
||||||
|
)
|
||||||
|
assert len(agg_name_filter["ResourceIdentifiers"]) == len(CONFIG_REGIONS)
|
||||||
assert (
|
assert (
|
||||||
config_client.list_aggregate_discovered_resources(
|
agg_name_filter["ResourceIdentifiers"][0]["ResourceName"] == policies[5]["name"]
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
|
||||||
ResourceType="AWS::IAM::Policy",
|
|
||||||
Filters={"ResourceName": policies[5]["name"]},
|
|
||||||
Limit=1,
|
|
||||||
)["ResourceIdentifiers"][0]["ResourceName"]
|
|
||||||
== policies[5]["name"]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
agg_name_filter = config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator_two_regions",
|
||||||
|
ResourceType="AWS::IAM::Policy",
|
||||||
|
Filters={"ResourceName": policies[5]["name"]},
|
||||||
|
)
|
||||||
|
assert len(agg_name_filter["ResourceIdentifiers"]) == len(
|
||||||
|
two_region_account_aggregation_source["AwsRegions"]
|
||||||
|
)
|
||||||
|
assert agg_name_filter["ResourceIdentifiers"][0]["ResourceId"] == policies[5]["id"]
|
||||||
|
|
||||||
|
agg_id_filter = config_client.list_aggregate_discovered_resources(
|
||||||
|
ConfigurationAggregatorName="test_aggregator",
|
||||||
|
ResourceType="AWS::IAM::Policy",
|
||||||
|
Filters={"ResourceId": policies[4]["id"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(agg_id_filter["ResourceIdentifiers"]) == len(CONFIG_REGIONS)
|
||||||
assert (
|
assert (
|
||||||
config_client.list_aggregate_discovered_resources(
|
agg_id_filter["ResourceIdentifiers"][0]["ResourceName"] == policies[4]["name"]
|
||||||
ConfigurationAggregatorName="test_aggregator",
|
)
|
||||||
ResourceType="AWS::IAM::Policy",
|
|
||||||
Filters={"ResourceId": policies[4]["id"]},
|
agg_name_filter = config_client.list_aggregate_discovered_resources(
|
||||||
Limit=1,
|
ConfigurationAggregatorName="test_aggregator_two_regions",
|
||||||
)["ResourceIdentifiers"][0]["ResourceName"]
|
ResourceType="AWS::IAM::Policy",
|
||||||
== policies[4]["name"]
|
Filters={"ResourceId": policies[5]["id"]},
|
||||||
|
)
|
||||||
|
assert len(agg_name_filter["ResourceIdentifiers"]) == len(
|
||||||
|
two_region_account_aggregation_source["AwsRegions"]
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
agg_name_filter["ResourceIdentifiers"][0]["ResourceName"] == policies[5]["name"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test name/id filter with pagination
|
# Test name/id filter with pagination
|
||||||
@ -3678,7 +3860,7 @@ def test_policy_config_client():
|
|||||||
ResourceIdentifiers=[
|
ResourceIdentifiers=[
|
||||||
{
|
{
|
||||||
"SourceAccountId": ACCOUNT_ID,
|
"SourceAccountId": ACCOUNT_ID,
|
||||||
"SourceRegion": "global",
|
"SourceRegion": "us-east-2",
|
||||||
"ResourceId": policies[8]["id"],
|
"ResourceId": policies[8]["id"],
|
||||||
"ResourceType": "AWS::IAM::Policy",
|
"ResourceType": "AWS::IAM::Policy",
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user