Using workload identity with AWS

This topic describes how to enable workload identity for your GKE on AWS workloads to control their access to AWS resources.

For information about using workload identity with Google Cloud Identity and Access Management (IAM) accounts to control access to GCP resources, see Using workload identity with Google Cloud.

Overview

Workload identity uses AWS IAM permissions to control access to cloud resources. With workload identity, you can assign different IAM roles to each workload. This fine-grained control of permissions lets you follow the principle of least privilege. Without workload identity, you must assign AWS IAM roles to your GKE on AWS nodes, giving all workloads on the node the same permissions as the node itself.

To enable workload identity for your cluster, complete the following steps, which are grouped by the administrative roles that perform them.

Cluster administrator

  1. Create a Cloud Storage bucket to store OIDC discovery data.
  2. Create an Identity and Access Management role to read from that bucket.
  3. Create a user cluster with workload identity enabled.
  4. Create a webhook on your cluster that applies workload identity credentials to Pods on creation. If you don't want to use the webhook, you can manually set environment variables in your pods.
  5. Configure the AWS OIDC provider.
  6. Create AWS IAM roles and policies.
Cluster administrator or developer
  1. Create Kubernetes service accounts, and bind AWS policies to them.
Developer
  1. Apply credentials to your Pods.

Prerequisites

To complete the steps in this document, you must have the following setup:

  • An GKE on AWS management service.
  • User clusters that are running a Kubernetes version greater than 1.17.9.

  • The following permissions and tools.

Permissions

To create a cluster with workload identity enabled, you need the following permissions:

Google Cloud

  • Create a publicly readable Cloud Storage bucket with uniform bucket-level access enabled.
  • Grant management-sa@PROJECT_NAME.iam.gserviceaccount.com read/write permissions to the bucket.

AWS

  • Create an AWS OIDC provider
  • Create AWS IAM roles

Tools

On your local machine, we recommend having the jq tool installed.

Creating the OIDC discovery bucket

This section is for cluster administrators.

Your user cluster needs to store the OIDC discovery data in a publicly accessible Cloud Storage bucket. The bucket includes OIDC discovery configuration and public keys. AWS uses the contents to authenticate requests from your user clusters.

Your bucket must have the following attributes:

If you don't have a bucket with these attributes, create one by using the following gsutil commands:

BUCKET=BUCKET_NAME
gsutil mb -b on gs://${BUCKET}
gsutil iam ch allUsers:objectViewer gs://${BUCKET}

Replace BUCKET_NAME with the name of your new bucket.

Grant the management service account permissions

The Identity and Access Management service account for the GKE on AWS management service needs permissions to read and write objects into this bucket.

  1. Grant your management service account permissions by using the following gsutil command.

    MANAGEMENT_SA=management-sa@PROJECT_NAME.iam.gserviceaccount.com
    gsutil iam ch serviceAccount:${MANAGEMENT_SA}:admin gs://${BUCKET}
    

    Replace PROJECT_NAME with your Google Cloud project.

  2. Create a new IAM role with permissions to manage this bucket. To create the role, first save the role definition to a file, then create the role and bind the role to your management service account.

    To complete these steps, run the following commands:

    cat << EOF >  anthos-oidc-role.yaml
    title: anthosAwsOidcStorageAdmin
    description: permissions to manage the OIDC buckets
    stage: GA
    includedPermissions:
    - storage.buckets.get
    EOF
    
    gcloud iam roles create anthosAwsOidcStorageAdmin --project=PROJECT_NAME \
      --file=anthos-oidc-role.yaml
    
    gcloud projects add-iam-policy-binding \
      PROJECT_NAME \
      --member=serviceAccount:${MANAGEMENT_SA} \
      --role=projects/PROJECT_NAME/roles/anthosAwsOidcStorageAdmin
    

    Replace PROJECT_NAME with your Google Cloud project.

    The Google Cloud CLI confirms that the policy binding is created.

Creating a user cluster

This section is for cluster administrators.

Create a user cluster with workload identity enabled

Create a user cluster that contains details about your OIDC discovery bucket. You set this information in your AWSCluster's spec.controlPlane.workloadIdentity.oidcDiscoveryGCSBucket field.

In this example, you create a cluster manually from AWSCluster and AWSNodePool CRDs.

  1. Change to the directory with your GKE on AWS configuration. You created this directory when Installing the management service.

    cd anthos-aws

  2. From your anthos-aws directory, use anthos-gke to switch context to your management service.

    cd anthos-aws
    anthos-gke aws management get-credentials

  3. Open a text editor and copy the following AWSCluster definition into a file named custom-cluster.yaml.

    apiVersion: multicloud.cluster.gke.io/v1
    kind: AWSCluster
    metadata:
      name: CLUSTER_NAME
    spec:
      region: AWS_REGION
      networking:
        vpcID: VPC_ID
        podAddressCIDRBlocks: POD_ADDRESS_CIDR_BLOCKS
        serviceAddressCIDRBlocks: SERVICE_ADDRESS_CIDR_BLOCKS
        ServiceLoadBalancerSubnetIDs: SERVICE_LOAD_BALANCER_SUBNETS
      controlPlane:
        version:  CLUSTER_VERSION # Latest version is 1.25.5-gke.2100
        instanceType: AWS_INSTANCE_TYPE
        keyName: SSH_KEY_NAME
        subnetIDs:
        - CONTROL_PLANE_SUBNET_IDS
        securityGroupIDs:
        - CONTROL_PLANE_SECURITY_GROUPS
        iamInstanceProfile: CONTROL_PLANE_IAM_ROLE
        rootVolume:
          sizeGiB: ROOT_VOLUME_SIZE
          volumeType: ROOT_VOLUME_TYPE # Optional
          iops: ROOT_VOLUME_IOPS # Optional
          kmsKeyARN: ROOT_VOLUME_KEY # Optional
        etcd:
          mainVolume:
            sizeGiB: ETCD_VOLUME_SIZE
            volumeType: ETCD_VOLUME_TYPE # Optional
            iops: ETCD_VOLUME_IOPS # Optional
            kmsKeyARN: ETCD_VOLUME_KEY # Optional
        databaseEncryption:
          kmsKeyARN: ARN_OF_KMS_KEY
        hub: # Optional
          membershipName: ANTHOS_CONNECT_NAME
        cloudOperations: # Optional
          projectID: YOUR_PROJECT
          location: GCP_REGION
          enableLogging: ENABLE_LOGGING
          enableMonitoring: ENABLE_MONITORING
        workloadIdentity: # Optional
          oidcDiscoveryGCSBucket: WORKLOAD_IDENTITY_BUCKET
    

    Replace the following:

    • CLUSTER_NAME: the name of your cluster.
    • AWS_REGION: the AWS region where your cluster runs.

    • VPC_ID: the ID of the VPC where your cluster runs.

    • POD_ADDRESS_CIDR_BLOCKS: the range of IPv4 addresses used by the cluster's pods. Currently only a single range is supported. The range must not overlap with any subnets reachable from your network. It is safe to use the same range across multiple different AWSCluster objects. For example, 10.2.0.0/16.

    • SERVICE_ADDRESS_CIDR_BLOCKS: the range of IPv4 addresses used by the cluster's services. Currently only a single range is supported. The range must not overlap with any subnets reachable from your network. It is safe to use the same range across multiple different AWSCluster objects. For example, 10.1.0.0/16.

    • SERVICE_LOAD_BALANCER_SUBNETS: the subnet IDs where GKE on AWS can create public or private load balancers.

    • CLUSTER_VERSION: a Kubernetes version supported by GKE on AWS. The most recent version is 1.25.5-gke.2100.

    • AWS_INSTANCE_TYPE: a supported EC2 instance type.

    • SSH_KEY_NAME: an AWS EC2 key pair.

    • CONTROL_PLANE_SUBNET_IDS: the subnet IDs in the AZs where your control plane instances run.

    • CONTROL_PLANE_SECURITY_GROUPS: a securityGroupID created during the management service installation. You can customize this by adding any securityGroupIDs required to connect to the control plane.

    • CONTROL_PLANE_IAM_PROFILE: name of the AWS EC2 instance profile assigned to control plane replicas.

    • ROOT_VOLUME_SIZE: the size, in gibibyte (GiB), of your control plane root volumes.

    • ROOT_VOLUME_TYPE with the EBS volume type. For example, gp3.

    • ROOT_VOLUME_IOPS with the amount of provisioned IO operations per second (IOPS) for the volume. Only valid when volumeType is GP3. For more information, see General Purpose SSD volumes (gp3).

    • ROOT_VOLUME_KEY with the Amazon Resource Name of the AWS KMS key that encrypts your control plane instance root volumes.

    • ETCD_VOLUME_SIZE: the size of volumes used by etcd.

    • ETCD_VOLUME_TYPE with the EBS volume type. For example, gp3.

    • ETCD_VOLUME_IOPS with the amount of provisioned IO operations per second (IOPS) for the volume. Only valid when volumeType is gp3. For more information, see General Purpose SSD volumes (gp3).

    • ETCD_VOLUME_KEY with the Amazon Resource Name of the AWS KMS key that encrypts your control plane etcd data volumes.

    • ARN_OF_KMS_KEY: the AWS KMS key used to encrypt cluster Secrets.

    • ANTHOS_CONNECT_NAME: the Connect membership name used to register your cluster. The membership name must be unique. For example, projects/YOUR_PROJECT/locations/global/memberships/CLUSTER_NAME, where YOUR_PROJECT is your Google Cloud project and CLUSTER_NAME is a unique name in your project. This field is optional.

    • YOUR_PROJECT: your project ID.

    • GCP_REGION: the Google Cloud region where you want to store logs. Choose a region that is near the AWS region. For more information, see Global Locations - Regions & Zones — for example, us-central1.

    • ENABLE_LOGGING: true or false, whether Cloud Logging is enabled on control plane nodes.

    • ENABLE_MONITORING: true or false, whether Cloud Monitoring is enabled on control plane nodes.

    • WORKLOAD_IDENTITY_BUCKET: the Cloud Storage bucket name containing your workload identity discovery information. This field is optional.

  4. Create one or more AWSNodePools for your cluster. Open a text editor and copy the following AWSCluster definition into a file named custom-nodepools.yaml.

    apiVersion: multicloud.cluster.gke.io/v1
    kind: AWSNodePool
    metadata:
      name: NODE_POOL_NAME
    spec:
      clusterName: AWSCLUSTER_NAME
      version:  CLUSTER_VERSION # latest version is 1.25.5-gke.2100
      region: AWS_REGION
      subnetID: AWS_SUBNET_ID
      minNodeCount: MINIMUM_NODE_COUNT
      maxNodeCount: MAXIMUM_NODE_COUNT
      maxPodsPerNode: MAXIMUM_PODS_PER_NODE_COUNT
      instanceType: AWS_NODE_TYPE
      keyName: KMS_KEY_PAIR_NAME
      iamInstanceProfile: NODE_IAM_PROFILE
      proxySecretName: PROXY_SECRET_NAME
      rootVolume:
        sizeGiB: ROOT_VOLUME_SIZE
        volumeType: VOLUME_TYPE # Optional
        iops: IOPS # Optional
        kmsKeyARN: NODE_VOLUME_KEY # Optional 
    

    Replace the following:

    • NODE_POOL_NAME: a unique name for your AWSNodePool.
    • AWSCLUSTER_NAME: your AWSCluster's name. For example, staging-cluster.
    • CLUSTER_VERSION: a supported GKE on AWS Kubernetes version.
    • AWS_REGION: the same AWS region as your AWSCluster.
    • AWS_SUBNET_ID: an AWS subnet in the same region as your AWSCluster.
    • MINIMUM_NODE_COUNT: the minimum number of nodes in the node pool. See Scaling user clusters for more information.
    • MAXIMUM_NODE_COUNT: the maximum number of nodes in the node pool.
    • MAXIMUM_PODS_PER_NODE_COUNT: the maximum number of pods that GKE on AWS can allocate to a node.
    • AWS_NODE_TYPE: an AWS EC2 instance type.
    • KMS_KEY_PAIR_NAME: the AWS KMS key pair assigned to each node pool worker.
    • NODE_IAM_PROFILE: the name of the AWS EC2 instance profile assigned to nodes in the pool.
    • ROOT_VOLUME_SIZE: the size, in gibibyte (GiB), of your control plane root volumes.
    • VOLUME_TYPE: the node's AWS EBS volume type. For example, gp3.
    • IOPS: the amount of provisioned IO operations per second (IOPS) for volumes. Only valid when volumeType is gp3.
    • NODE_VOLUME_KEY: the ARN of the AWS KMS key used to encrypt the volume. For more information, see Using a customer managed CMK to encrypt volumes.
  5. Apply the manifests to your management service.

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl apply -f custom-cluster.yaml
    env HTTPS_PROXY=http://localhost:8118 \
      kubectl apply -f custom-nodepools.yaml
    

Create a kubeconfig

While your user cluster starts, you can create a kubeconfig context for your new user cluster. You use the context to authenticate to a user or management cluster.

  1. Use anthos-gke aws clusters get-credentials to generate a kubeconfig for your user cluster in ~/.kube/config.

    env HTTPS_PROXY=http://localhost:8118 \
      anthos-gke aws clusters get-credentials CLUSTER_NAME
    

    Replace CLUSTER_NAME with your cluster's name. For example, cluster-0.

  2. Use kubectl to authenticate to your new user cluster.

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl cluster-info
    

    If your cluster is ready, the output includes the URLs for Kubernetes components within your cluster.

Viewing your cluster's status

The management service provisions AWS resources when you apply an AWSCluster or AWSNodePool.

  1. From your anthos-aws directory, use anthos-gke to switch context to your management service.

    cd anthos-aws
    anthos-gke aws management get-credentials

  2. To list your clusters, use kubectl get AWSClusters.

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl get AWSClusters
    

    The output includes each cluster's name, state, age, version, and endpoint.

    For example, the following output includes only one AWSCluster named cluster-0:

    NAME        STATE          AGE     VERSION         ENDPOINT
    cluster-0   Provisioning   2m41s   1.25.5-gke.2100   gke-xyz.elb.us-east-1.amazonaws.com
    

View your cluster's events

To see recent Kubernetes Events from your user cluster, use kubectl get events.

  1. From your anthos-aws directory, use anthos-gke to switch context to your management service.

    cd anthos-aws
    anthos-gke aws management get-credentials

  2. Run kubectl get events.

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl get events
    

The output includes information, warning, and errors related to from your management service.

Creating the workload identity webhook

This section is for cluster administrators.

To provide workload identity credentials to your workloads with no additional configuration, you can optionally create a webhook on your user clusters. This webhook intercepts Pod creation requests and then makes the following AWS IAM information available as environment variables to the Pod:

  • AWS_ROLE_ARN: the Amazon Resource Name (ARN) of the IAM role
  • aws-iam-token: the token exchanged for AWS IAM credentials
  • AWS_WEB_IDENTITY_TOKEN_FILE: the path where the token is stored

With these variables, your workloads can call the AWS command-line tool or SDK can access the resources granted to the AWS role.

Creating the webhook is optional. If you decide not to create the webhook, you need to set the environment variables listed previously in the Pod. For information about not using a webhook, see Applying credentials without the webhook.

Create YAML files for the webhook

To deploy the webhook, perform the following steps:

  1. From your anthos-aws directory, use anthos-gke to switch context to your management service.

    cd anthos-aws
    anthos-gke aws management get-credentials

  2. Get the user cluster name with kubectl:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl get awscluster
    

    kubectl lists all your user clusters. Choose the user cluster you created with workload identity enabled.

  3. Set the cluster's name in an environment variable.

    CLUSTER_NAME=CLUSTER_NAME
    

    Replace CLUSTER_NAME with the name of your cluster. For example, cluster-0.

  4. Set environment variables for the workload identity Pod image and namespace.

    IDENTITY_IMAGE=amazon/amazon-eks-pod-identity-webhook:ed8c41f
    
    WEBHOOK_NAMESPACE=workload-identity-webhook
    
  5. Generate the webhook YAML manifest in a file named aws-webhook.yaml by performing the following steps:

    env HTTPS_PROXY=http://localhost:8118 \
      anthos-gke aws clusters get-credentials ${CLUSTER_NAME}
    
    CLUSTER_CA=$(env HTTPS_PROXY=http://localhost:8118 \
      kubectl config view --raw -o json  | jq -r '.clusters[] | select(.name == "'$(kubectl config current-context)'") | .cluster."certificate-authority-data"')
    
    cat << EOF > aws-webhook.yaml
    apiVersion: v1
    kind: Namespace
    metadata:
      name: ${WEBHOOK_NAMESPACE}
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: pod-identity-webhook
      namespace: ${WEBHOOK_NAMESPACE}
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: pod-identity-webhook
      namespace: ${WEBHOOK_NAMESPACE}
    rules:
      - apiGroups: ['']
        resources: ['secrets']
        verbs: ['create']
      - apiGroups: ['']
        resources: ['secrets']
        verbs: ['get', 'update', 'patch']
        resourceNames:
          - pod-identity-webhook
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: pod-identity-webhook
      namespace: ${WEBHOOK_NAMESPACE}
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: pod-identity-webhook
    subjects:
      - kind: ServiceAccount
        name: pod-identity-webhook
        namespace: ${WEBHOOK_NAMESPACE}
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: pod-identity-webhook
    rules:
      - apiGroups: ['']
        resources: ['serviceaccounts']
        verbs: ['get', 'watch',  'list']
      - apiGroups:  ['certificates.k8s.io']
        resources: ['certificatesigningrequests']
        verbs:  ['create', 'get', 'list', 'watch']
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: pod-identity-webhook
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: pod-identity-webhook
    subjects:
      - kind: ServiceAccount
        name: pod-identity-webhook
        namespace: ${WEBHOOK_NAMESPACE}
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: pod-identity-webhook
      namespace: ${WEBHOOK_NAMESPACE}
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: pod-identity-webhook
      template:
        metadata:
          labels:
            app: pod-identity-webhook
        spec:
          serviceAccountName: pod-identity-webhook
          containers:
            - name: pod-identity-webhook
              image: ${IDENTITY_IMAGE}
              imagePullPolicy: Always
              command:
                - /webhook
                - --in-cluster
                - --namespace=${WEBHOOK_NAMESPACE}
                - --service-name=pod-identity-webhook
                - --tls-secret=pod-identity-webhook
                - --annotation-prefix=eks.amazonaws.com
                - --token-audience=sts.amazonaws.com
                - --logtostderr
              volumeMounts:
                - name: webhook-certs
                  mountPath: /var/run/app/certs
                  readOnly: false
          volumes:
            - name: webhook-certs
              emptyDir: {}
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: pod-identity-webhook
      namespace: ${WEBHOOK_NAMESPACE}
      annotations:
        prometheus.io/port: '443'
        prometheus.io/scheme: https
        prometheus.io/scrape: 'true'
    spec:
      ports:
        - port: 443
          targetPort: 443
      selector:
        app: pod-identity-webhook
    ---
    apiVersion: admissionregistration.k8s.io/v1
    kind: MutatingWebhookConfiguration
    metadata:
      name: pod-identity-webhook
      namespace: ${WEBHOOK_NAMESPACE}
    webhooks:
      - name: pod-identity-webhook.amazonaws.com
        failurePolicy: Ignore
        sideEffects: 'None'
        admissionReviewVersions: ['v1beta1']
        clientConfig:
          service:
            name: pod-identity-webhook
            namespace: ${WEBHOOK_NAMESPACE}
            path: /mutate
          caBundle: ${CLUSTER_CA}
        rules:
          - operations: ['CREATE']
            apiGroups: ['']
            apiVersions: ['v1']
            resources: ['pods']
    EOF
    

    The contents of aws-webhook.yaml are ready to apply to your cluster.

Apply the webhook to your user cluster

To apply the webhook to your user cluster, perform the following steps.

  1. Apply the aws-webhook.yaml file to your user cluster.

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl apply -f aws-webhook.yaml
    
  2. When you apply the manifest, the webhook Pod generates Kubernetes certificate signing requests (CSR). Approve all requests from system:serviceaccount:${WEBHOOK_NAMESPACE}:pod-identity-webhook with kubectl certificate approve.

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl certificate approve $(env HTTPS_PROXY=http://localhost:8118 \ &&\
      kubectl get csr -o \
        jsonpath="{.items[?(@.spec.username==\"system:serviceaccount:${WEBHOOK_NAMESPACE}:pod-identity-webhook\")].metadata.name}")
    
  3. Verify that there are no remaining unapproved CSRs.

    Use kubectl get csr to check that all CSRs from the requestor system:serviceaccount:${WEBHOOK_NAMESPACE}:pod-identity-webhook are approved:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl get csr
    

    Response:

    NAME        AGE   REQUESTOR                                            CONDITION
    csr-mxrt8   10s   system:serviceaccount:default:pod-identity-webhook   Approved,Issued
    

Configuring the AWS OIDC provider

This section is for cluster administrators.

To create an OIDC provider at AWS, AWS requires an intermediate Certificate Authority (CA) or server certificate thumbprint. Your OIDC discovery credentials are stored on storage.googleapis.com, with a certificate signed by an intermediate CA named GTS CA 1C3. The SHA-1 thumbprint of its intermediate CA GTS CA 1C3 is 08745487E891C19E3078C1F2A07E452950EF36F6.

To register your OIDC discovery bucket as an OIDC provider with AWS, perform the following steps:

  1. From your anthos-aws directory, use anthos-gke to switch context to your management service.

    cd anthos-aws
    anthos-gke aws management get-credentials

  2. Save the OIDC issuer URL, issuer host path, and Cloud Storage thumbprint in environment variables.

    ISSUER_URL=$(env HTTPS_PROXY=http://localhost:8118 \
      kubectl get awscluster ${CLUSTER_NAME} -o jsonpath='{.status.workloadIdentityInfo.issuerURL}')
    ISSUER_HOSTPATH=${ISSUER_URL#"https://"}
    CA_THUMBPRINT=08745487E891C19E3078C1F2A07E452950EF36F6
    
  3. Use the aws command-line tool to create an OIDC provider on AWS.

    aws iam create-open-id-connect-provider \
      --url ${ISSUER_URL} \
      --thumbprint-list ${CA_THUMBPRINT} \
      --client-id-list sts.amazonaws.com
    

Update the thumbprint

If Google rotates the CA for storage.googleapis.com, run the following commands:

  1. Copy the updated certificate thumbprint, 08745487E891C19E3078C1F2A07E452950EF36F6.

  2. Follow the instructions for the aws iam update-open-id-connect-provider-thumbprint command. Use storage.googleapis.com as the target hostname and 08745487E891C19E3078C1F2A07E452950EF36F6 as the thumbprint.

Creating AWS IAM roles and policies

This section is for cluster administrators.

Create an AWS IAM role to bind to a Kubernetes service account. The IAM role has permissions for sts:AssumeRoleWithWebIdentity.

To create the role, perform the following steps:

  1. Find or create an AWS IAM policy that grants the necessary permissions for your workloads.

    You need the policy's Amazon resource name (ARN) AWS IAM policy. For example, arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess.

  2. Set environment variables with your authentication information.

    KSA_NAME=KUBERNETES_SERVICE_ACCOUNT
    WORKLOAD_NAMESPACE=WORKLOAD_IDENTITY_NAMESPACE
    
    AWS_ROLE_NAME=AWS_ROLE_NAME
    AWS_POLICY=EXISTING_AWS_POLICY
    

    Replace the following:

    • KUBERNETES_SERVICE_ACCOUNT: the name of the new Kubernetes service account
    • WORKLOAD_IDENTITY_NAMESPACE: the name of the namespace where workloads run
    • AWS_ROLE_NAME: the name for a new AWS role for your workloads
    • EXISTING_AWS_POLICY: the Amazon resource name (ARN) of an existing AWS IAM policy For example, arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess.
  3. From your anthos-aws directory, use anthos-gke to switch context to your management service.

    cd anthos-aws
    anthos-gke aws management get-credentials

  4. Create an AWS IAM policy that allows your user cluster to assume temporary security credentials with the AWS Security Token Service:

    CLUSTER_ID=$(env HTTPS_PROXY=http://localhost:8118 \
      kubectl get awscluster ${CLUSTER_NAME} -o jsonpath='{.status.clusterID}')
    
    # Get the ID Provider ARN
    PROVIDER_ARN=$(aws iam list-open-id-connect-providers  \
    | jq '.OpenIDConnectProviderList' \
    | jq ".[] | select(.Arn |  contains(\"${CLUSTER_ID}\"))"   \
    | jq  '.Arn' | tr -d '"')
    
    # Create AWS role and policy
    cat > irp-trust-policy.json << EOF
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Federated": "${PROVIDER_ARN}"
          },
          "Action": "sts:AssumeRoleWithWebIdentity",
          "Condition": {
            "StringEquals": {
              "${ISSUER_HOSTPATH}:sub": "system:serviceaccount:${WORKLOAD_NAMESPACE}:${KSA_NAME}"
            }
          }
        }
      ]
    }
    EOF
    
  5. To create an AWS IAM role with this policy and attach your existing policy to the role, perform the following commands:

    aws iam create-role \
      --role-name ${AWS_ROLE_NAME} \
      --assume-role-policy-document file://irp-trust-policy.json
    aws iam update-assume-role-policy \
      --role-name ${AWS_ROLE_NAME} \
      --policy-document file://irp-trust-policy.json
    aws iam attach-role-policy \
      --role-name ${AWS_ROLE_NAME} \
      --policy-arn ${AWS_POLICY}
    

    The aws command-line tool confirms that the policy is attached to your role.

Creating Kubernetes service accounts for workloads

This section is for developers or cluster administrators.

To create Kubernetes service accounts bound to the AWS IAM role that was specified previously, perform the following steps:

  1. From your anthos-aws directory, use anthos-gke to switch context to your user cluster.

    cd anthos-aws
    env HTTPS_PROXY=http://localhost:8118 \
      anthos-gke aws clusters get-credentials CLUSTER_NAME
    Replace CLUSTER_NAME with your user cluster name.

  2. Create the Kubernetes service account by running the following commands:

    S3_ROLE_ARN=$(aws iam get-role \
      --role-name AWS_ROLE_NAME \
      --query Role.Arn --output text)
    
    cat << EOF  > k8s-service-account.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: ${KSA_NAME}
      namespace: WORKLOAD_IDENTITY_NAMESPACE
    EOF
    
    env HTTPS_PROXY=http://localhost:8118 \
    kubectl apply -f k8s-service-account.yaml
    
    env HTTPS_PROXY=http://localhost:8118 \
    kubectl annotate sa --namespace ${WORKLOAD_NAMESPACE} ${KSA_NAME} eks.amazonaws.com/role-arn=${S3_ROLE_ARN}
    

    Replace the following:

    • AWS_ROLE_NAME: the name of the AWS IAM role to apply to your workloads
    • WORKLOAD_IDENTITY_NAMESPACE: the name of the namespace where workloads run

Applying credentials to your Pods

This section is for developers.

This section assumes that you have deployed the workload identity webhook. If you haven't deployed the webhook, skip to Applying credentials without the webhook.

Apply credentials with the webhook

This section describes how to configure your Pods to read credentials made available by the webhook.

Add the service account to the Pod

To use workload identity with a workload, add the Kubernetes service account to the following fields:

  • For a Deployment: spec.template.spec.serviceAccountName
  • For a Pod: spec.serviceAccount

The following Pod manifest launches a base CentOS image and contains the spec.serviceAccount field.

apiVersion: v1
kind: Pod
metadata:
  name: sample-centos-pod
  namespace: WORKLOAD_IDENTITY_NAMESPACE
spec:
  containers:
  - command:
    - /bin/bash
    - -ec
    - while :; do echo '.'; sleep 500 ; done
    image: amazon/aws-cli
    name: centos
  serviceAccount: KUBERNETES_SERVICE_ACCOUNT

Replace the following:

  • WORKLOAD_IDENTITY_NAMESPACE: the name of the namespace where workloads run
  • KUBERNETES_SERVICE_ACCOUNT: the name of the Kubernetes service account you created previously

Check if Pods have the environment variables set

To check if Pods have the environment variables set, run the following command to get the Pod's information:

kubectl get pod --namespace WORKLOAD_IDENTITY_NAMESPACE POD_NAME -o yaml

Replace the following:

  • WORKLOAD_IDENTITY_NAMESPACE: the name of the namespace where workloads run
  • POD_NAME: the name of the Pod to check

The output contains the environment variable values in spec.containers.command.env and the mount point for the AWS IAM token. An example Pod manifest follows.

apiVersion: v1
kind: Pod
metadata:
  ...
spec:
  containers:
  - command:
    - /bin/bash
    - -ec
    - while :; do echo '.'; sleep 500 ; done
    env:
    - name: AWS_ROLE_ARN
      value: arn:aws:iam::1234567890:role/my-example-workload-role-1
    - name: AWS_WEB_IDENTITY_TOKEN_FILE
      value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    image: amazon/aws-cli
    imagePullPolicy: IfNotPresent
    name: centos
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: my-k8s-serviceaccount-token-d4nz4
      readOnly: true
    - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
      name: aws-iam-token
      readOnly: true
  serviceAccount: my-k8s-serviceaccount
  serviceAccountName: my-k8s-serviceaccount
  volumes:
  - name: aws-iam-token
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          audience: sts.amazonaws.com
          expirationSeconds: 86400
          path: token
  - name: my-k8s-serviceaccount-token-d4nz4
    secret:
      defaultMode: 420
      secretName: my-k8s-serviceaccount-token-d4nz4
   ...
status:
  ...

Apply credentials without the webhook

If you do not deploy the workload identity webhook, you need to do the following:

Create a Pod with credentials for workload identity

To create a Pod that includes the necessary credentials for workload identity, perform the following steps:

  1. Copy the following Pod manifest into a file named sample-pod-no-webhook.yaml. The configuration launches a base CentOS image with the necessary credentials.

    apiVersion: v1
    kind: Pod
    metadata:
      name: sample-centos-pod-no-webhook
      namespace: WORKLOAD_IDENTITY_NAMESPACE
    spec:
      containers:
      - command:
        - /bin/bash
        - -ec
        - while :; do echo '.'; sleep 500 ; done
        image: centos:7
        name: centos
        env:
        - name: AWS_ROLE_ARN
          value: IAM_ROLE_ARN
        - name: AWS_WEB_IDENTITY_TOKEN_FILE
          value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
        volumeMounts:
        - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
          name: aws-iam-token
          readOnly: true
      volumes:
      - name: aws-iam-token
        projected:
          defaultMode: 420
          sources:
          - serviceAccountToken:
              audience: sts.amazonaws.com
              expirationSeconds: 86400
              path: token
      serviceAccount: KUBERNETES_SERVICE_ACCOUNT
    

    Replace the following:

    • WORKLOAD_IDENTITY_NAMESPACE: the name of the namespace where workloads run.
    • IAM_ROLE_ARN: the ARN of the IAM role granted to the Pod. For example, arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess.
    • KUBERNETES_SERVICE_ACCOUNT: the name of the Kubernetes service account you created previously.
  2. Apply the Pod manifest to your cluster by using kubectl:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl apply -f sample-pod-no-webhook.yaml
    

Check if Pods can access AWS resources

The following procedure describes how to check whether the Pod has received the credentials necessary for workload identity to function.

To complete the steps, you need to have the following:

  • bash shell access to the container; most production images don't have a shell available. The following example shows you how to use the Pod specified in the preceding section to access AWS S3.

  • Your Pod needs to have outbound access to the internet to download the AWS command-line interface.

To check if the Pod can access an S3 bucket, perform the following steps:

  1. Use kubectl exec to launch an interactive bash shell on the Pod sample-centos-pod-no-webhook:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl exec -it --namespace ${WORKLOAD_NAMESPACE} sample-centos-pod-no-webhook -- bash
    

    Your terminal opens the bash shell on the Pod.

  2. Check the AWS IAM permissions and credentials by using the aws tool:

    aws sts assume-role-with-web-identity \
     --role-arn ${AWS_ROLE_ARN} \
     --role-session-name mh9test \
     --web-identity-token file:///var/run/secrets/eks.amazonaws.com/serviceaccount/token \
     --duration-seconds 1000
    

    The aws tool prints credentials information similar to the following:

    {
        "AssumedRoleUser": {
            "AssumedRoleId": "AROAR2ZZZLEXVSDCDJ37N:mh9test",
            "Arn": "arn:aws:sts::126285863215:assumed-role/my-example-workload-role-1/mh9test"
        },
        "Audience": "sts.amazonaws.com",
        "Provider": "arn:aws:iam::126285863215:oidc-provider/storage.googleapis.com/gke-issuer-cec6c353",
        "SubjectFromWebIdentityToken": "system:serviceaccount:default:my-s3-reader-ksa",
        "Credentials": {
            "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
            "SessionToken": "MY_TOKEN",
            "Expiration": "2020-08-14T22:46:36Z",
            "AccessKeyId": "AKIAIOSFODNN7EXAMPLE"
        }
    }
    

    If you see the following message, check that the bucket is publicly accessible: An error occurred (InvalidIdentityToken) when calling the AssumeRoleWithWebIdentity operation: Couldn't retrieve verification key from your identity provider, please reference AssumeRoleWithWebIdentity documentation for requirements

Upgrading the webhook

If you created a Kubernetes 1.18 or lower cluster with workload identity enabled and the workload identity webhook version release-0.2.2-gke.0, you must upgrade the webhook before upgrading to Kubernetes 1.19.

To upgrade the webhook, perform the following steps:

  1. Confirm the webhook is installed by running the following commands:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl get MutatingWebhookConfiguration
    

    If your cluster has the webhook deployed, the output includes the following:

    NAME                   WEBHOOKS   AGE
    pod-identity-webhook   1          11m
    

    If the webhook is not deployed on your cluster, you can skip the following steps.

  2. If you saved the aws-webhook.yaml file, you can delete the manifest. If you don't have this file available, you can delete the webhook's components manually. Choose from file or components below.

    File

    If you still have the aws-webhook.yaml file, run the following command to delete the webhook:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl delete -f aws-webhook.yaml
    

    Components

    To delete the webhook's components manually, run the following commands:

    env HTTPS_PROXY=http://localhost:8118 \
       kubectl delete namespace WEBHOOK_NAMESPACE
    env HTTPS_PROXY=http://localhost:8118 \
       kubectl delete clusterrole pod-identity-webhook
    env HTTPS_PROXY=http://localhost:8118 \
       kubectl delete clusterrolebinding pod-identity-webhook
    env HTTPS_PROXY=http://localhost:8118 \
       kubectl delete mutatingwebhookconfiguration pod-identity-webhook
    

    Replace WEBHOOK_NAMESPACE with the namespace where you installed the workload identity webhook. For example— workload-identity-webhook.

  3. Check if you have any remaining certificate signing requests (CSRs) by run the following command:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl get csr |grep pod-identity-webhook
    

    If the output is blank, skip to the next step. If there are any remaining CSRs, the kubectl command will list existing CSRs. To remove the CSRs, run the following command:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl delete csr $(kubectl get csr -o \
      jsonpath="{.items[?(@.spec.username==\"system:serviceaccount:WEBHOOK_NAMESPACE:pod-identity-webhook\")].metadata.name}")
    

    Replace WEBHOOK_NAMESPACE with the namespace where you installed the workload identity webhook. For example— workload-identity-webhook.

  4. Follow the steps in Create the webhook to deploy the new webhook version.

    After you deploy the new webhook version, you need to restart the Pods that use the webhook. You can restart your Pods by Upgrading a user cluster.

Cleaning up

This section shows you how to remove resources that you created earlier in this document.

Clean up the service account and its associated IAM role

To delete the service account and its associated IAM role, perform the following steps:

  1. Clean up the service account:

    env HTTPS_PROXY=http://localhost:8118 \
      kubectl delete sa KUBERNETES_SERVICE_ACCOUNT --namespace WORKLOAD_IDENTITY_NAMESPACE
    

    Replace the following:

    • KUBERNETES_SERVICE_ACCOUNT: the name of the new Kubernetes service account
    • WORKLOAD_IDENTITY_NAMESPACE: the name of the namespace where workloads run
  2. Clean up the AWS IAM role. Choose from one of the following:

    • Delete the AWS IAM role with the AWS console.

    • Delete the role with the AWS command-line tool using the following commands:

      aws iam  detach-role-policy \
        --role-name=${AWS_ROLE_NAME} \
        --policy-arn=${AWS_POLICY}
      aws iam delete-role --role-name=${AWS_ROLE_NAME}
      

Delete your user cluster

To delete your user cluster, perform the steps in Uninstalling GKE on AWS.

Clean up the AWS OIDC provider

After the user cluster is deleted, unregister and delete the OIDC provider on AWS by using either the following bash shell command or the AWS console.

  1. From your anthos-aws directory, use anthos-gke to switch context to your management service.

    cd anthos-aws
    anthos-gke aws management get-credentials

  2. Delete the role with the AWS command-line tool with the following commands:

    CLUSTER_ID=$(env HTTPS_PROXY=http://localhost:8118 \
      kubectl get awscluster ${CLUSTER_NAME} -o jsonpath='{.status.clusterID}')
    
    PROVIDER_ARN=$(aws iam list-open-id-connect-providers  \
    | jq '.OpenIDConnectProviderList' \
    | jq ".[] | select(.Arn |  contains(\"${CLUSTER_ID}\"))"   \
    | jq  '.Arn' | tr -d '"')
    
    aws iam delete-open-id-connect-provider \
      --open-id-connect-provider-arn=${PROVIDER_ARN}
    

    You receive confirmation that the AWS OIDC provider is deleted.

What's next