Skip to content

Commit

Permalink
IAM: Urlencode IAM policies in responses to match AWS (#8157)
Browse files Browse the repository at this point in the history
  • Loading branch information
dfangl authored Oct 15, 2024
1 parent d0affa8 commit 1a2a733
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 18 deletions.
18 changes: 15 additions & 3 deletions moto/iam/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import re
import string
from datetime import datetime
from datetime import date, datetime
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from urllib import parse

Expand Down Expand Up @@ -101,6 +101,12 @@ def mark_account_as_visited(
pass


def _serialize_version_datetime(value: Any) -> str:
if isinstance(value, date):
return value.strftime("%Y-%m-%d")
raise TypeError("Unable to serialize value.")


LIMIT_KEYS_PER_USER = 2


Expand Down Expand Up @@ -745,10 +751,16 @@ def create_from_cloudformation_json( # type: ignore[misc]
properties = cloudformation_json["Properties"]
role_name = properties.get("RoleName", resource_name)

assume_role_policy_document = properties["AssumeRolePolicyDocument"]
if not isinstance(assume_role_policy_document, str):
assume_role_policy_document = json.dumps(
assume_role_policy_document, default=_serialize_version_datetime
)

iam_backend = iam_backends[account_id][get_partition(region_name)]
role = iam_backend.create_role(
role_name=role_name,
assume_role_policy_document=properties["AssumeRolePolicyDocument"],
assume_role_policy_document=assume_role_policy_document,
path=properties.get("Path", "/"),
permissions_boundary=properties.get("PermissionsBoundary", ""),
description=properties.get("Description", ""),
Expand Down Expand Up @@ -898,7 +910,7 @@ def to_xml(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
{% if role.description is not none %}
<Description>{{ role.description_escaped }}</Description>
{% endif %}
Expand Down
26 changes: 13 additions & 13 deletions moto/iam/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,7 @@ def untag_instance_profile(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
</member>
Expand Down Expand Up @@ -1444,7 +1444,7 @@ def untag_instance_profile(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
</member>
Expand Down Expand Up @@ -1482,7 +1482,7 @@ def untag_instance_profile(self) -> str:
<GetRolePolicyResult>
<PolicyName>{{ policy_name }}</PolicyName>
<RoleName>{{ role_name }}</RoleName>
<PolicyDocument>{{ policy_document }}</PolicyDocument>
<PolicyDocument>{{ policy_document | urlencode }}</PolicyDocument>
</GetRolePolicyResult>
<ResponseMetadata>
<RequestId>7e7cd8bc-99ef-11e1-a4c3-27EXAMPLE804</RequestId>
Expand Down Expand Up @@ -1566,7 +1566,7 @@ def untag_instance_profile(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
<MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration>
Expand Down Expand Up @@ -1662,7 +1662,7 @@ def untag_instance_profile(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
</member>
Expand Down Expand Up @@ -1831,7 +1831,7 @@ def untag_instance_profile(self) -> str:
<GetGroupPolicyResult>
<PolicyName>{{ policy_name }}</PolicyName>
<GroupName>{{ group_name }}</GroupName>
<PolicyDocument>{{ policy_document }}</PolicyDocument>
<PolicyDocument>{{ policy_document | urlencode }}</PolicyDocument>
</GetGroupPolicyResult>
<ResponseMetadata>
<RequestId>7e7cd8bc-99ef-11e1-a4c3-27EXAMPLE804</RequestId>
Expand Down Expand Up @@ -1927,7 +1927,7 @@ def untag_instance_profile(self) -> str:
<UserName>{{ user_name }}</UserName>
<PolicyName>{{ policy_name }}</PolicyName>
<PolicyDocument>
{{ policy_document }}
{{ policy_document | urlencode }}
</PolicyDocument>
</GetUserPolicyResult>
<ResponseMetadata>
Expand Down Expand Up @@ -2134,7 +2134,7 @@ def untag_instance_profile(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_policy_document | urlencode }}</AssumeRolePolicyDocument>
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
</member>
Expand Down Expand Up @@ -2319,7 +2319,7 @@ def untag_instance_profile(self) -> str:
{% for policy in user.policies %}
<member>
<PolicyName>{{ policy }}</PolicyName>
<PolicyDocument>{{ user.policies[policy] }}</PolicyDocument>
<PolicyDocument>{{ user.policies[policy] | urlencode }}</PolicyDocument>
</member>
{% endfor %}
</UserPolicyList>
Expand Down Expand Up @@ -2355,7 +2355,7 @@ def untag_instance_profile(self) -> str:
{% for policy in group.policies %}
<member>
<PolicyName>{{ policy }}</PolicyName>
<PolicyDocument>{{ group.policies[policy] }}</PolicyDocument>
<PolicyDocument>{{ group.policies[policy] | urlencode }}</PolicyDocument>
</member>
{% endfor %}
</GroupPolicyList>
Expand All @@ -2369,7 +2369,7 @@ def untag_instance_profile(self) -> str:
{% for inline_policy in role.policies %}
<member>
<PolicyName>{{ inline_policy }}</PolicyName>
<PolicyDocument>{{ role.policies[inline_policy] }}</PolicyDocument>
<PolicyDocument>{{ role.policies[inline_policy] | urlencode }}</PolicyDocument>
</member>
{% endfor %}
</RolePolicyList>
Expand Down Expand Up @@ -2399,7 +2399,7 @@ def untag_instance_profile(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
{% if role.description is not none %}
<Description>{{ role.description_escaped }}</Description>
{% endif %}
Expand All @@ -2424,7 +2424,7 @@ def untag_instance_profile(self) -> str:
<Path>{{ role.path }}</Path>
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
</member>
Expand Down
99 changes: 97 additions & 2 deletions tests/test_iam/test_iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,11 @@ def test_get_instance_profile__should_throw__when_instance_profile_does_not_exis
def test_create_role_and_instance_profile():
conn = boto3.client("iam", region_name="us-east-1")
conn.create_instance_profile(InstanceProfileName="my-profile", Path="my-path")
assume_role_policy_document = {"value": "some policy"}
conn.create_role(
RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/"
RoleName="my-role",
AssumeRolePolicyDocument=json.dumps(assume_role_policy_document),
Path="/my-path/",
)

conn.add_role_to_instance_profile(
Expand All @@ -219,7 +222,7 @@ def test_create_role_and_instance_profile():

role = conn.get_role(RoleName="my-role")["Role"]
assert role["Path"] == "/my-path/"
assert role["AssumeRolePolicyDocument"] == "some policy"
assert role["AssumeRolePolicyDocument"] == assume_role_policy_document

profile = conn.get_instance_profile(InstanceProfileName="my-profile")[
"InstanceProfile"
Expand Down Expand Up @@ -3209,6 +3212,98 @@ def test_create_role_no_path():
assert resp["Role"]["Description"] == "test"


@mock_aws()
def test_role_policy_encoding():
role_name = "my-role"
policy_name = "my-policy"
conn = boto3.client("iam", region_name="us-east-1")
assume_policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {"Service": "lambda.amazonaws.com"},
"Effect": "Allow",
"Condition": {
"StringEquals": {"aws:SourceArn": "arn:aws:test%3Aencoded%3Astring"}
},
}
],
}
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["apigatway:PUT"],
"Resource": ["arn:aws:test%3Aencoded%3Astring"],
}
],
}
resp = conn.create_role(
RoleName=role_name, AssumeRolePolicyDocument=json.dumps(assume_policy_document)
)
assert resp["Role"]["AssumeRolePolicyDocument"] == assume_policy_document
conn.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=json.dumps(policy_document),
)
resp = conn.get_role_policy(RoleName=role_name, PolicyName=policy_name)
assert resp["PolicyDocument"] == policy_document


@mock_aws()
def test_user_policy_encoding():
user_name = "my-user"
policy_name = "my-policy"
conn = boto3.client("iam", region_name="us-east-1")

policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["apigatway:PUT"],
"Resource": ["arn:aws:test%3Aencoded%3Astring"],
}
],
}
conn.create_user(UserName=user_name)
conn.put_user_policy(
UserName=user_name,
PolicyName=policy_name,
PolicyDocument=json.dumps(policy_document),
)
resp = conn.get_user_policy(UserName=user_name, PolicyName=policy_name)
assert resp["PolicyDocument"] == policy_document


@mock_aws()
def test_group_policy_encoding():
group_name = "my-group"
policy_name = "my-policy"
conn = boto3.client("iam", region_name="us-east-1")
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["apigatway:PUT"],
"Resource": ["arn:aws:test%3Aencoded%3Astring"],
}
],
}
conn.create_group(GroupName=group_name)
conn.put_group_policy(
GroupName=group_name,
PolicyName=policy_name,
PolicyDocument=json.dumps(policy_document),
)
resp = conn.get_group_policy(GroupName=group_name, PolicyName=policy_name)
assert resp["PolicyDocument"] == policy_document


@mock_aws()
@pytest.mark.parametrize(
"region,partition", [("us-west-2", "aws"), ("cn-north-1", "aws-cn")]
Expand Down

0 comments on commit 1a2a733

Please sign in to comment.