moto icon indicating copy to clipboard operation
moto copied to clipboard

IAM: Omit role tags field from response when not set

Open viren-nadkarni opened this issue 6 months ago • 2 comments

This PR fixes a small cosmetic bug which popped up in LocalStack snapshot tests.

Moto IAM CreateRole always returns the Tags field.

$ aws --endpoint-url=http://localhost:5000 iam create-role \
    --role-name hello0 \
    --assume-role-policy-document '{"Version": "2012-10-17", "Statement": [{"Action": "sts:AssumeRole", "Principal": {"AWS": "123456789012"}, "Effect": "Allow"}]}'
{
    "Role": {
        "Path": "/",
        "RoleName": "hello0",
        "RoleId": "AROARZPUZDIKGYSLBPCKW",
        "Arn": "arn:aws:iam::123456789012:role/hello0",
        "CreateDate": "2025-06-19T12:42:46.393417Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "AWS": "123456789012"
                    },
                    "Effect": "Allow"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "Tags": [],
        "RoleLastUsed": {}
    }
}

AWS IAM CreateRole only returns the Tags field when specified in the request.

# Without tags
$ aws iam create-role \
    --role-name hello1 \
    --assume-role-policy-document '{"Version": "2012-10-17", "Statement": [{"Action": "sts:AssumeRole", "Principal": {"AWS": "123456789012"}, "Effect": "Allow"}]}'                                                                                                    
{                               
    "Role": {                 
        "Path": "/",
        "RoleName": "hello1",
        "RoleId": "AROAZCRR4CRXXXXXXXXXX",
        "Arn": "arn:aws:iam::123456789012:role/hello1",
        "CreateDate": "2025-06-19T12:28:18Z",
        "AssumeRolePolicyDocument": {                                                                    
            "Version": "2012-10-17",                                                                                                                                                                              
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "AWS": "123456789012"
                    },
                    "Effect": "Allow"
                }
            ]
        }
    }
}
# With tags
$ aws iam create-role \
    --role-name hello2 \
    --assume-role-policy-document '{"Version": "2012-10-17", "Statement": [{"Action": "sts:AssumeRole", "Principal": {"AWS": "123456789012"}, "Effect": "Allow"}]}' --tags Key=colour,Value=red
{
    "Role": {
        "Path": "/",
        "RoleName": "hello2",
        "RoleId": "AROAZCRR4CRXXXXXXXXXX",
        "Arn": "arn:aws:iam::123456789012:role/hello2",
        "CreateDate": "2025-06-19T12:28:42Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "AWS": "123456789012"
                    },
                    "Effect": "Allow"
                }
            ]
        },
        "Tags": [
            {
                "Key": "colour",
                "Value": "red"
            }
        ]
    }
}

See https://github.com/getmoto/moto/pull/8970

viren-nadkarni avatar Jun 19 '25 12:06 viren-nadkarni

The tags-attribute is always a list, either empty or not. When serializing, it uses the standard xmltodict.unparse-method to serialize it, which means that for an empty list it will always return <Tags />.

My 'solution' is to explicitly set the tags to None to prevent it from being serialized, but ideally we come up with a better (more generic) solution.

  • botocore does not seem to have any info on whether a field should be serialized when empty, so we cannot use that
  • In an ideal world this behaviour would be centralized, so empty lists (in all services/responses) should never be serialized. I'd be very surprised if AWS has a standard here though, so we can't do that either.

@bpandola Any ideas on how we could handle this?

bblommers avatar Jun 20 '25 19:06 bblommers

Codecov Report

All modified and coverable lines are covered by tests :white_check_mark:

Project coverage is 92.85%. Comparing base (9cca1cf) to head (ef76579). Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #8997   +/-   ##
=======================================
  Coverage   92.85%   92.85%           
=======================================
  Files        1287     1287           
  Lines      112758   112761    +3     
=======================================
+ Hits       104700   104703    +3     
  Misses       8058     8058           
Flag Coverage Δ
servertests 28.51% <0.00%> (-0.01%) :arrow_down:
unittests 92.82% <100.00%> (+<0.01%) :arrow_up:

Flags with carried forward coverage won't be shown. Click here to find out more.

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

codecov[bot] avatar Jun 20 '25 19:06 codecov[bot]

@bblommers As far as I can tell, the Smithy docs do not explicitly state whether or not empty lists should be serialized (in responses). There does not appear to be a standard either, as IAM and RDS (both using the "query" protocol) differ in their behavior with respect to returning (or not returning) empty lists.

I am not in favor of altering the model, i.e. setting tags to None when the list is empty. This is a serialization concern and should be handled in the responses.py layer. The other suggestion of copying the model object and manually setting tags to None for the response would probably be fine as a one-off, but I think there's a larger issue here. I'm already working on a PR for IAM to encapsulate the logic for url-encoding policies in responses -- something similar looks like it will be needed for tags. I see a number of botocore/boto3 issues related to tags not being present in IAM reponses (e.g. https://github.com/boto/boto3/issues/1855), so having the logic in one place will be helpful as we work toward parity with AWS.

I will get something for you to look at soon (since I'm the one who made the breaking change and am most familiar with the new serialization code). I will incorporate the tests from this PR. How does that sound?

bpandola avatar Jun 23 '25 09:06 bpandola

I will get something for you to look at soon (since I'm the one who made the breaking change and am most familiar with the new serialization code). I will incorporate the tests from this PR. How does that sound?

Sounds good! Appreciate you looking into this @bpandola :pray:

bblommers avatar Jun 23 '25 09:06 bblommers

Other attributes that seem to be affected:

viren-nadkarni avatar Jun 24 '25 08:06 viren-nadkarni

@viren-nadkarni @bblommers Have a look at #9018 and let me know what you think. Thanks.

bpandola avatar Jun 27 '25 08:06 bpandola

Superseded by #9018

bpandola avatar Jun 30 '25 20:06 bpandola