Home > Software design >  Accessing GKE resources through kubectl (or client-go) after OAuth authentication to Google Cloud
Accessing GKE resources through kubectl (or client-go) after OAuth authentication to Google Cloud

Time:07-26

I have successfully completed an OAuth 2.0 flow to Google Cloud using the https://www.googleapis.com/auth/cloud-platform scope.

I am now in possession of a token.

I want to be able to access kubernetes resources in GKE using kubectl or (preferably) through client-go library.

How can I use this token to

  • get the credentials for a specific cluster that resides in a specific project?
  • run something in the likes of creating / accessing a secret, e.g.
createdSecret, _ := clientset.CoreV1().Secrets(secret.Namespace).Create(ctx, secret, metav1.CreateOptions{})

CodePudding user response:

A good question.

It's a two-step process:

  • Use GCP credentials to access Container service
  • Use Container service methods to build Kubernetes cluster credentials

Here's a copy of some code that I have to do this. I've edited it to remove logging and some of my other "fluff" but I hope it retains a sufficient explanation for your needs.

In my case, I'm using CRDs in my cluster and this requires an additional path to permit access to all resources (not just built-in).

package cluster

import (
    // Requires GCP auth provider
    "context"
    "encoding/base64"
    "fmt"
    "os"

    "google.golang.org/api/container/v1"

    // Necessary to support GCP authentication
    _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"

    "k8s.io/client-go/rest"
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/tools/clientcmd/api"
)

var (
    clusterName     = os.Getenv("CLUSTER_NAME")
    clusterLocation = os.Getenv("CLUSTER_LOCATION")
    clusterProject  = os.Getenv("CLUSTER_PROJECT")
)


func init() {
    if clusterName == "" {
        panic(msg)
    }
    if clusterLocation == "" {
        panic(msg)
    }
    if clusterProject == "" {
        panic(msg)
    }
}

// KubeConfig is a function that uses Kubernetes Engine
// to query a cluster for a Kubernetes config
// The function dynamically generates the equivalent
// cluster, context and user entries for ~/.kubeconfig
func KubeConfig() (api.Config, error) {
    ctx := context.Background()
    containerService, _ := container.NewService(ctx)

    name := fmt.Sprintf("projects/%s/locations/%s/clusters/%s",
        clusterProject,
        clusterLocation,
        clusterName,
    )
    rqst := containerService.Projects.Locations.Clusters.Get(name)
    resp, _ := rqst.Do()

    cert, _ := base64.StdEncoding.DecodeString(
        resp.MasterAuth.ClusterCaCertificate,
    )

    // Create Config for Cluster
    // Equivalent to ~/.kubeconfig
    // apiVersion: v1
    // kind: Config
    // clusters:
    // - name: gke_{project}_{location}_ackal-system
    //   cluster:
    //     certificate-authority-data: DATA OMITTED
    //     server: https://...
    // contexts:
    // - name: gke_{project}_{location}_ackal-system
    //   context:
    //     cluster: gke_{project}_{location}_ackal-system
    //     user: gke_{project}_{location}_ackal-system
    // users:
    // - name: gke_{project}_{location}_ackal-system
    //   user:
    //     auth-provider:
    //       name: gcp
    //       config:
    //         scopes:

    server := fmt.Sprintf("https://%s", resp.Endpoint)
    scopes := "https://www.googleapis.com/auth/cloud-platform"

    apiConfig := api.Config{
        APIVersion: "v1",
        Kind:       "Config",
        Clusters: map[string]*api.Cluster{
            clusterName: {
                CertificateAuthorityData: cert,
                Server:                   server,
            },
        },
        Contexts: map[string]*api.Context{
            clusterName: {
                Cluster:  clusterName,
                AuthInfo: clusterName,
            },
        },
        // CurrentContext: clusterName,
        AuthInfos: map[string]*api.AuthInfo{
            clusterName: {
                AuthProvider: &api.AuthProviderConfig{
                    Name: "gcp",
                    Config: map[string]string{
                        "scopes": scopes,
                    },
                },
            },
        },
    }
    return apiConfig, nil
}

// RESTConfig is a function that creates a Kubernetes REST API
// config from an API (Kube) config
// This is necessary because CRDs are not in-built Kubernetes
// objects and aren't represented by k8s.io
// Using the REST API enables arbitrary (REST) API calls against
// the Kubernetes API server
func RESTConfig(config api.Config) (*rest.Config, error) {
    restConfig, err := clientcmd.NewNonInteractiveClientConfig(
        config,
        clusterName,
        &clientcmd.ConfigOverrides{
            CurrentContext: clusterName,
        },
        nil,
    ).ClientConfig()
    return restConfig, err
}

CodePudding user response:

The answer complements the one suggested by @DazWilkin

Assuming token is the *oauth2.Token object already retrieved via the OAuth2.0 flow. The dynamic in-memory kube config objects should be constructed as follows

 apiConfig := api.Config{
        APIVersion: "v1",
        Kind:       "Config",
        Clusters: map[string]*api.Cluster{
            clusterName: {
                CertificateAuthorityData: cert,
                Server:                   server,
            },
        },
        Contexts: map[string]*api.Context{
            clusterName: {
                Cluster:  clusterName,
                AuthInfo: clusterName,
            },
        },
        // CurrentContext: clusterName,
        AuthInfos: map[string]*api.AuthInfo{
            Token: token.AccessToken,
            clusterName: {
                AuthProvider: &api.AuthProviderConfig{
                    Name: "gcp",
                    Config: map[string]string{
                        "scopes": scopes,
                    },
                },
            },
        },
    }

Notice the addition of Token: token.AccessToken, in the api.AuthInfo struct.

  • Related