mirror of
https://github.com/rancher/k3k.git
synced 2026-08-23 22:36:17 +00:00
Add kubeconfig subcommand (#87)
* Add kubeconfig subcommand Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com> * Add kubeconfig subcommand Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com> * Add kubeconfig subcommand Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com> * Add kubeconfig subcommand Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com> --------- Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var clusterSubcommands = []cli.Command{
|
||||
var subcommands = []cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "Create new cluster",
|
||||
@@ -16,10 +16,10 @@ var clusterSubcommands = []cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func NewClusterCommand() cli.Command {
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "cluster",
|
||||
Usage: "cluster command",
|
||||
Subcommands: clusterSubcommands,
|
||||
Subcommands: subcommands,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package cluster
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -14,19 +13,17 @@ import (
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/server"
|
||||
"github.com/rancher/k3k/pkg/controller/kubeconfig"
|
||||
"github.com/rancher/k3k/pkg/controller/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
@@ -174,11 +171,16 @@ func createCluster(clx *cli.Context) error {
|
||||
}
|
||||
|
||||
logrus.Infof("Extracting Kubeconfig for [%s] cluster", name)
|
||||
cfg := &kubeconfig.KubeConfig{
|
||||
CN: util.AdminCommonName,
|
||||
ORG: []string{user.SystemPrivilegedGroup},
|
||||
ExpiryDate: 0,
|
||||
}
|
||||
logrus.Infof("waiting for cluster to be available..")
|
||||
var kubeconfig []byte
|
||||
if err := retry.OnError(backoff, apierrors.IsNotFound, func() error {
|
||||
kubeconfig, err = extractKubeconfig(ctx, ctrlClient, cluster, host[0])
|
||||
kubeconfig, err = cfg.Extract(ctx, ctrlClient, cluster, host[0])
|
||||
if err != nil {
|
||||
logrus.Infof("waiting for cluster to be available: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -250,83 +252,3 @@ func newCluster(name, token string, servers, agents int32, clusterCIDR, serviceC
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func extractKubeconfig(ctx context.Context, client client.Client, cluster *v1alpha1.Cluster, serverIP string) ([]byte, error) {
|
||||
nn := types.NamespacedName{
|
||||
Name: cluster.Name + "-kubeconfig",
|
||||
Namespace: util.ClusterNamespace(cluster),
|
||||
}
|
||||
|
||||
var kubeSecret v1.Secret
|
||||
if err := client.Get(ctx, nn, &kubeSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kubeconfig := kubeSecret.Data["kubeconfig.yaml"]
|
||||
if kubeconfig == nil {
|
||||
return nil, errors.New("empty kubeconfig")
|
||||
}
|
||||
|
||||
nn = types.NamespacedName{
|
||||
Name: "k3k-server-service",
|
||||
Namespace: util.ClusterNamespace(cluster),
|
||||
}
|
||||
|
||||
var k3kService v1.Service
|
||||
if err := client.Get(ctx, nn, &k3kService); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if k3kService.Spec.Type == v1.ServiceTypeNodePort {
|
||||
nodePort := k3kService.Spec.Ports[0].NodePort
|
||||
|
||||
restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hostURL := fmt.Sprintf("https://%s:%d", serverIP, nodePort)
|
||||
restConfig.Host = hostURL
|
||||
|
||||
clientConfig := generateKubeconfigFromRest(restConfig)
|
||||
|
||||
b, err := clientcmd.Write(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kubeconfig = b
|
||||
}
|
||||
|
||||
return kubeconfig, nil
|
||||
}
|
||||
|
||||
func generateKubeconfigFromRest(config *rest.Config) clientcmdapi.Config {
|
||||
clusters := make(map[string]*clientcmdapi.Cluster)
|
||||
clusters["default-cluster"] = &clientcmdapi.Cluster{
|
||||
Server: config.Host,
|
||||
CertificateAuthorityData: config.CAData,
|
||||
}
|
||||
|
||||
contexts := make(map[string]*clientcmdapi.Context)
|
||||
contexts["default-context"] = &clientcmdapi.Context{
|
||||
Cluster: "default-cluster",
|
||||
Namespace: "default",
|
||||
AuthInfo: "default",
|
||||
}
|
||||
|
||||
authinfos := make(map[string]*clientcmdapi.AuthInfo)
|
||||
authinfos["default"] = &clientcmdapi.AuthInfo{
|
||||
ClientCertificateData: config.CertData,
|
||||
ClientKeyData: config.KeyData,
|
||||
}
|
||||
|
||||
clientConfig := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: clusters,
|
||||
Contexts: contexts,
|
||||
CurrentContext: "default-context",
|
||||
AuthInfos: authinfos,
|
||||
}
|
||||
|
||||
return clientConfig
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/k3k/cli/cmds"
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1"
|
||||
"github.com/rancher/k3k/pkg/controller/kubeconfig"
|
||||
"github.com/rancher/k3k/pkg/controller/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func init() {
|
||||
_ = clientgoscheme.AddToScheme(Scheme)
|
||||
_ = v1alpha1.AddToScheme(Scheme)
|
||||
}
|
||||
|
||||
var (
|
||||
Scheme = runtime.NewScheme()
|
||||
name string
|
||||
cn string
|
||||
org cli.StringSlice
|
||||
altNames cli.StringSlice
|
||||
expirationDays int64
|
||||
configName string
|
||||
backoff = wait.Backoff{
|
||||
Steps: 5,
|
||||
Duration: 20 * time.Second,
|
||||
Factor: 2,
|
||||
Jitter: 0.1,
|
||||
}
|
||||
generateKubeconfigFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "cluster name",
|
||||
Destination: &name,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "config-name",
|
||||
Usage: "the name of the generated kubeconfig file",
|
||||
Destination: &configName,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cn",
|
||||
Usage: "Common name (CN) of the generated certificates for the kubeconfig",
|
||||
Destination: &cn,
|
||||
Value: util.AdminCommonName,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "org",
|
||||
Usage: "Organization name (ORG) of the generated certificates for the kubeconfig",
|
||||
Value: &org,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "altNames",
|
||||
Usage: "altNames of the generated certificates for the kubeconfig",
|
||||
Value: &altNames,
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "expiration-days",
|
||||
Usage: "Expiration date of the certificates used for the kubeconfig",
|
||||
Destination: &expirationDays,
|
||||
Value: 356,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var subcommands = []cli.Command{
|
||||
{
|
||||
Name: "generate",
|
||||
Usage: "Generate kubeconfig for clusters",
|
||||
SkipFlagParsing: false,
|
||||
SkipArgReorder: true,
|
||||
Action: generate,
|
||||
Flags: append(cmds.CommonFlags, generateKubeconfigFlags...),
|
||||
},
|
||||
}
|
||||
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "kubeconfig",
|
||||
Usage: "Manage kubeconfig for clusters",
|
||||
Subcommands: subcommands,
|
||||
}
|
||||
}
|
||||
|
||||
func generate(clx *cli.Context) error {
|
||||
var cluster v1alpha1.Cluster
|
||||
ctx := context.Background()
|
||||
|
||||
restConfig, err := clientcmd.BuildConfigFromFlags("", cmds.Kubeconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrlClient, err := client.New(restConfig, client.Options{
|
||||
Scheme: Scheme,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterKey := types.NamespacedName{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
if err := ctrlClient.Get(ctx, clusterKey, &cluster); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url, err := url.Parse(restConfig.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host := strings.Split(url.Host, ":")
|
||||
|
||||
certAltNames := kubeconfig.AddSANs(altNames)
|
||||
if org == nil {
|
||||
org = cli.StringSlice{user.SystemPrivilegedGroup}
|
||||
}
|
||||
cfg := kubeconfig.KubeConfig{
|
||||
CN: cn,
|
||||
ORG: org,
|
||||
ExpiryDate: time.Hour * 24 * time.Duration(expirationDays),
|
||||
AltNames: certAltNames,
|
||||
}
|
||||
logrus.Infof("waiting for cluster to be available..")
|
||||
var kubeconfig []byte
|
||||
if err := retry.OnError(backoff, apierrors.IsNotFound, func() error {
|
||||
kubeconfig, err = cfg.Extract(ctx, ctrlClient, &cluster, host[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if configName == "" {
|
||||
configName = cluster.Name + "-kubeconfig.yaml"
|
||||
}
|
||||
|
||||
logrus.Infof(`You can start using the cluster with:
|
||||
|
||||
export KUBECONFIG=%s
|
||||
kubectl cluster-info
|
||||
`, filepath.Join(pwd, configName))
|
||||
|
||||
return os.WriteFile(configName, kubeconfig, 0644)
|
||||
}
|
||||
+3
-1
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/rancher/k3k/cli/cmds"
|
||||
"github.com/rancher/k3k/cli/cmds/cluster"
|
||||
"github.com/rancher/k3k/cli/cmds/kubeconfig"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@@ -18,7 +19,8 @@ const (
|
||||
func main() {
|
||||
app := cmds.NewApp()
|
||||
app.Commands = []cli.Command{
|
||||
cluster.NewClusterCommand(),
|
||||
cluster.NewCommand(),
|
||||
kubeconfig.NewCommand(),
|
||||
}
|
||||
app.Version = version + " (" + gitCommit + ")"
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/agent"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/config"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/server"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/server/bootstrap"
|
||||
"github.com/rancher/k3k/pkg/controller/kubeconfig"
|
||||
"github.com/rancher/k3k/pkg/controller/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
|
||||
@@ -244,12 +246,12 @@ func (c *ClusterReconciler) createCluster(ctx context.Context, cluster *v1alpha1
|
||||
}
|
||||
}
|
||||
|
||||
kubeconfigSecret, err := s.GenerateNewKubeConfig(ctx, serviceIP)
|
||||
bootstrapSecret, err := bootstrap.Generate(ctx, cluster, serviceIP)
|
||||
if err != nil {
|
||||
return util.LogAndReturnErr("failed to generate new kubeconfig", err)
|
||||
}
|
||||
|
||||
if err := c.Client.Create(ctx, kubeconfigSecret); err != nil {
|
||||
if err := c.Client.Create(ctx, bootstrapSecret); err != nil {
|
||||
if !apierrors.IsAlreadyExists(err) {
|
||||
return util.LogAndReturnErr("failed to create kubeconfig secret", err)
|
||||
}
|
||||
@@ -540,18 +542,18 @@ func (c *ClusterReconciler) getETCDTLS(cluster *v1alpha1.Cluster) (*tls.Config,
|
||||
klog.Infof("generating etcd TLS client certificate for cluster [%s]", cluster.Name)
|
||||
token := cluster.Spec.Token
|
||||
endpoint := "k3k-server-service." + util.ClusterNamespace(cluster)
|
||||
var bootstrap *server.ControlRuntimeBootstrap
|
||||
var b *bootstrap.ControlRuntimeBootstrap
|
||||
if err := retry.OnError(retry.DefaultBackoff, func(err error) bool {
|
||||
return true
|
||||
}, func() error {
|
||||
var err error
|
||||
bootstrap, err = server.DecodedBootstrap(token, endpoint)
|
||||
b, err = bootstrap.DecodedBootstrap(token, endpoint)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
etcdCert, etcdKey, err := server.CreateClientCertKey("etcd-client", nil, nil, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, bootstrap.ETCDServerCA.Content, bootstrap.ETCDServerCAKey.Content)
|
||||
etcdCert, etcdKey, err := kubeconfig.CreateClientCertKey("etcd-client", nil, nil, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, 0, b.ETCDServerCA.Content, b.ETCDServerCAKey.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -560,7 +562,7 @@ func (c *ClusterReconciler) getETCDTLS(cluster *v1alpha1.Cluster) (*tls.Config,
|
||||
return nil, err
|
||||
}
|
||||
// create rootCA CertPool
|
||||
cert, err := certutil.ParseCertsPEM([]byte(bootstrap.ETCDServerCA.Content))
|
||||
cert, err := certutil.ParseCertsPEM([]byte(b.ETCDServerCA.Content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+12
-111
@@ -1,34 +1,22 @@
|
||||
package server
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
certutil "github.com/rancher/dynamiclistener/cert"
|
||||
"github.com/rancher/k3k/pkg/controller/util"
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
const (
|
||||
adminCommonName = "system:admin"
|
||||
port = 6443
|
||||
)
|
||||
|
||||
type ControlRuntimeBootstrap struct {
|
||||
ServerCA content
|
||||
ServerCAKey content
|
||||
ServerCA content `json:"serverCA"`
|
||||
ServerCAKey content `json:"server"`
|
||||
ClientCA content
|
||||
ClientCAKey content
|
||||
ETCDServerCA content
|
||||
@@ -40,13 +28,11 @@ type content struct {
|
||||
Content string
|
||||
}
|
||||
|
||||
// GenerateNewKubeConfig generates the kubeconfig for the cluster:
|
||||
// Generate generates the bootstrap for the cluster:
|
||||
// 1- use the server token to get the bootstrap data from k3s
|
||||
// 2- generate client admin cert/key
|
||||
// 3- use the ca cert from the bootstrap data & admin cert/key to write a new kubeconfig
|
||||
// 4- save the new kubeconfig as a secret
|
||||
func (s *Server) GenerateNewKubeConfig(ctx context.Context, ip string) (*v1.Secret, error) {
|
||||
token := s.cluster.Spec.Token
|
||||
// 2- save the bootstrap data as a secret
|
||||
func Generate(ctx context.Context, cluster *v1alpha1.Cluster, ip string) (*v1.Secret, error) {
|
||||
token := cluster.Spec.Token
|
||||
|
||||
var bootstrap *ControlRuntimeBootstrap
|
||||
if err := retry.OnError(retry.DefaultBackoff, func(err error) bool {
|
||||
@@ -63,32 +49,21 @@ func (s *Server) GenerateNewKubeConfig(ctx context.Context, ip string) (*v1.Secr
|
||||
return nil, err
|
||||
}
|
||||
|
||||
adminCert, adminKey, err := CreateClientCertKey(
|
||||
adminCommonName, []string{user.SystemPrivilegedGroup},
|
||||
nil, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
bootstrap.ClientCA.Content,
|
||||
bootstrap.ClientCAKey.Content)
|
||||
bootstrapData, err := json.Marshal(bootstrap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://%s:%d", ip, port)
|
||||
kubeconfigData, err := kubeconfig(url, []byte(bootstrap.ServerCA.Content), adminCert, adminKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: s.cluster.Name + "-kubeconfig",
|
||||
Namespace: util.ClusterNamespace(s.cluster),
|
||||
Name: cluster.Name + "-bootstrap",
|
||||
Namespace: "k3k-" + cluster.Name,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"kubeconfig.yaml": kubeconfigData,
|
||||
"bootstrap": bootstrapData,
|
||||
},
|
||||
}, nil
|
||||
|
||||
@@ -126,80 +101,6 @@ func requestBootstrap(token, serverIP string) (*ControlRuntimeBootstrap, error)
|
||||
return &runtimeBootstrap, nil
|
||||
}
|
||||
|
||||
func CreateClientCertKey(commonName string, organization []string, altNames *certutil.AltNames, extKeyUsage []x509.ExtKeyUsage, caCert, caKey string) ([]byte, []byte, error) {
|
||||
caKeyPEM, err := certutil.ParsePrivateKeyPEM([]byte(caKey))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCertPEM, err := certutil.ParseCertsPEM([]byte(caCert))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
b, err := generateKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key, err := certutil.ParsePrivateKeyPEM(b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cfg := certutil.Config{
|
||||
CommonName: commonName,
|
||||
Organization: organization,
|
||||
Usages: extKeyUsage,
|
||||
}
|
||||
if altNames != nil {
|
||||
cfg.AltNames = *altNames
|
||||
}
|
||||
cert, err := certutil.NewSignedCert(cfg, key.(crypto.Signer), caCertPEM[0], caKeyPEM.(crypto.Signer))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCertPEM[0])...), b, nil
|
||||
}
|
||||
|
||||
func generateKey() (data []byte, err error) {
|
||||
generatedData, err := certutil.MakeEllipticPrivateKeyPEM()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating key: %v", err)
|
||||
}
|
||||
|
||||
return generatedData, nil
|
||||
}
|
||||
|
||||
func kubeconfig(url string, serverCA, clientCert, clientKey []byte) ([]byte, error) {
|
||||
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"
|
||||
|
||||
kubeconfig, err := clientcmd.Write(*config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kubeconfig, nil
|
||||
}
|
||||
|
||||
func basicAuth(username, password string) string {
|
||||
auth := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
@@ -15,6 +15,8 @@ const (
|
||||
nginxSSLPassthroughAnnotation = "nginx.ingress.kubernetes.io/ssl-passthrough"
|
||||
nginxBackendProtocolAnnotation = "nginx.ingress.kubernetes.io/backend-protocol"
|
||||
nginxSSLRedirectAnnotation = "nginx.ingress.kubernetes.io/ssl-redirect"
|
||||
serverPort = 6443
|
||||
etcdPort = 2379
|
||||
)
|
||||
|
||||
func (s *Server) Ingress(ctx context.Context, client client.Client) (*networkingv1.Ingress, error) {
|
||||
@@ -59,7 +61,7 @@ func (s *Server) ingressRules(addresses []string) []networkingv1.IngressRule {
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
Name: "k3k-server-service",
|
||||
Port: networkingv1.ServiceBackendPort{
|
||||
Number: port,
|
||||
Number: serverPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -36,12 +36,12 @@ func (s *Server) Service(cluster *v1alpha1.Cluster) *v1.Service {
|
||||
{
|
||||
Name: "k3s-server-port",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: port,
|
||||
Port: serverPort,
|
||||
},
|
||||
{
|
||||
Name: "k3s-etcd-port",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: 2379,
|
||||
Port: etcdPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -70,12 +70,12 @@ func (s *Server) StatefulServerService(cluster *v1alpha1.Cluster) *v1.Service {
|
||||
{
|
||||
Name: "k3s-server-port",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: 6443,
|
||||
Port: serverPort,
|
||||
},
|
||||
{
|
||||
Name: "k3s-etcd-port",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: 2379,
|
||||
Port: etcdPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
certutil "github.com/rancher/dynamiclistener/cert"
|
||||
)
|
||||
|
||||
func CreateClientCertKey(commonName string, organization []string, altNames *certutil.AltNames, extKeyUsage []x509.ExtKeyUsage, expiresAt time.Duration, caCert, caKey string) ([]byte, []byte, error) {
|
||||
caKeyPEM, err := certutil.ParsePrivateKeyPEM([]byte(caKey))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCertPEM, err := certutil.ParseCertsPEM([]byte(caCert))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
b, err := generateKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key, err := certutil.ParsePrivateKeyPEM(b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cfg := certutil.Config{
|
||||
CommonName: commonName,
|
||||
Organization: organization,
|
||||
Usages: extKeyUsage,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
if altNames != nil {
|
||||
cfg.AltNames = *altNames
|
||||
}
|
||||
cert, err := certutil.NewSignedCert(cfg, key.(crypto.Signer), caCertPEM[0], caKeyPEM.(crypto.Signer))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCertPEM[0])...), b, nil
|
||||
}
|
||||
|
||||
func generateKey() (data []byte, err error) {
|
||||
generatedData, err := certutil.MakeEllipticPrivateKeyPEM()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating key: %v", err)
|
||||
}
|
||||
|
||||
return generatedData, nil
|
||||
}
|
||||
|
||||
func AddSANs(sans []string) certutil.AltNames {
|
||||
var altNames certutil.AltNames
|
||||
for _, san := range sans {
|
||||
ip := net.ParseIP(san)
|
||||
if ip == nil {
|
||||
altNames.DNSNames = append(altNames.DNSNames, san)
|
||||
} else {
|
||||
altNames.IPs = append(altNames.IPs, ip)
|
||||
}
|
||||
}
|
||||
return altNames
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
certutil "github.com/rancher/dynamiclistener/cert"
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/server/bootstrap"
|
||||
"github.com/rancher/k3k/pkg/controller/util"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type KubeConfig struct {
|
||||
AltNames certutil.AltNames
|
||||
CN string
|
||||
ORG []string
|
||||
ExpiryDate time.Duration
|
||||
}
|
||||
|
||||
func (k *KubeConfig) Extract(ctx context.Context, client client.Client, cluster *v1alpha1.Cluster, hostServerIP string) ([]byte, error) {
|
||||
nn := types.NamespacedName{
|
||||
Name: cluster.Name + "-bootstrap",
|
||||
Namespace: util.ClusterNamespace(cluster),
|
||||
}
|
||||
|
||||
var bootstrapSecret v1.Secret
|
||||
if err := client.Get(ctx, nn, &bootstrapSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bootstrapData := bootstrapSecret.Data["bootstrap"]
|
||||
if bootstrapData == nil {
|
||||
return nil, errors.New("empty bootstrap")
|
||||
}
|
||||
|
||||
var bootstrap bootstrap.ControlRuntimeBootstrap
|
||||
if err := json.Unmarshal(bootstrapData, &bootstrap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
adminCert, adminKey, err := CreateClientCertKey(
|
||||
k.CN, k.ORG,
|
||||
&k.AltNames, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, k.ExpiryDate,
|
||||
bootstrap.ClientCA.Content,
|
||||
bootstrap.ClientCAKey.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// get the server service to extract the right IP
|
||||
nn = types.NamespacedName{
|
||||
Name: "k3k-server-service",
|
||||
Namespace: util.ClusterNamespace(cluster),
|
||||
}
|
||||
|
||||
var k3kService v1.Service
|
||||
if err := client.Get(ctx, nn, &k3kService); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://%s:%d", k3kService.Spec.ClusterIP, util.ServerPort)
|
||||
if k3kService.Spec.Type == v1.ServiceTypeNodePort {
|
||||
nodePort := k3kService.Spec.Ports[0].NodePort
|
||||
url = fmt.Sprintf("https://%s:%d", hostServerIP, nodePort)
|
||||
}
|
||||
kubeconfigData, err := kubeconfig(url, []byte(bootstrap.ServerCA.Content), adminCert, adminKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kubeconfigData, nil
|
||||
}
|
||||
|
||||
func kubeconfig(url string, serverCA, clientCert, clientKey []byte) ([]byte, error) {
|
||||
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"
|
||||
|
||||
kubeconfig, err := clientcmd.Write(*config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kubeconfig, nil
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
const (
|
||||
namespacePrefix = "k3k-"
|
||||
k3SImageName = "rancher/k3s"
|
||||
AdminCommonName = "system:admin"
|
||||
ServerPort = 6443
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user