mirror of
https://github.com/imjasonh/gke-auth
synced 2026-07-07 00:23:18 +00:00
By default the context/cluster/user entries in kubeconfig are named
gke_{project}_{location}_{cluster}, which is verbose and hard to type.
Add --alias so users can set a short, memorable name instead.
215 lines
6 KiB
Go
215 lines
6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli/config"
|
|
"golang.org/x/oauth2/google"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
clientauthv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
"k8s.io/client-go/tools/clientcmd/api"
|
|
)
|
|
|
|
var (
|
|
project = flag.String("project", "", "Name of the project")
|
|
location = flag.String("location", "", "Location of the cluster")
|
|
clusterName = flag.String("cluster", "", "Name of the cluster")
|
|
alias = flag.String("alias", "", "Alias name for the kubeconfig context (defaults to gke_{project}_{location}_{cluster})")
|
|
|
|
get = flag.Bool("get", false, "If true, print auth information")
|
|
clear = flag.Bool("clear", false, "If true, clear auth for this cluster")
|
|
configureDocker = flag.Bool("configure-docker", false, "If true, configure docker to use this token")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
ctx := context.Background()
|
|
|
|
if *get && *clear {
|
|
log.Fatal("cannot pass both --get and --clear")
|
|
}
|
|
if (*get || *clear) && *configureDocker {
|
|
log.Fatal("cannot pass --get or --clear with --configure-docker")
|
|
}
|
|
|
|
if *configureDocker {
|
|
if *location == "" {
|
|
log.Fatal("must pass --location when using --configure-docker")
|
|
}
|
|
cfg, err := config.Load(config.Dir())
|
|
if err != nil {
|
|
log.Fatalf("Loading docker config: %v", err)
|
|
}
|
|
if cfg.CredentialHelpers == nil {
|
|
cfg.CredentialHelpers = map[string]string{}
|
|
}
|
|
host := fmt.Sprintf("%s-docker.pkg.dev", *location)
|
|
cfg.CredentialHelpers[host] = "gke-auth"
|
|
path, err := exec.LookPath(os.Args[0])
|
|
if err != nil {
|
|
log.Fatalf("Looking up path: %v", err)
|
|
}
|
|
sl := filepath.Dir(path) + "/docker-credential-gke-auth"
|
|
if err := os.Remove(sl); err != nil && !os.IsNotExist(err) {
|
|
log.Fatalf("Removing existing symlink: %v", err)
|
|
}
|
|
if err := os.Symlink(path, filepath.Dir(path)+"/docker-credential-gke-auth"); err != nil {
|
|
log.Fatalf("Symlinking: %v", err)
|
|
}
|
|
if err := cfg.Save(); err != nil {
|
|
log.Fatalf("Saving docker config: %v", err)
|
|
}
|
|
log.Printf("Docker configured to use gke-auth for %q in %q", host, *location)
|
|
return
|
|
}
|
|
|
|
// get a token
|
|
scopes := []string{
|
|
// Base scope for GCP auth
|
|
"https://www.googleapis.com/auth/cloud-platform",
|
|
// Needed in order to use service account emails instead of unique IDs in K8S RBAC.
|
|
"https://www.googleapis.com/auth/userinfo.email",
|
|
}
|
|
ts, err := google.DefaultTokenSource(ctx, scopes...)
|
|
if err != nil {
|
|
log.Fatalf("google.DefaultTokenSource: %v", err)
|
|
}
|
|
tok, err := ts.Token()
|
|
if err != nil {
|
|
log.Fatalf("ts.Token: %v", err)
|
|
}
|
|
|
|
if strings.HasSuffix(os.Args[0], "docker-credential-gke-auth") {
|
|
urlb, err := io.ReadAll(io.LimitReader(os.Stdin, 1000))
|
|
if err != nil {
|
|
log.Fatalf("Reading stdin: %v", err)
|
|
}
|
|
|
|
if err := json.NewEncoder(os.Stdout).Encode(&struct {
|
|
ServerURL string `json:"ServerURL"`
|
|
Username string `json:"Username"`
|
|
Secret string `json:"Secret"`
|
|
}{
|
|
ServerURL: string(urlb),
|
|
Username: "oauth2accesstoken",
|
|
Secret: tok.AccessToken,
|
|
}); err != nil {
|
|
log.Fatalf("Encoding JSON: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !*get && (*project == "" || *location == "" || *clusterName == "") {
|
|
log.Fatal("must pass --project and --location and --cluster")
|
|
}
|
|
|
|
if *get {
|
|
// just print the token
|
|
if err := json.NewEncoder(os.Stdout).Encode(&clientauthv1.ExecCredential{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "client.authentication.k8s.io/v1",
|
|
Kind: "ExecCredential",
|
|
},
|
|
Status: &clientauthv1.ExecCredentialStatus{
|
|
ExpirationTimestamp: &metav1.Time{Time: tok.Expiry},
|
|
Token: tok.AccessToken,
|
|
},
|
|
}); err != nil {
|
|
log.Fatalf("Encoding JSON: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
url := fmt.Sprintf("https://container.googleapis.com/v1/projects/%s/locations/%s/clusters/%s", *project, *location, *clusterName)
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
log.Fatalf("http.NewRequest: %v", err)
|
|
}
|
|
tok.SetAuthHeader(req)
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
log.Fatalf("http.Do: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
all, _ := io.ReadAll(resp.Body)
|
|
log.Fatalf("http.Do: %d %s %s", resp.StatusCode, resp.Status, string(all))
|
|
}
|
|
var cluster struct {
|
|
Endpoint string
|
|
MasterAuth struct {
|
|
ClusterCaCertificate string
|
|
}
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&cluster); err != nil {
|
|
log.Fatalf("json.Decode: %v", err)
|
|
}
|
|
|
|
cfg, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
|
|
if err != nil {
|
|
log.Fatalf("Loading kubeconfig %v", err)
|
|
}
|
|
|
|
// get kubeconfig location
|
|
key := fmt.Sprintf("gke_%s_%s_%s", *project, *location, *clusterName)
|
|
if *alias != "" {
|
|
key = *alias
|
|
}
|
|
kcfgPath := clientcmd.RecommendedHomeFile
|
|
if auth := cfg.AuthInfos[key]; auth != nil && auth.LocationOfOrigin != "" {
|
|
kcfgPath = auth.LocationOfOrigin
|
|
}
|
|
|
|
// add user
|
|
if *clear {
|
|
cfg.AuthInfos[key] = &api.AuthInfo{}
|
|
cfg.Clusters[key] = &api.Cluster{}
|
|
cfg.Contexts[key] = &api.Context{}
|
|
cfg.CurrentContext = ""
|
|
log.Println("Auth config cleared")
|
|
} else {
|
|
cfg.AuthInfos[key] = &api.AuthInfo{
|
|
Exec: &api.ExecConfig{
|
|
APIVersion: "client.authentication.k8s.io/v1",
|
|
Command: os.Args[0],
|
|
Args: []string{"--get"},
|
|
InteractiveMode: api.NeverExecInteractiveMode,
|
|
InstallHint: "go install github.com/imjasonh/gke-auto@latest",
|
|
},
|
|
}
|
|
|
|
// add cluster
|
|
dec, err := base64.StdEncoding.DecodeString(cluster.MasterAuth.ClusterCaCertificate)
|
|
if err != nil {
|
|
log.Fatalf("Decoding CA cert: %v", err)
|
|
}
|
|
cfg.Clusters[key] = &api.Cluster{
|
|
Server: "https://" + cluster.Endpoint,
|
|
CertificateAuthorityData: dec,
|
|
}
|
|
|
|
// update current context
|
|
cfg.Contexts[key] = &api.Context{
|
|
AuthInfo: key,
|
|
Cluster: key,
|
|
}
|
|
cfg.CurrentContext = key
|
|
}
|
|
|
|
// write the file back
|
|
if err := clientcmd.WriteToFile(*cfg, kcfgPath); err != nil {
|
|
log.Fatalf("Writing kubeconfig %q: %v", kcfgPath, err)
|
|
}
|
|
}
|