This repository has been archived by the owner on Oct 10, 2023. It is now read-only.
/
kube_config.go
217 lines (179 loc) · 7.1 KB
/
kube_config.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// Copyright 2021-2022 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package tkgauth
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
"k8s.io/client-go/discovery"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeutils "github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/auth/utils/kubeconfig"
"github.com/vmware-tanzu/tanzu-framework/pinniped-components/common/pkg/pinnipedinfo"
)
const (
// ConciergeAuthenticatorType is the pinniped concierge authenticator type
ConciergeAuthenticatorType = "jwt"
// ConciergeAuthenticatorName is the pinniped concierge authenticator object name
ConciergeAuthenticatorName = "tkg-jwt-authenticator"
// PinnipedOIDCScopes are the scopes of pinniped oidc
PinnipedOIDCScopes = "offline_access,openid,pinniped:request-audience"
// TanzuLocalKubeDir is the local config directory
TanzuLocalKubeDir = ".kube-tanzu"
// TanzuKubeconfigFile is the name the of the kubeconfig file
TanzuKubeconfigFile = "config"
// DefaultPinnipedLoginTimeout is the default login timeout
DefaultPinnipedLoginTimeout = time.Minute
// DefaultClusterInfoConfigMap is the default ConfigMap looked up in the kube-public namespace when generating a kubeconfig.
DefaultClusterInfoConfigMap = "cluster-info"
)
// KubeConfigOptions contains the kubeconfig options
type KubeConfigOptions struct {
MergeFilePath string
}
// A DiscoveryStrategy contains information about how various discovery
// information should be looked up from an endpoint when setting up a
// kubeconfig.
type DiscoveryStrategy struct {
DiscoveryPort *int
ClusterInfoConfigMap string
}
// KubeconfigWithPinnipedAuthLoginPlugin prepares the kubeconfig with tanzu pinniped-auth login as client-go exec plugin
func KubeconfigWithPinnipedAuthLoginPlugin(endpoint string, options *KubeConfigOptions, discoveryStrategy DiscoveryStrategy) (mergeFilePath, currentContext string, err error) {
clusterInfo, err := GetClusterInfoFromCluster(endpoint, discoveryStrategy.ClusterInfoConfigMap)
if err != nil {
err = errors.Wrap(err, "failed to get cluster-info")
return
}
pinnipedInfo, err := GetPinnipedInfoFromCluster(clusterInfo, discoveryStrategy.DiscoveryPort)
if err != nil {
err = errors.Wrap(err, "failed to get pinniped-info")
return
}
if pinnipedInfo == nil {
err = errors.New("failed to get pinniped-info from cluster")
return
}
config, err := GetPinnipedKubeconfig(clusterInfo, pinnipedInfo, pinnipedInfo.ClusterName, pinnipedInfo.Issuer)
if err != nil {
err = errors.Wrap(err, "unable to get the kubeconfig")
return
}
kubeconfigBytes, err := json.Marshal(config)
if err != nil {
err = errors.Wrap(err, "unable to marshall the kubeconfig")
return
}
mergeFilePath = ""
if options != nil && options.MergeFilePath != "" {
mergeFilePath = options.MergeFilePath
} else {
mergeFilePath, err = TanzuLocalKubeConfigPath()
if err != nil {
err = errors.Wrap(err, "unable to get the Tanzu local kubeconfig path")
return
}
}
err = kubeutils.MergeKubeConfigWithoutSwitchContext(kubeconfigBytes, mergeFilePath)
if err != nil {
err = errors.Wrap(err, "unable to merge cluster kubeconfig to the Tanzu local kubeconfig path")
return
}
currentContext = config.CurrentContext
return mergeFilePath, currentContext, err
}
// GetServerKubernetesVersion uses the kubeconfig to get the server k8s version.
func GetServerKubernetesVersion(kubeconfigPath, context string) (string, error) {
var discoveryClient discovery.DiscoveryInterface
kubeConfigBytes, err := loadKubeconfigAndEnsureContext(kubeconfigPath, context)
if err != nil {
return "", errors.Errorf("unable to read kubeconfig")
}
restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigBytes)
if err != nil {
return "", errors.Errorf("Unable to set up rest config due to : %v", err)
}
// set the timeout to give user sufficient time to enter the login credentials
restConfig.Timeout = DefaultPinnipedLoginTimeout
discoveryClient, err = discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
return "", errors.Errorf("Error getting discovery client due to : %v", err)
}
if _, err := discoveryClient.ServerVersion(); err != nil {
return "", errors.Errorf("Failed to invoke API on cluster : %v", err)
}
return "", nil
}
func loadKubeconfigAndEnsureContext(kubeConfigPath, context string) ([]byte, error) {
config, err := clientcmd.LoadFromFile(kubeConfigPath)
if err != nil {
return []byte{}, err
}
if context != "" {
config.CurrentContext = context
}
return clientcmd.Write(*config)
}
// GetPinnipedKubeconfig generate kubeconfig given cluster-info and pinniped-info and the requested audience
func GetPinnipedKubeconfig(cluster *clientcmdapi.Cluster, pinnipedInfo *pinnipedinfo.PinnipedInfo, clustername, audience string) (*clientcmdapi.Config, error) {
execConfig := clientcmdapi.ExecConfig{
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
Args: []string{},
Env: []clientcmdapi.ExecEnvVar{},
}
execConfig.Command = "tanzu"
execConfig.Args = append([]string{"pinniped-auth", "login"}, execConfig.Args...)
conciergeEndpoint := cluster.Server
if pinnipedInfo.ConciergeEndpoint != "" {
conciergeEndpoint = pinnipedInfo.ConciergeEndpoint
}
// configure concierge
execConfig.Args = append(execConfig.Args,
"--enable-concierge",
"--concierge-authenticator-name="+ConciergeAuthenticatorName,
"--concierge-authenticator-type="+ConciergeAuthenticatorType,
"--concierge-endpoint="+conciergeEndpoint,
"--concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(cluster.CertificateAuthorityData),
"--issuer="+pinnipedInfo.Issuer, // configure OIDC
"--scopes="+PinnipedOIDCScopes,
"--ca-bundle-data="+pinnipedInfo.IssuerCABundleData,
"--request-audience="+audience,
)
if os.Getenv("TANZU_CLI_PINNIPED_AUTH_LOGIN_SKIP_BROWSER") != "" {
execConfig.Args = append(execConfig.Args, "--skip-browser")
}
username := "tanzu-cli-" + clustername
contextName := fmt.Sprintf("%s@%s", username, clustername)
return &clientcmdapi.Config{
Kind: "Config",
APIVersion: clientcmdapi.SchemeGroupVersion.Version,
Clusters: map[string]*clientcmdapi.Cluster{clustername: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{username: {Exec: &execConfig}},
Contexts: map[string]*clientcmdapi.Context{contextName: {Cluster: clustername, AuthInfo: username}},
CurrentContext: contextName,
}, nil
}
// TanzuLocalKubeConfigPath returns the local tanzu kubeconfig path
func TanzuLocalKubeConfigPath() (path string, err error) {
home, err := os.UserHomeDir()
if err != nil {
return path, errors.Wrap(err, "could not locate local tanzu dir")
}
path = filepath.Join(home, TanzuLocalKubeDir)
// create tanzu kubeconfig directory
if _, err := os.Stat(path); os.IsNotExist(err) {
err = os.MkdirAll(path, 0755)
if err != nil {
return "", err
}
} else if err != nil {
return "", err
}
configFilePath := filepath.Join(path, TanzuKubeconfigFile)
return configFilePath, nil
}