Sign in
Log inSign up
Binding AWS IAM roles to Kubernetes Service Account for on-prem clusters

Binding AWS IAM roles to Kubernetes Service Account for on-prem clusters

Daniele Polencic's photo
Daniele Polencic
·Jun 15, 2022·

4 min read

TL;DR: In this short tutorial, you will learn how to configure the IAM roles for Service Account for a bare-metal cluster using minikube as an example.

Create a cluster with a specific service account issuer:

minikube start --extra-config=apiserver.service-account-issuer="https://[bucket name].s3.amazonaws.com"

Create a service account:

kubectl create sa test

Create a Pod with a projected service account token and an audience:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  serviceAccount: test # <- service account
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-td7d6
      readOnly: true
  volumes:
  - name: kube-api-access-td7d6
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          path: token
          audience: test # <- audience
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

Exec into the pod:

kubectl exec -ti nginx -- bash

And retrieve the projected service account token, the OpenID configuration and the JWT signing keys:

export APISERVER=https://kubernetes.default.svc
export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
export TOKEN=$(cat ${SERVICEACCOUNT}/token)
export CACERT=${SERVICEACCOUNT}/ca.crt
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/.well-known/openid-configuration
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/openid/v1/jwks

The content of the openid-configuration file should be:

{
  "issuer": "https://[bucket name].s3.amazonaws.com",
  "jwks_uri": "https://[bucket name].s3.amazonaws.com/openid/v1/jwks",
  "response_types_supported": [
    "id_token"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ]
}

And for the jwks:

{
  "keys": [
    {
      "use": "sig",
      "kty": "RSA",
      "kid": "ZO4TUgVjBzMWKVP8mmBwKLvsuyn8z-gfqUp27q9lO4w",
      "alg": "RS256",
      "n": "34a81xuM…",
      "e": "AQAB"
    }
  ]
}

Upload the files on the S3 bucket with the following directory structure:

  • .well-known/openid-configuration for the configuration.
  • openid/v1/jwks for the configuration.

Test that the endpoints are publicly reachable:

curl https://[bucket name].s3.amazonaws.com/.well-known/openid-config…
curl https://[bucket name].s3.amazonaws.com/openid/v1/jwks

Create an Open ID connect provider on AWS:

aws iam create-open-id-connect-provider   --url https://[bucket name].s3.amazonaws.com   --thumbprint-list "1234567890123456789012345678901234567890"  # <- this is ignored, but you need to enter 40 digits
  --client-id-list test # <- this is the audience

The thumbprint-list is ignored if you host on an S3 bucket.

From the AWS console, navigate to IAM > Identity Providers > click on the IdP you just created.

On the top right corner, click on assign a role for the Identity Provider and assign the permissions you wish to use (e.g. AmazonS3ReadOnlyAccess).

  • Create a new role.
  • It's for a "web identity".
  • Assign the policies.
  • Edit the trust relationships.

The content for the trust relationship should be the following:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::[aws account id]:oidc-provider/[bucket name].s3.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "[bucket name].s3.amazonaws.com:aud": "test", // <- this is the audience
                    "[bucket name].s3.amazonaws.com:sub": "system:serviceaccount:default:test" // <- optional
                }
            }
        }
    ]
}

Note, there's a limited number of fields that you can match.

Make a note of the Role ARN.

At this point, if your application uses the AWS SDK, you can expose the following environment variables from your pods:

  • AWS_ROLE_ARN — the ARN role.
  • AWS_WEB_IDENTITY_TOKEN_FILE — this is the value of the projected service account token.

The AWS SDK knows how to use those two to obtain an access and secret key.

You can also generate the access and secret token manually.

Make a call to obtain the credentials:

export AWS_ROLE_ARN=arn:aws:iam::[aws account id]:role/oidc-test # Role ARN
export AWS_WEB_IDENTITY_TOKEN_FILE=eyJhbGciOi… # make sure this did not expire in the meantime
aws sts assume-role-with-web-identity   --role-arn $AWS_ROLE_ARN   --role-session-name test   --web-identity-token $AWS_WEB_IDENTITY_TOKEN_FILE   --duration-seconds 1000

The output is a new set of credentials:

{
    "Credentials": {
        "AccessKeyId": "ASIAWY4CVPOBS4OIBWNL", // <- always starts with ASIA
        "SecretAccessKey": "02n52u8Smc76…",
        "SessionToken": "IQoJb3JpZ…",
        "Expiration": "2022-06-13T10:50:25+00:00"
    },
    "SubjectFromWebIdentityToken": "system:serviceaccount:default:test",
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAWY4CVPOBXUSBA5C2B:test",
        "Arn": "arn:aws:sts::[aws account id]:assumed-role/oidc-test-2/mh9test"
    },
    "Provider": "arn:aws:iam::[aws account id]:oidc-provider/[bucket name].s3.amazonaws.com",
    "Audience": "test"
}

Create a new entry in the ~/.aws/config file:

[profile test]
aws_access_key_id = ASIAWY4CVPO…
aws_secret_access_key = Hcp3Xt+B0…
aws_session_token = IQoJb3JpZ2luX2V…

Test the access with:

aws s3 ls --profile test

Closing remarks

And finally, if you've enjoyed this short post, you might also like the Kubernetes workshops that we run at Learnk8s or this collection of past Twitter threads twitter.com/danielepolencic/status/1298543….