improve IAM module

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-05-22 09:35:05 +08:00
parent 0d12529051
commit 8f93266ec0
640 changed files with 50221 additions and 18179 deletions

View File

@@ -18,17 +18,309 @@
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/apiserver/pkg/authentication/user"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
certutil "k8s.io/client-go/util/cert"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/utils/pkiutil"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"time"
)
const (
inClusterCAFilePath = "/run/secrets/kubernetes.io/serviceaccount/ca.crt"
configMapPrefix = "kubeconfig-"
kubeconfigNameFormat = configMapPrefix + "%s"
defaultClusterName = "local"
defaultNamespace = "default"
kubeconfigFileName = "config"
configMapKind = "ConfigMap"
configMapAPIVersion = "v1"
)
type Interface interface {
GetKubeConfig(username string) (string, error)
CreateKubeConfig(user *iamv1alpha2.User) error
DelKubeConfig(username string) error
UpdateKubeconfig(username string, certificate []byte) error
}
type operator struct {
k8sclient kubernetes.Interface
config *rest.Config
masterURL string
}
func (o operator) GetKubeConfig(username string) (string, error) {
panic("implement me")
func NewOperator(k8sclient kubernetes.Interface, config *rest.Config, masterURL string) Interface {
return &operator{k8sclient: k8sclient, config: config, masterURL: masterURL}
}
func NewKubeconfigOperator() Interface {
return &operator{}
func (o *operator) CreateKubeConfig(user *iamv1alpha2.User) error {
configName := fmt.Sprintf(kubeconfigNameFormat, user.Name)
_, err := o.k8sclient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Get(configName, metav1.GetOptions{})
if err == nil {
return nil
}
if !errors.IsNotFound(err) {
klog.Error(err)
return err
}
var ca []byte
if len(o.config.CAData) > 0 {
ca = o.config.CAData
} else {
ca, err = ioutil.ReadFile(inClusterCAFilePath)
if err != nil {
klog.Errorln(err)
return err
}
}
clientKey, err := o.createCSR(user.Name)
if err != nil {
klog.Errorln(err)
return err
}
currentContext := fmt.Sprintf("%s@%s", user.Name, defaultClusterName)
config := clientcmdapi.Config{
Kind: configMapKind,
APIVersion: configMapAPIVersion,
Preferences: clientcmdapi.Preferences{},
Clusters: map[string]*clientcmdapi.Cluster{defaultClusterName: {
Server: o.config.Host,
InsecureSkipTLSVerify: false,
CertificateAuthorityData: ca,
}},
AuthInfos: map[string]*clientcmdapi.AuthInfo{user.Name: {
ClientKeyData: clientKey,
}},
Contexts: map[string]*clientcmdapi.Context{currentContext: {
Cluster: defaultClusterName,
AuthInfo: user.Name,
Namespace: defaultNamespace,
}},
CurrentContext: currentContext,
}
kubeconfig, err := clientcmd.Write(config)
if err != nil {
klog.Error(err)
return err
}
cm := &corev1.ConfigMap{TypeMeta: metav1.TypeMeta{Kind: configMapKind, APIVersion: configMapAPIVersion},
ObjectMeta: metav1.ObjectMeta{Name: configName, Annotations: map[string]string{constants.UsernameAnnotationKey: user.Name}},
Data: map[string]string{kubeconfigFileName: string(kubeconfig)}}
err = controllerutil.SetControllerReference(user, cm, scheme.Scheme)
if err != nil {
klog.Errorln(err)
return err
}
_, err = o.k8sclient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Create(cm)
if err != nil {
klog.Errorln(err)
return err
}
return nil
}
func (o *operator) GetKubeConfig(username string) (string, error) {
configName := fmt.Sprintf(kubeconfigNameFormat, username)
configMap, err := o.k8sclient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Get(configName, metav1.GetOptions{})
if err != nil {
klog.Errorln(err)
return "", err
}
data := []byte(configMap.Data[kubeconfigFileName])
kubeconfig, err := clientcmd.Load(data)
if err != nil {
klog.Errorln(err)
return "", err
}
masterURL := o.masterURL
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 (o *operator) DelKubeConfig(username string) error {
configName := fmt.Sprintf(kubeconfigNameFormat, username)
deletePolicy := metav1.DeletePropagationBackground
err := o.k8sclient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Delete(configName, &metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
if err != nil {
klog.Errorln(err)
return err
}
return nil
}
func (o *operator) createCSR(username string) ([]byte, error) {
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
err = pem.Encode(&keyBuffer, &pem.Block{Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(x509key)})
if err != nil {
klog.Errorln(err)
return nil, err
}
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, x509csr, x509key)
if err != nil {
klog.Errorln(err)
return nil, err
}
err = pem.Encode(&csrBuffer, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
if err != nil {
klog.Errorln(err)
return nil, err
}
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,
Annotations: map[string]string{constants.UsernameAnnotationKey: username},
},
Spec: certificatesv1beta1.CertificateSigningRequestSpec{
Request: csr,
Usages: []certificatesv1beta1.KeyUsage{certificatesv1beta1.UsageServerAuth, certificatesv1beta1.UsageKeyEncipherment, certificatesv1beta1.UsageClientAuth, certificatesv1beta1.UsageDigitalSignature},
Username: username,
Groups: []string{user.AllAuthenticated},
},
}
// create csr
k8sCSR, err = o.k8sclient.CertificatesV1beta1().CertificateSigningRequests().Create(k8sCSR)
if err != nil {
klog.Errorln(err)
return nil, err
}
return key, nil
}
func (o *operator) UpdateKubeconfig(username string, certificate []byte) error {
configName := fmt.Sprintf(kubeconfigNameFormat, username)
configMap, err := o.k8sclient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Get(configName, metav1.GetOptions{})
if err != nil {
klog.Errorln(err)
return err
}
configMap = appendCert(configMap, certificate)
_, err = o.k8sclient.CoreV1().ConfigMaps(constants.KubeSphereControlNamespace).Update(configMap)
if err != nil {
klog.Errorln(err)
return err
}
return nil
}
func appendCert(cm *corev1.ConfigMap, cert []byte) *corev1.ConfigMap {
data := []byte(cm.Data[kubeconfigFileName])
kubeconfig, err := clientcmd.Load(data)
// ignore if invalid format
if err != nil {
klog.Warning(err)
return cm
}
username := getControlledUsername(cm)
if kubeconfig.AuthInfos[username] != nil {
kubeconfig.AuthInfos[username].ClientCertificateData = cert
}
data, err = clientcmd.Write(*kubeconfig)
// ignore if invalid format
if err != nil {
klog.Warning(err)
return cm
}
cm.Data[kubeconfigFileName] = string(data)
return cm
}
func getControlledUsername(cm *corev1.ConfigMap) string {
for _, ownerReference := range cm.OwnerReferences {
if ownerReference.Kind == iamv1alpha2.ResourceKindUser {
return ownerReference.Name
}
}
return ""
}