-
Notifications
You must be signed in to change notification settings - Fork 0
/
kubeconfig.go
203 lines (178 loc) · 5.78 KB
/
kubeconfig.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Package kubeconfig manages teleport entries in a local kubeconfig file.
package kubeconfig
import (
"bytes"
"os"
"path/filepath"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
"github.com/sirupsen/logrus"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
var log = logrus.WithFields(logrus.Fields{
trace.Component: teleport.ComponentKubeClient,
})
// Values are Teleport user data needed to generate kubeconfig entries.
type Values struct {
// Name is used to name kubeconfig sections ("context", "cluster" and
// "user"). Should match Teleport cluster name.
Name string
// ClusterAddr is the public address the Kubernetes client will talk to,
// usually a proxy.
ClusterAddr string
// Credentials are user credentials to use for authentication the
// ClusterAddr. Only TLS fields (key/cert/CA) from Credentials are used.
Credentials *client.Key
}
// UpdateWithClient adds Teleport configuration to kubeconfig based on the
// configured TeleportClient.
//
// If `path` is empty, UpdateWithClient will try to guess it based on the
// environment or known defaults.
func UpdateWithClient(path string, tc *client.TeleportClient) error {
clusterAddr := tc.KubeClusterAddr()
clusterName, _ := tc.KubeProxyHostPort()
if tc.SiteName != "" {
clusterName = tc.SiteName
}
creds, err := tc.LocalAgent().GetKey()
if err != nil {
return trace.Wrap(err)
}
return Update(path, Values{
Name: clusterName,
ClusterAddr: clusterAddr,
Credentials: creds,
})
}
// Update adds Teleport configuration to kubeconfig.
//
// If `path` is empty, Update will try to guess it based on the environment or
// known defaults.
func Update(path string, v Values) error {
config, err := Load(path)
if err != nil {
return trace.Wrap(err)
}
cas := bytes.Join(v.Credentials.TLSCAs(), []byte("\n"))
// Validate the provided credentials, to avoid partially-populated
// kubeconfig.
if len(v.Credentials.Priv) == 0 {
return trace.BadParameter("private key missing in provided credentials")
}
if len(v.Credentials.TLSCert) == 0 {
return trace.BadParameter("TLS certificate missing in provided credentials")
}
if len(cas) == 0 {
return trace.BadParameter("TLS trusted CAs missing in provided credentials")
}
config.AuthInfos[v.Name] = &clientcmdapi.AuthInfo{
ClientCertificateData: v.Credentials.TLSCert,
ClientKeyData: v.Credentials.Priv,
}
config.Clusters[v.Name] = &clientcmdapi.Cluster{
Server: v.ClusterAddr,
CertificateAuthorityData: cas,
}
lastContext := config.Contexts[v.Name]
newContext := &clientcmdapi.Context{
Cluster: v.Name,
AuthInfo: v.Name,
}
if lastContext != nil {
newContext.Namespace = lastContext.Namespace
newContext.Extensions = lastContext.Extensions
}
config.Contexts[v.Name] = newContext
config.CurrentContext = v.Name
return save(path, *config)
}
// Remove removes Teleport configuration from kubeconfig.
//
// If `path` is empty, Remove will try to guess it based on the environment or
// known defaults.
func Remove(path, name string) error {
// Load existing kubeconfig from disk.
config, err := Load(path)
if err != nil {
return trace.Wrap(err)
}
// Remove Teleport related AuthInfos, Clusters, and Contexts from kubeconfig.
delete(config.AuthInfos, name)
delete(config.Clusters, name)
delete(config.Contexts, name)
// Take an element from the list of contexts and make it the current
// context, unless current context points to something else.
if config.CurrentContext == name && len(config.Contexts) > 0 {
for name := range config.Contexts {
config.CurrentContext = name
break
}
}
// Update kubeconfig on disk.
return save(path, *config)
}
// Load tries to read a kubeconfig file and if it can't, returns an error.
// One exception, missing files result in empty configs, not an error.
func Load(path string) (*clientcmdapi.Config, error) {
filename, err := finalPath(path)
if err != nil {
return nil, trace.Wrap(err)
}
config, err := clientcmd.LoadFromFile(filename)
if err != nil && !os.IsNotExist(err) {
err = trace.ConvertSystemError(err)
return nil, trace.WrapWithMessage(err, "failed to parse existing kubeconfig %q: %v", filename, err)
}
if config == nil {
config = clientcmdapi.NewConfig()
}
return config, nil
}
// save saves updated config to location specified by environment variable or
// default location
func save(path string, config clientcmdapi.Config) error {
filename, err := finalPath(path)
if err != nil {
return trace.Wrap(err)
}
if err := clientcmd.WriteToFile(config, filename); err != nil {
return trace.ConvertSystemError(err)
}
return nil
}
// finalPath returns the final path to kubeceonfig using, in order of
// precedence:
// - `customPath`, if not empty
// - ${KUBECONFIG} environment variable
// - ${HOME}/.kube/config
//
// finalPath also creates any parent directories for the returned path, if
// missing.
func finalPath(customPath string) (string, error) {
if customPath == "" {
customPath = pathFromEnv()
}
finalPath, err := utils.EnsureLocalPath(customPath, teleport.KubeConfigDir, teleport.KubeConfigFile)
if err != nil {
return "", trace.Wrap(err)
}
return finalPath, nil
}
// pathFromEnv extracts location of kubeconfig from the environment.
func pathFromEnv() string {
kubeconfig := os.Getenv(teleport.EnvKubeConfig)
// The KUBECONFIG environment variable is a list. On Windows it's
// semicolon-delimited. On Linux and macOS it's colon-delimited.
parts := filepath.SplitList(kubeconfig)
// Default behavior of kubectl is to return the first file from list.
var configPath string
if len(parts) > 0 {
configPath = parts[0]
log.Debugf("Using kubeconfig from environment: %q.", configPath)
}
return configPath
}