Skip to content

Instantly share code, notes, and snippets.

@inkblot
Last active November 19, 2023 15:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save inkblot/aed97400ee9cc741e09e0b00bbb4ce4c to your computer and use it in GitHub Desktop.
Save inkblot/aed97400ee9cc741e09e0b00bbb4ce4c to your computer and use it in GitHub Desktop.
Authenticate to vault using IAM instance profile credentials in bash using curl, openssl, and jq
#!/bin/bash
_SELF="${0##*/}"
_HERE="$(dirname $(realpath ${0}))"
function aws_instance_profile_arn() {
curl -s http://169.254.169.254/2019-10-01/meta-data/iam/info | jq -r .InstanceProfileArn
}
function aws_instance_profile_name() {
aws_instance_profile_arn | sed -e 's/.*\///'
}
function aws_credentials() {
curl -s http://169.254.169.254/2019-10-01/meta-data/iam/security-credentials/$(aws_instance_profile_name)
}
function aws_access_key_id() {
aws_credentials | jq -r .AccessKeyId
}
function aws_secret_access_key() {
aws_credentials | jq -r .SecretAccessKey
}
function aws_session_token() {
aws_credentials | jq -r .Token
}
function aws_environment() {
cat <<EOS
export AWS_ACCESS_KEY_ID=$(aws_access_key_id)
export AWS_SECRET_ACCESS_KEY=$(aws_secret_access_key)
export AWS_SESSION_TOKEN=$(aws_session_token)
EOS
}
if [ "${_SELF}" = "aws-credentials.sh" ]; then
set -eu
aws_environment
fi
#!/bin/bash
function AWS4_HMAC_SHA256() {
openssl dgst -sha256 -hex -mac HMAC -macopt "$1" 2>/dev/null | awk '{print $2}'
}
function AWS4_SHA256() {
openssl dgst -sha256 -hex 2>/dev/null | awk '{print $2}'
}
function AWS4_BASE64() {
openssl base64 -A
}
function AWS4_SIGN() {
local kSecret="AWS4$1"
local kDate=$(printf '%s' "$2" | AWS4_HMAC_SHA256 "key:${kSecret}")
local kRegion=$(printf '%s' "$3" | AWS4_HMAC_SHA256 "hexkey:${kDate}")
local kService=$(printf '%s' "$4" | AWS4_HMAC_SHA256 "hexkey:${kRegion}")
local kSigning=$(printf '%s' "aws4_request" | AWS4_HMAC_SHA256 "hexkey:${kService}")
local signedString=$(printf '%s' "$5" | AWS4_HMAC_SHA256 "hexkey:${kSigning}")
printf '%s' "${signedString}"
}
#!/bin/bash
_SELF="${0##*/}"
_HERE="$(dirname $(realpath ${0}))"
source "${_HERE}/parse-url.sh"
source "${_HERE}/aws-credentials.sh"
source "${_HERE}/aws4-sign.sh"
function vault_iam_auth() {
local vault_role="$1"
local vault_addr="${2:-${VAULT_ADDR:-http://vault.example.internal:8200}}"
local vault_ca_cert="${3:-${VAULT_CA_CERT:-}}"
local aws_access_key_id="$(aws_access_key_id)"
local aws_secret_access_key="$(aws_secret_access_key)"
local aws_session_token="$(aws_session_token)"
local vault_host=$(parse_url "${vault_addr}" | jq -r .host)
local auth_type="AWS4-HMAC-SHA256"
local amz_date=$(TZ=Z date +%Y%m%dT%H%M%SZ)
local content_type="application/x-www-form-urlencoded charset=utf-8"
local iam_request_body="Action=GetCallerIdentity&Version=2011-06-15"
local iam_request_host="sts.amazonaws.com"
local iam_request_url="https://${iam_request_host}/"
local signed_headers="content-type;host;x-amz-date;x-amz-security-token;x-vault-aws-iam-server-id"
local canonical_request="$(cat <<EOR
POST
/
content-type:${content_type}
host:${iam_request_host}
x-amz-date:${amz_date}
x-amz-security-token:${aws_session_token}
x-vault-aws-iam-server-id:${vault_host}
${signed_headers}
$(printf '%s' "${iam_request_body}" | AWS4_SHA256)
EOR
)"
local credential_scope="${amz_date:0:8}/us-east-1/sts/aws4_request"
local signed_string="$(cat <<EOS
${auth_type}
${amz_date}
${credential_scope}
$(printf '%s' "${canonical_request}" | AWS4_SHA256)
EOS
)"
#printf 'signed_string: $%s^\n\n' "${signed_string}"
local signature=$(AWS4_SIGN "${aws_secret_access_key}" "${amz_date:0:8}" "us-east-1" "sts" "${signed_string}")
local authorization="${auth_type} Credential=${aws_access_key_id}/${credential_scope}, SignedHeaders=${signed_headers}, Signature=${signature}"
local iam_request_headers="$(cat <<EOH
{
"Content-Type": ["${content_type}"],
"Host": ["${iam_request_host}"],
"X-Amz-Date": ["${amz_date}"],
"X-Amz-Security-Token": ["${aws_session_token}"],
"X-Vault-AWS-IAM-Server-Id": ["${vault_host}"],
"Authorization": ["${authorization}"]
}
EOH
)"
local data="$(cat <<EOJ
{
"role": "${vault_role}",
"iam_http_request_method": "POST",
"iam_request_url": "$(printf '%s' "${iam_request_url}" | AWS4_BASE64)",
"iam_request_body": "$(printf '%s' "${iam_request_body}" | AWS4_BASE64)",
"iam_request_headers": "$(printf '%s' "${iam_request_headers}" | AWS4_BASE64)"
}
EOJ
)"
curl -s ${vault_ca_cert:+--cacert ${vault_ca_cert}} --request POST --data "$(jq -c . <<<"${data}")" "${vault_addr}/v1/auth/aws/login"
}
if [ "${_SELF}" = "vault-iam-auth.sh" ]; then
set -eu
vault_iam_auth "$@"
fi
@c0d5x
Copy link

c0d5x commented May 26, 2022

How can it be used ?

@inkblot
Copy link
Author

inkblot commented Jun 29, 2022

I use these scripts to authenticate to vault using an EC2 instance's IAM role. To do this requires that you have a vault server. In this vault server the aws authentication backend must be mounted. In the aws backend there must be a backend role and policy bound to an IAM role. With these things in place, if you start an EC2 instance with the IAM role in its instance profile and put these shell scripts in the filesystem on the EC2 instance, you can authenticate to vault.

To get a vault token, log into the EC2 instance and run a command similar to vault_iam_auth "ec2-instance-role" "https://vault:8200" ${vault_ca_cert:-}. The first parameter ec2-instance-role is the name of the vault aws backend role. The second parameter is the URL of the vault server. vault_ca_cert here can be set to the path of a file containing the CA certificate which has issued the server certificate for the vault server, or - if you intend to pass this certificate via stdin. The stdout from this command is JSON produced by the authentication with vault. The token can be extracted from this JSON using the command jq -r .auth.client_token. This command accepts the JSON on stdin. With this token, you can access vault in accordance with the policy that is bound to the aws backend role.

These scripts intentionally depend on very few tools. I think the complete list of commands that these scripts invoke is bash, openssl, curl, jq, cat, sed, awk, dirname, and realpath.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment