As of the time of this writing AWS still seems to be working on their not so streamlined IAM for Service Accounts feature.
Its not difficult to get running but you have to jump through quite a few hoops especially when it comes to the command line.
If you do everything with eksctl
or the AWS Console, then its really easy. If you didn't use eksctl
to build your cluster and would like to make things simple and script it... well that's a different story.
What needs to happen
- Create an OpenIDConnect Provider in IAM based off your EKS cluster's issuer URL. This requires the following.
- EKS cluster's issuer URL
- ClientID string: "sts.amazonaws.com"
- EKS cluster's domain footprint
- Role for attaching to a service account. This requires the following.
- IAM Policy(s)
- trust relationship document
- Create/Annotate your pod's service account
Whats the problem
Some of the requirements listed above are not so straightforward to obtain or setup. In particular two of them come to mind.
- EKS cluster's domain footprint
- Role trust relationship document
lets take a look at these.
Domain Footprint
This was the biggest issue I faced... and purely because the documentation for obtaining this in anyway other than the AWS Console is incorrect. In particular (2) in the linked document returns the following error.
{"message":"Missing Authentication Token"}
I tried for sometime to accomplish (2) but I eventually gave up and searched for another way. In my search I came across an issue in terraforms-provider-aws That gave me the clue I needed and I was finally able to move on after writing a function to not only grab the footprint but create the IAM provider if not exists.
function create_provider_if_not_exists() {
local PROVIDER_LIST PROVIDER_ARN THUMBPRINT
PROVIDER_LIST=$(aws iam list-open-id-connect-providers --query OpenIDConnectProviderList --output text)
PROVIDER_ARN=$(for PROVIDER in ${PROVIDER_LIST}; do
if [[ "${PROVIDER}" == *"${2}"* ]]; then
echo "${PROVIDER}"
return
fi
done)
if ! [ "${PROVIDER_ARN}" ]; then
FOOTPRINT=$(echo | openssl s_client -servername "${3}" -showcerts -connect "${3}":443 2>&- \
| tac | sed -n '/-----END CERTIFICATE-----/,/-----BEGIN CERTIFICATE-----/p; /-----BEGIN CERTIFICATE-----/q' \
| tac | openssl x509 -fingerprint -sha1 -noout \
| sed 's/://g' | awk -F= '{print tolower($2)}')
aws iam create-open-id-connect-provider \
--url "${1}" \
--client-id-list "sts.amazonaws.com" \
--thumbprint-list "${FOOTPRINT}" \
--query OpenIDConnectProviderArn
fi
}
Role Trust Relationship
This wasn't particularly hard, just annoying if not automated. I used AWS documentation here and created a function to create and destroy the required document.
function create_trust_relationship_file() {
local TRUST_RELATIONSHIP
read -r -d '' TRUST_RELATIONSHIP <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "${1}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${1#*/}:sub": "system:serviceaccount:${3}:${2}"
}
}
}
]
}
EOF
echo "${TRUST_RELATIONSHIP}" > trust.json
}
function delete_trust_relationship_file() {
rm -f trust.json
}
Complete Script
With that finished I just needed to fill in the blanks and got the following which creates required resources and gives you the service annotation to add to your service account.
function create_eks_pod_role() {
local CLUSTER_NAME ROLE_NAME ROLE_DESCRIPTION CONTAINER_POLICY_ARN SERVICE_ACCOUNT_NAME
local ISSUER_ADDRESS ISSUER_ID ISSUER_DOMAIN PROVIDER_ARN ROLE_ARN NAMESPACE
function usage() {
echo "Outputs rendered files for specified chart into current directory.
${FUNCNAME[0]} -
[-h] print this help
-c Name of the EKS Cluster
-r Name of the IAM role to create
-d Description for IAM role
-p IAM policy ARN of to attach to the role
-s Name of the service account
-n Namespace of the service account";
}
function check_policy_exists() {
local POLICY_LIST POLICY
POLICY_LIST="$(aws iam list-policies --query Policies[].Arn --output text)"
for POLICY in ${POLICY_LIST}; do
if [[ "${POLICY}" == "${1}" ]]; then
return 0
fi
done
echo "INVALID: your policy arn seems to be invalid please verify"
return 1
}
function create_provider_if_not_exists() {
local PROVIDER_LIST PROVIDER_ARN THUMBPRINT
PROVIDER_LIST=$(aws iam list-open-id-connect-providers --query OpenIDConnectProviderList --output text)
PROVIDER_ARN=$(for PROVIDER in ${PROVIDER_LIST}; do
if [[ "${PROVIDER}" == *"${2}"* ]]; then
echo "${PROVIDER}"
return
fi
done)
if ! [ "${PROVIDER_ARN}" ]; then
FOOTPRINT=$(echo | openssl s_client -servername "${3}" -showcerts -connect "${3}":443 2>&- \
| tac | sed -n '/-----END CERTIFICATE-----/,/-----BEGIN CERTIFICATE-----/p; /-----BEGIN CERTIFICATE-----/q' \
| tac | openssl x509 -fingerprint -sha1 -noout \
| sed 's/://g' | awk -F= '{print tolower($2)}')
aws iam create-open-id-connect-provider \
--url "${1}" \
--client-id-list "sts.amazonaws.com" \
--thumbprint-list "${FOOTPRINT}" \
--query OpenIDConnectProviderArn
else
echo "${PROVIDER_ARN}"
fi
}
function create_trust_relationship_file() {
local TRUST_RELATIONSHIP
read -r -d '' TRUST_RELATIONSHIP <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "${1}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${1#*/}:sub": "system:serviceaccount:${3}:${2}"
}
}
}
]
}
EOF
echo "${TRUST_RELATIONSHIP}" > trust.json
}
function delete_trust_relationship_file() {
rm -f trust.json
}
# Set options
while getopts ':c:r:d:p:s:n:' OPTION; do
case "$OPTION" in
c)
CLUSTER_NAME="${OPTARG}"
;;
r)
ROLE_NAME="${OPTARG}"
;;
d)
ROLE_DESCRIPTION="${OPTARG}"
;;
p)
CONTAINER_POLICY_ARN="${OPTARG}"
;;
s)
SERVICE_ACCOUNT_NAME="${OPTARG}"
;;
n)
NAMESPACE="${OPTARG}"
;;
*)
usage;
return 1;
;;
esac;
done;
[ "${CLUSTER_NAME}" ] && \
[ "${ROLE_NAME}" ] && \
[ "${ROLE_DESCRIPTION}" ] && \
[ "${CONTAINER_POLICY_ARN}" ] && \
[ "${SERVICE_ACCOUNT_NAME}" ] && \
[ "${NAMESPACE}" ] \
|| { usage; return 1; }
if ! check_policy_exists "${CONTAINER_POLICY_ARN}"; then
return 1
fi
ISSUER_ADDRESS=$(aws eks describe-cluster --name "${CLUSTER_NAME}" --query "cluster.identity.oidc.issuer" --output text)
ISSUER_ID="${ISSUER_ADDRESS#https://}"
ISSUER_DOMAIN="${ISSUER_ID%%/*}"
PROVIDER_ARN=$(create_provider_if_not_exists "${ISSUER_ADDRESS}" "${ISSUER_ID}" "${ISSUER_DOMAIN}")
create_trust_relationship_file "${PROVIDER_ARN}" "${SERVICE_ACCOUNT_NAME}" "${NAMESPACE}"
ROLE_ARN="$(aws iam create-role --role-name "${ROLE_NAME}" \
--assume-role-policy-document file://trust.json \
--description "${ROLE_DESCRIPTION}" \
--output text --query Role.Arn)"
delete_trust_relationship_file
aws iam attach-role-policy --role-name "${ROLE_NAME}" --policy-arn="${CONTAINER_POLICY_ARN}"
echo "コンテナーのサービスアカウントの annotations に下記を追加"
echo " eks.amazonaws.com/role-arn: ${ROLE_ARN}"
}
Some example output:
$ AWS_DEFAULT_REGION=us-east-1 create_eks_pod_role -c TestCluster -r loki-s3-role-test-2 -d "test role for loki s3 integration" -p arn:aws:iam::aws:policy/AmazonS3FullAccess -s a9t-loki -n monitoring-system
コンテナーのサービスアカウントの annotations に下記を追加
eks.amazonaws.com/role-arn: arn:aws:iam::636082426924:role/loki-s3-role-test-2
Don't mind the Japanese it just states that the below is to be added to the annotations of your pods service account.
Conclusion
In conclusion, it was kind of a pain to create this script.. but its definitely simplifies my work-flow when creating roles for my pods. Feel free to use it if you like, and don't hesitate to let me know if you have any questions or concerns!
Top comments (0)