Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read secrets for onboarding-token validation #2715

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions controllers/storagecluster/storageclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const (
tokenLifetimeInHours = 48
onboardingPrivateKeyFilePath = "/etc/private-key/key"
)
const tokenLifetimeInHours = 48

type storageClient struct{}

Expand All @@ -27,11 +24,17 @@ func (s *storageClient) ensureCreated(r *StorageClusterReconciler, storagecluste
return s.ensureDeleted(r, storagecluster)
}

privateKey, err := util.ReadPrivateKey(r.Client)
if err != nil {
r.Log.Info("Unable to get privatekey:")
return reconcile.Result{}, nil
Comment on lines +29 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is an err while reading the private key we should return the err

}

storageClient := &ocsclientv1a1.StorageClient{}
storageClient.Name = storagecluster.Name
_, err := controllerutil.CreateOrUpdate(r.ctx, r.Client, storageClient, func() error {
_, err = controllerutil.CreateOrUpdate(r.ctx, r.Client, storageClient, func() error {
if storageClient.Status.ConsumerID == "" {
token, err := util.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, nil)
token, err := util.GenerateOnboardingToken(tokenLifetimeInHours, privateKey, nil)
if err != nil {
return fmt.Errorf("unable to generate onboarding token: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion controllers/storagecluster/storagecluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func (r *StorageClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
Owns(&appsv1.Deployment{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&corev1.Service{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&corev1.ConfigMap{}, builder.MatchEveryOwner, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&corev1.Secret{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Owns(&corev1.Secret{}, builder.MatchEveryOwner, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change is needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will trigger reconcilation to all owners whenever their is change in secrets.

Owns(&routev1.Route{}).
Owns(&templatev1.Template{}).
Watches(&storagev1.StorageClass{}, enqueueStorageClusterRequest).
Expand Down
41 changes: 41 additions & 0 deletions controllers/util/onboardings_secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package util

import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func ReadPrivateKey(cl client.Client) (*rsa.PrivateKey, error) {
klog.Info("Getting the Pem key")
ctx := context.Background()

operatorNamespace, err := GetOperatorNamespace()
if err != nil {
return nil, fmt.Errorf("unable to get operator namespace: %v", err)
}

privateSecret := &corev1.Secret{}
privateSecret.Name = onboardingValidationPrivateKeySecretName
privateSecret.Namespace = operatorNamespace

err = cl.Get(ctx, client.ObjectKeyFromObject(privateSecret), privateSecret)
if err != nil {
return nil, fmt.Errorf("failed to get private secret: %v", err)
}

Block, _ := pem.Decode(privateSecret.Data["key"])
privateKey, err := x509.ParsePKCS1PrivateKey(Block.Bytes)

if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}

return privateKey, nil
}
26 changes: 2 additions & 24 deletions controllers/util/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,19 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"os"
"time"

"github.com/google/uuid"
"github.com/red-hat-storage/ocs-operator/v4/services"
)

// GenerateOnboardingToken generates a token valid for a duration of "tokenLifetimeInHours".
// The token content is predefined and signed by the private key which'll be read from supplied "privateKeyPath".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are updating the parameter type so I would suggest this comment should be update accordingly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack

// The token content is predefined and signed by the private key which'll be read from supplied "privateKey"
// The storageQuotaInGiB is optional, and it is used to limit the storage of PVC in the application cluster.
func GenerateOnboardingToken(tokenLifetimeInHours int, privateKeyPath string, storageQuotaInGiB *uint) (string, error) {
func GenerateOnboardingToken(tokenLifetimeInHours int, privateKey *rsa.PrivateKey, storageQuotaInGiB *uint) (string, error) {
tokenExpirationDate := time.Now().
Add(time.Duration(tokenLifetimeInHours) * time.Hour).
Unix()
Expand All @@ -46,11 +43,6 @@ func GenerateOnboardingToken(tokenLifetimeInHours int, privateKeyPath string, st
return "", fmt.Errorf("failed to hash onboarding token payload: %v", err)
}

privateKey, err := readAndDecodePrivateKey(privateKeyPath)
if err != nil {
return "", fmt.Errorf("failed to read and decode private key: %v", err)
}

msgHashSum := msgHash.Sum(nil)
// In order to generate the signature, we provide a random number generator,
// our private key, the hashing algorithm that we used, and the hash sum
Expand All @@ -63,17 +55,3 @@ func GenerateOnboardingToken(tokenLifetimeInHours int, privateKeyPath string, st
encodedSignature := base64.StdEncoding.EncodeToString(signature)
return fmt.Sprintf("%s.%s", encodedPayload, encodedSignature), nil
}

func readAndDecodePrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) {
pemString, err := os.ReadFile(privateKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to read private key: %v", err)
}

Block, _ := pem.Decode(pemString)
privateKey, err := x509.ParsePKCS1PrivateKey(Block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}
return privateKey, nil
}
2 changes: 2 additions & 0 deletions controllers/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const onboardingValidationPrivateKeySecretName = "onboarding-private-key"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its also defined in storageclient.go

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed unused code


func RemoveDuplicatesFromStringSlice(slice []string) []string {
keys := make(map[string]bool)
list := []string{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,10 @@ spec:
- name: ONBOARDING_TOKEN_LIFETIME
- name: UX_BACKEND_PORT
- name: TLS_ENABLED
- name: OPERATOR_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/ocs-dev/ocs-operator:latest
imagePullPolicy: IfNotPresent
name: ux-backend-server
Expand All @@ -717,8 +721,6 @@ spec:
readOnlyRootFilesystem: true
runAsNonRoot: true
volumeMounts:
- mountPath: /etc/private-key
name: onboarding-private-key
- mountPath: /etc/tls/private
name: ux-cert-secret
- args:
Expand Down Expand Up @@ -754,10 +756,6 @@ spec:
operator: Equal
value: "true"
volumes:
- name: onboarding-private-key
secret:
optional: true
secretName: onboarding-private-key
- name: ux-proxy-secret
secret:
secretName: ux-backend-proxy
Expand Down
21 changes: 13 additions & 8 deletions services/ux-backend/handlers/onboardingtokens/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import (
"github.com/red-hat-storage/ocs-operator/v4/services/ux-backend/handlers"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
)

const (
onboardingPrivateKeyFilePath = "/etc/private-key/key"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var unitToGib = map[string]uint{
Expand All @@ -22,20 +19,27 @@ var unitToGib = map[string]uint{
"Pi": 1024 * 1024,
}

func HandleMessage(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int) {
func HandleMessage(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int, cl client.Client) {
switch r.Method {
case "POST":
handlePost(w, r, tokenLifetimeInHours)
handlePost(w, r, tokenLifetimeInHours, cl)
default:
handleUnsupportedMethod(w, r)
}
}

func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int) {
func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int, cl client.Client) {
var storageQuotaInGiB *uint
// When ContentLength is 0 that means request body is empty and
// storage quota is unlimited
var err error

privateKey, err := util.ReadPrivateKey(cl)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to get private key: %v", err), http.StatusBadRequest)
return
}

if r.ContentLength != 0 {
var quota = struct {
Value uint `json:"value"`
Expand All @@ -57,7 +61,8 @@ func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int
}
storageQuotaInGiB = ptr.To(unitAsGiB * quota.Value)
}
if onboardingToken, err := util.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, storageQuotaInGiB); err != nil {

if onboardingToken, err := util.GenerateOnboardingToken(tokenLifetimeInHours, privateKey, storageQuotaInGiB); err != nil {
klog.Errorf("failed to get onboardig token: %v", err)
w.WriteHeader(http.StatusInternalServerError)
w.Header().Set("Content-Type", handlers.ContentTypeTextPlain)
Expand Down
39 changes: 36 additions & 3 deletions services/ux-backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ import (
"os"
"strconv"

"k8s.io/klog/v2"

v1 "github.com/red-hat-storage/ocs-operator/api/v4/v1"
"github.com/red-hat-storage/ocs-operator/v4/services/ux-backend/handlers/onboardingtokens"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)

type serverConfig struct {
client.Client
listenPort int
tokenLifetimeInHours int
tlsEnabled bool
Expand Down Expand Up @@ -61,8 +66,14 @@ func main() {
klog.Info("shutting down!")
os.Exit(-1)
}

cl, err := newClient()
if err != nil {
klog.Exitf("failed to create client: %v", err)
}

http.HandleFunc("/onboarding-tokens", func(w http.ResponseWriter, r *http.Request) {
onboardingtokens.HandleMessage(w, r, config.tokenLifetimeInHours)
onboardingtokens.HandleMessage(w, r, config.tokenLifetimeInHours, cl)
})

klog.Info("ux backend server listening on port ", config.listenPort)
Expand All @@ -82,3 +93,25 @@ func main() {
log.Fatal(err)

}

func newClient() (client.Client, error) {
klog.Info("Setting up k8s client")
scheme := runtime.NewScheme()
if err := v1.AddToScheme(scheme); err != nil {
return nil, err
}
if err := corev1.AddToScheme(scheme); err != nil {
return nil, err
}

config, err := config.GetConfig()
if err != nil {
return nil, err
}
k8sClient, err := client.New(config, client.Options{Scheme: scheme})
if err != nil {
return nil, err
}

return k8sClient, nil
}
21 changes: 8 additions & 13 deletions tools/csv-merger/csv-merger.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,10 +644,6 @@ func getUXBackendServerDeployment() appsv1.DeploymentSpec {
{
Name: "ux-backend-server",
VolumeMounts: []corev1.VolumeMount{
{
Name: "onboarding-private-key",
MountPath: "/etc/private-key",
},
{
Name: "ux-cert-secret",
MountPath: "/etc/tls/private",
Expand All @@ -674,6 +670,14 @@ func getUXBackendServerDeployment() appsv1.DeploymentSpec {
Name: "TLS_ENABLED",
Value: os.Getenv("TLS_ENABLED"),
},
{
Name: util.OperatorNamespaceEnvVar,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
},
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(true),
Expand Down Expand Up @@ -716,15 +720,6 @@ func getUXBackendServerDeployment() appsv1.DeploymentSpec {
},
},
Volumes: []corev1.Volume{
{
Name: "onboarding-private-key",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "onboarding-private-key",
Optional: ptr.To(true),
},
},
},
{
Name: "ux-proxy-secret",
VolumeSource: corev1.VolumeSource{
Expand Down
Loading