{"id":13819803,"url":"https://github.com/doitintl/gtoken","last_synced_at":"2025-04-30T08:33:37.916Z","repository":{"id":40413245,"uuid":"225873590","full_name":"doitintl/gtoken","owner":"doitintl","description":"Securely access AWS services from GKE cluster","archived":false,"fork":false,"pushed_at":"2023-10-25T21:24:42.000Z","size":376,"stargazers_count":73,"open_issues_count":15,"forks_count":33,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-08-04T08:03:02.321Z","etag":null,"topics":["aws","gcp","gke","google-cloud","iam","kubernetes","service-account","workload-identity"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doitintl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-04T13:26:38.000Z","updated_at":"2024-07-31T12:59:11.000Z","dependencies_parsed_at":"2024-01-13T15:45:00.375Z","dependency_job_id":"40c62ff2-de03-4eaf-ba79-3a26e853dfbe","html_url":"https://github.com/doitintl/gtoken","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doitintl%2Fgtoken","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doitintl%2Fgtoken/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doitintl%2Fgtoken/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doitintl%2Fgtoken/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doitintl","download_url":"https://codeload.github.com/doitintl/gtoken/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224203935,"owners_count":17273019,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","gcp","gke","google-cloud","iam","kubernetes","service-account","workload-identity"],"created_at":"2024-08-04T08:00:53.193Z","updated_at":"2024-11-12T02:22:56.422Z","avatar_url":"https://github.com/doitintl.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"[![](https://github.com/doitintl/gtoken/workflows/Docker%20Image%20CI/badge.svg)](https://github.com/doitintl/gtoken/actions?query=workflow%3A\"Docker+Image+CI\") [![Docker Pulls](https://img.shields.io/docker/pulls/doitintl/gtoken.svg?style=popout)](https://hub.docker.com/r/doitintl/gtoken \"gtoken image\") [![Docker Pulls](https://img.shields.io/docker/pulls/doitintl/gtoken-webhook.svg?style=popout)](https://hub.docker.com/r/doitintl/gtoken-webhook \"gtoken-webhook image\") [![](https://images.microbadger.com/badges/image/doitintl/gtoken.svg)](https://microbadger.com/images/doitintl/gtoken \"gtoken image\") [![](https://images.microbadger.com/badges/image/doitintl/gtoken-webhook.svg)](https://microbadger.com/images/doitintl/gtoken-webhook \"gtoken-webhook image\")\n\n# Securely access AWS Services from GKE cluster\n\nEver wanted to access AWS services from Google Kubernetes cluster (GKE) without using AWS IAM credentials?\n\nThis solution can help you to get and exchange Google OIDC token for temporary AWS IAM security credentials are generated by AWS STS service. This approach allows you to access AWS services form a GKE cluster without pre-generated long-living AWS credentials.\n\nRead more about this solution on DoiT [Securely Access AWS Services from Google Kubernetes Engine (GKE)](https://blog.doit-intl.com/securely-access-aws-from-gke-dba1c6dbccba?source=friends_link\u0026sk=779821ca975ddb312916e1be732c637f) blog post.\n\n# `gtoken` tool\n\nThe `gtoken` tool can get Google Cloud ID token when running with under GCP Service Account (for example, GKE Pod with Workload Identity).\n\n## `gtoken` command syntax\n\n```text\nNAME:\n   gtoken - generate ID token with current Google Cloud service account\n\nUSAGE:\n   gtoken [global options] command [command options] [arguments...]\n\nCOMMANDS:\n   help, h  Shows a list of commands or help for one command\n\nGLOBAL OPTIONS:\n   --refresh      auto refresh ID token before it expires (default: true)\n   --file value   write ID token into file (stdout, if not specified)\n   --help, -h     show help (default: false)\n   --version, -v  print the version\n```\n\n# `gtoken-webhook` Kubernetes webhook\n\nThe `gtoken-webhook` is a Kubernetes mutating admission webhook, that mutates any K8s Pod running under specially annotated Kubernetes Service Account (see details below).\n\n## `gtoken-webhook` mutation\n\nThe `gtoken-webhook` injects a `gtoken` `initContainer` into a target Pod and an additional `gtoken` sidekick container (to refresh an ID OIDC token a moment before expiration), mounts _token volume_ and injects three AWS-specific environment variables. The `gtoken` container generates a valid GCP OIDC ID Token and writes it to the _token volume_.\n\nInjected AWS environment variables:\n\n- `AWS_WEB_IDENTITY_TOKEN_FILE` - the path to the web identity token file (OIDC ID token)\n- `AWS_ROLE_ARN` - the ARN of the role to assume by Pod containers\n- `AWS_ROLE_SESSION_NAME` - the name applied to this assume-role session\n\nThe AWS SDK will automatically make the corresponding `AssumeRoleWithWebIdentity` calls to AWS STS on your behalf. It will handle in memory caching as well as refreshing credentials as needed.\n\n### skip injection\n\nThe `gtoken-webhook` can be configured to skip injection for all Pods in the specific Namespace by adding the `admission.gtoken/ignore` label to the Namespace.\n\n## `gtoken-webhook` deployment\n\n1. Create a new `gtoken` namespace:\n\n```sh\nkubectl create -f deployment/namespace.yaml\n```\n\n```\n\n1. To deploy the `gtoken-webhook` server, we need to create a webhook service and a deployment in our Kubernetes cluster. It’s pretty straightforward, except one thing, which is the server’s TLS configuration. If you’d care to examine the [deployment.yaml](https://github.com/doitintl/gtoken/blob/master/deployment/deployment.yaml) file, you’ll find that the certificate and corresponding private key files are read from command line arguments, and that the path to these files comes from a volume mount that points to a Kubernetes secret:\n\n```yaml\n[...]\n      args:\n      [...]\n      - --tls-cert-file=/etc/webhook/certs/cert.pem\n      - --tls-private-key-file=/etc/webhook/certs/key.pem\n      volumeMounts:\n      - name: webhook-certs\n        mountPath: /etc/webhook/certs\n        readOnly: true\n[...]\n   volumes:\n   - name: webhook-certs\n     secret:\n       secretName: gtoken-webhook-certs\n```\n\nThe most important thing to remember is to set the corresponding CA certificate later in the webhook configuration, so the `apiserver` will know that it should be accepted. For now, we’ll reuse the script originally written by the Istio team to generate a certificate signing request. Then we’ll send the request to the Kubernetes API, fetch the certificate, and create the required secret from the result.\n\nFirst, run [webhook-create-signed-cert.sh](https://github.com/doitintl/gtoken/blob/master/deployment/webhook-create-signed-cert.sh) script and check if the secret holding the certificate and key has been created:\n\n```text\n./deployment/webhook-create-signed-cert.sh\n\ncreating certs in tmpdir /var/folders/vl/gxsw2kf13jsf7s8xrqzcybb00000gp/T/tmp.xsatrckI71\nGenerating RSA private key, 2048 bit long modulus\n.........................+++\n....................+++\ne is 65537 (0x10001)\ncertificatesigningrequest.certificates.k8s.io/gtoken-webhook-svc.gtoken created\nNAME                         AGE   REQUESTOR              CONDITION\ngtoken-webhook-svc.gtoken   1s    alexei@doit-intl.com   Pending\ncertificatesigningrequest.certificates.k8s.io/gtoken-webhook-svc.gtoken approved\nsecret/gtoken-webhook-certs configured\n```\n\n**Note** For the GKE Autopilot, run the [webhook-create-self-signed-cert.sh](https://github.com/doitintl/gtoken/blob/master/deployment/webhook-create-self-signed-cert.sh) script to generate a self-signed certificate.\n\nExport CA Bundle as environment variable:\n\n```sh\nexport CA_BUNDLE=[output value of the previous script \"Encoded CA:\"]\n```\n\nThen, we’ll create the webhook service and deployment.\n\nFirst, create a Kubernetes Service Account to be used with the `gtoken-webhook`:\n\n```sh\nkubectl create -f deployment/service-account.yaml\n```\n\nOnce the secret is created, we can create deployment and service. These are standard Kubernetes deployment and service resources. Up until this point we’ve produced nothing but an HTTP server that’s accepting requests through a service on port 443:\n\n```sh\nkubectl create -f deployment/deployment.yaml\n\nkubectl create -f deployment/service.yaml\n```\n\n### configure mutating admission webhook\n\nNow that our webhook server is running, it can accept requests from the `apiserver`. However, we should create some configuration resources in Kubernetes first. Let’s start with our validating webhook, then we’ll configure the mutating webhook later. If you take a look at the [webhook configuration](https://github.com/doitintl/gtoken/blob/master/deployment/mutatingwebhook.yaml), you’ll notice that it contains a placeholder for `CA_BUNDLE`:\n\n```yaml\n[...]\n      service:\n        name: gtoken-webhook-svc\n        namespace: gtoken\n        path: \"/pods\"\n      caBundle: ${CA_BUNDLE}\n[...]\n```\n\nThere is a [small script](https://github.com/doitintl/gtoken/blob/master/deployment/webhook-patch-ca-bundle.sh) that substitutes the CA_BUNDLE placeholder in the configuration with this CA. Run this command before creating the validating webhook configuration:\n\n```sh\ncat ./deployment/mutatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh \u003e ./deployment/mutatingwebhook-bundle.yaml\n```\n\nCreate mutating webhook configuration:\n\n```sh\nkubectl create -f deployment/mutatingwebhook-bundle.yaml\n```\n\n### configure RBAC for gtoken-webhook\n\nDefine RBAC permission for webhook service account:\n\n```sh\n# create a cluster role\nkubectl create -f deployment/clusterrole.yaml\n# define a cluster role binding\nkubectl create -f deployment/clusterrolebinding.yaml\n```\n\n## Configuration Flow\n\n### Flow variables\n\n- `PROJECT_ID` - GCP project ID\n- `CLUSTER_NAME` - GKE cluster name\n- `CLUSTER_ZONE` - GKE cluster zone\n- `GSA_NAME` - Google Cloud Service Account name (choose any)\n- `GSA_ID` - Google Cloud Service Account unique ID (generated by Google)\n- `KSA_NAME` - Kubernetes Service Account name (choose any)\n- `KSA_NAMESPACE` - Kubernetes namespace\n- `AWS_ROLE_NAME` - AWS IAM role name (choose any)\n- `AWS_POLICY_NAME` - an **existing** AWS IAM policy to assign to IAM role\n- `AWS_ROLE_ARN` - AWS IAM Role ARN identifier (generated by AWS)\n\n### GCP: Enable GKE Workload Identity\n\nCreate a new GKE cluster with [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) enabled:\n\n```sh\ngcloud container clusters create ${CLUSTER_NAME} \\\n    --zone=${CLUSTER_ZONE} \\\n    --workload-pool=${PROJECT_ID}.svc.id.goog\n```\n\nor update an existing cluster:\n\n```sh\ngcloud container clusters update ${CLUSTER_NAME} \\\n    --zone=${CLUSTER_ZONE} \\\n    --workload-pool=${PROJECT_ID}.svc.id.goog\n```\n\n### GCP: Configure GCP Service Account\n\nCreate Google Cloud Service Account:\n\n```sh\n# create GCP Service Account\ngcloud iam service-accounts create ${GSA_NAME}\n\n# get GCP SA UID to be used for AWS Role with Google OIDC Web Identity\nGSA_ID=$(gcloud iam service-accounts describe --format json ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com  | jq -r '.uniqueId')\n```\n\nUpdate `GSA_NAME` Google Service Account with following roles:\n\n- `roles/iam.workloadIdentityUser` - impersonate service accounts from GKE Workloads\n- `roles/iam.serviceAccountTokenCreator` - impersonate service accounts to create OAuth2 access tokens, sign blobs, or sign JWTs\n\n```sh\ngcloud projects add-iam-policy-binding ${PROJECT_ID} \\\n  --member serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \\\n  --role roles/iam.serviceAccountTokenCreator\n\ngcloud iam service-accounts add-iam-policy-binding \\\n  --role roles/iam.workloadIdentityUser \\\n  --member \"serviceAccount:${PROJECT_ID}.svc.id.goog[${K8S_NAMESPACE}/${KSA_NAME}]\" \\\n  ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\n```\n\n### AWS: Create AWS IAM Role with Google OIDC Web Identity\n\n```sh\n# prepare role trust policy document for Google OIDC provider\ncat \u003e gcp-trust-policy.json \u003c\u003c EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Federated\": \"accounts.google.com\"\n      },\n      \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"accounts.google.com:sub\": \"${GSA_ID}\"\n        }\n      }\n    }\n  ]\n}\nEOF\n\n# create AWS IAM Rome with Google Web Identity\naws iam create-role --role-name ${AWS_ROLE_NAME} --assume-role-policy-document file://gcp-trust-policy.json\n\n# assign AWS role desired policies\naws iam attach-role-policy --role-name ${AWS_ROLE_NAME} --policy-arn arn:aws:iam::aws:policy/${AWS_POLICY_NAME}\n\n# get AWS Role ARN to be used in K8s SA annotation\nAWS_ROLE_ARN=$(aws iam get-role --role-name ${AWS_ROLE_NAME} --query Role.Arn --output text)\n```\n\n### GKE: Kubernetes Service Account\n\nCreate K8s namespace:\n\n```sh\nkubectl create namespace ${K8S_NAMESPACE}\n```\n\nCreate K8s Service Account:\n\n```sh\nkubectl create serviceaccount --namespace ${K8S_NAMESPACE} ${KSA_NAME}\n```\n\nAnnotate K8s Service Account with GKE Workload Identity (GCP Service Account email)\n\n```sh\nkubectl annotate serviceaccount --namespace ${K8S_NAMESPACE} ${KSA_NAME} \\\n  iam.gke.io/gcp-service-account=${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com\n\n```\n\nAnnotate K8s Service Account with AWS Role ARN:\n\n```sh\nkubectl annotate serviceaccount --namespace ${K8S_NAMESPACE} ${KSA_NAME} \\\n  amazonaws.com/role-arn=${AWS_ROLE_ARN}\n```\n\n### Run demo\n\nRun a new K8s Pod with K8s ${KSA_NAME} Service Account:\n\n```sh\n# run a pod (with AWS CLI onboard) in interactive mod\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: v1\nkind: Pod\nmetadata:\n  name: test-pod\n  namespace: ${K8S_NAMESPACE}\nspec:\n  serviceAccountName: ${KSA_NAME}\n  containers:\n  - name: test-pod\n    image: mikesir87/aws-cli\n    command: [\"tail\", \"-f\", \"/dev/null\"]\nEOF\n\n# in Pod shell: check AWS assumed role\naws sts get-caller-identity\n\n# the output should look similar to below\n{\n    \"UserId\": \"AROA9GB4GPRFFXVHNSLCK:gtoken-webhook-gyaashbbeeqhpvfw\",\n    \"Account\": \"906385953612\",\n    \"Arn\": \"arn:aws:sts::906385953612:assumed-role/bucket-full-gtoken/gtoken-webhook-gyaashbbeeqhpvfw\"\n}\n\n```\n\n## External references\n\nI've borrowed an initial mutating admission webhook code and deployment guide from [banzaicloud/admission-webhook-example](https://github.com/banzaicloud/admission-webhook-example) repository. Big thanks to Banzai Cloud team!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoitintl%2Fgtoken","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoitintl%2Fgtoken","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoitintl%2Fgtoken/lists"}