From 4d50c5aa62771f587d243498c0fde22f5135c270 Mon Sep 17 00:00:00 2001 From: micnncim Date: Mon, 7 Feb 2022 03:39:06 +0900 Subject: [PATCH 1/3] Support Workload Identity Adds support for Workload Identity, with which credentials no longer need to be present in Secrets. If `InjectedIdentity` is specified, a token source for application default credentials by a GCP Service Account, which is specified in the `iam.gke.io/gcp-service-account` annotation of a provider's Kubernetes Service Account. Tested this in the following environment and process. ``` $ kubectl version --short Client Version: v1.20.7 Server Version: v1.20.12-gke.1500 ``` ``` $ gcloud container clusters describe $CLUSTER --format="value(workloadIdentityConfig.workloadPool)" $PROJECT_ID.svc.id.goog ``` ``` $ kubectl get deploy crossplane \ -o jsonpath="{.spec.template.spec.containers[*].image}" \ -n crossplane-system crossplane/crossplane:v1.6.2 ``` Created a `Provider` and `ProviderConfig` with `InjectedIdentity` and then created a `Topic` managed resource: ``` $ cat < --- apis/v1beta1/providerconfig_types.go | 2 +- go.mod | 1 + go.sum | 3 ++- .../gcp.crossplane.io_providerconfigs.yaml | 1 + pkg/clients/gcp.go | 21 +++++++++++++++---- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apis/v1beta1/providerconfig_types.go b/apis/v1beta1/providerconfig_types.go index 747dfca67..8623580fc 100644 --- a/apis/v1beta1/providerconfig_types.go +++ b/apis/v1beta1/providerconfig_types.go @@ -34,7 +34,7 @@ type ProviderConfigSpec struct { // ProviderCredentials required to authenticate. type ProviderCredentials struct { // Source of the provider credentials. - // +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem + // +kubebuilder:validation:Enum=None;Secret;InjectedIdentity;Environment;Filesystem Source xpv1.CredentialsSource `json:"source"` xpv1.CommonCredentialSelectors `json:",inline"` diff --git a/go.mod b/go.mod index 7fb43a6c3..077213bac 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/imdario/mergo v0.3.12 github.com/mitchellh/copystructure v1.0.0 github.com/pkg/errors v0.9.1 + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 google.golang.org/api v0.52.0 google.golang.org/grpc v1.39.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 diff --git a/go.sum b/go.sum index ae4c94024..c5151809b 100644 --- a/go.sum +++ b/go.sum @@ -929,8 +929,9 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/package/crds/gcp.crossplane.io_providerconfigs.yaml b/package/crds/gcp.crossplane.io_providerconfigs.yaml index 46077282f..90a5c5841 100644 --- a/package/crds/gcp.crossplane.io_providerconfigs.yaml +++ b/package/crds/gcp.crossplane.io_providerconfigs.yaml @@ -96,6 +96,7 @@ spec: enum: - None - Secret + - InjectedIdentity - Environment - Filesystem type: string diff --git a/pkg/clients/gcp.go b/pkg/clients/gcp.go index eb24e411e..d7085226a 100644 --- a/pkg/clients/gcp.go +++ b/pkg/clients/gcp.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/google/go-cmp/cmp" + "golang.org/x/oauth2/google" "google.golang.org/api/googleapi" "google.golang.org/api/option" "google.golang.org/grpc/codes" @@ -31,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/errors" "github.com/crossplane/crossplane-runtime/pkg/resource" @@ -39,6 +41,8 @@ import ( "github.com/crossplane/provider-gcp/apis/v1beta1" ) +const scopeCloudPlatform = "https://www.googleapis.com/auth/cloud-platform" + // GetAuthInfo returns the necessary authentication information that is necessary // to use when the controller connects to GCP API in order to reconcile the managed // resource. @@ -79,11 +83,20 @@ func UseProviderConfig(ctx context.Context, c client.Client, mg resource.Managed if err := c.Get(ctx, types.NamespacedName{Name: mg.GetProviderConfigReference().Name}, pc); err != nil { return "", nil, err } - data, err := resource.CommonCredentialExtractor(ctx, pc.Spec.Credentials.Source, c, pc.Spec.Credentials.CommonCredentialSelectors) - if err != nil { - return "", nil, errors.Wrap(err, "cannot get credentials") + switch s := pc.Spec.Credentials.Source; s { //nolint:exhaustive + case xpv1.CredentialsSourceInjectedIdentity: + ts, err := google.DefaultTokenSource(ctx, scopeCloudPlatform) + if err != nil { + return "", nil, errors.Wrap(err, "cannot get application default credentials token") + } + return pc.Spec.ProjectID, option.WithTokenSource(ts), nil + default: + data, err := resource.CommonCredentialExtractor(ctx, pc.Spec.Credentials.Source, c, pc.Spec.Credentials.CommonCredentialSelectors) + if err != nil { + return "", nil, errors.Wrap(err, "cannot get credentials") + } + return pc.Spec.ProjectID, option.WithCredentialsJSON(data), nil } - return pc.Spec.ProjectID, option.WithCredentialsJSON(data), nil } // IsErrorNotFoundGRPC gets a value indicating whether the given error represents From 64d283e9da6a0a8511f36947def44a9b3a3c4f86 Mon Sep 17 00:00:00 2001 From: micnncim Date: Wed, 9 Feb 2022 21:20:42 +0900 Subject: [PATCH 2/3] Add authentication guide for Google Cloud APIs Adds a guide for configuring authentication to Google Cloud APIs. Though this provides enough information to cover the feature added in this PR, we should improve the way to configure a `ServiceAccount` for practical use cases since in any methods users need to reconfigure IAM stuff every time a new `ProviderRevision` is created. Signed-off-by: micnncim --- docs/AUTHENTICATION.md | 162 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 docs/AUTHENTICATION.md diff --git a/docs/AUTHENTICATION.md b/docs/AUTHENTICATION.md new file mode 100644 index 000000000..15c5abd83 --- /dev/null +++ b/docs/AUTHENTICATION.md @@ -0,0 +1,162 @@ +# Authenticating to Google Cloud APIs + +`provider-gcp` requires credentials to be provided in order to authenticate to +the Google Cloud APIs. This can be done in one of the following ways: + +- Authenticating using a base-64 encoded service account key in a Kubernetes + `Secret`. This is described in detail [here](https://crossplane.io/docs/v1.6/getting-started/install-configure.html#get-gcp-account-keyfile). +- Authenticating using [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity). + This is described in the [section below](#authenticating-with-workload-identity). + +## Authenticating with Workload Identity + +*Note: This method is supported in `provider-gcp` v0.20.0 and later.* + +Using Workload Identity requires some additional setup. +Many of the steps can also be found in the [documentation](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity). + +### Steps + +These steps assume you already have a running GKE cluster which has already +enabled Workload Identity and has a sufficiently large node pool. + +Note that you can specify any valid strings to the variables below unless the +variable is explicitly assigned. + +#### 1. Install Crossplane + +Install Crossplane from `stable` channel: + +```bash +$ helm repo add crossplane-stable https://charts.crossplane.io/stable +$ helm install crossplane --create-namespace --namespace crossplane-system crossplane-stable/crossplane +``` + +`provider-gcp` can be installed with either the [Crossplane CLI](https://crossplane.io/docs/v1.6/getting-started/install-configure.html#install-crossplane-cli) +or a `Provider` resource as below: + +```console +$ cat < Date: Sat, 12 Feb 2022 12:40:50 +0900 Subject: [PATCH 3/3] Add step to define variables' names Adds a step to define variables' names so that users can smoothly set up authentication with Workload Identity in `provider-gcp`. Signed-off-by: micnncim --- docs/AUTHENTICATION.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/AUTHENTICATION.md b/docs/AUTHENTICATION.md index 15c5abd83..f61b5a9bb 100644 --- a/docs/AUTHENTICATION.md +++ b/docs/AUTHENTICATION.md @@ -20,8 +20,20 @@ Many of the steps can also be found in the [documentation](https://cloud.google. These steps assume you already have a running GKE cluster which has already enabled Workload Identity and has a sufficiently large node pool. -Note that you can specify any valid strings to the variables below unless the -variable is explicitly assigned. +#### 0. Prepare your variables + +In the following sections, you'll need to name your resources. +Define the variables below with any names valid in Kubernetes or GCP so that you +can smoothly set it up: + +```console +$ PROJECT_ID= # e.g.) acme-prod +$ PROVIDER_GCP= # e.g.) provider-gcp +$ VERSION= # e.g.) 0.20.0 +$ GCP_SERVICE_ACCOUNT= # e.g.) crossplane +$ ROLE= # e.g.) roles/cloudsql.admin +$ CONTROLLER_CONFIG= # e.g.) gcp-config (Optional) +``` #### 1. Install Crossplane @@ -87,6 +99,12 @@ $ KUBERNETES_SERVICE_ACCOUNT=${REVISION} ##### 2.1. [Option 2] Use a user-managed `ServiceAccount` +Name your Kubernetes `ServiceAccount`: + +```console +$ KUBERNETES_SERVICE_ACCOUNT= +``` + Create a `ServiceAccount`, `ControllerConfig`, and `ClusterRoleBinding`: ```console @@ -150,7 +168,7 @@ kind: ProviderConfig metadata: name: default spec: - projectID: $PROJECT_ID + projectID: ${PROJECT_ID} credentials: source: InjectedIdentity EOF