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:
Andrew Lytvynov 2020-04-13 14:50:01 -07:00 committed by Andrew Lytvynov
parent 12952b4904
commit 8c75759b98
2 changed files with 250 additions and 8 deletions

View file

@ -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]

View 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
}