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.