diff --git a/cmd/common.go b/cmd/common.go index a894c0b0..65e5f312 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -34,11 +34,13 @@ import ( managementClient "github.com/rancher/types/client/management/v3" "github.com/sirupsen/logrus" "github.com/urfave/cli" + "k8s.io/client-go/tools/clientcmd/api" ) const ( - letters = "abcdefghijklmnopqrstuvwxyz0123456789" - cfgFile = "cli2.json" + letters = "abcdefghijklmnopqrstuvwxyz0123456789" + cfgFile = "cli2.json" + kubeConfigKeyFormat = "%s-%s" ) var ( @@ -143,6 +145,31 @@ func listRoleTemplateBindings(ctx *cli.Context, b []RoleTemplateBinding) error { return writer.Err() } +func getKubeConfigForUser(ctx *cli.Context, user string) (*api.Config, error) { + cf, err := loadConfig(ctx) + if err != nil { + return nil, err + } + + focusedServer := cf.FocusedServer() + kubeConfig, _ := focusedServer.KubeConfigs[fmt.Sprintf(kubeConfigKeyFormat, user, focusedServer.FocusedCluster())] + return kubeConfig, nil +} + +func setKubeConfigForUser(ctx *cli.Context, user string, kubeConfig *api.Config) error { + cf, err := loadConfig(ctx) + if err != nil { + return err + } + + if cf.FocusedServer().KubeConfigs == nil { + cf.FocusedServer().KubeConfigs = make(map[string]*api.Config) + } + focusedServer := cf.FocusedServer() + focusedServer.KubeConfigs[fmt.Sprintf(kubeConfigKeyFormat, user, focusedServer.FocusedCluster())] = kubeConfig + return cf.Write() +} + func usersToNameMapping(u []managementClient.User) map[string]string { userMapping := make(map[string]string) for _, user := range u { diff --git a/cmd/kubectl.go b/cmd/kubectl.go index 9114e4cc..4af54304 100644 --- a/cmd/kubectl.go +++ b/cmd/kubectl.go @@ -5,8 +5,13 @@ import ( "io/ioutil" "os" "os/exec" + "strings" + "github.com/rancher/norman/clientbase" + client "github.com/rancher/types/client/management/v3" "github.com/urfave/cli" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" ) func KubectlCommand() cli.Command { @@ -37,29 +42,72 @@ func runKubectl(ctx *cli.Context) error { return err } - cluster, err := getClusterByID(c, c.UserConfig.FocusedCluster()) + config, err := loadConfig(ctx) if err != nil { return err } - config, err := c.ManagementClient.Cluster.ActionGenerateKubeconfig(cluster) + currentRancherServer := config.FocusedServer() + if currentRancherServer == nil { + return fmt.Errorf("no focused server") + } + + currentToken := currentRancherServer.AccessKey + t, err := c.ManagementClient.Token.ByID(currentToken) if err != nil { return err } + currentUser := t.UserID + kubeConfig, err := getKubeConfigForUser(ctx, currentUser) + if err != nil { + return err + } + + var isTokenValid bool + if kubeConfig != nil { + tokenID, err := extractKubeconfigTokenID(*kubeConfig) + if err != nil { + return err + } + isTokenValid, err = validateToken(tokenID, c.ManagementClient.Token) + if err != nil { + return err + } + } + + if kubeConfig == nil || !isTokenValid { + cluster, err := getClusterByID(c, c.UserConfig.FocusedCluster()) + if err != nil { + return err + } + + config, err := c.ManagementClient.Cluster.ActionGenerateKubeconfig(cluster) + if err != nil { + return err + } + + kubeConfigBytes := []byte(config.Config) + kubeConfig, err = clientcmd.Load(kubeConfigBytes) + if err != nil { + return err + } + + if err := setKubeConfigForUser(ctx, currentUser, kubeConfig); err != nil { + return err + } + } + tmpfile, err := ioutil.TempFile("", "rancher-") if err != nil { return err } defer os.Remove(tmpfile.Name()) - _, err = tmpfile.Write([]byte(config.Config)) - if err != nil { + if err := clientcmd.WriteToFile(*kubeConfig, tmpfile.Name()); err != nil { return err } - - err = tmpfile.Close() - if err != nil { + if err := tmpfile.Close(); err != nil { return err } @@ -74,3 +122,29 @@ func runKubectl(ctx *cli.Context) error { } return nil } + +func extractKubeconfigTokenID(kubeconfig api.Config) (string, error) { + if len(kubeconfig.AuthInfos) != 1 { + return "", fmt.Errorf("invalid kubeconfig, expected to contain exactly 1 user") + } + var parts []string + for _, val := range kubeconfig.AuthInfos { + parts = strings.Split(val.Token, ":") + if len(parts) != 2 { + return "", fmt.Errorf("failed to parse kubeconfig token") + } + } + + return parts[0], nil +} + +func validateToken(tokenID string, tokenClient client.TokenOperations) (bool, error) { + token, err := tokenClient.ByID(tokenID) + if err != nil { + if !clientbase.IsNotFound(err) { + return false, err + } + return false, nil + } + return !token.Expired, nil +} diff --git a/config/config.go b/config/config.go index a35da313..ed678fe4 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/sirupsen/logrus" + "k8s.io/client-go/tools/clientcmd/api" ) // Config holds the main config for the user @@ -28,6 +29,7 @@ type ServerConfig struct { Project string `json:"project"` CACerts string `json:"cacert"` KubeCredentials map[string]*ExecCredential `json:"kubeCredentials"` + KubeConfigs map[string]*api.Config `json:"kubeConfigs"` } func (c Config) Write() error { diff --git a/go.mod b/go.mod index 25ad2d3a..cab41acd 100644 --- a/go.mod +++ b/go.mod @@ -24,4 +24,5 @@ require ( golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 gopkg.in/yaml.v2 v2.2.8 + k8s.io/client-go v12.0.0+incompatible )