diff --git a/examples/workload_identity/main.tf b/examples/workload_identity/main.tf index 9508195b9..731a1a2d6 100644 --- a/examples/workload_identity/main.tf +++ b/examples/workload_identity/main.tf @@ -53,6 +53,7 @@ module "gke" { ] } +# example without existing KSA module "workload_identity" { source = "../../modules/workload-identity" project_id = var.project_id @@ -61,5 +62,27 @@ module "workload_identity" { use_existing_k8s_sa = false } + +# example with existing KSA +resource "kubernetes_service_account" "test" { + metadata { + name = "foo-ksa" + } + secret { + name = "bar" + } +} + +module "workload_identity_existing_ksa" { + source = "../../modules/workload-identity" + project_id = var.project_id + name = "existing-${module.gke.name}" + cluster_name = module.gke.name + location = module.gke.location + namespace = "default" + use_existing_k8s_sa = true + k8s_sa_name = kubernetes_service_account.test.metadata.0.name +} + data "google_client_config" "default" { } diff --git a/modules/workload-identity/README.md b/modules/workload-identity/README.md index a4db783e8..d4dec6fd3 100644 --- a/modules/workload-identity/README.md +++ b/modules/workload-identity/README.md @@ -51,9 +51,6 @@ resource "kubernetes_service_account" "preexisting" { metadata { name = "preexisting-sa" namespace = "prod" - annotations = { - "iam.gke.io/gcp-service-account" = "preexisting-sa@${var.project_id}.iam.gserviceaccount.com" - } } } @@ -71,7 +68,9 @@ module "my-app-workload-identity" { | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| +| cluster\_name | Cluster name. Required if using existing KSA. | string | `""` | no | | k8s\_sa\_name | Name for the existing Kubernetes service account | string | `"null"` | no | +| location | Cluster location (region if regional cluster, zone if zonal cluster). Required if using existing KSA. | string | `""` | no | | name | Name for both service accounts | string | n/a | yes | | namespace | Namespace for k8s service account | string | `"default"` | no | | project\_id | GCP project ID | string | n/a | yes | diff --git a/modules/workload-identity/main.tf b/modules/workload-identity/main.tf index cd0504549..a27e45860 100644 --- a/modules/workload-identity/main.tf +++ b/modules/workload-identity/main.tf @@ -19,9 +19,23 @@ locals { gcp_sa_email = google_service_account.cluster_service_account.email # This will cause terraform to block returning outputs until the service account is created - k8s_given_name = var.k8s_sa_name != null ? var.k8s_sa_name : var.name - output_k8s_name = var.use_existing_k8s_sa ? local.k8s_given_name : kubernetes_service_account.main[0].metadata[0].name - output_k8s_namespace = var.use_existing_k8s_sa ? var.namespace : kubernetes_service_account.main[0].metadata[0].namespace + k8s_given_name = var.k8s_sa_name != null ? var.k8s_sa_name : var.name + output_k8s_name = var.use_existing_k8s_sa ? local.k8s_given_name : kubernetes_service_account.main[0].metadata[0].name + output_k8s_namespace = var.use_existing_k8s_sa ? var.namespace : kubernetes_service_account.main[0].metadata[0].namespace + token = var.use_existing_k8s_sa ? data.google_client_config.default.0.access_token : "" + cluster_ca_certificate = var.use_existing_k8s_sa ? data.google_container_cluster.primary.0.master_auth.0.cluster_ca_certificate : "" + cluster_endpoint = var.use_existing_k8s_sa ? "https://${data.google_container_cluster.primary.0.endpoint}" : "" +} + +data "google_container_cluster" "primary" { + count = var.use_existing_k8s_sa ? 1 : 0 + name = var.cluster_name + project = var.project_id + location = var.location +} + +data "google_client_config" "default" { + count = var.use_existing_k8s_sa ? 1 : 0 } resource "google_service_account" "cluster_service_account" { @@ -51,11 +65,11 @@ module "annotate-sa" { enabled = var.use_existing_k8s_sa skip_download = true - create_cmd_entrypoint = "kubectl" - create_cmd_body = "annotate sa -n ${local.output_k8s_namespace} ${local.k8s_given_name} iam.gke.io/gcp-service-account=${local.gcp_sa_email}" + create_cmd_entrypoint = "${path.module}/scripts/kubectl_wrapper.sh" + create_cmd_body = "${local.cluster_endpoint} ${local.token} ${local.cluster_ca_certificate} kubectl annotate --overwrite sa -n ${local.output_k8s_namespace} ${local.k8s_given_name} iam.gke.io/gcp-service-account=${local.gcp_sa_email}" - destroy_cmd_entrypoint = "kubectl" - destroy_cmd_body = "annotate sa -n ${local.output_k8s_namespace} ${local.k8s_given_name} iam.gke.io/gcp-service-account-" + destroy_cmd_entrypoint = "${path.module}/scripts/kubectl_wrapper.sh" + destroy_cmd_body = "${local.cluster_endpoint} ${local.token} ${local.cluster_ca_certificate} kubectl annotate sa -n ${local.output_k8s_namespace} ${local.k8s_given_name} iam.gke.io/gcp-service-account-" } resource "google_service_account_iam_member" "main" { diff --git a/modules/workload-identity/scripts/kubectl_wrapper.sh b/modules/workload-identity/scripts/kubectl_wrapper.sh new file mode 100755 index 000000000..e92300bcb --- /dev/null +++ b/modules/workload-identity/scripts/kubectl_wrapper.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +set -e + +if [ "$#" -lt 3 ]; then + >&2 echo "Not all expected arguments set." + exit 1 +fi + +HOST=$1 +TOKEN=$2 +CA_CERTIFICATE=$3 + +shift 3 + +RANDOM_ID="${RANDOM}_${RANDOM}" +export TMPDIR="/tmp/kubectl_wrapper_${RANDOM_ID}" + +function cleanup { + rm -rf "${TMPDIR}" +} +trap cleanup EXIT + +mkdir "${TMPDIR}" + +export KUBECONFIG="${TMPDIR}/config" + +# shellcheck disable=SC1117 +base64 --help | grep "\--decode" && B64_ARG="--decode" || B64_ARG="-d" +echo "${CA_CERTIFICATE}" | base64 ${B64_ARG} > "${TMPDIR}/ca_certificate" + +kubectl config set-cluster kubectl-wrapper --server="${HOST}" --certificate-authority="${TMPDIR}/ca_certificate" --embed-certs=true 1>/dev/null +rm -f "${TMPDIR}/ca_certificate" +kubectl config set-context kubectl-wrapper --cluster=kubectl-wrapper --user=kubectl-wrapper --namespace=default 1>/dev/null +kubectl config set-credentials kubectl-wrapper --token="${TOKEN}" 1>/dev/null +kubectl config use-context kubectl-wrapper 1>/dev/null +kubectl version 1>/dev/null + +"$@" diff --git a/modules/workload-identity/variables.tf b/modules/workload-identity/variables.tf index db991097a..d939b7bd9 100644 --- a/modules/workload-identity/variables.tf +++ b/modules/workload-identity/variables.tf @@ -19,6 +19,18 @@ variable "name" { type = string } +variable "cluster_name" { + description = "Cluster name. Required if using existing KSA." + type = string + default = "" +} + +variable "location" { + description = "Cluster location (region if regional cluster, zone if zonal cluster). Required if using existing KSA." + type = string + default = "" +} + variable "k8s_sa_name" { description = "Name for the existing Kubernetes service account" type = string