mirror of https://github.com/kubernetes/kops.git
Merge pull request #9667 from justinsb/kubectl_auth_helper
Support authentication helper for kubectl
This commit is contained in:
commit
5d09a9a95b
|
@ -69,6 +69,7 @@ go_library(
|
|||
"//pkg/cloudinstances:go_default_library",
|
||||
"//pkg/clusteraddons:go_default_library",
|
||||
"//pkg/commands:go_default_library",
|
||||
"//pkg/commands/commandutils:go_default_library",
|
||||
"//pkg/dump:go_default_library",
|
||||
"//pkg/edit:go_default_library",
|
||||
"//pkg/featureflag:go_default_library",
|
||||
|
|
|
@ -204,9 +204,9 @@ func RunDeleteCluster(ctx context.Context, f *util.Factory, out io.Writer, optio
|
|||
}
|
||||
}
|
||||
|
||||
b := kubeconfig.NewKubeconfigBuilder(clientcmd.NewDefaultPathOptions())
|
||||
b := kubeconfig.NewKubeconfigBuilder()
|
||||
b.Context = clusterName
|
||||
err = b.DeleteKubeConfig()
|
||||
err = b.DeleteKubeConfig(clientcmd.NewDefaultPathOptions())
|
||||
if err != nil {
|
||||
klog.Warningf("error removing kube config: %v", err)
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ type ExportKubecfgOptions struct {
|
|||
admin time.Duration
|
||||
user string
|
||||
internal bool
|
||||
|
||||
// UseKopsAuthenticationPlugin controls whether we should use the kops auth helper instead of a static credential
|
||||
UseKopsAuthenticationPlugin bool
|
||||
}
|
||||
|
||||
func NewCmdExportKubecfg(f *util.Factory, out io.Writer) *cobra.Command {
|
||||
|
@ -85,6 +88,7 @@ func NewCmdExportKubecfg(f *util.Factory, out io.Writer) *cobra.Command {
|
|||
cmd.Flags().Lookup("admin").NoOptDefVal = kubeconfig.DefaultKubecfgAdminLifetime.String()
|
||||
cmd.Flags().StringVar(&options.user, "user", options.user, "add an existing user to the cluster context")
|
||||
cmd.Flags().BoolVar(&options.internal, "internal", options.internal, "use the cluster's internal DNS name")
|
||||
cmd.Flags().BoolVar(&options.UseKopsAuthenticationPlugin, "auth-plugin", options.UseKopsAuthenticationPlugin, "use the kops authentication plugin")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -135,12 +139,21 @@ func RunExportKubecfg(ctx context.Context, f *util.Factory, out io.Writer, optio
|
|||
return err
|
||||
}
|
||||
|
||||
conf, err := kubeconfig.BuildKubecfg(cluster, keyStore, secretStore, &commands.CloudDiscoveryStatusStore{}, buildPathOptions(options), options.admin, options.user, options.internal)
|
||||
conf, err := kubeconfig.BuildKubecfg(
|
||||
cluster,
|
||||
keyStore,
|
||||
secretStore,
|
||||
&commands.CloudDiscoveryStatusStore{},
|
||||
options.admin,
|
||||
options.user,
|
||||
options.internal,
|
||||
f.KopsStateStore(),
|
||||
options.UseKopsAuthenticationPlugin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conf.WriteKubecfg(); err != nil {
|
||||
if err := conf.WriteKubecfg(buildPathOptions(options)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ limitations under the License.
|
|||
package main // import "k8s.io/kops/cmd/kops"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"k8s.io/kops/pkg/commands/commandutils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -28,6 +27,5 @@ func main() {
|
|||
// exitWithError will terminate execution with an error result
|
||||
// It prints the error to stderr and exits with a non-zero exit code
|
||||
func exitWithError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||
os.Exit(1)
|
||||
commandutils.ExitWithError(err)
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"k8s.io/kops/cmd/kops/util"
|
||||
kopsapi "k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/client/simple"
|
||||
"k8s.io/kops/pkg/commands"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
@ -143,6 +144,7 @@ func NewCmdRoot(f *util.Factory, out io.Writer) *cobra.Command {
|
|||
cmd.AddCommand(NewCmdEdit(f, out))
|
||||
cmd.AddCommand(NewCmdExport(f, out))
|
||||
cmd.AddCommand(NewCmdGet(f, out))
|
||||
cmd.AddCommand(commands.NewCmdHelpers(f, out))
|
||||
cmd.AddCommand(NewCmdUpdate(f, out))
|
||||
cmd.AddCommand(NewCmdReplace(f, out))
|
||||
cmd.AddCommand(NewCmdRollingUpdate(f, out))
|
||||
|
|
|
@ -306,12 +306,23 @@ func RunUpdateCluster(ctx context.Context, f *util.Factory, clusterName string,
|
|||
firstRun = !hasKubecfg
|
||||
|
||||
klog.Infof("Exporting kubecfg for cluster")
|
||||
conf, err := kubeconfig.BuildKubecfg(cluster, keyStore, secretStore, &commands.CloudDiscoveryStatusStore{}, clientcmd.NewDefaultPathOptions(), c.admin, c.user, c.internal)
|
||||
// TODO: Another flag?
|
||||
useKopsAuthenticationPlugin := false
|
||||
conf, err := kubeconfig.BuildKubecfg(
|
||||
cluster,
|
||||
keyStore,
|
||||
secretStore,
|
||||
&commands.CloudDiscoveryStatusStore{},
|
||||
c.admin,
|
||||
c.user,
|
||||
c.internal,
|
||||
f.KopsStateStore(),
|
||||
useKopsAuthenticationPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conf.WriteKubecfg()
|
||||
err = conf.WriteKubecfg(clientcmd.NewDefaultPathOptions())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -124,3 +124,8 @@ func (f *Factory) Clientset() (simple.Clientset, error) {
|
|||
|
||||
return f.clientset, nil
|
||||
}
|
||||
|
||||
// KopsStateStore returns the configured KOPS_STATE_STORE in use
|
||||
func (f *Factory) KopsStateStore() string {
|
||||
return f.options.RegistryPath
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ kops export kubecfg CLUSTERNAME [flags]
|
|||
```
|
||||
--admin duration[=18h0m0s] export a cluster admin user credential with the given lifetime and add it to the cluster context
|
||||
--all export all clusters from the kops state store
|
||||
--auth-plugin use the kops authentication plugin
|
||||
-h, --help help for kubecfg
|
||||
--internal use the cluster's internal DNS name
|
||||
--kubeconfig string the location of the kubeconfig file to create.
|
||||
|
|
1
go.mod
1
go.mod
|
@ -73,6 +73,7 @@ require (
|
|||
github.com/go-ini/ini v1.51.0
|
||||
github.com/go-logr/logr v0.2.1-0.20200730175230-ee2de8da5be6
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gophercloud/gophercloud v0.11.1-0.20200518183226-7aec46f32c19
|
||||
github.com/gorilla/mux v1.7.3
|
||||
|
|
|
@ -84,6 +84,8 @@ k8s.io/kops/pkg/client/simple/vfsclientset
|
|||
k8s.io/kops/pkg/cloudinstances
|
||||
k8s.io/kops/pkg/clusteraddons
|
||||
k8s.io/kops/pkg/commands
|
||||
k8s.io/kops/pkg/commands/commandutils
|
||||
k8s.io/kops/pkg/commands/helpers
|
||||
k8s.io/kops/pkg/configbuilder
|
||||
k8s.io/kops/pkg/diff
|
||||
k8s.io/kops/pkg/dns
|
||||
|
|
|
@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"helpers.go",
|
||||
"helpers_readwrite.go",
|
||||
"set_cluster.go",
|
||||
"status_discovery.go",
|
||||
|
@ -17,6 +18,7 @@ go_library(
|
|||
"//pkg/apis/kops/validation:go_default_library",
|
||||
"//pkg/assets:go_default_library",
|
||||
"//pkg/client/simple:go_default_library",
|
||||
"//pkg/commands/helpers:go_default_library",
|
||||
"//pkg/featureflag:go_default_library",
|
||||
"//pkg/resources/digitalocean:go_default_library",
|
||||
"//upup/pkg/fi/cloudup:go_default_library",
|
||||
|
@ -30,6 +32,8 @@ go_library(
|
|||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//vendor/k8s.io/kubectl/pkg/util/i18n:go_default_library",
|
||||
"//vendor/k8s.io/kubectl/pkg/util/templates:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["exit.go"],
|
||||
importpath = "k8s.io/kops/pkg/commands/commandutils",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package commandutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ExitWithError will terminate execution with an error result
|
||||
// It prints the error to stderr and exits with a non-zero exit code
|
||||
func ExitWithError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/cmd/kops/util"
|
||||
"k8s.io/kops/pkg/commands/helpers"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
helpersLong = templates.LongDesc(i18n.T(`
|
||||
Commands intended for integration with other systems.`))
|
||||
|
||||
helpersShort = i18n.T(`Commands for use with other systems.`)
|
||||
)
|
||||
|
||||
// NewCmdHelpers builds the cobra command tree for the `helpers` subcommand
|
||||
func NewCmdHelpers(f *util.Factory, out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "helpers",
|
||||
Short: helpersShort,
|
||||
Long: helpersLong,
|
||||
|
||||
// We hide the command, as it is intended for internal usage
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
cmd.AddCommand(helpers.NewCmdHelperKubectlAuth(f, out))
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["kubectl_auth.go"],
|
||||
importpath = "k8s.io/kops/pkg/commands/helpers",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/kops/util:go_default_library",
|
||||
"//pkg/commands/commandutils:go_default_library",
|
||||
"//pkg/pki:go_default_library",
|
||||
"//pkg/rbac:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
|
||||
"//vendor/k8s.io/klog/v2:go_default_library",
|
||||
"//vendor/k8s.io/kubectl/pkg/util/i18n:go_default_library",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kops/cmd/kops/util"
|
||||
"k8s.io/kops/pkg/commands/commandutils"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
"k8s.io/kops/pkg/rbac"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
kubectlAuthShort = i18n.T(`kubectl authentication plugin`)
|
||||
)
|
||||
|
||||
// HelperKubectlAuthOptions holds the options for generating an authentication token
|
||||
type HelperKubectlAuthOptions struct {
|
||||
// ClusterName is the name of the cluster we are targeting
|
||||
ClusterName string
|
||||
|
||||
// Lifetime specifies the desired duration of the credential
|
||||
Lifetime time.Duration
|
||||
|
||||
// APIVersion specifies the version of the client.authentication.k8s.io schema in use
|
||||
APIVersion string
|
||||
}
|
||||
|
||||
// InitDefaults populates the default values of options
|
||||
func (o *HelperKubectlAuthOptions) InitDefaults() {
|
||||
o.Lifetime = 1 * time.Hour
|
||||
o.APIVersion = "v1beta1"
|
||||
}
|
||||
|
||||
// NewCmdHelperKubectlAuth builds a cobra command for the kubectl-auth command
|
||||
func NewCmdHelperKubectlAuth(f *util.Factory, out io.Writer) *cobra.Command {
|
||||
options := &HelperKubectlAuthOptions{}
|
||||
options.InitDefaults()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "kubectl-auth",
|
||||
Short: kubectlAuthShort,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.TODO()
|
||||
|
||||
err := RunKubectlAuthHelper(ctx, f, out, options)
|
||||
if err != nil {
|
||||
commandutils.ExitWithError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&options.APIVersion, "api-version", options.APIVersion, "version of client.authentication.k8s.io schema in use")
|
||||
cmd.Flags().StringVar(&options.ClusterName, "cluster", options.ClusterName, "cluster to target")
|
||||
cmd.Flags().DurationVar(&options.Lifetime, "lifetime", options.Lifetime, "lifetime of the credential to issue")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunKubectlAuthHelper implements the kubectl auth helper, which creates an authentication token
|
||||
func RunKubectlAuthHelper(ctx context.Context, f *util.Factory, out io.Writer, options *HelperKubectlAuthOptions) error {
|
||||
if options.ClusterName == "" {
|
||||
return fmt.Errorf("ClusterName is required")
|
||||
}
|
||||
|
||||
execCredential := &ExecCredential{
|
||||
Kind: "ExecCredential",
|
||||
}
|
||||
|
||||
switch options.APIVersion {
|
||||
case "":
|
||||
return fmt.Errorf("api-version must be specified")
|
||||
case "v1alpha1":
|
||||
execCredential.APIVersion = "client.authentication.k8s.io/v1alpha1"
|
||||
case "v1beta1":
|
||||
execCredential.APIVersion = "client.authentication.k8s.io/v1beta1"
|
||||
|
||||
default:
|
||||
return fmt.Errorf("api-version %q is not supported", options.APIVersion)
|
||||
}
|
||||
|
||||
cacheFilePath := cacheFilePath(f.KopsStateStore(), options.ClusterName)
|
||||
cached, err := loadCachedExecCredential(cacheFilePath)
|
||||
if err != nil {
|
||||
klog.Infof("cached credential %q was not valid: %v", cacheFilePath, err)
|
||||
cached = nil
|
||||
}
|
||||
|
||||
if cached != nil && cached.APIVersion != execCredential.APIVersion {
|
||||
klog.Infof("cached credential had wrong api version")
|
||||
cached = nil
|
||||
}
|
||||
|
||||
isCached := false
|
||||
if cached != nil {
|
||||
execCredential = cached
|
||||
isCached = true
|
||||
} else {
|
||||
status, err := buildCredentials(ctx, f, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
execCredential.Status = *status
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(execCredential, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling json: %v", err)
|
||||
}
|
||||
_, err = out.Write(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %v", err)
|
||||
}
|
||||
|
||||
if !isCached {
|
||||
if err := os.MkdirAll(filepath.Dir(cacheFilePath), 0755); err != nil {
|
||||
klog.Warningf("failed to make cache directory for %q: %v", cacheFilePath, err)
|
||||
}
|
||||
if err := ioutil.WriteFile(cacheFilePath, b, 0600); err != nil {
|
||||
klog.Warningf("failed to write cache file %q: %v", cacheFilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecCredential specifies the client.authentication.k8s.io ExecCredential object
|
||||
type ExecCredential struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Status ExecCredentialStatus `json:"status"`
|
||||
}
|
||||
|
||||
// ExecCredentialStatus specifies the status of the client.authentication.k8s.io ExecCredential object
|
||||
type ExecCredentialStatus struct {
|
||||
ClientCertificateData string `json:"clientCertificateData,omitempty"`
|
||||
ClientKeyData string `json:"clientKeyData,omitempty"`
|
||||
ExpirationTimestamp time.Time `json:"expirationTimestamp,omitempty"`
|
||||
}
|
||||
|
||||
func cacheFilePath(kopsStateStore string, clusterName string) string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString(kopsStateStore)
|
||||
b.WriteByte(0)
|
||||
b.WriteString(clusterName)
|
||||
b.WriteByte(0)
|
||||
|
||||
hash := fmt.Sprintf("%x", sha256.New().Sum(b.Bytes()))
|
||||
sanitizedName := strings.Map(func(r rune) rune {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
return r
|
||||
case r >= 'A' && r <= 'Z':
|
||||
return r
|
||||
case r >= '0' && r <= '9':
|
||||
return r
|
||||
default:
|
||||
return '_'
|
||||
}
|
||||
}, clusterName)
|
||||
return filepath.Join(homedir.HomeDir(), ".kube", "cache", "kops-authentication", sanitizedName+"_"+hash)
|
||||
}
|
||||
|
||||
func loadCachedExecCredential(cacheFilePath string) (*ExecCredential, error) {
|
||||
b, err := ioutil.ReadFile(cacheFilePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// expected - a cache miss
|
||||
return nil, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
execCredential := &ExecCredential{}
|
||||
if err := json.Unmarshal(b, execCredential); err != nil {
|
||||
return nil, fmt.Errorf("error parsing: %v", err)
|
||||
}
|
||||
|
||||
if execCredential.Status.ExpirationTimestamp.Before(time.Now()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if execCredential.Status.ClientCertificateData == "" || execCredential.Status.ClientKeyData == "" {
|
||||
return nil, fmt.Errorf("no credentials in cached file")
|
||||
}
|
||||
|
||||
return execCredential, nil
|
||||
}
|
||||
|
||||
func buildCredentials(ctx context.Context, f *util.Factory, options *HelperKubectlAuthOptions) (*ExecCredentialStatus, error) {
|
||||
clientset, err := f.Clientset()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cluster, err := clientset.GetCluster(ctx, options.ClusterName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cluster == nil {
|
||||
return nil, fmt.Errorf("cluster not found %q", options.ClusterName)
|
||||
}
|
||||
|
||||
keyStore, err := clientset.KeyStore(cluster)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get cluster keystore: %v", err)
|
||||
}
|
||||
|
||||
cn := "kubecfg"
|
||||
user, err := user.Current()
|
||||
if err != nil || user == nil {
|
||||
klog.Infof("unable to get user: %v", err)
|
||||
} else {
|
||||
cn += "-" + user.Name
|
||||
}
|
||||
|
||||
req := pki.IssueCertRequest{
|
||||
Signer: fi.CertificateIDCA,
|
||||
Type: "client",
|
||||
Subject: pkix.Name{
|
||||
CommonName: cn,
|
||||
|
||||
Organization: []string{rbac.SystemPrivilegedGroup},
|
||||
},
|
||||
Validity: options.Lifetime,
|
||||
}
|
||||
cert, privateKey, _, err := pki.IssueCert(&req, keyStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to issue certificate: %v", err)
|
||||
}
|
||||
|
||||
status := &ExecCredentialStatus{}
|
||||
status.ClientCertificateData, err = cert.AsString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status.ClientKeyData, err = privateKey.AsString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subtract a few minutes from the validity for clock skew
|
||||
status.ExpirationTimestamp = cert.Certificate.NotAfter.Add(-5 * time.Minute)
|
||||
|
||||
return status, nil
|
||||
}
|
|
@ -32,6 +32,6 @@ go_test(
|
|||
"//pkg/pki:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//util/pkg/vfs:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
"sort"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/apis/kops/util"
|
||||
|
@ -35,7 +34,7 @@ import (
|
|||
|
||||
const DefaultKubecfgAdminLifetime = 18 * time.Hour
|
||||
|
||||
func BuildKubecfg(cluster *kops.Cluster, keyStore fi.Keystore, secretStore fi.SecretStore, status kops.StatusStore, configAccess clientcmd.ConfigAccess, admin time.Duration, configUser string, internal bool) (*KubeconfigBuilder, error) {
|
||||
func BuildKubecfg(cluster *kops.Cluster, keyStore fi.Keystore, secretStore fi.SecretStore, status kops.StatusStore, admin time.Duration, configUser string, internal bool, kopsStateStore string, useKopsAuthenticationPlugin bool) (*KubeconfigBuilder, error) {
|
||||
clusterName := cluster.ObjectMeta.Name
|
||||
|
||||
var master string
|
||||
|
@ -98,7 +97,7 @@ func BuildKubecfg(cluster *kops.Cluster, keyStore fi.Keystore, secretStore fi.Se
|
|||
}
|
||||
}
|
||||
|
||||
b := NewKubeconfigBuilder(configAccess)
|
||||
b := NewKubeconfigBuilder()
|
||||
|
||||
b.Context = clusterName
|
||||
b.Server = server
|
||||
|
@ -141,7 +140,6 @@ func BuildKubecfg(cluster *kops.Cluster, keyStore fi.Keystore, secretStore fi.Se
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.ClientCert, err = cert.AsBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -152,6 +150,16 @@ func BuildKubecfg(cluster *kops.Cluster, keyStore fi.Keystore, secretStore fi.Se
|
|||
}
|
||||
}
|
||||
|
||||
if useKopsAuthenticationPlugin {
|
||||
b.AuthenticationExec = []string{
|
||||
"kops",
|
||||
"helpers",
|
||||
"kubectl-auth",
|
||||
"--cluster=" + clusterName,
|
||||
"--state=" + kopsStateStore,
|
||||
}
|
||||
}
|
||||
|
||||
b.Server = server
|
||||
|
||||
k8sVersion, err := util.ParseKubernetesVersion(cluster.Spec.KubernetesVersion)
|
||||
|
|
|
@ -18,11 +18,10 @@ package kubeconfig
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
|
@ -123,14 +122,13 @@ func TestBuildKubecfg(t *testing.T) {
|
|||
}()
|
||||
|
||||
type args struct {
|
||||
cluster *kops.Cluster
|
||||
keyStore fakeKeyStore
|
||||
secretStore fi.SecretStore
|
||||
status fakeStatusStore
|
||||
configAccess clientcmd.ConfigAccess
|
||||
admin time.Duration
|
||||
user string
|
||||
internal bool
|
||||
cluster *kops.Cluster
|
||||
secretStore fi.SecretStore
|
||||
status fakeStatusStore
|
||||
admin time.Duration
|
||||
user string
|
||||
internal bool
|
||||
useKopsAuthenticationPlugin bool
|
||||
}
|
||||
|
||||
publiccluster := buildMinimalCluster("testcluster", "testcluster.test.com")
|
||||
|
@ -138,106 +136,65 @@ func TestBuildKubecfg(t *testing.T) {
|
|||
gossipCluster := buildMinimalCluster("testgossipcluster.k8s.local", "")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *KubeconfigBuilder
|
||||
wantErr bool
|
||||
name string
|
||||
args args
|
||||
want *KubeconfigBuilder
|
||||
wantErr bool
|
||||
wantClientCert bool
|
||||
}{
|
||||
{
|
||||
"Test Kube Config Data For Public DNS with admin",
|
||||
args{
|
||||
publiccluster,
|
||||
fakeKeyStore{
|
||||
FindKeypairFn: func(name string) (*pki.Certificate, *pki.PrivateKey, bool, error) {
|
||||
return fakeCertificate(),
|
||||
fakePrivateKey(),
|
||||
true,
|
||||
nil
|
||||
},
|
||||
},
|
||||
nil,
|
||||
fakeStatusStore{},
|
||||
nil,
|
||||
DefaultKubecfgAdminLifetime,
|
||||
"",
|
||||
false,
|
||||
name: "Test Kube Config Data For Public DNS with admin",
|
||||
args: args{
|
||||
cluster: publiccluster,
|
||||
status: fakeStatusStore{},
|
||||
admin: DefaultKubecfgAdminLifetime,
|
||||
user: "",
|
||||
},
|
||||
&KubeconfigBuilder{
|
||||
want: &KubeconfigBuilder{
|
||||
Context: "testcluster",
|
||||
Server: "https://testcluster.test.com",
|
||||
CACert: []byte(certData),
|
||||
User: "testcluster",
|
||||
},
|
||||
false,
|
||||
wantClientCert: true,
|
||||
},
|
||||
{
|
||||
"Test Kube Config Data For Public DNS without admin",
|
||||
args{
|
||||
publiccluster,
|
||||
fakeKeyStore{
|
||||
FindKeypairFn: func(name string) (*pki.Certificate, *pki.PrivateKey, bool, error) {
|
||||
return fakeCertificate(),
|
||||
fakePrivateKey(),
|
||||
true,
|
||||
nil
|
||||
},
|
||||
},
|
||||
nil,
|
||||
fakeStatusStore{},
|
||||
nil,
|
||||
0,
|
||||
"myuser",
|
||||
false,
|
||||
name: "Test Kube Config Data For Public DNS without admin",
|
||||
args: args{
|
||||
cluster: publiccluster,
|
||||
status: fakeStatusStore{},
|
||||
admin: 0,
|
||||
user: "myuser",
|
||||
},
|
||||
&KubeconfigBuilder{
|
||||
want: &KubeconfigBuilder{
|
||||
Context: "testcluster",
|
||||
Server: "https://testcluster.test.com",
|
||||
CACert: []byte(certData),
|
||||
User: "myuser",
|
||||
},
|
||||
false,
|
||||
wantClientCert: false,
|
||||
},
|
||||
{
|
||||
"Test Kube Config Data For Public DNS with Empty Master Name",
|
||||
args{
|
||||
emptyMasterPublicNameCluster,
|
||||
fakeKeyStore{
|
||||
FindKeypairFn: func(name string) (*pki.Certificate, *pki.PrivateKey, bool, error) {
|
||||
return fakeCertificate(),
|
||||
fakePrivateKey(),
|
||||
true,
|
||||
nil
|
||||
},
|
||||
},
|
||||
nil,
|
||||
fakeStatusStore{},
|
||||
nil,
|
||||
0,
|
||||
"",
|
||||
false,
|
||||
name: "Test Kube Config Data For Public DNS with Empty Master Name",
|
||||
args: args{
|
||||
cluster: emptyMasterPublicNameCluster,
|
||||
status: fakeStatusStore{},
|
||||
admin: 0,
|
||||
user: "",
|
||||
},
|
||||
&KubeconfigBuilder{
|
||||
want: &KubeconfigBuilder{
|
||||
Context: "emptyMasterPublicNameCluster",
|
||||
Server: "https://api.emptyMasterPublicNameCluster",
|
||||
CACert: []byte(certData),
|
||||
User: "emptyMasterPublicNameCluster",
|
||||
},
|
||||
false,
|
||||
wantClientCert: false,
|
||||
},
|
||||
{
|
||||
"Test Kube Config Data For Gossip cluster",
|
||||
args{
|
||||
gossipCluster,
|
||||
fakeKeyStore{
|
||||
FindKeypairFn: func(name string) (*pki.Certificate, *pki.PrivateKey, bool, error) {
|
||||
return fakeCertificate(),
|
||||
fakePrivateKey(),
|
||||
true,
|
||||
nil
|
||||
},
|
||||
},
|
||||
nil,
|
||||
fakeStatusStore{
|
||||
name: "Test Kube Config Data For Gossip cluster",
|
||||
args: args{
|
||||
cluster: gossipCluster,
|
||||
status: fakeStatusStore{
|
||||
GetApiIngressStatusFn: func(cluster *kops.Cluster) ([]kops.ApiIngressStatus, error) {
|
||||
return []kops.ApiIngressStatus{
|
||||
{
|
||||
|
@ -246,57 +203,74 @@ func TestBuildKubecfg(t *testing.T) {
|
|||
}, nil
|
||||
},
|
||||
},
|
||||
nil,
|
||||
0,
|
||||
"",
|
||||
false,
|
||||
},
|
||||
&KubeconfigBuilder{
|
||||
want: &KubeconfigBuilder{
|
||||
Context: "testgossipcluster.k8s.local",
|
||||
Server: "https://elbHostName",
|
||||
CACert: []byte(certData),
|
||||
User: "testgossipcluster.k8s.local",
|
||||
},
|
||||
false,
|
||||
wantClientCert: false,
|
||||
},
|
||||
{
|
||||
"Test Kube Config Data For internal DNS name with admin",
|
||||
args{
|
||||
publiccluster,
|
||||
fakeKeyStore{
|
||||
FindKeypairFn: func(name string) (*pki.Certificate, *pki.PrivateKey, bool, error) {
|
||||
return fakeCertificate(),
|
||||
fakePrivateKey(),
|
||||
true,
|
||||
nil
|
||||
},
|
||||
name: "Public DNS with kops auth plugin",
|
||||
args: args{
|
||||
cluster: publiccluster,
|
||||
status: fakeStatusStore{},
|
||||
admin: 0,
|
||||
useKopsAuthenticationPlugin: true,
|
||||
},
|
||||
want: &KubeconfigBuilder{
|
||||
Context: "testcluster",
|
||||
Server: "https://testcluster.test.com",
|
||||
CACert: []byte(certData),
|
||||
User: "testcluster",
|
||||
AuthenticationExec: []string{
|
||||
"kops",
|
||||
"helpers",
|
||||
"kubectl-auth",
|
||||
"--cluster=testcluster",
|
||||
"--state=memfs://example-state-store",
|
||||
},
|
||||
nil,
|
||||
fakeStatusStore{},
|
||||
nil,
|
||||
DefaultKubecfgAdminLifetime,
|
||||
"",
|
||||
true,
|
||||
},
|
||||
&KubeconfigBuilder{
|
||||
Context: "testcluster",
|
||||
Server: "https://internal.testcluster.test.com",
|
||||
CACert: []byte(certData),
|
||||
ClientCert: []byte(certData),
|
||||
ClientKey: []byte(privatekeyData),
|
||||
User: "testcluster",
|
||||
wantClientCert: false,
|
||||
},
|
||||
{
|
||||
name: "Test Kube Config Data For internal DNS name with admin",
|
||||
args: args{
|
||||
cluster: publiccluster,
|
||||
status: fakeStatusStore{},
|
||||
admin: DefaultKubecfgAdminLifetime,
|
||||
internal: true,
|
||||
},
|
||||
false,
|
||||
want: &KubeconfigBuilder{
|
||||
Context: "testcluster",
|
||||
Server: "https://internal.testcluster.test.com",
|
||||
CACert: []byte(certData),
|
||||
User: "testcluster",
|
||||
},
|
||||
wantClientCert: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := BuildKubecfg(tt.args.cluster, tt.args.keyStore, tt.args.secretStore, tt.args.status, tt.args.configAccess, tt.args.admin, tt.args.user, tt.args.internal)
|
||||
kopsStateStore := "memfs://example-state-store"
|
||||
|
||||
keyStore := fakeKeyStore{
|
||||
FindKeypairFn: func(name string) (*pki.Certificate, *pki.PrivateKey, bool, error) {
|
||||
return fakeCertificate(),
|
||||
fakePrivateKey(),
|
||||
true,
|
||||
nil
|
||||
},
|
||||
}
|
||||
|
||||
got, err := BuildKubecfg(tt.args.cluster, keyStore, tt.args.secretStore, tt.args.status, tt.args.admin, tt.args.user, tt.args.internal, kopsStateStore, tt.args.useKopsAuthenticationPlugin)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("BuildKubecfg() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.args.admin != 0 {
|
||||
if tt.wantClientCert {
|
||||
if got.ClientCert == nil {
|
||||
t.Errorf("Expected ClientCert, got nil")
|
||||
}
|
||||
|
@ -306,8 +280,8 @@ func TestBuildKubecfg(t *testing.T) {
|
|||
tt.want.ClientCert = got.ClientCert
|
||||
tt.want.ClientKey = got.ClientKey
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("BuildKubecfg() = %+v, want %+v", got, tt.want)
|
||||
if diff := cmp.Diff(got, tt.want); diff != "" {
|
||||
t.Errorf("BuildKubecfg() diff (+got, -want): %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -42,18 +42,16 @@ type KubeconfigBuilder struct {
|
|||
ClientCert []byte
|
||||
ClientKey []byte
|
||||
|
||||
configAccess clientcmd.ConfigAccess
|
||||
AuthenticationExec []string
|
||||
}
|
||||
|
||||
// Create new KubeconfigBuilder
|
||||
func NewKubeconfigBuilder(configAccess clientcmd.ConfigAccess) *KubeconfigBuilder {
|
||||
c := &KubeconfigBuilder{}
|
||||
c.configAccess = configAccess
|
||||
return c
|
||||
func NewKubeconfigBuilder() *KubeconfigBuilder {
|
||||
return &KubeconfigBuilder{}
|
||||
}
|
||||
|
||||
func (b *KubeconfigBuilder) DeleteKubeConfig() error {
|
||||
config, err := b.configAccess.GetStartingConfig()
|
||||
func (b *KubeconfigBuilder) DeleteKubeConfig(configAccess clientcmd.ConfigAccess) error {
|
||||
config, err := configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading kubeconfig: %v", err)
|
||||
}
|
||||
|
@ -72,7 +70,7 @@ func (b *KubeconfigBuilder) DeleteKubeConfig() error {
|
|||
config.CurrentContext = ""
|
||||
}
|
||||
|
||||
if err := clientcmd.ModifyConfig(b.configAccess, *config, false); err != nil {
|
||||
if err := clientcmd.ModifyConfig(configAccess, *config, false); err != nil {
|
||||
return fmt.Errorf("error writing kubeconfig: %v", err)
|
||||
}
|
||||
|
||||
|
@ -101,8 +99,8 @@ func (c *KubeconfigBuilder) BuildRestConfig() (*rest.Config, error) {
|
|||
}
|
||||
|
||||
// Write out a new kubeconfig
|
||||
func (b *KubeconfigBuilder) WriteKubecfg() error {
|
||||
config, err := b.configAccess.GetStartingConfig()
|
||||
func (b *KubeconfigBuilder) WriteKubecfg(configAccess clientcmd.ConfigAccess) error {
|
||||
config, err := configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading kubeconfig: %v", err)
|
||||
}
|
||||
|
@ -146,6 +144,14 @@ func (b *KubeconfigBuilder) WriteKubecfg() error {
|
|||
authInfo.ClientKeyData = b.ClientKey
|
||||
}
|
||||
|
||||
if len(b.AuthenticationExec) != 0 {
|
||||
authInfo.Exec = &clientcmdapi.ExecConfig{
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
Command: b.AuthenticationExec[0],
|
||||
Args: b.AuthenticationExec[1:],
|
||||
}
|
||||
}
|
||||
|
||||
if config.AuthInfos == nil {
|
||||
config.AuthInfos = make(map[string]*clientcmdapi.AuthInfo)
|
||||
}
|
||||
|
@ -198,7 +204,7 @@ func (b *KubeconfigBuilder) WriteKubecfg() error {
|
|||
|
||||
config.CurrentContext = b.Context
|
||||
|
||||
if err := clientcmd.ModifyConfig(b.configAccess, *config, true); err != nil {
|
||||
if err := clientcmd.ModifyConfig(configAccess, *config, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -279,6 +279,7 @@ github.com/golang/snappy
|
|||
# github.com/google/btree v1.0.0
|
||||
github.com/google/btree
|
||||
# github.com/google/go-cmp v0.4.0
|
||||
## explicit
|
||||
github.com/google/go-cmp/cmp
|
||||
github.com/google/go-cmp/cmp/internal/diff
|
||||
github.com/google/go-cmp/cmp/internal/flags
|
||||
|
|
Loading…
Reference in New Issue