From 25fdcf27d95d1f0b09fe13819653a614219fecf8 Mon Sep 17 00:00:00 2001 From: Umanga Chapagain Date: Tue, 16 Jul 2024 21:19:43 +0530 Subject: [PATCH] create StorageCluster peer token secret on the hub Signed-off-by: Umanga Chapagain --- addons/agent_mirrorpeer_controller.go | 48 ++++++++ addons/onboarding_token.go | 106 ++++++++++++++++++ .../spoke_clusterrole.yaml | 2 +- 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 addons/onboarding_token.go diff --git a/addons/agent_mirrorpeer_controller.go b/addons/agent_mirrorpeer_controller.go index 8ff05be8..b919ee58 100644 --- a/addons/agent_mirrorpeer_controller.go +++ b/addons/agent_mirrorpeer_controller.go @@ -20,6 +20,7 @@ import ( "context" "crypto/sha1" "encoding/hex" + "encoding/json" "fmt" "log/slog" "strconv" @@ -156,6 +157,53 @@ func (r *MirrorPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, fmt.Errorf("few failures occurred while labeling RBD StorageClasses: %v", errs) } } + + if mirrorPeer.Spec.Type == multiclusterv1alpha1.Async { + if mirrorPeer.Status.Phase == multiclusterv1alpha1.ExchangedSecret { + logger.Info("Cleaning up stale onboarding token", "Token", string(mirrorPeer.GetUID())) + err = deleteStorageClusterPeerTokenSecret(ctx, r.HubClient, r.SpokeClusterName, string(mirrorPeer.GetUID())) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + if mirrorPeer.Status.Phase == multiclusterv1alpha1.ExchangingSecret { + var token corev1.Secret + err = r.HubClient.Get(ctx, types.NamespacedName{Namespace: r.SpokeClusterName, Name: string(mirrorPeer.GetUID())}, &token) + if err != nil && !errors.IsNotFound(err) { + return ctrl.Result{}, err + } + if err == nil { + // TODO: Replace it with exported type from ocs-operator + type OnboardingTicket struct { + ID string `json:"id"` + ExpirationDate int64 `json:"expirationDate,string"` + StorageQuotaInGiB uint `json:"storageQuotaInGiB,omitempty"` + } + var ticketData OnboardingTicket + err = json.Unmarshal(token.Data["storagecluster-peer-token"], &ticketData) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to unmarshal onboarding ticket message. %w", err) + } + if ticketData.ExpirationDate > time.Now().Unix() { + logger.Info("Onboarding token has not expired yet. Not renewing it.", "Token", token.Name, "ExpirationDate", ticketData.ExpirationDate) + return ctrl.Result{}, nil + } + logger.Info("Onboarding token has expired. Deleting it", "Token", token.Name) + err = deleteStorageClusterPeerTokenSecret(ctx, r.HubClient, r.SpokeClusterName, string(mirrorPeer.GetUID())) + if err != nil { + return ctrl.Result{}, err + } + } + logger.Info("Creating a new onboarding token", "Token", token.Name) + err = createStorageClusterPeerTokenSecret(ctx, r.HubClient, r.Scheme, r.SpokeClusterName, "openshift-storage", mirrorPeer, scr) //TODO: get odfOperatorNamespace from addon flags + if err != nil { + logger.Error("Failed to create StorageCluster peer token on the hub.", "error", err) + return ctrl.Result{}, err + } + } + } + return ctrl.Result{}, nil } diff --git a/addons/onboarding_token.go b/addons/onboarding_token.go new file mode 100644 index 00000000..2f7cb6cf --- /dev/null +++ b/addons/onboarding_token.go @@ -0,0 +1,106 @@ +package addons + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + + "github.com/red-hat-storage/odf-multicluster-orchestrator/api/v1alpha1" + multiclusterv1alpha1 "github.com/red-hat-storage/odf-multicluster-orchestrator/api/v1alpha1" + "github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func requestStorageClusterPeerToken(ctx context.Context, proxyServiceNamespace string) (string, error) { + token, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") + if err != nil { + return "", err + } + url := fmt.Sprintf("https://ux-backend-proxy.%s.svc.cluster.local:8888/onboarding-tokens", proxyServiceNamespace) + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + + req, err := http.NewRequestWithContext(ctx, "POST", url, nil) + if err != nil { + return "", err + } + + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(token))) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf(http.StatusText(resp.StatusCode)) + } + + return string(body), nil +} + +func createStorageClusterPeerTokenSecret(ctx context.Context, client client.Client, scheme *runtime.Scheme, spokeClusterName string, odfOperatorNamespace string, mirrorPeer multiclusterv1alpha1.MirrorPeer, storageClusterRef *v1alpha1.StorageClusterRef) error { + uniqueSecretName := string(mirrorPeer.GetUID()) + var storageClusterPeerTokenSecret corev1.Secret + err := client.Get(ctx, types.NamespacedName{Namespace: spokeClusterName, Name: uniqueSecretName}, &storageClusterPeerTokenSecret) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("got an error. %w", err) + } + if err == nil { + return errors.NewAlreadyExists(corev1.Resource("string"), uniqueSecretName) + } + + token, err := requestStorageClusterPeerToken(ctx, odfOperatorNamespace) + if err != nil { + return fmt.Errorf("unable to generate StorageClusterPeer token. %w", err) + } + + customData := make(map[string][]byte, 2) + customData["storagecluster-peer-token"] = []byte(token) + customData[utils.NamespaceKey] = []byte(storageClusterRef.Namespace) + + expectedStorageClusterPeerTokenSecret, err := generateBlueSecret(corev1.Secret{}, utils.InternalLabel, uniqueSecretName, storageClusterRef.Name, spokeClusterName, customData) + if err != nil { + return fmt.Errorf("unable to generate secret from StorageClusterPeer token. %w", err) + } + + err = controllerutil.SetOwnerReference(&mirrorPeer, expectedStorageClusterPeerTokenSecret, scheme) + if err != nil { + return fmt.Errorf("unable to generate secret from StorageClusterPeer token. %w", err) + } + + return client.Create(ctx, expectedStorageClusterPeerTokenSecret) +} + +func deleteStorageClusterPeerTokenSecret(ctx context.Context, client client.Client, tokenNamespace string, tokenName string) error { + token := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: tokenName, + Namespace: tokenNamespace, + }, + } + + err := client.Delete(ctx, token) + if err != nil && !errors.IsNotFound(err) { + return err + } + return nil +} diff --git a/addons/setup/tokenexchange-manifests/spoke_clusterrole.yaml b/addons/setup/tokenexchange-manifests/spoke_clusterrole.yaml index 09d79be5..ef37395f 100644 --- a/addons/setup/tokenexchange-manifests/spoke_clusterrole.yaml +++ b/addons/setup/tokenexchange-manifests/spoke_clusterrole.yaml @@ -14,7 +14,7 @@ rules: verbs: ["get", "list", "watch", "update"] - apiGroups: ["ocs.openshift.io"] resources: ["storageclusters"] - verbs: ["get", "list", "watch", "update"] + verbs: ["get", "list", "watch", "create", "update"] - apiGroups: ["objectbucket.io"] resources: ["objectbucketclaims"] verbs: ["get", "create", "list", "watch", "delete"]