Mock EC2 metadata service that can run on a developer machine.
It is possible in AWS to protect API access with MFA. However, this is not convenient to setup or use since you must regularly refresh your credentials.
Amazon EC2 instances have a metadata service that can be accessed to retrieve the instance profile credentials. Those credentials are rotated regularly and have an associated expiration. Most AWS SDKs check this service to use the instance profile credentials.
This service runs on a developer machine and masquerades as the Amazon metadata service. This allows it to integrate with the SDKs and provide session credentials when needed. The credentials are generated on demand using the STS GetSessionToken or AssumeRole API.
There are three effectives modes of operation for the service:
- If the user has no MFA device setup, the credentials are generated without a one time pass code. The resulting credentials should have the same permissions as the user's credentials. This operation is transparent to the user.
- If the user has a virtual MFA device and has provided the MFA secret, the pass code is generated by this service and used to create the credentials. This operation is completely transparent to the user.
- If the user has an MFA device and has not provided the MFA secret, a small prompt window pops up to enter the OTP code when the service needs to generate new credentials. This will occur the first time the service starts up and after previous credentials expire. This mode can be useful if the user has a hardware keyfob, for testing the service with an existing virtual MFA, or if the user doesn't want to store the MFA secret.
This application is still beta and may change in breaking ways in the future.
- python 2
- python 3 has not been tested
- boto python library
- this may change to botocore, which will impact config file format
Currently only works on OSX, but should be trivial to make it work on other platforms.
This should be changed to run as a launchd daemon at some point.
- Clone the repo
- Add AWS access key and secret key to server.conf in the repo directory
- See Configuration section
- These permanent keys are used to generate the session keys using the MFA token
- There must be an MFA device associated with the account
- Run
bin/server-macos
- Prompts for password to setup an IP alias and firewall forwarding rule.
- You can examine the script and run the commands separately. The script is only provided for convenience.
SERVICE_HOME/server.conf
~/.aws-mock-metadata/config
[aws]
access_key=... # Optional. Access key to generate temp keys with.
# If not provided, the service falls back on boto's
# credential discovery.
secret_key=... # Optional. Secret key to generate temp keys with.
# If not provided, the service falls back on boto's
# credential discovery.
region=us-east-1 # Optional. Not generally necessary since IAM and STS
# are not region specific. Default is us-east-1.
mfa_secret=... # Optional. Base32 encoded virtual MFA device secret.
# If set, the metadata server will generate OTP codes
# internally instead of showing a prompt. The secret
# is provided when setting up the virtual mfa device.
[metadata]
host=169.254.169.254 # Optional. Interface to bind to. Default is
# 169.254.169.254.
port=45000 # Optional. Port to bind to. Default is 45000.
token_duration=43200 # Optional. Timeout, in seconds, for the generated
# keys. Minimum is 15 minutes.
# Default is 12 hours for sts:GetSessionToken and
# 1 hour for sts:AssumeRole. Maximum is 36 hours
# for sts:GetSessionToken and 1 hour for
# sts:AssumeRole. If you specify a value higher
# than the maximum, the maximum is used.
role_arn=arn:aws:iam::123456789012:role/${aws:username}
# Optional. ARN of a role to assume. If specified,
# the metadata server uses sts:AssumeRole to create
# the temporary credentials. Otherwise, the
# metadata server uses sts:GetSessionToken.
# The string '${aws:username}' will be replaced
# with the name of the user requesting the
# credentials. No other variables are currently
# supported.
profile=... # Name of the initial profile to select. Default is
# "default".
# Define a profile. A profile consists of a set of options
# used to create a session.
# Multiple profiles can be defined and switched on-the-fly.
# Default option values are taken from the [aws] and [metadata]
# sections.
[profile:NAME] # NAME is a string used to identify the profile
access_key=...
secret_key=...
mfa_secret=...
region=...
token_duration=...
role_arn=...
If you don't provide the MFA secret and use a separate device to generate OTP codes, it is recommended to update the local metadata service timeout for the AWS command line interface. This ensures that you have enough time to enter the MFA token before the request expires and your script can continue without interruption.
~/.aws/config
[default]
metadata_service_timeout = 15.0 # 15 second timeout to enter MFA token
The following EC2 metadata service endpoints are implemented.
169.254.169.254/latest/meta-data/iam/security-credentials/
169.254.169.254/latest/meta-data/iam/security-credentials/local-credentials
If the MFA device secret is not provided and
/latest/meta-data/iam/security-credentials/local-credentials
is
requested and there are no session credentials available, a dialog pops
up prompting for the MFA token. The dialog blocks the request until the
correct token is entered. Once the token is provided, the session
credentials are generated and cached until they expire. Once they
expire, a new token prompt will appear on the next request for
credentials.
A simple UI to view, reset, and update the session credentials is available by loading http://169.254.169.254/manage in your browser.
There is an API available to query and update the MFA session credentials.
GET 169.254.169.254/manage/profiles
{
"profiles": [
{
"name": "...",
"accessKey": "...",
"region": "...",
"tokenDuration": 123, // Optional
"roleArn": "...", // Optional
"session": { // Optional, if there is no active session for the profile
"accessKey": "....",
"secretKey": "....",
"sessionToken": "...",
"expiration": "2014-04-09T09:00:44Z"
},
}
]
}
GET 169.254.169.254/manage/profiles/NAME
- Profile does not exist.
{
"profile": {
"name": "...",
"accessKey": "...",
"region": "...",
"tokenDuration": 123, // Optional
"roleArn": "...", // Optional
"session": { // Optional, if there is no active session for the profile
"accessKey": "....",
"secretKey": "....",
"sessionToken": "...",
"expiration": "2014-04-09T09:00:44Z"
},
}
}
GET 169.254.169.254/manage/session
Returns current session information.
{
"session": { // Optional, if there is no active session for the profile
"accessKey": "....",
"secretKey": "....",
"sessionToken": "...",
"expiration": "2014-04-09T09:00:44Z"
},
"profile": {
"accessKey": "...",
"region": "...",
"name": "...",
"tokenDuration": 123, // Optional
"roleArn": "..." // Optional
}
}
DELETE 169.254.169.254/manage/session
Session credentials were cleared or no session exists.
POST 169.254.169.254/manage/session
Create a new session, change the current profile, or both.
Examples:
curl -X POST -d token=123456 169.254.169.254/manage/session
curl -X POST -d session=other 169.254.169.254/manage/session
curl -X POST -d 'token=123456&session=other' 169.254.169.254/manage/session
Content-Type: application/x-www-form-urlencoded
token=123456&session=other
Content-Type: application/json
{
"token": "123456",
"session": "other"
}
- Invalid MFA token string format. Must be 6 digits.
- The specified profile name does not exist.
- Neither profile nor token was specified.
- Specified MFA token does not match expected token for the MFA device.
Session was created successfully and/or profile the was changed. Returns the session information. The response will contain a session if a valid token was provided. The response will not contain a session if only a new profile name was specified and the profile does not have an existing session.
{
"session": { // Optional, if there is no active session for the profile
"accessKey": "....",
"secretKey": "....",
"sessionToken": "...",
"expiration": "2014-04-09T09:00:44Z"
},
"profile": {
"accessKey": "...",
"region": "...",
"name": "...",
"tokenDuration": 123, // Optional
"roleArn": "..." // Optional
}
}
The MIT License (MIT) Copyright (c) 2014 Cory Thomas
See LICENSE