From 84f921641b56ba5f4f55aaec7fa6842e1d4beeec Mon Sep 17 00:00:00 2001 From: Hussein Galal Date: Fri, 1 Nov 2024 21:27:03 +0200 Subject: [PATCH] Token random generation (#136) Signed-off-by: galal-hussein --- charts/k3k/crds/k3k.io_clusters.yaml | 35 ++++--- cli/cmds/cluster/create.go | 36 +++++-- k3k-kubelet/config.go | 3 + k3k-kubelet/kubelet.go | 16 +-- pkg/apis/k3k.io/v1alpha1/types.go | 10 +- pkg/controller/cluster/agent/agent.go | 6 +- pkg/controller/cluster/agent/shared.go | 11 ++- pkg/controller/cluster/agent/virtual.go | 6 +- pkg/controller/cluster/cluster.go | 15 ++- pkg/controller/cluster/pod.go | 27 ++++- .../cluster/server/bootstrap/bootstrap.go | 4 +- pkg/controller/cluster/server/config.go | 18 ++-- pkg/controller/cluster/server/server.go | 4 +- pkg/controller/cluster/token.go | 98 +++++++++++++++++++ 14 files changed, 225 insertions(+), 64 deletions(-) create mode 100644 pkg/controller/cluster/token.go diff --git a/charts/k3k/crds/k3k.io_clusters.yaml b/charts/k3k/crds/k3k.io_clusters.yaml index feecf19..e644638 100644 --- a/charts/k3k/crds/k3k.io_clusters.yaml +++ b/charts/k3k/crds/k3k.io_clusters.yaml @@ -129,12 +129,6 @@ spec: - enabled type: object type: object - nodeSelector: - additionalProperties: - type: string - description: NodeSelector is the node selector that will be applied - to all server/agent pods - type: object mode: description: Mode is the cluster provisioning mode which can be either "virtual" or "shared". Defaults to "shared" @@ -144,6 +138,12 @@ spec: rule: self == oldSelf - message: invalid value for mode rule: self == "virtual" || self == "shared" + nodeSelector: + additionalProperties: + type: string + description: NodeSelector is the node selector that will be applied + to all server/agent pods + type: object persistence: description: |- Persistence contains options controlling how the etcd data of the virtual cluster is persisted. By default, no data @@ -187,13 +187,21 @@ spec: items: type: string type: array - token: - description: Token is the token used to join the worker nodes to the - cluster. - type: string - x-kubernetes-validations: - - message: token is immutable - rule: self == oldSelf + tokenSecretRef: + description: |- + TokenSecretRef is Secret reference used as a token join server and worker nodes to the cluster. The controller + assumes that the secret has a field "token" in its data, any other fields in the secret will be ignored. + properties: + name: + description: name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: namespace defines the space within which the secret + name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic version: description: Version is a string representing the Kubernetes version to be used by the virtual nodes. @@ -202,7 +210,6 @@ spec: - agents - mode - servers - - token - version type: object status: diff --git a/cli/cmds/cluster/create.go b/cli/cmds/cluster/create.go index 6c95374..95a4df2 100644 --- a/cli/cmds/cluster/create.go +++ b/cli/cmds/cluster/create.go @@ -11,11 +11,12 @@ import ( "github.com/rancher/k3k/cli/cmds" "github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1" "github.com/rancher/k3k/pkg/controller" - "github.com/rancher/k3k/pkg/controller/cluster" + k3kcluster "github.com/rancher/k3k/pkg/controller/cluster" "github.com/rancher/k3k/pkg/controller/cluster/server" "github.com/rancher/k3k/pkg/controller/kubeconfig" "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" @@ -45,6 +46,7 @@ var ( persistenceType string storageClassName string version string + mode string clusterCreateFlags = []cli.Flag{ cli.StringFlag{ @@ -105,6 +107,12 @@ var ( Destination: &version, Value: "v1.26.1-k3s1", }, + cli.StringFlag{ + Name: "mode", + Usage: "k3k mode type", + Destination: &mode, + Value: "shared", + }, } ) @@ -126,10 +134,18 @@ func create(clx *cli.Context) error { if err != nil { return err } + if token != "" { + logrus.Infof("Creating cluster token secret") + obj := k3kcluster.TokenSecretObj(token, name, cmds.Namespace()) + if err := ctrlClient.Create(ctx, &obj); err != nil { + return err + } + } logrus.Infof("Creating a new cluster [%s]", name) cluster := newCluster( name, cmds.Namespace(), + mode, token, int32(servers), int32(agents), @@ -198,13 +214,10 @@ func validateCreateFlags() error { persistenceType != server.DynamicNodesType { return errors.New("invalid persistence type") } - if token == "" { - return errors.New("empty cluster token") - } if name == "" { return errors.New("empty cluster name") } - if name == cluster.ClusterInvalidName { + if name == k3kcluster.ClusterInvalidName { return errors.New("invalid cluster name") } if servers <= 0 { @@ -217,8 +230,8 @@ func validateCreateFlags() error { return nil } -func newCluster(name, namespace, token string, servers, agents int32, clusterCIDR, serviceCIDR string, serverArgs, agentArgs []string) *v1alpha1.Cluster { - return &v1alpha1.Cluster{ +func newCluster(name, namespace, mode, token string, servers, agents int32, clusterCIDR, serviceCIDR string, serverArgs, agentArgs []string) *v1alpha1.Cluster { + cluster := &v1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, @@ -228,7 +241,6 @@ func newCluster(name, namespace, token string, servers, agents int32, clusterCID APIVersion: "k3k.io/v1alpha1", }, Spec: v1alpha1.ClusterSpec{ - Token: token, Servers: &servers, Agents: &agents, ClusterCIDR: clusterCIDR, @@ -236,10 +248,18 @@ func newCluster(name, namespace, token string, servers, agents int32, clusterCID ServerArgs: serverArgs, AgentArgs: agentArgs, Version: version, + Mode: mode, Persistence: &v1alpha1.PersistenceConfig{ Type: persistenceType, StorageClassName: storageClassName, }, }, } + if token != "" { + cluster.Spec.TokenSecretRef = &v1.SecretReference{ + Name: k3kcluster.TokenSecretName(name), + Namespace: namespace, + } + } + return cluster } diff --git a/k3k-kubelet/config.go b/k3k-kubelet/config.go index 7dd9a53..e820d58 100644 --- a/k3k-kubelet/config.go +++ b/k3k-kubelet/config.go @@ -47,6 +47,9 @@ func (c *config) unmarshalYAML(data []byte) error { if c.NodeName == "" { c.NodeName = conf.NodeName } + if c.Token == "" { + c.Token = conf.Token + } return nil } diff --git a/k3k-kubelet/kubelet.go b/k3k-kubelet/kubelet.go index 06e0a0a..df79c11 100644 --- a/k3k-kubelet/kubelet.go +++ b/k3k-kubelet/kubelet.go @@ -56,6 +56,7 @@ type kubelet struct { virtualMgr manager.Manager node *nodeutil.Node logger *k3klog.Logger + token string } func newKubelet(ctx context.Context, c *config, logger *k3klog.Logger) (*kubelet, error) { @@ -70,8 +71,7 @@ func newKubelet(ctx context.Context, c *config, logger *k3klog.Logger) (*kubelet if err != nil { return nil, err } - - virtConfig, err := virtRestConfig(ctx, c.VirtualConfigPath, hostClient, c.ClusterName, c.ClusterNamespace) + virtConfig, err := virtRestConfig(ctx, c.VirtualConfigPath, hostClient, c.ClusterName, c.ClusterNamespace, c.Token, logger) if err != nil { return nil, err } @@ -120,6 +120,7 @@ func newKubelet(ctx context.Context, c *config, logger *k3klog.Logger) (*kubelet virtualMgr: virtualMgr, virtClient: virtClient, logger: logger.Named(k3kKubeletName), + token: c.Token, }, nil } @@ -194,7 +195,7 @@ func (k *kubelet) nodeOpts(ctx context.Context, srvPort, namespace, name, hostna } c.Handler = mux - tlsConfig, err := loadTLSConfig(ctx, k.hostClient, name, namespace, k.name, hostname) + tlsConfig, err := loadTLSConfig(ctx, k.hostClient, name, namespace, k.name, hostname, k.token) if err != nil { return fmt.Errorf("unable to get tls config: %w", err) } @@ -203,7 +204,7 @@ func (k *kubelet) nodeOpts(ctx context.Context, srvPort, namespace, name, hostna } } -func virtRestConfig(ctx context.Context, virtualConfigPath string, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace string) (*rest.Config, error) { +func virtRestConfig(ctx context.Context, virtualConfigPath string, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace, token string, logger *k3klog.Logger) (*rest.Config, error) { if virtualConfigPath != "" { return clientcmd.BuildConfigFromFlags("", virtualConfigPath) } @@ -218,7 +219,8 @@ func virtRestConfig(ctx context.Context, virtualConfigPath string, hostClient ct return err != nil }, func() error { var err error - b, err = bootstrap.DecodedBootstrap(cluster.Spec.Token, endpoint) + b, err = bootstrap.DecodedBootstrap(token, endpoint) + logger.Infow("decoded bootstrap", zap.Error(err)) return err }); err != nil { return nil, fmt.Errorf("unable to decode bootstrap: %w", err) @@ -263,7 +265,7 @@ func kubeconfigBytes(url string, serverCA, clientCert, clientKey []byte) ([]byte return clientcmd.Write(*config) } -func loadTLSConfig(ctx context.Context, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace, nodeName, hostname string) (*tls.Config, error) { +func loadTLSConfig(ctx context.Context, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace, nodeName, hostname, token string) (*tls.Config, error) { var ( cluster v1alpha1.Cluster b *bootstrap.ControlRuntimeBootstrap @@ -276,7 +278,7 @@ func loadTLSConfig(ctx context.Context, hostClient ctrlruntimeclient.Client, clu return err != nil }, func() error { var err error - b, err = bootstrap.DecodedBootstrap(cluster.Spec.Token, endpoint) + b, err = bootstrap.DecodedBootstrap(token, endpoint) return err }); err != nil { return nil, fmt.Errorf("unable to decode bootstrap: %w", err) diff --git a/pkg/apis/k3k.io/v1alpha1/types.go b/pkg/apis/k3k.io/v1alpha1/types.go index e971ca3..1d958f8 100644 --- a/pkg/apis/k3k.io/v1alpha1/types.go +++ b/pkg/apis/k3k.io/v1alpha1/types.go @@ -27,13 +27,15 @@ type ClusterSpec struct { // +kubebuilder:validation:XValidation:message="invalid value for agents",rule="self >= 0" // Agents is the number of K3s pods to run in agent (worker) mode. Agents *int32 `json:"agents"` + // +optional // NodeSelector is the node selector that will be applied to all server/agent pods NodeSelector map[string]string `json:"nodeSelector,omitempty"` // Limit is the limits that apply for the server/worker nodes. Limit *ClusterLimit `json:"clusterLimit,omitempty"` - // +kubebuilder:validation:XValidation:message="token is immutable",rule="self == oldSelf" - // Token is the token used to join the worker nodes to the cluster. - Token string `json:"token"` + // +optional + // TokenSecretRef is Secret reference used as a token join server and worker nodes to the cluster. The controller + // assumes that the secret has a field "token" in its data, any other fields in the secret will be ignored. + TokenSecretRef *v1.SecretReference `json:"tokenSecretRef"` // +kubebuilder:validation:XValidation:message="clusterCIDR is immutable",rule="self == oldSelf" // ClusterCIDR is the CIDR range for the pods of the cluster. Defaults to 10.42.0.0/16. ClusterCIDR string `json:"clusterCIDR,omitempty"` @@ -53,7 +55,7 @@ type ClusterSpec struct { // Addons is a list of secrets containing raw YAML which will be deployed in the virtual K3k cluster on startup. Addons []Addon `json:"addons,omitempty"` // +kubebuilder:validation:XValidation:message="mode is immutable",rule="self == oldSelf" - // +kubebuilder:validation:XValidation:message="invalid value for mode",rule="self == virtual || self == shared" + // +kubebuilder:validation:XValidation:message="invalid value for mode",rule="self == \"virtual\" || self == \"shared\"" // Mode is the cluster provisioning mode which can be either "virtual" or "shared". Defaults to "shared" Mode string `json:"mode"` diff --git a/pkg/controller/cluster/agent/agent.go b/pkg/controller/cluster/agent/agent.go index 6d6ad6b..ee3a11c 100644 --- a/pkg/controller/cluster/agent/agent.go +++ b/pkg/controller/cluster/agent/agent.go @@ -16,11 +16,11 @@ type Agent interface { Resources() []ctrlruntimeclient.Object } -func New(cluster *v1alpha1.Cluster, serviceIP, sharedAgentImage string) Agent { +func New(cluster *v1alpha1.Cluster, serviceIP, sharedAgentImage, token string) Agent { if cluster.Spec.Mode == VirtualNodeMode { - return NewVirtualAgent(cluster, serviceIP) + return NewVirtualAgent(cluster, serviceIP, token) } - return NewSharedAgent(cluster, serviceIP, sharedAgentImage) + return NewSharedAgent(cluster, serviceIP, sharedAgentImage, token) } func configSecretName(clusterName string) string { diff --git a/pkg/controller/cluster/agent/shared.go b/pkg/controller/cluster/agent/shared.go index cf0a6ac..1ab2c6f 100644 --- a/pkg/controller/cluster/agent/shared.go +++ b/pkg/controller/cluster/agent/shared.go @@ -22,18 +22,20 @@ type SharedAgent struct { cluster *v1alpha1.Cluster serviceIP string sharedAgentImage string + token string } -func NewSharedAgent(cluster *v1alpha1.Cluster, serviceIP, sharedAgentImage string) Agent { +func NewSharedAgent(cluster *v1alpha1.Cluster, serviceIP, sharedAgentImage, token string) Agent { return &SharedAgent{ cluster: cluster, serviceIP: serviceIP, sharedAgentImage: sharedAgentImage, + token: token, } } func (s *SharedAgent) Config() (ctrlruntimeclient.Object, error) { - config := sharedAgentData(s.cluster) + config := sharedAgentData(s.cluster, s.token, s.Name()) return &v1.Secret{ TypeMeta: metav1.TypeMeta{ @@ -50,14 +52,13 @@ func (s *SharedAgent) Config() (ctrlruntimeclient.Object, error) { }, nil } -func sharedAgentData(cluster *v1alpha1.Cluster) string { - nodeName := cluster.Name + "-" + "k3k-kubelet" +func sharedAgentData(cluster *v1alpha1.Cluster, token, nodeName string) string { return fmt.Sprintf(`clusterName: %s clusterNamespace: %s nodeName: %s agentHostname: %s token: %s`, - cluster.Name, cluster.Namespace, nodeName, nodeName, cluster.Spec.Token) + cluster.Name, cluster.Namespace, nodeName, nodeName, token) } func (s *SharedAgent) Resources() []ctrlruntimeclient.Object { diff --git a/pkg/controller/cluster/agent/virtual.go b/pkg/controller/cluster/agent/virtual.go index 062e4dc..c0a97b0 100644 --- a/pkg/controller/cluster/agent/virtual.go +++ b/pkg/controller/cluster/agent/virtual.go @@ -20,17 +20,19 @@ const ( type VirtualAgent struct { cluster *v1alpha1.Cluster serviceIP string + token string } -func NewVirtualAgent(cluster *v1alpha1.Cluster, serviceIP string) Agent { +func NewVirtualAgent(cluster *v1alpha1.Cluster, serviceIP, token string) Agent { return &VirtualAgent{ cluster: cluster, serviceIP: serviceIP, + token: token, } } func (v *VirtualAgent) Config() (ctrlruntimeclient.Object, error) { - config := virtualAgentData(v.serviceIP, v.cluster.Spec.Token) + config := virtualAgentData(v.serviceIP, v.token) return &v1.Secret{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/controller/cluster/cluster.go b/pkg/controller/cluster/cluster.go index f59f53c..62e5627 100644 --- a/pkg/controller/cluster/cluster.go +++ b/pkg/controller/cluster/cluster.go @@ -116,7 +116,12 @@ func (c *ClusterReconciler) createCluster(ctx context.Context, cluster *v1alpha1 log.Errorw("invalid change", zap.Error(err)) return nil } - s := server.New(cluster, c.Client) + token, err := c.token(ctx, cluster) + if err != nil { + return err + } + + s := server.New(cluster, c.Client, token) if cluster.Spec.Persistence != nil { cluster.Status.Persistence = cluster.Spec.Persistence @@ -154,7 +159,7 @@ func (c *ClusterReconciler) createCluster(ctx context.Context, cluster *v1alpha1 return err } - if err := c.agent(ctx, cluster, serviceIP); err != nil { + if err := c.agent(ctx, cluster, serviceIP, token); err != nil { return err } @@ -173,7 +178,7 @@ func (c *ClusterReconciler) createCluster(ctx context.Context, cluster *v1alpha1 } } - bootstrapSecret, err := bootstrap.Generate(ctx, cluster, serviceIP) + bootstrapSecret, err := bootstrap.Generate(ctx, cluster, serviceIP, token) if err != nil { return err } @@ -274,8 +279,8 @@ func (c *ClusterReconciler) server(ctx context.Context, cluster *v1alpha1.Cluste return nil } -func (c *ClusterReconciler) agent(ctx context.Context, cluster *v1alpha1.Cluster, serviceIP string) error { - agent := agent.New(cluster, serviceIP, c.SharedAgentImage) +func (c *ClusterReconciler) agent(ctx context.Context, cluster *v1alpha1.Cluster, serviceIP, token string) error { + agent := agent.New(cluster, serviceIP, c.SharedAgentImage, token) agentsConfig, err := agent.Config() if err != nil { return err diff --git a/pkg/controller/cluster/pod.go b/pkg/controller/cluster/pod.go index 4919ad0..f3a9c0e 100644 --- a/pkg/controller/cluster/pod.go +++ b/pkg/controller/cluster/pod.go @@ -116,7 +116,7 @@ func (p *PodReconciler) handleServerPod(ctx context.Context, cluster v1alpha1.Cl } return nil } - tlsConfig, err := p.getETCDTLS(&cluster, log) + tlsConfig, err := p.getETCDTLS(ctx, &cluster, log) if err != nil { return err } @@ -150,9 +150,12 @@ func (p *PodReconciler) handleServerPod(ctx context.Context, cluster v1alpha1.Cl return nil } -func (p *PodReconciler) getETCDTLS(cluster *v1alpha1.Cluster, log *zap.SugaredLogger) (*tls.Config, error) { +func (p *PodReconciler) getETCDTLS(ctx context.Context, cluster *v1alpha1.Cluster, log *zap.SugaredLogger) (*tls.Config, error) { log.Infow("generating etcd TLS client certificate", "Cluster", cluster.Name, "Namespace", cluster.Namespace) - token := cluster.Spec.Token + token, err := p.clusterToken(ctx, cluster) + if err != nil { + return nil, err + } endpoint := server.ServiceName(cluster.Name) + "." + cluster.Namespace var b *bootstrap.ControlRuntimeBootstrap if err := retry.OnError(k3kcontroller.Backoff, func(err error) bool { @@ -218,3 +221,21 @@ func removePeer(ctx context.Context, client *clientv3.Client, name, address stri return nil } + +func (p *PodReconciler) clusterToken(ctx context.Context, cluster *v1alpha1.Cluster) (string, error) { + var tokenSecret v1.Secret + nn := types.NamespacedName{ + Name: TokenSecretName(cluster.Name), + Namespace: cluster.Namespace, + } + if cluster.Spec.TokenSecretRef != nil { + nn.Name = TokenSecretName(cluster.Name) + } + if err := p.Client.Get(ctx, nn, &tokenSecret); err != nil { + return "", err + } + if _, ok := tokenSecret.Data["token"]; !ok { + return "", fmt.Errorf("no token field in secret %s/%s", nn.Namespace, nn.Name) + } + return string(tokenSecret.Data["token"]), nil +} diff --git a/pkg/controller/cluster/server/bootstrap/bootstrap.go b/pkg/controller/cluster/server/bootstrap/bootstrap.go index 9e94dc0..65ada7d 100644 --- a/pkg/controller/cluster/server/bootstrap/bootstrap.go +++ b/pkg/controller/cluster/server/bootstrap/bootstrap.go @@ -32,9 +32,7 @@ type content struct { // 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 Generate(ctx context.Context, cluster *v1alpha1.Cluster, ip string) (*v1.Secret, error) { - token := cluster.Spec.Token - +func Generate(ctx context.Context, cluster *v1alpha1.Cluster, ip, token string) (*v1.Secret, error) { var bootstrap *ControlRuntimeBootstrap if err := retry.OnError(controller.Backoff, func(err error) bool { return true diff --git a/pkg/controller/cluster/server/config.go b/pkg/controller/cluster/server/config.go index b5203c4..66339cc 100644 --- a/pkg/controller/cluster/server/config.go +++ b/pkg/controller/cluster/server/config.go @@ -18,9 +18,9 @@ func (s *Server) Config(init bool, serviceIP string) (*v1.Secret, error) { fmt.Sprintf("%s.%s", ServiceName(s.cluster.Name), s.cluster.Namespace), ) - config := serverConfigData(serviceIP, s.cluster) + config := serverConfigData(serviceIP, s.cluster, s.token) if init { - config = initConfigData(s.cluster) + config = initConfigData(s.cluster, s.token) } return &v1.Secret{ TypeMeta: metav1.TypeMeta{ @@ -37,20 +37,20 @@ func (s *Server) Config(init bool, serviceIP string) (*v1.Secret, error) { }, nil } -func serverConfigData(serviceIP string, cluster *v1alpha1.Cluster) string { - return "cluster-init: true\nserver: https://" + serviceIP + ":6443\n" + serverOptions(cluster) +func serverConfigData(serviceIP string, cluster *v1alpha1.Cluster, token string) string { + return "cluster-init: true\nserver: https://" + serviceIP + ":6443\n" + serverOptions(cluster, token) } -func initConfigData(cluster *v1alpha1.Cluster) string { - return "cluster-init: true\n" + serverOptions(cluster) +func initConfigData(cluster *v1alpha1.Cluster, token string) string { + return "cluster-init: true\n" + serverOptions(cluster, token) } -func serverOptions(cluster *v1alpha1.Cluster) string { +func serverOptions(cluster *v1alpha1.Cluster, token string) string { var opts string // TODO: generate token if not found - if cluster.Spec.Token != "" { - opts = "token: " + cluster.Spec.Token + "\n" + if token != "" { + opts = "token: " + token + "\n" } if cluster.Status.ClusterCIDR != "" { opts = opts + "cluster-cidr: " + cluster.Status.ClusterCIDR + "\n" diff --git a/pkg/controller/cluster/server/server.go b/pkg/controller/cluster/server/server.go index 72470bd..d878e43 100644 --- a/pkg/controller/cluster/server/server.go +++ b/pkg/controller/cluster/server/server.go @@ -31,12 +31,14 @@ const ( type Server struct { cluster *v1alpha1.Cluster client client.Client + token string } -func New(cluster *v1alpha1.Cluster, client client.Client) *Server { +func New(cluster *v1alpha1.Cluster, client client.Client, token string) *Server { return &Server{ cluster: cluster, client: client, + token: token, } } diff --git a/pkg/controller/cluster/token.go b/pkg/controller/cluster/token.go new file mode 100644 index 0000000..cfc0be4 --- /dev/null +++ b/pkg/controller/cluster/token.go @@ -0,0 +1,98 @@ +package cluster + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + + "github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1" + "github.com/rancher/k3k/pkg/controller" + 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/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (c *ClusterReconciler) token(ctx context.Context, cluster *v1alpha1.Cluster) (string, error) { + if cluster.Spec.TokenSecretRef == nil { + return c.ensureTokenSecret(ctx, cluster) + } + // get token data from secretRef + nn := types.NamespacedName{ + Name: cluster.Spec.TokenSecretRef.Name, + Namespace: cluster.Spec.TokenSecretRef.Namespace, + } + var tokenSecret v1.Secret + if err := c.Client.Get(ctx, nn, &tokenSecret); err != nil { + return "", err + } + if _, ok := tokenSecret.Data["token"]; !ok { + return "", fmt.Errorf("no token field in secret %s/%s", nn.Namespace, nn.Name) + } + return string(tokenSecret.Data["token"]), nil +} + +func (c *ClusterReconciler) ensureTokenSecret(ctx context.Context, cluster *v1alpha1.Cluster) (string, error) { + // check if the secret is already created + var ( + tokenSecret v1.Secret + nn = types.NamespacedName{ + Name: TokenSecretName(cluster.Name), + Namespace: cluster.Namespace, + } + ) + if err := c.Client.Get(ctx, nn, &tokenSecret); err != nil { + if !apierrors.IsNotFound(err) { + return "", err + } + } + + if tokenSecret.Data != nil { + return string(tokenSecret.Data["token"]), nil + } + c.logger.Info("Token secret is not specified, creating a random token") + token, err := random(16) + if err != nil { + return "", err + } + tokenSecret = TokenSecretObj(token, cluster.Name, cluster.Namespace) + if err := controllerutil.SetControllerReference(cluster, &tokenSecret, c.Scheme); err != nil { + return "", err + } + if err := c.ensure(ctx, &tokenSecret, false); err != nil { + return "", err + } + return token, nil + +} + +func random(size int) (string, error) { + token := make([]byte, size) + _, err := rand.Read(token) + if err != nil { + return "", err + } + return hex.EncodeToString(token), err +} + +func TokenSecretObj(token, name, namespace string) v1.Secret { + return v1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: TokenSecretName(name), + Namespace: namespace, + }, + Data: map[string][]byte{ + "token": []byte(token), + }, + } +} + +func TokenSecretName(clusterName string) string { + return controller.SafeConcatNameWithPrefix(clusterName, "token") +}