Add IAM Role and Policy to Config
This commit is contained in:
parent
2e0e542efe
commit
7bc5b5c08f
@ -23,8 +23,8 @@ However, this will only work on resource types that have this enabled.
|
||||
|
||||
### Current enabled resource types:
|
||||
|
||||
1. S3
|
||||
|
||||
1. S3 (all)
|
||||
1. IAM (Role, Policy)
|
||||
|
||||
## Developer Guide
|
||||
|
||||
|
@ -47,8 +47,8 @@ from moto.config.exceptions import (
|
||||
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
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.iam.config import role_config_query, policy_config_query
|
||||
|
||||
POP_STRINGS = [
|
||||
"capitalizeStart",
|
||||
@ -64,6 +64,8 @@ DEFAULT_PAGE_SIZE = 100
|
||||
RESOURCE_MAP = {
|
||||
"AWS::S3::Bucket": s3_config_query,
|
||||
"AWS::S3::AccountPublicAccessBlock": s3_account_public_access_block_query,
|
||||
"AWS::IAM::Role": role_config_query,
|
||||
"AWS::IAM::Policy": policy_config_query,
|
||||
}
|
||||
|
||||
|
||||
|
@ -766,6 +766,27 @@ class ConfigQueryModel(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def aggregate_regions(self, path, backend_region, resource_region):
|
||||
"""
|
||||
Returns a list of "region\1eresourcename" strings
|
||||
"""
|
||||
|
||||
filter_region = backend_region or resource_region
|
||||
if filter_region:
|
||||
filter_resources = list(self.backends[filter_region].__dict__[path].keys())
|
||||
return map(
|
||||
lambda resource: "{}\1e{}".format(filter_region, 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("{}\1e{}".format(region, resource))
|
||||
return ret
|
||||
|
||||
|
||||
class base_decorator(object):
|
||||
mock_backend = MockAWS
|
||||
|
173
moto/iam/config.py
Normal file
173
moto/iam/config.py
Normal file
@ -0,0 +1,173 @@
|
||||
import json
|
||||
|
||||
from moto.core.exceptions import InvalidNextTokenException
|
||||
from moto.core.models import ConfigQueryModel
|
||||
from moto.iam import iam_backends
|
||||
|
||||
|
||||
class RoleConfigQuery(ConfigQueryModel):
|
||||
def list_config_service_resources(
|
||||
self,
|
||||
resource_ids,
|
||||
resource_name,
|
||||
limit,
|
||||
next_token,
|
||||
backend_region=None,
|
||||
resource_region=None,
|
||||
):
|
||||
# For aggregation -- did we get both a resource ID and a resource name?
|
||||
if resource_ids and resource_name:
|
||||
# If the values are different, then return an empty list:
|
||||
if resource_name not in resource_ids:
|
||||
return [], None
|
||||
|
||||
role_list = self.aggregate_regions("roles", backend_region, resource_region)
|
||||
|
||||
if not role_list:
|
||||
return [], None
|
||||
|
||||
# Pagination logic:
|
||||
sorted_roles = sorted(role_list)
|
||||
new_token = None
|
||||
|
||||
# Get the start:
|
||||
if not next_token:
|
||||
start = 0
|
||||
else:
|
||||
# "Tokens" are region + \00 + resource ID.
|
||||
if next_token not in sorted_roles:
|
||||
raise InvalidNextTokenException()
|
||||
|
||||
start = sorted_roles.index(next_token)
|
||||
|
||||
# Get the list of items to collect:
|
||||
role_list = sorted_roles[start : (start + limit)]
|
||||
|
||||
if len(sorted_roles) > (start + limit):
|
||||
new_token = sorted_roles[start + limit]
|
||||
|
||||
return (
|
||||
[
|
||||
{
|
||||
"type": "AWS::IAM::Role",
|
||||
"id": role.split("\1e")[1],
|
||||
"name": role.split("\1e")[1],
|
||||
"region": role.split("\1e")[0],
|
||||
}
|
||||
for role in role_list
|
||||
],
|
||||
new_token,
|
||||
)
|
||||
|
||||
def get_config_resource(
|
||||
self, resource_id, resource_name=None, backend_region=None, resource_region=None
|
||||
):
|
||||
|
||||
role = self.backends["global"].roles.get(resource_id, {})
|
||||
|
||||
if not role:
|
||||
return
|
||||
|
||||
if resource_name and role.name != resource_name:
|
||||
return
|
||||
|
||||
# Format the bucket to the AWS Config format:
|
||||
config_data = role.to_config_dict()
|
||||
|
||||
# The 'configuration' field is also a JSON string:
|
||||
config_data["configuration"] = json.dumps(config_data["configuration"])
|
||||
|
||||
# Supplementary config need all values converted to JSON strings if they are not strings already:
|
||||
for field, value in config_data["supplementaryConfiguration"].items():
|
||||
if not isinstance(value, str):
|
||||
config_data["supplementaryConfiguration"][field] = json.dumps(value)
|
||||
|
||||
return config_data
|
||||
|
||||
|
||||
class PolicyConfigQuery(ConfigQueryModel):
|
||||
def list_config_service_resources(
|
||||
self,
|
||||
resource_ids,
|
||||
resource_name,
|
||||
limit,
|
||||
next_token,
|
||||
backend_region=None,
|
||||
resource_region=None,
|
||||
):
|
||||
# For aggregation -- did we get both a resource ID and a resource name?
|
||||
if resource_ids and resource_name:
|
||||
# If the values are different, then return an empty list:
|
||||
if resource_name not in resource_ids:
|
||||
return [], None
|
||||
|
||||
# We don't want to include AWS Managed Policies
|
||||
policy_list = filter(
|
||||
lambda policy: not policy.split("\1e")[1].startswith("arn:aws:iam::aws"),
|
||||
self.aggregate_regions("managed_policies", backend_region, resource_region),
|
||||
)
|
||||
|
||||
if not policy_list:
|
||||
return [], None
|
||||
|
||||
# Pagination logic:
|
||||
sorted_policies = sorted(policy_list)
|
||||
new_token = None
|
||||
|
||||
# Get the start:
|
||||
if not next_token:
|
||||
start = 0
|
||||
else:
|
||||
# "Tokens" are region + \00 + resource ID.
|
||||
if next_token not in sorted_policies:
|
||||
raise InvalidNextTokenException()
|
||||
|
||||
start = sorted_policies.index(next_token)
|
||||
|
||||
# Get the list of items to collect:
|
||||
policy_list = sorted_policies[start : (start + limit)]
|
||||
|
||||
if len(sorted_policies) > (start + limit):
|
||||
new_token = sorted_policies[start + limit]
|
||||
|
||||
return (
|
||||
[
|
||||
{
|
||||
"type": "AWS::IAM::Policy",
|
||||
"id": policy.split("\1e")[1],
|
||||
"name": policy.split("\1e")[1],
|
||||
"region": policy.split("\1e")[0],
|
||||
}
|
||||
for policy in policy_list
|
||||
],
|
||||
new_token,
|
||||
)
|
||||
|
||||
def get_config_resource(
|
||||
self, resource_id, resource_name=None, backend_region=None, resource_region=None
|
||||
):
|
||||
|
||||
policy = self.backends["global"].managed_policies.get(resource_id, {})
|
||||
|
||||
if not policy:
|
||||
return
|
||||
|
||||
if resource_name and policy.name != resource_name:
|
||||
return
|
||||
|
||||
# Format the bucket to the AWS Config format:
|
||||
config_data = policy.to_config_dict()
|
||||
|
||||
# The 'configuration' field is also a JSON string:
|
||||
config_data["configuration"] = json.dumps(config_data["configuration"])
|
||||
|
||||
# Supplementary config need all values converted to JSON strings if they are not strings already:
|
||||
for field, value in config_data["supplementaryConfiguration"].items():
|
||||
if not isinstance(value, str):
|
||||
config_data["supplementaryConfiguration"][field] = json.dumps(value)
|
||||
|
||||
return config_data
|
||||
|
||||
|
||||
role_config_query = RoleConfigQuery(iam_backends)
|
||||
policy_config_query = PolicyConfigQuery(iam_backends)
|
@ -8,11 +8,13 @@ import sys
|
||||
from datetime import datetime
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
from six.moves.urllib import parse
|
||||
from moto.core.exceptions import RESTError
|
||||
from moto.core import BaseBackend, BaseModel, ACCOUNT_ID, CloudFormationModel
|
||||
from moto.core.utils import (
|
||||
@ -153,7 +155,7 @@ class OpenIDConnectProvider(BaseModel):
|
||||
self._errors = []
|
||||
self._validate(url, thumbprint_list, client_id_list)
|
||||
|
||||
parsed_url = urlparse(url)
|
||||
parsed_url = parse.urlparse(url)
|
||||
self.url = parsed_url.netloc + parsed_url.path
|
||||
self.thumbprint_list = thumbprint_list
|
||||
self.client_id_list = client_id_list
|
||||
@ -201,7 +203,7 @@ class OpenIDConnectProvider(BaseModel):
|
||||
|
||||
self._raise_errors()
|
||||
|
||||
parsed_url = urlparse(url)
|
||||
parsed_url = parse.urlparse(url)
|
||||
if not parsed_url.scheme or not parsed_url.netloc:
|
||||
raise ValidationError("Invalid Open ID Connect Provider URL")
|
||||
|
||||
@ -265,6 +267,48 @@ class ManagedPolicy(Policy):
|
||||
def arn(self):
|
||||
return "arn:aws:iam::{0}:policy{1}{2}".format(ACCOUNT_ID, self.path, self.name)
|
||||
|
||||
def to_config_dict(self):
|
||||
return {
|
||||
"version": "1.3",
|
||||
"configurationItemCaptureTime": str(self.create_date),
|
||||
"configurationItemStatus": "OK",
|
||||
"configurationStateId": str(
|
||||
int(time.mktime(self.create_date.timetuple()))
|
||||
), # PY2 and 3 compatible
|
||||
"arn": "arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, self.name),
|
||||
"resourceType": "AWS::IAM::Policy",
|
||||
"resourceId": self.id,
|
||||
"resourceName": self.name,
|
||||
"awsRegion": "global",
|
||||
"availabilityZone": "Not Applicable",
|
||||
"resourceCreationTime": str(self.create_date),
|
||||
"configuration": {
|
||||
"policyName": self.name,
|
||||
"policyId": self.id,
|
||||
"arn": "arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, self.name),
|
||||
"path": self.path,
|
||||
"defaultVersionId": self.default_version_id,
|
||||
"attachmentCount": self.attachment_count,
|
||||
"permissionsBoundaryUsageCount": 0,
|
||||
"isAttachable": ManagedPolicy.is_attachable,
|
||||
"description": self.description,
|
||||
"createDate": str(self.create_date.isoformat()),
|
||||
"updateDate": str(self.create_date.isoformat()),
|
||||
"policyVersionList": list(
|
||||
map(
|
||||
lambda version: {
|
||||
"document": parse.quote(version.document),
|
||||
"versionId": version.version_id,
|
||||
"isDefaultVersion": version.is_default,
|
||||
"createDate": str(version.create_date),
|
||||
},
|
||||
self.versions,
|
||||
)
|
||||
),
|
||||
},
|
||||
"supplementaryConfiguration": {},
|
||||
}
|
||||
|
||||
|
||||
class AWSManagedPolicy(ManagedPolicy):
|
||||
"""AWS-managed policy."""
|
||||
@ -513,6 +557,69 @@ class Role(CloudFormationModel):
|
||||
def arn(self):
|
||||
return "arn:aws:iam::{0}:role{1}{2}".format(ACCOUNT_ID, self.path, self.name)
|
||||
|
||||
def to_config_dict(self):
|
||||
_managed_policies = []
|
||||
for key in self.managed_policies.keys():
|
||||
_managed_policies.append(
|
||||
{"policyArn": key, "policyName": iam_backend.managed_policies[key].name}
|
||||
)
|
||||
|
||||
_role_policy_list = []
|
||||
for key, value in self.policies.items():
|
||||
_role_policy_list.append(
|
||||
{"policyName": key, "policyDocument": parse.quote(value)}
|
||||
)
|
||||
|
||||
_instance_profiles = []
|
||||
for key, instance_profile in iam_backend.instance_profiles.items():
|
||||
for role in instance_profile.roles:
|
||||
_instance_profiles.append(instance_profile.to_embedded_config_dict())
|
||||
break
|
||||
|
||||
config_dict = {
|
||||
"version": "1.3",
|
||||
"configurationItemCaptureTime": str(self.create_date),
|
||||
"configurationItemStatus": "ResourceDiscovered",
|
||||
"configurationStateId": str(
|
||||
int(time.mktime(self.create_date.timetuple()))
|
||||
), # PY2 and 3 compatible
|
||||
"arn": "arn:aws:iam::{}:role/{}".format(ACCOUNT_ID, self.name),
|
||||
"resourceType": "AWS::IAM::Role",
|
||||
"resourceId": self.name,
|
||||
"resourceName": self.name,
|
||||
"awsRegion": "global",
|
||||
"availabilityZone": "Not Applicable",
|
||||
"resourceCreationTime": str(self.create_date),
|
||||
"relatedEvents": [],
|
||||
"relationships": [],
|
||||
"tags": self.tags,
|
||||
"configuration": {
|
||||
"path": self.path,
|
||||
"roleName": self.name,
|
||||
"roleId": self.id,
|
||||
"arn": "arn:aws:iam::{}:role/{}".format(ACCOUNT_ID, self.name),
|
||||
"assumeRolePolicyDocument": parse.quote(
|
||||
self.assume_role_policy_document
|
||||
)
|
||||
if self.assume_role_policy_document
|
||||
else None,
|
||||
"instanceProfileList": _instance_profiles,
|
||||
"rolePolicyList": _role_policy_list,
|
||||
"createDate": self.create_date.isoformat(),
|
||||
"attachedManagedPolicies": _managed_policies,
|
||||
"permissionsBoundary": self.permissions_boundary,
|
||||
"tags": list(
|
||||
map(
|
||||
lambda key: {"key": key, "value": self.tags[key]["Value"]},
|
||||
self.tags,
|
||||
)
|
||||
),
|
||||
"roleLastUsed": None,
|
||||
},
|
||||
"supplementaryConfiguration": {},
|
||||
}
|
||||
return config_dict
|
||||
|
||||
def put_policy(self, policy_name, policy_json):
|
||||
self.policies[policy_name] = policy_json
|
||||
|
||||
@ -590,6 +697,43 @@ class InstanceProfile(CloudFormationModel):
|
||||
return self.arn
|
||||
raise UnformattedGetAttTemplateException()
|
||||
|
||||
def to_embedded_config_dict(self):
|
||||
# Instance Profiles aren't a config item itself, but they are returned in IAM roles with
|
||||
# a "config like" json structure It's also different than Role.to_config_dict()
|
||||
roles = []
|
||||
for role in self.roles:
|
||||
roles.append(
|
||||
{
|
||||
"path": role.path,
|
||||
"roleName": role.name,
|
||||
"roleId": role.id,
|
||||
"arn": "arn:aws:iam::{}:role/{}".format(ACCOUNT_ID, role.name),
|
||||
"createDate": str(role.create_date),
|
||||
"assumeRolePolicyDocument": parse.quote(
|
||||
role.assume_role_policy_document
|
||||
),
|
||||
"description": role.description,
|
||||
"maxSessionDuration": None,
|
||||
"permissionsBoundary": role.permissions_boundary,
|
||||
"tags": list(
|
||||
map(
|
||||
lambda key: {"key": key, "value": role.tags[key]["Value"]},
|
||||
role.tags,
|
||||
)
|
||||
),
|
||||
"roleLastUsed": None,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"path": self.path,
|
||||
"instanceProfileName": self.name,
|
||||
"instanceProfileId": self.id,
|
||||
"arn": "arn:aws:iam::{}:instance-profile/{}".format(ACCOUNT_ID, self.name),
|
||||
"createDate": str(self.create_date),
|
||||
"roles": roles,
|
||||
}
|
||||
|
||||
|
||||
class Certificate(BaseModel):
|
||||
def __init__(self, cert_name, cert_body, private_key, cert_chain=None, path=None):
|
||||
|
@ -19,6 +19,7 @@ from nose.tools import raises
|
||||
from datetime import datetime
|
||||
from tests.helpers import requires_boto_gte
|
||||
from uuid import uuid4
|
||||
from six.moves.urllib import parse
|
||||
|
||||
|
||||
MOCK_CERT = """-----BEGIN CERTIFICATE-----
|
||||
@ -2882,3 +2883,450 @@ def test_delete_role_with_instance_profiles_present():
|
||||
role_names = [role["RoleName"] for role in iam.list_roles()["Roles"]]
|
||||
assert "Role1" in role_names
|
||||
assert "Role2" not in role_names
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_delete_account_password_policy_errors():
|
||||
client = boto3.client("iam", region_name="us-east-1")
|
||||
|
||||
client.delete_account_password_policy.when.called_with().should.throw(
|
||||
ClientError, "The account policy with name PasswordPolicy cannot be found."
|
||||
)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_role_list_config_discovered_resources():
|
||||
from moto.iam.config import role_config_query
|
||||
from moto.iam.utils import random_resource_id
|
||||
|
||||
# Without any roles
|
||||
assert role_config_query.list_config_service_resources(None, None, 100, None) == (
|
||||
[],
|
||||
None,
|
||||
)
|
||||
|
||||
# Create a role
|
||||
role_config_query.backends["global"].create_role(
|
||||
role_name="something",
|
||||
assume_role_policy_document=None,
|
||||
path="/",
|
||||
permissions_boundary=None,
|
||||
description="something",
|
||||
tags=[],
|
||||
max_session_duration=3600,
|
||||
)
|
||||
|
||||
result = role_config_query.list_config_service_resources(None, None, 100, None)[0]
|
||||
assert len(result) == 1
|
||||
|
||||
# The role gets a random ID, so we have to grab it
|
||||
role = result[0]
|
||||
assert role["type"] == "AWS::IAM::Role"
|
||||
assert len(role["id"]) == len(random_resource_id())
|
||||
assert role["id"] == role["name"]
|
||||
assert role["region"] == "global"
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_policy_list_config_discovered_resources():
|
||||
from moto.iam.config import policy_config_query
|
||||
|
||||
# Without any policies
|
||||
assert policy_config_query.list_config_service_resources(None, None, 100, None) == (
|
||||
[],
|
||||
None,
|
||||
)
|
||||
|
||||
basic_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Action": ["ec2:DeleteKeyPair"], "Effect": "Deny", "Resource": "*"}
|
||||
],
|
||||
}
|
||||
|
||||
# Create a role
|
||||
policy_config_query.backends["global"].create_policy(
|
||||
description="mypolicy",
|
||||
path="",
|
||||
policy_document=json.dumps(basic_policy),
|
||||
policy_name="mypolicy",
|
||||
)
|
||||
|
||||
result = policy_config_query.list_config_service_resources(None, None, 100, None)[0]
|
||||
assert len(result) == 1
|
||||
|
||||
policy = result[0]
|
||||
assert policy["type"] == "AWS::IAM::Policy"
|
||||
assert policy["id"] == policy["name"] == "arn:aws:iam::123456789012:policy/mypolicy"
|
||||
assert policy["region"] == "global"
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_role_config_dict():
|
||||
from moto.iam.config import role_config_query, policy_config_query
|
||||
from moto.iam.utils import random_resource_id
|
||||
|
||||
# Without any roles
|
||||
assert not role_config_query.get_config_resource("something")
|
||||
assert role_config_query.list_config_service_resources(None, None, 100, None) == (
|
||||
[],
|
||||
None,
|
||||
)
|
||||
|
||||
basic_assume_role = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Principal": {"AWS": "*"}, "Action": "sts:AssumeRole"}
|
||||
],
|
||||
}
|
||||
|
||||
basic_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{"Action": ["ec2:*"], "Effect": "Allow", "Resource": "*"}],
|
||||
}
|
||||
|
||||
# Create a policy for use in role permissions boundary
|
||||
policy_config_query.backends["global"].create_policy(
|
||||
description="basic_policy",
|
||||
path="/",
|
||||
policy_document=json.dumps(basic_policy),
|
||||
policy_name="basic_policy",
|
||||
)
|
||||
|
||||
policy_arn = policy_config_query.list_config_service_resources(
|
||||
None, None, 100, None
|
||||
)[0][0]["id"]
|
||||
assert policy_arn is not None
|
||||
|
||||
# Create some roles (and grab them repeatedly since they create with random names)
|
||||
role_config_query.backends["global"].create_role(
|
||||
role_name="plain_role",
|
||||
assume_role_policy_document=None,
|
||||
path="/",
|
||||
permissions_boundary=None,
|
||||
description="plain_role",
|
||||
tags=[{"Key": "foo", "Value": "bar"}],
|
||||
max_session_duration=3600,
|
||||
)
|
||||
|
||||
plain_role = role_config_query.list_config_service_resources(None, None, 100, None)[
|
||||
0
|
||||
][0]
|
||||
assert plain_role is not None
|
||||
assert len(plain_role["id"]) == len(random_resource_id())
|
||||
|
||||
role_config_query.backends["global"].create_role(
|
||||
role_name="assume_role",
|
||||
assume_role_policy_document=json.dumps(basic_assume_role),
|
||||
path="/",
|
||||
permissions_boundary=None,
|
||||
description="assume_role",
|
||||
tags=[],
|
||||
max_session_duration=3600,
|
||||
)
|
||||
|
||||
assume_role = next(
|
||||
role
|
||||
for role in role_config_query.list_config_service_resources(
|
||||
None, None, 100, None
|
||||
)[0]
|
||||
if role["id"] not in [plain_role["id"]]
|
||||
)
|
||||
assert assume_role is not None
|
||||
assert len(assume_role["id"]) == len(random_resource_id())
|
||||
assert assume_role["id"] is not plain_role["id"]
|
||||
|
||||
role_config_query.backends["global"].create_role(
|
||||
role_name="assume_and_permission_boundary_role",
|
||||
assume_role_policy_document=json.dumps(basic_assume_role),
|
||||
path="/",
|
||||
permissions_boundary=policy_arn,
|
||||
description="assume_and_permission_boundary_role",
|
||||
tags=[],
|
||||
max_session_duration=3600,
|
||||
)
|
||||
|
||||
assume_and_permission_boundary_role = next(
|
||||
role
|
||||
for role in role_config_query.list_config_service_resources(
|
||||
None, None, 100, None
|
||||
)[0]
|
||||
if role["id"] not in [plain_role["id"], assume_role["id"]]
|
||||
)
|
||||
assert assume_and_permission_boundary_role is not None
|
||||
assert len(assume_and_permission_boundary_role["id"]) == len(random_resource_id())
|
||||
assert assume_and_permission_boundary_role["id"] is not plain_role["id"]
|
||||
assert assume_and_permission_boundary_role["id"] is not assume_role["id"]
|
||||
|
||||
role_config_query.backends["global"].create_role(
|
||||
role_name="role_with_attached_policy",
|
||||
assume_role_policy_document=json.dumps(basic_assume_role),
|
||||
path="/",
|
||||
permissions_boundary=None,
|
||||
description="role_with_attached_policy",
|
||||
tags=[],
|
||||
max_session_duration=3600,
|
||||
)
|
||||
role_config_query.backends["global"].attach_role_policy(
|
||||
policy_arn, "role_with_attached_policy"
|
||||
)
|
||||
role_with_attached_policy = next(
|
||||
role
|
||||
for role in role_config_query.list_config_service_resources(
|
||||
None, None, 100, None
|
||||
)[0]
|
||||
if role["id"]
|
||||
not in [
|
||||
plain_role["id"],
|
||||
assume_role["id"],
|
||||
assume_and_permission_boundary_role["id"],
|
||||
]
|
||||
)
|
||||
assert role_with_attached_policy is not None
|
||||
assert len(role_with_attached_policy["id"]) == len(random_resource_id())
|
||||
assert role_with_attached_policy["id"] is not plain_role["id"]
|
||||
assert role_with_attached_policy["id"] is not assume_role["id"]
|
||||
assert (
|
||||
role_with_attached_policy["id"] is not assume_and_permission_boundary_role["id"]
|
||||
)
|
||||
|
||||
role_config_query.backends["global"].create_role(
|
||||
role_name="role_with_inline_policy",
|
||||
assume_role_policy_document=json.dumps(basic_assume_role),
|
||||
path="/",
|
||||
permissions_boundary=None,
|
||||
description="role_with_inline_policy",
|
||||
tags=[],
|
||||
max_session_duration=3600,
|
||||
)
|
||||
role_config_query.backends["global"].put_role_policy(
|
||||
"role_with_inline_policy", "inline_policy", json.dumps(basic_policy)
|
||||
)
|
||||
|
||||
role_with_inline_policy = next(
|
||||
role
|
||||
for role in role_config_query.list_config_service_resources(
|
||||
None, None, 100, None
|
||||
)[0]
|
||||
if role["id"]
|
||||
not in [
|
||||
plain_role["id"],
|
||||
assume_role["id"],
|
||||
assume_and_permission_boundary_role["id"],
|
||||
role_with_attached_policy["id"],
|
||||
]
|
||||
)
|
||||
assert role_with_inline_policy is not None
|
||||
assert len(role_with_inline_policy["id"]) == len(random_resource_id())
|
||||
assert role_with_inline_policy["id"] is not plain_role["id"]
|
||||
assert role_with_inline_policy["id"] is not assume_role["id"]
|
||||
assert (
|
||||
role_with_inline_policy["id"] is not assume_and_permission_boundary_role["id"]
|
||||
)
|
||||
assert role_with_inline_policy["id"] is not role_with_attached_policy["id"]
|
||||
|
||||
# plain role
|
||||
plain_role_config = (
|
||||
role_config_query.backends["global"].roles[plain_role["id"]].to_config_dict()
|
||||
)
|
||||
assert plain_role_config["version"] == "1.3"
|
||||
assert plain_role_config["configurationItemStatus"] == "ResourceDiscovered"
|
||||
assert plain_role_config["configurationStateId"] is not None
|
||||
assert plain_role_config["arn"] == "arn:aws:iam::123456789012:role/plain_role"
|
||||
assert plain_role_config["resourceType"] == "AWS::IAM::Role"
|
||||
assert plain_role_config["resourceId"] == "plain_role"
|
||||
assert plain_role_config["resourceName"] == "plain_role"
|
||||
assert plain_role_config["awsRegion"] == "global"
|
||||
assert plain_role_config["availabilityZone"] == "Not Applicable"
|
||||
assert plain_role_config["resourceCreationTime"] is not None
|
||||
assert plain_role_config["tags"] == {"foo": {"Key": "foo", "Value": "bar"}}
|
||||
assert plain_role_config["configuration"]["path"] == "/"
|
||||
assert plain_role_config["configuration"]["roleName"] == "plain_role"
|
||||
assert plain_role_config["configuration"]["roleId"] == plain_role["id"]
|
||||
assert plain_role_config["configuration"]["arn"] == plain_role_config["arn"]
|
||||
assert plain_role_config["configuration"]["assumeRolePolicyDocument"] is None
|
||||
assert plain_role_config["configuration"]["instanceProfileList"] == []
|
||||
assert plain_role_config["configuration"]["rolePolicyList"] == []
|
||||
assert plain_role_config["configuration"]["attachedManagedPolicies"] == []
|
||||
assert plain_role_config["configuration"]["permissionsBoundary"] is None
|
||||
assert plain_role_config["configuration"]["tags"] == [
|
||||
{"key": "foo", "value": "bar"}
|
||||
]
|
||||
assert plain_role_config["supplementaryConfiguration"] == {}
|
||||
|
||||
# assume_role
|
||||
assume_role_config = (
|
||||
role_config_query.backends["global"].roles[assume_role["id"]].to_config_dict()
|
||||
)
|
||||
assert assume_role_config["arn"] == "arn:aws:iam::123456789012:role/assume_role"
|
||||
assert assume_role_config["resourceId"] == "assume_role"
|
||||
assert assume_role_config["resourceName"] == "assume_role"
|
||||
assert assume_role_config["configuration"][
|
||||
"assumeRolePolicyDocument"
|
||||
] == parse.quote(json.dumps(basic_assume_role))
|
||||
|
||||
# assume_and_permission_boundary_role
|
||||
assume_and_permission_boundary_role_config = (
|
||||
role_config_query.backends["global"]
|
||||
.roles[assume_and_permission_boundary_role["id"]]
|
||||
.to_config_dict()
|
||||
)
|
||||
assert (
|
||||
assume_and_permission_boundary_role_config["arn"]
|
||||
== "arn:aws:iam::123456789012:role/assume_and_permission_boundary_role"
|
||||
)
|
||||
assert (
|
||||
assume_and_permission_boundary_role_config["resourceId"]
|
||||
== "assume_and_permission_boundary_role"
|
||||
)
|
||||
assert (
|
||||
assume_and_permission_boundary_role_config["resourceName"]
|
||||
== "assume_and_permission_boundary_role"
|
||||
)
|
||||
assert assume_and_permission_boundary_role_config["configuration"][
|
||||
"assumeRolePolicyDocument"
|
||||
] == parse.quote(json.dumps(basic_assume_role))
|
||||
assert (
|
||||
assume_and_permission_boundary_role_config["configuration"][
|
||||
"permissionsBoundary"
|
||||
]
|
||||
== policy_arn
|
||||
)
|
||||
|
||||
# role_with_attached_policy
|
||||
role_with_attached_policy_config = (
|
||||
role_config_query.backends["global"]
|
||||
.roles[role_with_attached_policy["id"]]
|
||||
.to_config_dict()
|
||||
)
|
||||
assert (
|
||||
role_with_attached_policy_config["arn"]
|
||||
== "arn:aws:iam::123456789012:role/role_with_attached_policy"
|
||||
)
|
||||
assert role_with_attached_policy_config["configuration"][
|
||||
"attachedManagedPolicies"
|
||||
] == [{"policyArn": policy_arn, "policyName": "basic_policy"}]
|
||||
|
||||
# role_with_inline_policy
|
||||
role_with_inline_policy_config = (
|
||||
role_config_query.backends["global"]
|
||||
.roles[role_with_inline_policy["id"]]
|
||||
.to_config_dict()
|
||||
)
|
||||
assert (
|
||||
role_with_inline_policy_config["arn"]
|
||||
== "arn:aws:iam::123456789012:role/role_with_inline_policy"
|
||||
)
|
||||
assert role_with_inline_policy_config["configuration"]["rolePolicyList"] == [
|
||||
{
|
||||
"policyName": "inline_policy",
|
||||
"policyDocument": parse.quote(json.dumps(basic_policy)),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_policy_config_dict():
|
||||
from moto.iam.config import role_config_query, policy_config_query
|
||||
from moto.iam.utils import random_policy_id
|
||||
|
||||
# Without any roles
|
||||
assert not policy_config_query.get_config_resource(
|
||||
"arn:aws:iam::123456789012:policy/basic_policy"
|
||||
)
|
||||
assert policy_config_query.list_config_service_resources(None, None, 100, None) == (
|
||||
[],
|
||||
None,
|
||||
)
|
||||
|
||||
basic_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{"Action": ["ec2:*"], "Effect": "Allow", "Resource": "*"}],
|
||||
}
|
||||
|
||||
basic_policy_v2 = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Action": ["ec2:*", "s3:*"], "Effect": "Allow", "Resource": "*"}
|
||||
],
|
||||
}
|
||||
|
||||
policy_config_query.backends["global"].create_policy(
|
||||
description="basic_policy",
|
||||
path="/",
|
||||
policy_document=json.dumps(basic_policy),
|
||||
policy_name="basic_policy",
|
||||
)
|
||||
|
||||
policy_arn = policy_config_query.list_config_service_resources(
|
||||
None, None, 100, None
|
||||
)[0][0]["id"]
|
||||
assert policy_arn == "arn:aws:iam::123456789012:policy/basic_policy"
|
||||
assert (
|
||||
policy_config_query.get_config_resource(
|
||||
"arn:aws:iam::123456789012:policy/basic_policy"
|
||||
)
|
||||
is not None
|
||||
)
|
||||
|
||||
# Create a new version
|
||||
policy_config_query.backends["global"].create_policy_version(
|
||||
policy_arn, json.dumps(basic_policy_v2), "true"
|
||||
)
|
||||
|
||||
# Create role to trigger attachment
|
||||
role_config_query.backends["global"].create_role(
|
||||
role_name="role_with_attached_policy",
|
||||
assume_role_policy_document=None,
|
||||
path="/",
|
||||
permissions_boundary=None,
|
||||
description="role_with_attached_policy",
|
||||
tags=[],
|
||||
max_session_duration=3600,
|
||||
)
|
||||
role_config_query.backends["global"].attach_role_policy(
|
||||
policy_arn, "role_with_attached_policy"
|
||||
)
|
||||
|
||||
policy = (
|
||||
role_config_query.backends["global"]
|
||||
.managed_policies["arn:aws:iam::123456789012:policy/basic_policy"]
|
||||
.to_config_dict()
|
||||
)
|
||||
assert policy["version"] == "1.3"
|
||||
assert policy["configurationItemCaptureTime"] is not None
|
||||
assert policy["configurationItemStatus"] == "OK"
|
||||
assert policy["configurationStateId"] is not None
|
||||
assert policy["arn"] == "arn:aws:iam::123456789012:policy/basic_policy"
|
||||
assert policy["resourceType"] == "AWS::IAM::Policy"
|
||||
assert len(policy["resourceId"]) == len(random_policy_id())
|
||||
assert policy["resourceName"] == "basic_policy"
|
||||
assert policy["awsRegion"] == "global"
|
||||
assert policy["availabilityZone"] == "Not Applicable"
|
||||
assert policy["resourceCreationTime"] is not None
|
||||
assert policy["configuration"]["policyName"] == policy["resourceName"]
|
||||
assert policy["configuration"]["policyId"] == policy["resourceId"]
|
||||
assert policy["configuration"]["arn"] == policy["arn"]
|
||||
assert policy["configuration"]["path"] == "/"
|
||||
assert policy["configuration"]["defaultVersionId"] == "v2"
|
||||
assert policy["configuration"]["attachmentCount"] == 1
|
||||
assert policy["configuration"]["permissionsBoundaryUsageCount"] == 0
|
||||
assert policy["configuration"]["isAttachable"] == True
|
||||
assert policy["configuration"]["description"] == "basic_policy"
|
||||
assert policy["configuration"]["createDate"] is not None
|
||||
assert policy["configuration"]["updateDate"] is not None
|
||||
assert policy["configuration"]["policyVersionList"] == [
|
||||
{
|
||||
"document": str(parse.quote(json.dumps(basic_policy))),
|
||||
"versionId": "v1",
|
||||
"isDefaultVersion": False,
|
||||
"createDate": policy["configuration"]["policyVersionList"][0]["createDate"],
|
||||
},
|
||||
{
|
||||
"document": str(parse.quote(json.dumps(basic_policy_v2))),
|
||||
"versionId": "v2",
|
||||
"isDefaultVersion": True,
|
||||
"createDate": policy["configuration"]["policyVersionList"][1]["createDate"],
|
||||
},
|
||||
]
|
||||
assert policy["supplementaryConfiguration"] == {}
|
||||
|
Loading…
Reference in New Issue
Block a user