diff --git a/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml b/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml index 924c438840..9376976fc5 100644 --- a/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml +++ b/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml @@ -3294,6 +3294,10 @@ spec: - name: ONBOARDING_TOKEN_LIFETIME - name: UX_BACKEND_PORT - name: TLS_ENABLED + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace image: quay.io/ocs-dev/ocs-operator:latest imagePullPolicy: IfNotPresent name: ux-backend-server diff --git a/deploy/ocs-operator/manifests/ux_backend_role.yaml b/deploy/ocs-operator/manifests/ux_backend_role.yaml index f89b32672e..5f1bbd7f9d 100644 --- a/deploy/ocs-operator/manifests/ux_backend_role.yaml +++ b/deploy/ocs-operator/manifests/ux_backend_role.yaml @@ -14,3 +14,11 @@ rules: verbs: - get - list +- apiGroups: + - "" + resources: + - secrets + resourceNames: + - onboarding-ticket-key + verbs: + - delete diff --git a/rbac/ux_backend_role.yaml b/rbac/ux_backend_role.yaml index f89b32672e..5f1bbd7f9d 100644 --- a/rbac/ux_backend_role.yaml +++ b/rbac/ux_backend_role.yaml @@ -14,3 +14,11 @@ rules: verbs: - get - list +- apiGroups: + - "" + resources: + - secrets + resourceNames: + - onboarding-ticket-key + verbs: + - delete diff --git a/services/ux-backend/handlers/common.go b/services/ux-backend/handlers/common.go new file mode 100644 index 0000000000..fb34e83171 --- /dev/null +++ b/services/ux-backend/handlers/common.go @@ -0,0 +1,23 @@ +package handlers + +import ( + "os" +) + +const ( + ContentTypeTextPlain = "text/plain" +) + +var namespace string + +// returns namespace found in env value, will panic if value is empty +func GetPodNamespace() string { + if namespace != "" { + return namespace + } + if ns := os.Getenv("POD_NAMESPACE"); ns != "" { + namespace = ns + return namespace + } + panic("Value for env var 'POD_NAMESPACE' is empty") +} diff --git a/services/ux-backend/handlers/onboardingtokens/handler.go b/services/ux-backend/handlers/onboardingtokens/handler.go index bef1c5e044..8560b8428c 100644 --- a/services/ux-backend/handlers/onboardingtokens/handler.go +++ b/services/ux-backend/handlers/onboardingtokens/handler.go @@ -16,12 +16,12 @@ import ( "github.com/google/uuid" "github.com/red-hat-storage/ocs-operator/v4/services" + "github.com/red-hat-storage/ocs-operator/v4/services/ux-backend/handlers" "k8s.io/klog/v2" ) const ( onboardingPrivateKeyFilePath = "/etc/private-key/key" - ContentTypeTextPlain = "text/plain" ) func HandleMessage(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int) { @@ -37,7 +37,7 @@ func handlePost(w http.ResponseWriter, tokenLifetimeInHours int) { if onboardingToken, err := generateOnboardingToken(tokenLifetimeInHours); err != nil { klog.Errorf("failed to get onboardig token: %v", err) w.WriteHeader(http.StatusInternalServerError) - w.Header().Set("Content-Type", ContentTypeTextPlain) + w.Header().Set("Content-Type", handlers.ContentTypeTextPlain) if _, err := w.Write([]byte("Failed to generate token")); err != nil { klog.Errorf("failed write data to response writer, %v", err) @@ -45,7 +45,7 @@ func handlePost(w http.ResponseWriter, tokenLifetimeInHours int) { } else { klog.Info("onboarding token generated successfully") w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", ContentTypeTextPlain) + w.Header().Set("Content-Type", handlers.ContentTypeTextPlain) if _, err = w.Write([]byte(onboardingToken)); err != nil { klog.Errorf("failed write data to response writer: %v", err) @@ -56,7 +56,7 @@ func handlePost(w http.ResponseWriter, tokenLifetimeInHours int) { func handleUnsupportedMethod(w http.ResponseWriter, r *http.Request) { klog.Info("Only POST method should be used to send data to this endpoint /onboarding-tokens") w.WriteHeader(http.StatusMethodNotAllowed) - w.Header().Set("Content-Type", ContentTypeTextPlain) + w.Header().Set("Content-Type", handlers.ContentTypeTextPlain) w.Header().Set("Allow", "POST") if _, err := w.Write([]byte(fmt.Sprintf("Unsupported method : %s", r.Method))); err != nil { diff --git a/services/ux-backend/handlers/rotatekeys/handler.go b/services/ux-backend/handlers/rotatekeys/handler.go new file mode 100644 index 0000000000..0605693c5f --- /dev/null +++ b/services/ux-backend/handlers/rotatekeys/handler.go @@ -0,0 +1,62 @@ +package rotatekeys + +import ( + "context" + "fmt" + "net/http" + + "github.com/red-hat-storage/ocs-operator/v4/services/ux-backend/handlers" + corev1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + onboardingValidationPublicKeySecretName = "onboarding-ticket-key" +) + +func HandleMessage(w http.ResponseWriter, r *http.Request, cl client.Client) { + switch r.Method { + case "POST": + handlePost(r.Context(), w, cl) + default: + handleUnsupportedMethod(w, r) + } +} + +func handlePost(ctx context.Context, w http.ResponseWriter, cl client.Client) { + klog.Info("POST method on /rotate-keys endpoint is invoked") + w.Header().Set("Content-Type", handlers.ContentTypeTextPlain) + + publicKeySecret := &corev1.Secret{} + publicKeySecret.Name = onboardingValidationPublicKeySecretName + publicKeySecret.Namespace = handlers.GetPodNamespace() + err := cl.Delete(ctx, publicKeySecret) + if err != nil { + klog.Errorf("failed to delete public key secret: %v", err) + w.WriteHeader(http.StatusInternalServerError) + + // TODO: should we differentiate b/n secret not found and remaining errors? + if _, err = w.Write([]byte("Failed to rotate keys")); err != nil { + klog.Errorf("failed to write data to response writer, %v", err) + } + return + } + + klog.Info("onboarding validation keys are rotated successfully") + w.WriteHeader(http.StatusOK) + if _, err = w.Write([]byte("Successfully rotated keys")); err != nil { + klog.Errorf("failed to write data to response writer, %v", err) + } +} + +func handleUnsupportedMethod(w http.ResponseWriter, r *http.Request) { + klog.Info("Only POST method should be used to send data to this endpoint /rotate-keys") + w.WriteHeader(http.StatusMethodNotAllowed) + w.Header().Set("Content-Type", handlers.ContentTypeTextPlain) + w.Header().Set("Allow", "POST") + + if _, err := w.Write([]byte(fmt.Sprintf("Unsupported method : %s", r.Method))); err != nil { + klog.Errorf("failed to write data to response writer: %v", err) + } +} diff --git a/services/ux-backend/main.go b/services/ux-backend/main.go index 5651379225..2ff778d812 100644 --- a/services/ux-backend/main.go +++ b/services/ux-backend/main.go @@ -10,6 +10,9 @@ import ( "k8s.io/klog/v2" "github.com/red-hat-storage/ocs-operator/v4/services/ux-backend/handlers/onboardingtokens" + "github.com/red-hat-storage/ocs-operator/v4/services/ux-backend/handlers/rotatekeys" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" ) type serverConfig struct { @@ -51,6 +54,20 @@ func loadAndValidateServerConfig() (*serverConfig, error) { return &config, nil } +func newKubeClient() (client.Client, error) { + cfg, err := config.GetConfig() + if err != nil { + return nil, err + } + + newClient, err := client.New(cfg, client.Options{}) + if err != nil { + return nil, err + } + + return newClient, nil +} + func main() { klog.Info("Starting ux backend server") @@ -61,9 +78,19 @@ func main() { klog.Info("shutting down!") os.Exit(-1) } + + cl, err := newKubeClient() + if err != nil { + klog.Errorf("failed to create kubernetes api client: %v", err) + klog.Exit("shutting down!") + } + http.HandleFunc("/onboarding-tokens", func(w http.ResponseWriter, r *http.Request) { onboardingtokens.HandleMessage(w, r, config.tokenLifetimeInHours) }) + http.HandleFunc("/rotate-keys", func(w http.ResponseWriter, r *http.Request) { + rotatekeys.HandleMessage(w, r, cl) + }) klog.Info("ux backend server listening on port ", config.listenPort) diff --git a/tools/csv-merger/csv-merger.go b/tools/csv-merger/csv-merger.go index 36d8fc7da5..072cfa63cd 100644 --- a/tools/csv-merger/csv-merger.go +++ b/tools/csv-merger/csv-merger.go @@ -980,6 +980,14 @@ func getUXBackendServerDeployment() appsv1.DeploymentSpec { Name: "TLS_ENABLED", Value: os.Getenv("TLS_ENABLED"), }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, }, }, {