A role for all your EC2 instances
tl;dr: You can now pass an IAM role to every EC2 instance in your account + region.
On Feb 17th 2023, AWS Systems Manager released Default Host Management Configuration.
This is a way to use Systems Manager on all your EC2 instances: they don’t need
ssm:*
permissions in their instance profiles, nor do they even need an instance
profile at all.
So how does it work?
- You create a role
MyCoolRole
assumable byssm.amazonaws.com
- You configure DHMC by calling the following:
aws ssm update-service-setting \ --setting-id arn:aws:ssm:${AWS_REGION}:${AWS_ACCOUNT_ID}:servicesetting/ssm/managed-instance/default-ec2-instance-management-role \ --setting-value MyCoolRole
- The SSM agent on your EC2 instances can now retrieve credentials for
MyCoolRole
.
What’s interesting to note is that this role can have any permissions, it’s not
scoped down just to SSM agent-relevant permissions. I created a proof-of-concept
credential_process
credential provider called awsaccountcreds.
To try it out, create an EC2 instance without an associated instance profile,
install awsaccountcreds
and write the following to ~/.aws/config
:
[default]
credential_process = /home/ec2-user/awsaccountcreds # or wherever you placed the executable
Now you can run something in the AWS CLI, like aws sts get-caller-identity
or
(if you granted the DHMC role S3 access) aws s3 ls
.
How it works, sequence diagram-style
See the following sequence diagram. The CLI retrieves (from the instance metadata
service) “instance identity” SigV4 credentials. Prior to DHMC, these were only used for
EC2 Instance Connect (as far as I know). Calling sts:GetCallerIdentity
with these credentials reveal they have an interesting ARN format: arn:aws:sts::607481581596:assumed-role/aws:ec2-instance/i-0ab9ac31d8ff41296
.
Next an RSA keypair is generated. The key pair’s public key is sent to SSM using
the undocumented ssm:RegisterManagedInstance
API. This API call is signed using
the above credentials (C_0
in the diagram).
Finally, ssm:RequestManagedInstanceRoleToken
(also undocumented) is invoked.
This is also signed using C_0
credentials and has an additional SSM-AsymmetricKeyAuthorization
request header. This request header is an RSA signature over the Authorization
SigV4 header. This API returns credentials (C_1
in the diagram) for a role
session for the DHMC role with the instance ID as the role session name.
It seems only one RSA keypair can be registered for a given instance ID. This
keypair can then be used to retrieve credentials multiple times. I haven’t yet
looked into how the RSA keypair gets refreshed, but it seems to be a thing (the
API has a boolean UpdateKeyPair
response field)
Risks
I reported the potential for passing an over-privileged role to the AWS security
team, and they described it as working as designed. Which is correct, and this
probably falls on the customer side of the shared responsibility model. (Though
I’m not sure why Systems Manager doesn’t pass PolicyArns=[arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy]
when calling sts:AssumeRole
) But the risk remains: anyone with ssm:UpdateServiceSetting
and iam:PassRole
can affect every EC2 instance in a single API call. And in
my experience, these permissions are typically granted to developers.
The other risk is that even though this is described as a solution for managing
instances that don’t already have SSM privileges, it affects those instances
too. Because the SSM agent tries the instance profile first (and only falls back
to DHMC if the instance profile fails), it means that those instances remain
“unregistered” and ssm:RegisterManagedInstance
will succeed for a process
running on the machine.
Thanks
Thanks to Ian Mckay, Nick Frichette and Christophe Tafani-Dereeper
for sanity-checking this. Also big thanks to Ben Bridts, who pointed out
that Systems Manager couldn’t just pass the AmazonSSMManagedEC2InstanceDefaultPolicy
managed policy as a session policy, because that would preclude the S3 and
KMS privileges needed for other parts of Systems Manager functionality. Also
thanks to the Cloud Security Forum folks whose discussion of this functionality
prompted me to do the research.