mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 17:53:28 +00:00
Add unit tests for kubeconfig updates
Several tests to confirm correctness of kubeconfig update logic. Specifically - to make sure existing configuration is not deleted. `UpdateKubeconfig` was split into two functions because mocking `*client.TeleportClient` was really difficult. Fixes #3209
This commit is contained in:
parent
12952b4904
commit
8c75759b98
|
@ -1,6 +1,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -23,22 +24,21 @@ var log = logrus.WithFields(logrus.Fields{
|
||||||
|
|
||||||
// UpdateKubeconfig adds Teleport configuration to kubeconfig.
|
// UpdateKubeconfig adds Teleport configuration to kubeconfig.
|
||||||
func UpdateKubeconfig(tc *client.TeleportClient) error {
|
func UpdateKubeconfig(tc *client.TeleportClient) error {
|
||||||
config, err := LoadKubeConfig()
|
|
||||||
if err != nil {
|
|
||||||
return trace.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterName, proxyPort := tc.KubeProxyHostPort()
|
clusterName, proxyPort := tc.KubeProxyHostPort()
|
||||||
clusterAddr := fmt.Sprintf("https://%v:%v", clusterName, proxyPort)
|
clusterAddr := fmt.Sprintf("https://%v:%v", clusterName, proxyPort)
|
||||||
if tc.SiteName != "" {
|
if tc.SiteName != "" {
|
||||||
clusterName = tc.SiteName
|
clusterName = tc.SiteName
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, err := tc.LocalAgent().GetKey()
|
creds, err := tc.LocalAgent().GetKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return trace.Wrap(err)
|
return trace.Wrap(err)
|
||||||
}
|
}
|
||||||
certAuthorities, err := tc.LocalAgent().GetCertsPEM()
|
|
||||||
|
return updateKubeconfigWithValues(clusterName, clusterAddr, creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateKubeconfigWithValues(clusterName, clusterAddr string, creds *client.Key) error {
|
||||||
|
config, err := LoadKubeConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return trace.Wrap(err)
|
return trace.Wrap(err)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ func UpdateKubeconfig(tc *client.TeleportClient) error {
|
||||||
}
|
}
|
||||||
config.Clusters[clusterName] = &clientcmdapi.Cluster{
|
config.Clusters[clusterName] = &clientcmdapi.Cluster{
|
||||||
Server: clusterAddr,
|
Server: clusterAddr,
|
||||||
CertificateAuthorityData: certAuthorities,
|
CertificateAuthorityData: bytes.Join(creds.TLSCAs(), []byte("\n")),
|
||||||
}
|
}
|
||||||
|
|
||||||
lastContext := config.Contexts[clusterName]
|
lastContext := config.Contexts[clusterName]
|
||||||
|
|
242
lib/kube/client/kubeclient_test.go
Normal file
242
lib/kube/client/kubeclient_test.go
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gravitational/teleport"
|
||||||
|
"github.com/gravitational/teleport/lib/auth"
|
||||||
|
"github.com/gravitational/teleport/lib/auth/testauthority"
|
||||||
|
"github.com/gravitational/teleport/lib/client"
|
||||||
|
"github.com/gravitational/teleport/lib/defaults"
|
||||||
|
"github.com/gravitational/teleport/lib/sshutils"
|
||||||
|
"github.com/gravitational/teleport/lib/tlsca"
|
||||||
|
|
||||||
|
"github.com/gravitational/trace"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
"gopkg.in/check.v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKubeClient(t *testing.T) { check.TestingT(t) }
|
||||||
|
|
||||||
|
type KubeconfigSuite struct {
|
||||||
|
kubeconfigPath string
|
||||||
|
initialConfig clientcmdapi.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = check.Suite(&KubeconfigSuite{})
|
||||||
|
|
||||||
|
func (s *KubeconfigSuite) SetUpTest(c *check.C) {
|
||||||
|
f, err := ioutil.TempFile("", "kubeconfig")
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("failed to create temp kubeconfig file: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Note: LocationOfOrigin and Extensions would be automatically added on
|
||||||
|
// clientcmd.Write below. Set them explicitly so we can compare
|
||||||
|
// s.initialConfig against loaded config.
|
||||||
|
//
|
||||||
|
// TODO: use a comparison library that can ignore individual fields.
|
||||||
|
s.initialConfig = clientcmdapi.Config{
|
||||||
|
CurrentContext: "dev",
|
||||||
|
Clusters: map[string]*clientcmdapi.Cluster{
|
||||||
|
"cluster-1": {
|
||||||
|
CertificateAuthority: "fake-ca-file",
|
||||||
|
Server: "https://1.2.3.4",
|
||||||
|
LocationOfOrigin: f.Name(),
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
"cluster-2": {
|
||||||
|
InsecureSkipTLSVerify: true,
|
||||||
|
Server: "https://1.2.3.5",
|
||||||
|
LocationOfOrigin: f.Name(),
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||||
|
"developer": {
|
||||||
|
ClientCertificate: "fake-client-cert",
|
||||||
|
ClientKey: "fake-client-key",
|
||||||
|
LocationOfOrigin: f.Name(),
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
Username: "admin",
|
||||||
|
Password: "hunter1",
|
||||||
|
LocationOfOrigin: f.Name(),
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
Exec: &clientcmdapi.ExecConfig{
|
||||||
|
Command: "/bin/get_creds",
|
||||||
|
Args: []string{"--role=support"},
|
||||||
|
},
|
||||||
|
LocationOfOrigin: f.Name(),
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Contexts: map[string]*clientcmdapi.Context{
|
||||||
|
"dev": {
|
||||||
|
Cluster: "cluster-2",
|
||||||
|
AuthInfo: "developer",
|
||||||
|
LocationOfOrigin: f.Name(),
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
"prod": {
|
||||||
|
Cluster: "cluster-1",
|
||||||
|
AuthInfo: "admin",
|
||||||
|
LocationOfOrigin: f.Name(),
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Preferences: clientcmdapi.Preferences{
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
}
|
||||||
|
|
||||||
|
initialContent, err := clientcmd.Write(s.initialConfig)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
if _, err := f.Write(initialContent); err != nil {
|
||||||
|
c.Fatalf("failed to write kubeconfig: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.kubeconfigPath = f.Name()
|
||||||
|
os.Setenv(teleport.EnvKubeConfig, s.kubeconfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KubeconfigSuite) TearDownTest(c *check.C) {
|
||||||
|
os.Remove(s.kubeconfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KubeconfigSuite) TestLoadKubeConfig(c *check.C) {
|
||||||
|
config, err := LoadKubeConfig()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(*config, check.DeepEquals, s.initialConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KubeconfigSuite) TestSaveKubeConfig(c *check.C) {
|
||||||
|
cfg := clientcmdapi.Config{
|
||||||
|
CurrentContext: "a",
|
||||||
|
Clusters: map[string]*clientcmdapi.Cluster{
|
||||||
|
"cluster": {
|
||||||
|
CertificateAuthority: "fake-ca-file",
|
||||||
|
Server: "https://1.2.3.4",
|
||||||
|
LocationOfOrigin: s.kubeconfigPath,
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||||
|
"user": {
|
||||||
|
LocationOfOrigin: s.kubeconfigPath,
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Contexts: map[string]*clientcmdapi.Context{
|
||||||
|
"a": {
|
||||||
|
Cluster: "cluster",
|
||||||
|
AuthInfo: "user",
|
||||||
|
LocationOfOrigin: s.kubeconfigPath,
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Preferences: clientcmdapi.Preferences{
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
},
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveKubeConfig(cfg)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
config, err := LoadKubeConfig()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(*config, check.DeepEquals, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KubeconfigSuite) TestUpdateKubeconfigWithValues(c *check.C) {
|
||||||
|
const (
|
||||||
|
clusterName = "teleport-cluster"
|
||||||
|
clusterAddr = "https://1.2.3.6:3080"
|
||||||
|
)
|
||||||
|
creds, caCertPEM, err := s.genUserKey()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
err = updateKubeconfigWithValues(clusterName, clusterAddr, creds)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
wantConfig := s.initialConfig
|
||||||
|
wantConfig.Clusters[clusterName] = &clientcmdapi.Cluster{
|
||||||
|
Server: clusterAddr,
|
||||||
|
CertificateAuthorityData: caCertPEM,
|
||||||
|
LocationOfOrigin: s.kubeconfigPath,
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
}
|
||||||
|
wantConfig.AuthInfos[clusterName] = &clientcmdapi.AuthInfo{
|
||||||
|
ClientCertificateData: creds.TLSCert,
|
||||||
|
ClientKeyData: creds.Priv,
|
||||||
|
LocationOfOrigin: s.kubeconfigPath,
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
}
|
||||||
|
wantConfig.Contexts[clusterName] = &clientcmdapi.Context{
|
||||||
|
Cluster: clusterName,
|
||||||
|
AuthInfo: clusterName,
|
||||||
|
LocationOfOrigin: s.kubeconfigPath,
|
||||||
|
Extensions: map[string]runtime.Object{},
|
||||||
|
}
|
||||||
|
wantConfig.CurrentContext = clusterName
|
||||||
|
|
||||||
|
config, err := LoadKubeConfig()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(*config, check.DeepEquals, wantConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KubeconfigSuite) genUserKey() (*client.Key, []byte, error) {
|
||||||
|
caKey, caCert, err := tlsca.GenerateSelfSignedCA(pkix.Name{
|
||||||
|
CommonName: "localhost",
|
||||||
|
Organization: []string{"localhost"},
|
||||||
|
}, nil, defaults.CATTL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
ca, err := tlsca.New(caCert, caKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keygen := testauthority.New()
|
||||||
|
priv, pub, err := keygen.GenerateKeyPair("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
cryptoPub, err := sshutils.CryptoPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
clock := clockwork.NewRealClock()
|
||||||
|
tlsCert, err := ca.GenerateCertificate(tlsca.CertificateRequest{
|
||||||
|
Clock: clock,
|
||||||
|
PublicKey: cryptoPub,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "teleport-user",
|
||||||
|
},
|
||||||
|
NotAfter: clock.Now().UTC().Add(time.Minute),
|
||||||
|
})
|
||||||
|
|
||||||
|
return &client.Key{
|
||||||
|
Priv: priv,
|
||||||
|
Pub: pub,
|
||||||
|
TLSCert: tlsCert,
|
||||||
|
TrustedCA: []auth.TrustedCerts{{
|
||||||
|
TLSCertificates: [][]byte{caCert},
|
||||||
|
}},
|
||||||
|
}, caCert, nil
|
||||||
|
}
|
Loading…
Reference in a new issue