Skip to content

Instantly share code, notes, and snippets.

@inkblot
Last active January 23, 2025 23:00
Show Gist options
  • Select an option

  • Save inkblot/aed97400ee9cc741e09e0b00bbb4ce4c to your computer and use it in GitHub Desktop.

Select an option

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}))"
function parse_url() {
local url _url __url proto uphp user_pass host_port user pass host port path query_string
url="${1}"
proto="${url%://*}" # proto: everything up to the first '://'
# There should definitely be a proto, neither empty nor equal to url
if [ "${proto}" == "" -o "${proto}" == "$url" ]; then
echo "Invalid url: ${url}" >&2
return 1
fi
_url="${url:$((3 + ${#proto}))}" # _url: url minus "${proto}://"
uphp="${_url%%/*}" # uphp (User-Password-Host-Port): everything up to the first '/' in _url
__url="${_url:$((${#uphp} + 1))}" # __url: _url minus uphp
user_pass="${uphp%%@*}"
if [ "${user_pass}" == "${uphp}" ]; then
user_pass=""
host_port="${uphp}"
else
host_port="${uphp#*@}"
fi
user="${user_pass%%:*}" # user: everything up to the first ':' in user_pass
if [ "${user}" == "${user_pass}" ]; then
pass=""
else
pass="${user_pass#*:}" # pass: everything after the first ':' in user_pass, but only if there was a ':'
fi
host="${host_port%%:*}" # host: everything up to the first ':' in host_port
if [ "${host}" == "${host_port}" ]; then
port="80"
else
port="${host_port#*:}" # port: everything after the first ':' in host_port, but only if there was a ':'
fi
path="${__url%%\?*}" # path: everything up to the first '?' in __url
if [ "${path}" == "${__url}" ]; then
query_string=""
else
query_string="${__url%*\?}" # query_string: everything after the first '?' in __url, but only if there was a '?'
fi
echo "{"\
"\"proto\": \"${proto}\","\
"\"host\": \"${host}\","\
"\"port\": ${port},"\
"\"username\": \"${user}\","\
"\"password\": \"${pass}\","\
"\"path\": \"${path}\","\
"\"query_string\": \"${query_string}\""\
"}"
}
if [ "${_SELF}" = "parse-url.sh" ]; then
set -eu
parse_url "$@"
fi
#!/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
@tskinner-oppfi
Copy link

Awesome. Nice work!

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