mirror of https://github.com/rancher/k3k.git
187 lines
4.3 KiB
Go
187 lines
4.3 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"syscall"
|
|
"time"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
|
|
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
|
|
"github.com/rancher/k3k/pkg/controller"
|
|
)
|
|
|
|
var ErrServerNotReady = errors.New("server not ready")
|
|
|
|
type ControlRuntimeBootstrap struct {
|
|
ServerCA content `json:"serverCA"`
|
|
ServerCAKey content `json:"serverCAKey"`
|
|
ClientCA content `json:"clientCA"`
|
|
ClientCAKey content `json:"clientCAKey"`
|
|
ETCDServerCA content `json:"etcdServerCA"`
|
|
ETCDServerCAKey content `json:"etcdServerCAKey"`
|
|
}
|
|
|
|
type content struct {
|
|
Timestamp string
|
|
Content string
|
|
}
|
|
|
|
// Generate generates the bootstrap for the cluster:
|
|
// 1- use the server token to get the bootstrap data from k3s
|
|
// 2- save the bootstrap data as a secret
|
|
func GenerateBootstrapData(ctx context.Context, cluster *v1beta1.Cluster, ip, token string) ([]byte, error) {
|
|
bootstrap, err := requestBootstrap(token, ip)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to request bootstrap secret: %w", err)
|
|
}
|
|
|
|
if err := decodeBootstrap(bootstrap); err != nil {
|
|
return nil, fmt.Errorf("failed to decode bootstrap secret: %w", err)
|
|
}
|
|
|
|
return json.Marshal(bootstrap)
|
|
}
|
|
|
|
func requestBootstrap(token, serverIP string) (*ControlRuntimeBootstrap, error) {
|
|
url := "https://" + serverIP + "/v1-k3s/server-bootstrap"
|
|
|
|
client := http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
},
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Add("Authorization", "Basic "+basicAuth("server", token))
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
if errors.Is(err, syscall.ECONNREFUSED) {
|
|
return nil, ErrServerNotReady
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
|
|
var runtimeBootstrap ControlRuntimeBootstrap
|
|
if err := json.NewDecoder(resp.Body).Decode(&runtimeBootstrap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &runtimeBootstrap, nil
|
|
}
|
|
|
|
func basicAuth(username, password string) string {
|
|
auth := username + ":" + password
|
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
|
}
|
|
|
|
func decodeBootstrap(bootstrap *ControlRuntimeBootstrap) error {
|
|
// client-ca
|
|
decoded, err := base64.StdEncoding.DecodeString(bootstrap.ClientCA.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bootstrap.ClientCA.Content = string(decoded)
|
|
|
|
// client-ca-key
|
|
decoded, err = base64.StdEncoding.DecodeString(bootstrap.ClientCAKey.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bootstrap.ClientCAKey.Content = string(decoded)
|
|
|
|
// server-ca
|
|
decoded, err = base64.StdEncoding.DecodeString(bootstrap.ServerCA.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bootstrap.ServerCA.Content = string(decoded)
|
|
|
|
// server-ca-key
|
|
decoded, err = base64.StdEncoding.DecodeString(bootstrap.ServerCAKey.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bootstrap.ServerCAKey.Content = string(decoded)
|
|
|
|
// etcd-ca
|
|
decoded, err = base64.StdEncoding.DecodeString(bootstrap.ETCDServerCA.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bootstrap.ETCDServerCA.Content = string(decoded)
|
|
|
|
// etcd-ca-key
|
|
decoded, err = base64.StdEncoding.DecodeString(bootstrap.ETCDServerCAKey.Content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bootstrap.ETCDServerCAKey.Content = string(decoded)
|
|
|
|
return nil
|
|
}
|
|
|
|
func DecodedBootstrap(token, ip string) (*ControlRuntimeBootstrap, error) {
|
|
bootstrap, err := requestBootstrap(token, ip)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := decodeBootstrap(bootstrap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return bootstrap, nil
|
|
}
|
|
|
|
func GetFromSecret(ctx context.Context, client client.Client, cluster *v1beta1.Cluster) (*ControlRuntimeBootstrap, error) {
|
|
key := types.NamespacedName{
|
|
Name: controller.SafeConcatNameWithPrefix(cluster.Name, "bootstrap"),
|
|
Namespace: cluster.Namespace,
|
|
}
|
|
|
|
var bootstrapSecret v1.Secret
|
|
if err := client.Get(ctx, key, &bootstrapSecret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bootstrapData := bootstrapSecret.Data["bootstrap"]
|
|
if bootstrapData == nil {
|
|
return nil, errors.New("empty bootstrap")
|
|
}
|
|
|
|
var bootstrap ControlRuntimeBootstrap
|
|
|
|
err := json.Unmarshal(bootstrapData, &bootstrap)
|
|
|
|
return &bootstrap, err
|
|
}
|