Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EC2 Instance Profile is not removed from instance after dissociation #8562

Open
micheal-hill-sportsbet opened this issue Feb 4, 2025 · 1 comment
Labels

Comments

@micheal-hill-sportsbet
Copy link

micheal-hill-sportsbet commented Feb 4, 2025

What is the intended behaviour of the combination of ec2.describe_instances(...), ec2.associate_iam_instance_profile() and ec2.disassociate_iam_instance_profile(...)? Currently it's trivial to create an inconsistent state between these methods in moto.

Prior to #5617 the IamInstanceProfile was not linked to an ec2.models.instances.Instance, so describe_instances would never return an EC2 instance with the field IamInstanceProfile. That PR added logic to update the EC2 Instance attributes in the constructor of an IamInstanceProfileAssociation - but the EC2 instance is not updated again if the IamInstanceProfileAssociation is removed.

Is this a bug in moto, or intended behavior? If a bug, would it be amendable to a PR?


Background, if that's interesting/useful

I recently encountered an old test using moto@4.0.1 and have been working to upgrade it to the latest (5.0.28 as I write this).

One of the test cases uses an IamInstanceProfileAssociation, and calls iam_client.disassociate_iam_instance_profile(...) before making an assertion on the IamInstanceProfile key of the related EC2 instance.

  • The test passes by accident in moto>=4.0.1,<4.0.9. Prior to 4.0.9 the instance profile was never linked into the set of ec2 reservations stored by moto.
  • The test fails with moto>=4.0.9, as feat: link instance profile with ec2 instance #5617 updates the IamInstanceProfile of the related EC2 instance when associate_iam_instance_profile is called, however it does not update after calling disassociate_iam_instance_profile

Test case to reproduce (loosely based on the test I'm trying to fix)

def test_reproduce_iip_error(
    ec2_client, iam_client, fake_ec2_instance, fake_instance_profile
):
    iip_association = ec2_client.associate_iam_instance_profile(
        IamInstanceProfile={
            "Arn": fake_instance_profile["Arn"],
            "Name": "Unit-Testing-IP",
        },
        InstanceId=fake_ec2_instance["InstanceId"],
    )["IamInstanceProfileAssociation"]

    assert (
        len(
            ec2_client.describe_iam_instance_profile_associations()[
                "IamInstanceProfileAssociations"
            ]
        )
        == 1
    )
    ec2_instances = [
        instance
        for reservation in ec2_client.describe_instances()["Reservations"]
        for instance in reservation["Instances"]
    ]

    assert len(ec2_instances) == 1
    assert "IamInstanceProfile" in ec2_instances[0]  # <-- fails in moto < 4.0.9

    ec2_client.disassociate_iam_instance_profile(
        AssociationId=iip_association["AssociationId"]
    )

    assert (
        len(
            ec2_client.describe_iam_instance_profile_associations()[
                "IamInstanceProfileAssociations"
            ]
        )
        == 0
    )
    ec2_instances = [
        instance
        for reservation in ec2_client.describe_instances()["Reservations"]
        for instance in reservation["Instances"]
    ]

    assert len(ec2_instances) == 1
    assert "IamInstanceProfile" not in ec2_instances[0] # <-- fails in moto >= 4.0.9

details for fixture setup for the test, in case that's relevant

@pytest.fixture
def aws_credentials():
    """Mocked AWS Credentials for moto."""
    os.environ["AWS_ACCESS_KEY_ID"] = "testing"
    os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
    os.environ["AWS_SECURITY_TOKEN"] = "testing"
    os.environ["AWS_SESSION_TOKEN"] = "testing"
    os.environ["AWS_DEFAULT_REGION"] = "us-east-1"


@pytest.fixture
def ec2_client(aws_credentials):
    ec2_mocker = getattr(moto, "mock_ec2", getattr(moto, "mock_aws", None))

    with ec2_mocker():
        yield boto3.client("ec2")


@pytest.fixture
def iam_client(aws_credentials):
    try:
        iam_mocker = moto.mock_aws(config={"iam": {"load_aws_managed_policies": True}})
    except AttributeError:
        iam_mocker = moto.mock_iam()

    with iam_mocker:
        yield boto3.client("iam")


@pytest.fixture
def fake_ec2_instance(ec2_client, *, sg_name="example_sg"):
    vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
    subnet = ec2_client.create_subnet(VpcId=vpc["VpcId"], CidrBlock="10.0.0.0/18")[
        "Subnet"
    ]

    ec2_client.create_security_group(Description="test", GroupName=sg_name)

    return ec2_client.run_instances(
        ImageId="ami-1248f5a4",
        MinCount=1,
        MaxCount=1,
        SecurityGroups=[sg_name],
        SubnetId=subnet["SubnetId"],
    )["Instances"][0]


@pytest.fixture
def fake_instance_profile(
    iam_client,
    *,
    role_name="unit-testing-role",
    instance_profile_name="Unit-Testing-IP",
):
    # Create instance profiles for testing
    assume_role_policy_document = json.dumps(
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {"Service": "ec2.amazonaws.com"},
                    "Action": "sts:AssumeRole",
                }
            ],
        }
    )

    iam_client.create_role(
        Path="/",
        RoleName=role_name,
        AssumeRolePolicyDocument=assume_role_policy_document,
        Description="Test Role",
    )

    iam_client.attach_role_policy(
        RoleName=role_name, PolicyArn="arn:aws:iam::aws:policy/AmazonS3FullAccess"
    )

    instance_profile = iam_client.create_instance_profile(
        InstanceProfileName=instance_profile_name, Path="/"
    )["InstanceProfile"]

    iam_client.add_role_to_instance_profile(
        InstanceProfileName=instance_profile_name, RoleName=role_name
    )

    return instance_profile
@bblommers
Copy link
Collaborator

Hi @micheal-hill-sportsbet! I haven't looked into the details, but based on your description that sounds like bug.

A PR with a fix + test would be very welcome!

@bblommers bblommers added the bug label Feb 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants