diff --git a/common/bootstraptoken/token.go b/common/bootstraptoken/token.go new file mode 100644 index 000000000..7a869c311 --- /dev/null +++ b/common/bootstraptoken/token.go @@ -0,0 +1,79 @@ +package bootstraptoken + +import ( + "fmt" + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + restclient "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + bootstrapapi "k8s.io/cluster-bootstrap/token/api" + bootstraputil "k8s.io/cluster-bootstrap/token/util" +) + +func GetTokenIDSecretFromBootstrapTokenStr(tokenStr string) (string, string, error) { + substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(tokenStr) + if len(substrs) != 3 { // nolint: gomnd + return "", "", fmt.Errorf("the bootstrap token %q was not of the form %q", tokenStr, bootstrapapi.BootstrapTokenPattern) + } + tokenID := substrs[1] + tokenSecret := substrs[2] + + return tokenID, tokenSecret, nil +} + +func GenerateSecretFromBootstrapTokenStr(tokenStr string, ttl time.Duration) (*v1.Secret, error) { + tokenID, tokenSecret, err := GetTokenIDSecretFromBootstrapTokenStr(tokenStr) + if err != nil { + return nil, err + } + secretData := map[string][]byte{ + bootstrapapi.BootstrapTokenIDKey: []byte(tokenID), + bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret), + bootstrapapi.BootstrapTokenExpirationKey: []byte(time.Now().UTC().Add(ttl).Format(time.RFC3339)), + bootstrapapi.BootstrapTokenDescriptionKey: []byte("jsdj"), + bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("system:bootstrappers:byoh"), + bootstrapapi.BootstrapTokenUsageSigningKey: []byte("true"), + bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"), + } + + bootstrapSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: bootstraputil.BootstrapTokenSecretName(tokenID), + Namespace: metav1.NamespaceSystem, + }, + Type: bootstrapapi.SecretTypeBootstrapToken, + Data: secretData, + } + return bootstrapSecret, nil +} + +func GenerateBootstrapKubeconfigFromBootstrapToken(clientConfig *restclient.Config, tokenStr string) (*clientcmdapi.Config, error) { + tokenID, tokenSecret, err := GetTokenIDSecretFromBootstrapTokenStr(tokenStr) + if err != nil { + return nil, err + } + // Build resulting kubeconfig. + kubeconfigData := clientcmdapi.Config{ + // Define a cluster stanza based on the bootstrap kubeconfig. + Clusters: map[string]*clientcmdapi.Cluster{"default-cluster": { + Server: clientConfig.Host, + InsecureSkipTLSVerify: clientConfig.Insecure, + CertificateAuthorityData: clientConfig.CAData, + }}, + // Define auth based on the obtained client cert. + AuthInfos: map[string]*clientcmdapi.AuthInfo{"default-auth": { + Token: fmt.Sprintf(tokenID + "." + tokenSecret), + }}, + // Define a context that connects the auth info and cluster, and set it as the default + Contexts: map[string]*clientcmdapi.Context{"default-context": { + Cluster: "default-cluster", + AuthInfo: "default-auth", + Namespace: "default", + }}, + CurrentContext: "default-context", + } + + return &kubeconfigData, nil +} diff --git a/common/encoder/decoder.go b/common/encoder/decoder.go new file mode 100644 index 000000000..f78cf09e8 --- /dev/null +++ b/common/encoder/decoder.go @@ -0,0 +1,14 @@ +// Copyright 2022 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package encoder + +import ( + "encoding/base64" + "encoding/json" + "strings" +) + +func DecodeFromBase64(v interface{}, enc string) error { + return json.NewDecoder(base64.NewDecoder(base64.StdEncoding, strings.NewReader(enc))).Decode(v) +} diff --git a/common/encoder/encoder.go b/common/encoder/encoder.go new file mode 100644 index 000000000..b82dd4d2a --- /dev/null +++ b/common/encoder/encoder.go @@ -0,0 +1,21 @@ +// Copyright 2022 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package encoder + +import ( + "bytes" + "encoding/base64" + "encoding/json" +) + +func EncodeToBase64(v interface{}) (string, error) { + var buf bytes.Buffer + encoder := base64.NewEncoder(base64.StdEncoding, &buf) + err := json.NewEncoder(encoder).Encode(v) + if err != nil { + return "", err + } + encoder.Close() + return buf.String(), nil +} diff --git a/common/utils.go b/common/utils.go index 3d46c2b16..40dc92b2f 100644 --- a/common/utils.go +++ b/common/utils.go @@ -6,12 +6,9 @@ package common import ( "bytes" "compress/gzip" - "encoding/base64" - "encoding/json" "io" "os" "path/filepath" - "strings" "github.com/pkg/errors" ) @@ -69,18 +66,3 @@ func RemoveGlob(path string) error { } return nil } - -func EncodeToBase64(v interface{}) (string, error) { - var buf bytes.Buffer - encoder := base64.NewEncoder(base64.StdEncoding, &buf) - err := json.NewEncoder(encoder).Encode(v) - if err != nil { - return "", err - } - encoder.Close() - return buf.String(), nil -} - -func DecodeFromBase64(v interface{}, enc string) error { - return json.NewDecoder(base64.NewDecoder(base64.StdEncoding, strings.NewReader(enc))).Decode(v) -} diff --git a/controllers/infrastructure/bootstrapkubeconfig_controller.go b/controllers/infrastructure/bootstrapkubeconfig_controller.go index 6f8b645a1..5ba9d2f23 100644 --- a/controllers/infrastructure/bootstrapkubeconfig_controller.go +++ b/controllers/infrastructure/bootstrapkubeconfig_controller.go @@ -5,17 +5,14 @@ package controllers import ( "context" - "fmt" + "time" infrastructurev1beta1 "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/apis/infrastructure/v1beta1" - "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/common" - v1 "k8s.io/api/core/v1" + "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/common/bootstraptoken" + "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/common/encoder" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - bootstrapapi "k8s.io/cluster-bootstrap/token/api" bootstraputil "k8s.io/cluster-bootstrap/token/util" "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" @@ -57,63 +54,29 @@ func (r *BootstrapKubeconfigReconciler) Reconcile(ctx context.Context, req ctrl. if err != nil { return ctrl.Result{}, nil } - substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(tokenStr) - tokenID := substrs[1] - tokenSecret := substrs[2] - data := map[string][]byte{ - bootstrapapi.BootstrapTokenIDKey: []byte(tokenID), - bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret), + bootstrapKubeconfigSecret, err := bootstraptoken.GenerateSecretFromBootstrapTokenStr(tokenStr, time.Minute*30) + if err != nil { + return ctrl.Result{}, nil } - description := "BYOH controller generated bootstrap token" - data["Description"] = []byte(description) - // for _, usage := range token.Usages { - // data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true") - // } - data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte("system:bootstrappers:byoh") - bootstrapSecret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: bootstraputil.BootstrapTokenSecretName(tokenID), - Namespace: metav1.NamespaceSystem, - }, - Type: bootstrapapi.SecretTypeBootstrapToken, - Data: data, + // create secret + err = r.Client.Create(ctx, bootstrapKubeconfigSecret) + if err != nil { + return ctrl.Result{}, nil } - // TODO: check if secret with name already exists - // if exists, maybe update the secret? - err = r.Client.Create(ctx, bootstrapSecret) + bootstrapKubeconfigData, err := bootstraptoken.GenerateBootstrapKubeconfigFromBootstrapToken(r.Config, tokenStr) if err != nil { - return ctrl.Result{}, err + return ctrl.Result{}, nil } - // Build resulting kubeconfig. - kubeconfigData := clientcmdapi.Config{ - // Define a cluster stanza based on the bootstrap kubeconfig. - Clusters: map[string]*clientcmdapi.Cluster{"default-cluster": { - Server: r.Config.Host, - InsecureSkipTLSVerify: r.Config.Insecure, - CertificateAuthorityData: r.Config.CAData, - }}, - // Define auth based on the obtained client cert. - AuthInfos: map[string]*clientcmdapi.AuthInfo{"default-auth": { - Token: fmt.Sprintf(tokenID + "." + tokenSecret), - }}, - // Define a context that connects the auth info and cluster, and set it as the default - Contexts: map[string]*clientcmdapi.Context{"default-context": { - Cluster: "default-cluster", - AuthInfo: "default-auth", - Namespace: "default", - }}, - CurrentContext: "default-context", - } - encodedKubeConfig, err := common.EncodeToBase64(kubeconfigData) + encodedBootstrapKubeConfig, err := encoder.EncodeToBase64(bootstrapKubeconfigData) helper, err := patch.NewHelper(bootstrapKubeconfig, r.Client) if err != nil { return ctrl.Result{}, err } - bootstrapKubeconfig.Status.BootstrapKubeconfigData = encodedKubeConfig + bootstrapKubeconfig.Status.BootstrapKubeconfigData = encodedBootstrapKubeConfig return ctrl.Result{}, helper.Patch(ctx, bootstrapKubeconfig) } diff --git a/controllers/infrastructure/bootstrapkubeconfig_controller_test.go b/controllers/infrastructure/bootstrapkubeconfig_controller_test.go index 17b3a3423..158bb1907 100644 --- a/controllers/infrastructure/bootstrapkubeconfig_controller_test.go +++ b/controllers/infrastructure/bootstrapkubeconfig_controller_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" infrav1 "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/apis/infrastructure/v1beta1" - "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/common" + "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/common/encoder" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" @@ -68,7 +68,7 @@ var _ = Describe("Controllers/BoottrapKubeconfigController", func() { var config *clientcmdapi.Config // should be able to decode into a Config struct - err = common.DecodeFromBase64(&config, createdBootstrapKubeconfig.Status.BootstrapKubeconfigData) + err = encoder.DecodeFromBase64(&config, createdBootstrapKubeconfig.Status.BootstrapKubeconfigData) Expect(err).ToNot(HaveOccurred()) })