Files
kubesphere/pkg/models/kubeconfig/kubeconfig.go
2020-02-05 17:15:44 +08:00

259 lines
7.2 KiB
Go

/*
Copyright 2019 The KubeSphere 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 kubeconfig
import (
"bytes"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
certutil "k8s.io/client-go/util/cert"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/constants"
pkiutil "kubesphere.io/kubesphere/pkg/models/kubeconfig/internal"
"kubesphere.io/kubesphere/pkg/simple/client"
"time"
)
const (
inClusterCAFilePath = "/run/secrets/kubernetes.io/serviceaccount/ca.crt"
kubeconfigNameFormat = "kubeconfig-%s"
defaultClusterName = "local"
defaultNamespace = "default"
fileName = "config"
configMapKind = "ConfigMap"
configMapAPIVersion = "v1"
)
func CreateKubeConfig(username string) error {
k8sClient := client.ClientSets().K8s().Kubernetes()
configName := fmt.Sprintf(kubeconfigNameFormat, username)
_, err := k8sClient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Get(configName, metav1.GetOptions{})
if errors.IsNotFound(err) {
kubeconfig, err := createKubeConfig(username)
if err != nil {
klog.Errorln(err)
return err
}
data := map[string]string{fileName: string(kubeconfig)}
cm := &corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: configMapKind, APIVersion: configMapAPIVersion}, ObjectMeta: metav1.ObjectMeta{Name: configName}, Data: data}
_, err = k8sClient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Create(cm)
if err != nil && !errors.IsAlreadyExists(err) {
klog.Errorln(err)
return err
}
}
return nil
}
func createKubeConfig(username string) ([]byte, error) {
k8sClient := client.ClientSets().K8s().Kubernetes()
kubeconfig := client.ClientSets().K8s().Config()
var err error
var ca []byte
if len(kubeconfig.CAData) > 0 {
ca = kubeconfig.CAData
} else {
ca, err = ioutil.ReadFile(inClusterCAFilePath)
if err != nil {
klog.Errorln(err)
return nil, err
}
}
csrConfig := &certutil.Config{
CommonName: username,
Organization: nil,
AltNames: certutil.AltNames{},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
}
x509csr, x509key, err := pkiutil.NewCSRAndKey(csrConfig)
if err != nil {
klog.Errorln(err)
return nil, err
}
var csrBuffer, keyBuffer bytes.Buffer
pem.Encode(&keyBuffer, &pem.Block{Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(x509key)})
csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, x509csr, x509key)
pem.Encode(&csrBuffer, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
csr := csrBuffer.Bytes()
key := keyBuffer.Bytes()
csrName := fmt.Sprintf("%s-csr-%d", username, time.Now().Unix())
k8sCSR := &certificatesv1beta1.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{
Kind: "CertificateSigningRequest",
APIVersion: "certificates.k8s.io/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: csrName,
},
Spec: certificatesv1beta1.CertificateSigningRequestSpec{
Request: csr,
Usages: []certificatesv1beta1.KeyUsage{certificatesv1beta1.UsageServerAuth, certificatesv1beta1.UsageKeyEncipherment, certificatesv1beta1.UsageClientAuth, certificatesv1beta1.UsageDigitalSignature},
Username: username,
Groups: []string{"system:authenticated"},
},
}
// create csr
k8sCSR, err = k8sClient.CertificatesV1beta1().CertificateSigningRequests().Create(k8sCSR)
if err != nil {
klog.Errorln(err)
return nil, err
}
// release csr, if it fails need to delete it manually
defer func() {
err := k8sClient.CertificatesV1beta1().CertificateSigningRequests().Delete(csrName, &metav1.DeleteOptions{})
if err != nil {
klog.Errorln(err)
}
}()
k8sCSR.Status = certificatesv1beta1.CertificateSigningRequestStatus{
Conditions: []certificatesv1beta1.CertificateSigningRequestCondition{{
Type: "Approved",
Reason: "KubeSphereApprove",
Message: "This CSR was approved by KubeSphere certificate approve.",
LastUpdateTime: metav1.Time{
Time: time.Now(),
},
}},
}
// approve csr
k8sCSR, err = k8sClient.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(k8sCSR)
if err != nil {
klog.Errorln(err)
return nil, err
}
// get client cert
var cert []byte
maxRetries := 3
for i := 0; i < maxRetries; i++ {
k8sCSR, err = k8sClient.CertificatesV1beta1().CertificateSigningRequests().Get(csrName, metav1.GetOptions{})
if k8sCSR != nil && k8sCSR.Status.Certificate != nil {
cert = k8sCSR.Status.Certificate
break
}
// sleep 0/200/400 millisecond
time.Sleep(200 * time.Millisecond * time.Duration(i))
}
if cert == nil {
return nil, fmt.Errorf("create client certificate failed: %v", err)
}
currentContext := fmt.Sprintf("%s@%s", username, defaultClusterName)
config := clientcmdapi.Config{
Kind: configMapKind,
APIVersion: configMapAPIVersion,
Preferences: clientcmdapi.Preferences{},
Clusters: map[string]*clientcmdapi.Cluster{defaultClusterName: {
Server: kubeconfig.Host,
InsecureSkipTLSVerify: false,
CertificateAuthorityData: ca,
}},
AuthInfos: map[string]*clientcmdapi.AuthInfo{username: {
ClientCertificateData: cert,
ClientKeyData: key,
}},
Contexts: map[string]*clientcmdapi.Context{currentContext: {
Cluster: defaultClusterName,
AuthInfo: username,
Namespace: defaultNamespace,
}},
CurrentContext: currentContext,
}
return clientcmd.Write(config)
}
func GetKubeConfig(username string) (string, error) {
k8sClient := client.ClientSets().K8s().Kubernetes()
configName := fmt.Sprintf(kubeconfigNameFormat, username)
configMap, err := k8sClient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Get(configName, metav1.GetOptions{})
if err != nil {
klog.Errorln(err)
return "", err
}
data := []byte(configMap.Data[fileName])
kubeconfig, err := clientcmd.Load(data)
if err != nil {
klog.Errorln(err)
return "", err
}
masterURL := client.ClientSets().K8s().Master()
if cluster := kubeconfig.Clusters[defaultClusterName]; cluster != nil {
cluster.Server = masterURL
}
data, err = clientcmd.Write(*kubeconfig)
if err != nil {
klog.Errorln(err)
return "", err
}
return string(data), nil
}
func DelKubeConfig(username string) error {
k8sClient := client.ClientSets().K8s().Kubernetes()
configName := fmt.Sprintf(kubeconfigNameFormat, username)
deletePolicy := metav1.DeletePropagationBackground
err := k8sClient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Delete(configName, &metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
if err != nil {
klog.Errorln(err)
return err
}
return nil
}