AWS Cloud Operations & Migrations Blog

Providing temporary instance permissions with AWS Systems Manager Automations

Instances might have to call certain API actions or access certain resources during an AWS Systems Manager Automation execution. What if you don’t want to apply the additional permissions to the instance’s existing instance profile?

In this post, I show you how to provide temporary permissions to instances when executing an Automation within the document content. First, I create an example custom IAM policy. Then, I demonstrate how this policy can be used within the Automation document to create a temporary instance profile. The profile is attached to the target instances during Automation execution and removed at the end of the execution.

AWS Systems Manager provides the AmazonSSMManagedInstanceCore managed IAM policy that enables an instance to use Systems Manager core service functionality when added to an instance profile. Depending on the Automation workflow, additional permissions may be required to successfully execute that Automation document.

By creating dedicated IAM policies for each Automation workflow, you can incorporate instance profile creation and attachment in custom Automation documents for the target instances, without modifying the existing instance profiles.

This can be helpful if you prefer to avoid modifying instance profiles that are attached to instances long term. The instance profiles only contain the core permissions required for Systems Manager functionality but have Automations that require access to resources like Amazon S3 or AWS KMS.

For example, I’ve created a custom IAM policy named expandedPermissionsForAutomation that contains expanded permissions for S3, which can be seen in the upcoming code. For more details, see Creating IAM Policies.


{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:DescribeAssociation",
                "ssm:GetDeployablePatchSnapshotForInstance",
                "ssm:GetDocument",
                "ssm:GetManifest",
                "ssm:GetParameters",
                "ssm:ListAssociations",
                "ssm:ListInstanceAssociations",
                "ssm:PutInventory",
                "ssm:PutComplianceItems",
                "ssm:PutConfigurePackageResult",
                "ssm:UpdateAssociationStatus",
                "ssm:UpdateInstanceAssociationStatus",
                "ssm:UpdateInstanceInformation"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2messages:AcknowledgeMessage",
                "ec2messages:DeleteMessage",
                "ec2messages:FailMessage",
                "ec2messages:GetEndpoint",
                "ec2messages:GetMessages",
                "ec2messages:SendReply"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstanceStatus"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketLocation",
                "s3:PutObject",
                "s3:GetObject",
                "s3:GetEncryptionConfiguration",
                "s3:AbortMultipartUpload",
                "s3:ListMultipartUploadParts",
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads"
            ],
	    "Resource": [
                "arn:aws:s3:::my-script-bucket",
                "arn:aws:s3:::my-script-bucket/*"
            ]
        }
    ]
}

Now that the policy with the desired permissions has been created, you can incorporate the role creation, instance profile creation, and association with the target instances in the Automation document primarily using the aws:executeAwsApi plugin. For more details, see Systems Manager Automation Actions Reference.

I’ve demonstrated in the following code how these steps can be ordered for execution in a custom Automation document. onFailure properties have been defined for several steps to assist with cleanup should the execution fail.

In the initial steps, you gather details from the existing instance profile associated with your target instances. Then, the temporary instance profile is created and associated with the target instances using the AWS-AttachIAMToInstance Automation prior to continuing to the steps that require the additional permissions. Generally, these steps would precede the actions taken by the instances that require the applied permissions of your newly created policy, depending on the details of the step.


	{
            "name": "getOriginalInstanceProfile",
            "action": "aws:executeAwsApi",
            "onFailure": "Abort",
            "inputs": {
                "Service": "ec2",
                "Api": "DescribeIamInstanceProfileAssociations",
                "Filters": [
                    {
                        "Name": "instance-id",
                        "Values": ["{{ InstanceId }}"]
                    }
                ]
            },
            "outputs": [
                {
                    "Name": "instanceProfileArn",
                    "Selector": "$.IamInstanceProfileAssociations[0].IamInstanceProfile.Arn",
                    "Type": "String"
                },
                {
                    "Name": "associationId",
                    "Selector": "$.IamInstanceProfileAssociations[0].AssociationId",
                    "Type": "String"
                }
            ],
            "nextStep": "createTempAutomationInstanceRole"
        },
        {
            "name": "createTempAutomationInstanceRole",
            "action": "aws:executeAwsApi",
            "onFailure": "Abort",
            "inputs": {
                "Service": "iam",
                "Api": "CreateRole",
                "AssumeRolePolicyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}",
                "RoleName": "tempAutomationInstanceRole"
                },
            "nextStep": "attachTempAutomationPolicy"
        },
        {
            "name": "attachTempAutomationPolicy",
            "action": "aws:executeAwsApi",
            "onFailure": "step:detachTempAutomationPolicy",
            "inputs": {
                "Service": "iam",
                "Api": "AttachRolePolicy",
                "PolicyArn": "arn:aws:iam::01234567890:policy/expandedPermissionsForAutomation",
                "RoleName": "tempAutomationInstanceRole"
                },
            "nextStep": "executeAttachIAMAutomation"
        },
        {
            "name": "executeAttachIAMAutomation",
            "action": "aws:executeAutomation",
            "onFailure": "step:removeTempAutomationRoleFromInstanceProfile",
            "inputs": {
                "DocumentName": "AWS-AttachIAMToInstance",
                "RuntimeParameters": {
                    "RoleName": ["tempAutomationInstanceRole"],
                    "InstanceId": ["{{ InstanceId }}"]
                }
            },
            "nextStep": "verifyTempInstanceProfileAssociation"
        }

After associating tempAutomationInstanceRole, use the aws:waitForAwsResourceProperty plugin to make sure that the State value reflects the associated value before continuing to steps that require the permissions being applied. Also, gather the details from the temporary instance profile associated with your target instances, which are used later for the reassociation of the original instance profile.

Next, to demonstrate the use of the expanded permissions provided by the temporary instance profile, run the AWS-RunRemoteScript command.


	{
            "name": "verifyTempInstanceProfileAssociation",
            "action": "aws:waitForAwsResourceProperty",
            "maxAttempts": 10,
            "onFailure": "Abort",
            "inputs": {
              "Service": "ec2",
              "Api": "DescribeIamInstanceProfileAssociations",
              "Filters": [
                {
                  "Name": "instance-id",
                  "Values": ["{{ InstanceId }}"]
                }
              ],
              "PropertySelector": "$.IamInstanceProfileAssociations[0].State",
              "DesiredValues": [
                "associated"
              ]
            },
            "nextStep": "getTempInstanceProfile"
        },
        {
          "name": "getTempInstanceProfile",
          "action": "aws:executeAwsApi",
          "onFailure": "step:associateOriginalInstanceProfile",
          "inputs": {
              "Service": "ec2",
              "Api": "DescribeIamInstanceProfileAssociations",
              "Filters": [
                  {
                      "Name": "instance-id",
                      "Values": ["{{ InstanceId }}"]
                  }
              ]
          },
          "outputs": [
              {
                  "Name": "instanceProfileArn",
                  "Selector": "$.IamInstanceProfileAssociations[0].IamInstanceProfile.Arn",
                  "Type": "String"
              },
              {
                  "Name": "associationId",
                  "Selector": "$.IamInstanceProfileAssociations[0].AssociationId",
                  "Type": "String"
              }
          ],
          "nextStep": "runScriptFromS3"
        },
	{
          "name": "runScriptFromS3",
          "action": "aws:runCommand",
          "onFailure": "step:associateOriginalInstanceProfile",
          "inputs": {
              "DocumentName": "AWS-RunRemoteScript",
              "InstanceIds": ["{{ InstanceId }}"],
              "Parameters": {
                "sourceType": "S3",
                "sourceInfo": {
                  "path": "https://my-script-bucket.s3-us-west-
2.amazonaws.com/exampleScript.py"
                },
                "commandLine": "python3 exampleScript.py"
              }
          },
          "nextStep": "associateOriginalInstanceProfile"
        }

After the actions requiring the expanded permissions have been completed, you can replace the temporary instance profile with the original instance profile. To accomplish this, use the outputs from the earlier steps. Following the reassociation, you can then delete the tempAutomationInstanceRole, as it is no longer needed following the execution. For more details, see Deleting an IAM Role (AWS API).


	{
          "name": "associateOriginalInstanceProfile",
          "action": "aws:executeAwsApi",
          "onFailure": "Abort",
          "inputs": {
            "Service": "ec2",
            "Api": "ReplaceIamInstanceProfileAssociation",
            "AssociationId": "{{ getTempInstanceProfile.associationId }}",
            "IamInstanceProfile": {
                "Arn": "{{ getOriginalInstanceProfile.instanceProfileArn }}"
              }
          },
          "nextStep": "verifyOriginalInstanceProfileAssociation"
        },
        {
          "name": "verifyOriginalInstanceProfileAssociation",
          "action": "aws:waitForAwsResourceProperty",
          "maxAttempts": 10,
          "onFailure": "Abort",
          "inputs": {
            "Service": "ec2",
            "Api": "DescribeIamInstanceProfileAssociations",
            "Filters": [
              {
                "Name": "instance-id",
                "Values": ["{{ InstanceId }}"]
              }
            ],
            "PropertySelector": "$.IamInstanceProfileAssociations[0].State",
            "DesiredValues": [
              "associated"
            ]
          },
          "nextStep": "removeTempAutomationRoleFromInstanceProfile"
        },
        {
          "name": "removeTempAutomationRoleFromInstanceProfile",
          "action": "aws:executeAwsApi",
          "onFailure": "step:deleteTempAutomationInstanceProfile",
          "inputs": {
            "Service": "iam",
            "Api": "RemoveRoleFromInstanceProfile",
            "InstanceProfileName": "tempAutomationInstanceRole",
            "RoleName": "tempAutomationInstanceRole"
          },
          "nextStep": "deleteTempAutomationInstanceProfile"
        },
        {
          "name": "deleteTempAutomationInstanceProfile",
          "action": "aws:executeAwsApi",
          "onFailure": "step:detachTempAutomationPolicy",
          "inputs": {
            "Service": "iam",
            "Api": "DeleteInstanceProfile",
            "InstanceProfileName": "tempAutomationInstanceRole"
          },
          "nextStep": "detachTempAutomationPolicy"
        },
        {
          "name": "detachTempAutomationPolicy",
          "action": "aws:executeAwsApi",
          "onFailure": "step:deleteTempAutomationInstanceRole",
          "inputs": {
              "Service": "iam",
              "Api": "DetachRolePolicy",
              "PolicyArn": "arn:aws:iam::01234567890:policy/expandedPermissionsForAutomation",
              "RoleName": "tempAutomationInstanceRole"
              },
          "nextStep": "deleteTempAutomationInstanceRole"
        },
        {
          "name": "deleteTempAutomationInstanceRole",
          "action": "aws:executeAwsApi",
          "onFailure": "Abort",
          "inputs": {
            "Service": "iam",
            "Api": "DeleteRole",
            "RoleName": "tempAutomationInstanceRole"
          }
        }
    ]
}

Conclusion

In this post, I walked you through how to provide temporary permissions to target instances during Automation execution through temporary instance profiles. This avoids modifications to instance profiles that are attached to instances long-term, and which only contain the core permissions required for Systems Manager functionality. For more information, see AWS Systems Manager Automation in the User Guide.

About the Author

Caleb Gutierrez is a Cloud Support Engineer in AWS Premium Support. He specializes in Amazon EC2 Windows, AWS Directory Service, AWS Systems Manager, and Microsoft PowerShell. Outside of work, he enjoys volunteering, hiking, and rock climbing.