mirror of
https://github.com/rancher/k3k.git
synced 2026-08-19 12:26:17 +00:00
* Adding new k3s function to get the config * Adding a new way to get the bootstrap data directly from the server Pod * Gate the bootstrap fetch if external datastore is in use --------- Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>
188 lines
4.5 KiB
Go
188 lines
4.5 KiB
Go
package k3s
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/client-go/tools/remotecommand"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
"github.com/rancher/k3k/pkg/controller"
|
|
)
|
|
|
|
const (
|
|
TLSDir = "/var/lib/rancher/k3s/server/tls/"
|
|
)
|
|
|
|
type BootstrapData struct {
|
|
ServerCA cert `json:"serverCA"`
|
|
ServerCAKey cert `json:"serverCAKey"`
|
|
ClientCA cert `json:"clientCA"`
|
|
ClientCAKey cert `json:"clientCAKey"`
|
|
ETCDServerCA cert `json:"etcdServerCA"`
|
|
ETCDServerCAKey cert `json:"etcdServerCAKey"`
|
|
}
|
|
|
|
type cert struct {
|
|
Timestamp string
|
|
Content string
|
|
}
|
|
|
|
type K3SConfig struct {
|
|
ClusterInit bool `json:"ClusterInit"`
|
|
}
|
|
|
|
func (c *Client) GetServerConfig() (*K3SConfig, error) {
|
|
endpoint := "/v1-k3s/config"
|
|
return do[*K3SConfig](c, endpoint, "node", http.MethodGet)
|
|
}
|
|
|
|
func (c *Client) GetServerBootstrap() (*BootstrapData, error) {
|
|
endpoint := "/v1-k3s/server-bootstrap"
|
|
|
|
bootstrap, err := do[*BootstrapData](c, endpoint, "server", http.MethodGet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// we still need to decode each certs since the bootstrap data endpoint base64 encode each cert
|
|
if err := decode(bootstrap); err != nil {
|
|
return nil, fmt.Errorf("failed to decode bootstrap secret: %w", err)
|
|
}
|
|
|
|
return bootstrap, nil
|
|
}
|
|
|
|
func decode(data *BootstrapData) error {
|
|
// client-ca
|
|
decoded, err := base64.StdEncoding.DecodeString(data.ClientCA.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.ClientCA.Content = string(decoded)
|
|
|
|
// client-ca-key
|
|
decoded, err = base64.StdEncoding.DecodeString(data.ClientCAKey.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.ClientCAKey.Content = string(decoded)
|
|
|
|
// server-ca
|
|
decoded, err = base64.StdEncoding.DecodeString(data.ServerCA.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.ServerCA.Content = string(decoded)
|
|
|
|
// server-ca-key
|
|
decoded, err = base64.StdEncoding.DecodeString(data.ServerCAKey.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.ServerCAKey.Content = string(decoded)
|
|
|
|
// etcd-ca
|
|
decoded, err = base64.StdEncoding.DecodeString(data.ETCDServerCA.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.ETCDServerCA.Content = string(decoded)
|
|
|
|
// etcd-ca-key
|
|
decoded, err = base64.StdEncoding.DecodeString(data.ETCDServerCAKey.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.ETCDServerCAKey.Content = string(decoded)
|
|
|
|
return nil
|
|
}
|
|
|
|
func ReadBootstrapFromK3sPod(ctx context.Context, restConfig *rest.Config, clusterName, clusterNamespace string) (*BootstrapData, error) {
|
|
clientset, err := kubernetes.NewForConfig(restConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// using the first server in the statefulset to get the bootstrap data
|
|
serverPodName := controller.SafeConcatNameWithPrefix(clusterName, "server-0")
|
|
|
|
// skipping etcd since reading from pod only required with external datastore
|
|
bootstrapCerts := map[string]string{
|
|
"server-ca.crt": "",
|
|
"server-ca.key": "",
|
|
"client-ca.crt": "",
|
|
"client-ca.key": "",
|
|
}
|
|
for certName := range bootstrapCerts {
|
|
command := []string{"cat", filepath.Join(TLSDir, certName)}
|
|
|
|
certData, err := podExec(ctx, clientset, restConfig, clusterNamespace, serverPodName, command)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bootstrapCerts[certName] = string(certData)
|
|
}
|
|
|
|
bootstrap := &BootstrapData{
|
|
ServerCA: cert{Content: bootstrapCerts["server-ca.crt"]},
|
|
ServerCAKey: cert{Content: bootstrapCerts["server-ca.key"]},
|
|
ClientCA: cert{Content: bootstrapCerts["client-ca.crt"]},
|
|
ClientCAKey: cert{Content: bootstrapCerts["client-ca.key"]},
|
|
}
|
|
|
|
return bootstrap, nil
|
|
}
|
|
|
|
func podExec(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, namespace, name string, command []string) ([]byte, error) {
|
|
req := clientset.CoreV1().RESTClient().Post().
|
|
Resource("pods").
|
|
Name(name).
|
|
Namespace(namespace).
|
|
SubResource("exec")
|
|
|
|
scheme := runtime.NewScheme()
|
|
if err := corev1.AddToScheme(scheme); err != nil {
|
|
return nil, fmt.Errorf("error adding to scheme: %v", err)
|
|
}
|
|
|
|
parameterCodec := runtime.NewParameterCodec(scheme)
|
|
|
|
req.VersionedParams(&corev1.PodExecOptions{
|
|
Command: command,
|
|
Stdout: true,
|
|
}, parameterCodec)
|
|
|
|
exec, err := remotecommand.NewSPDYExecutor(config, http.MethodPost, req.URL())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error while creating Executor: %v", err)
|
|
}
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
|
|
Stdout: &stdout,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error in Stream: %v", err)
|
|
}
|
|
|
|
return stdout.Bytes(), nil
|
|
}
|