package kubeconfig import ( "context" "crypto/x509" "fmt" "slices" "time" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/authentication/user" "sigs.k8s.io/controller-runtime/pkg/client" certutil "github.com/rancher/dynamiclistener/cert" v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1" "github.com/rancher/k3k/pkg/controller" "github.com/rancher/k3k/pkg/controller/certs" "github.com/rancher/k3k/pkg/controller/cluster/server" "github.com/rancher/k3k/pkg/controller/cluster/server/bootstrap" ) type KubeConfig struct { AltNames certutil.AltNames CN string ORG []string ExpiryDate time.Duration } func New() *KubeConfig { return &KubeConfig{ CN: controller.AdminCommonName, ORG: []string{user.SystemPrivilegedGroup}, ExpiryDate: 0, } } func (k *KubeConfig) Generate(ctx context.Context, client client.Client, cluster *v1beta1.Cluster, hostServerIP string, port int) (*clientcmdapi.Config, error) { bootstrapData, err := bootstrap.GetFromSecret(ctx, client, cluster) if err != nil { return nil, err } serverCACert := []byte(bootstrapData.ServerCA.Content) adminCert, adminKey, err := certs.CreateClientCertKey( k.CN, k.ORG, &k.AltNames, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, k.ExpiryDate, bootstrapData.ClientCA.Content, bootstrapData.ClientCAKey.Content, ) if err != nil { return nil, err } url, err := getURLFromService(ctx, client, cluster, hostServerIP, port) if err != nil { return nil, err } config := NewConfig(url, serverCACert, adminCert, adminKey) return config, nil } func NewConfig(url string, serverCA, clientCert, clientKey []byte) *clientcmdapi.Config { config := clientcmdapi.NewConfig() cluster := clientcmdapi.NewCluster() cluster.CertificateAuthorityData = serverCA cluster.Server = url authInfo := clientcmdapi.NewAuthInfo() authInfo.ClientCertificateData = clientCert authInfo.ClientKeyData = clientKey context := clientcmdapi.NewContext() context.AuthInfo = "default" context.Cluster = "default" config.Clusters["default"] = cluster config.AuthInfos["default"] = authInfo config.Contexts["default"] = context config.CurrentContext = "default" return config } func getURLFromService(ctx context.Context, client client.Client, cluster *v1beta1.Cluster, hostServerIP string, serverPort int) (string, error) { // get the server service to extract the right IP key := types.NamespacedName{ Name: server.ServiceName(cluster.Name), Namespace: cluster.Namespace, } var k3kService v1.Service if err := client.Get(ctx, key, &k3kService); err != nil { return "", err } ip := k3kService.Spec.ClusterIP port := int32(443) if len(k3kService.Spec.Ports) == 0 { logrus.Warn("No ports exposed by the cluster service.") } switch k3kService.Spec.Type { case v1.ServiceTypeNodePort: ip = hostServerIP if len(k3kService.Spec.Ports) > 0 { port = k3kService.Spec.Ports[0].NodePort } case v1.ServiceTypeLoadBalancer: if len(k3kService.Status.LoadBalancer.Ingress) > 0 { ip = k3kService.Status.LoadBalancer.Ingress[0].IP } else { logrus.Warn("No ingress found in LoadBalancer service.") } if len(k3kService.Spec.Ports) > 0 { port = k3kService.Spec.Ports[0].Port } } if serverPort != 0 { port = int32(serverPort) } if !slices.Contains(cluster.Status.TLSSANs, ip) { logrus.Warnf("IP %s not in tlsSANs.", ip) if len(cluster.Spec.TLSSANs) > 0 { logrus.Warnf("Using the first TLS SAN in the spec as a fallback: %s", cluster.Spec.TLSSANs[0]) ip = cluster.Spec.TLSSANs[0] } else if len(cluster.Status.TLSSANs) > 0 { logrus.Warnf("No explicit tlsSANs specified. Trying to use the first TLS SAN in the status: %s", cluster.Status.TLSSANs[0]) ip = cluster.Status.TLSSANs[0] } else { logrus.Warn("IP not found in tlsSANs. This could cause issue with the certificate validation.") } } url := "https://" + ip if port != 443 { url = fmt.Sprintf("%s:%d", url, port) } // if ingress is specified, use the ingress host if cluster.Spec.Expose != nil && cluster.Spec.Expose.Ingress != nil { var k3kIngress networkingv1.Ingress ingressKey := types.NamespacedName{ Name: server.IngressName(cluster.Name), Namespace: cluster.Namespace, } if err := client.Get(ctx, ingressKey, &k3kIngress); err != nil { return "", err } url = fmt.Sprintf("https://%s", k3kIngress.Spec.Rules[0].Host) } return url, nil }