Skip to content

Commit

Permalink
Read secrets for client-onboarding-token-validation
Browse files Browse the repository at this point in the history
Signed-off-by: Mrudraia1 <mrudraia@redhat.com>
  • Loading branch information
mrudraia1 committed Nov 12, 2024
1 parent 502f1dd commit bbae1f7
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 76 deletions.
9 changes: 6 additions & 3 deletions controllers/storagecluster/storageclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import (
)

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

ocsClientConfigMapName = "ocs-client-operator-config"
manageNoobaaSubKey = "manageNoobaaSubscription"
Expand Down Expand Up @@ -47,7 +46,11 @@ func (s *storageClient) ensureCreated(r *StorageClusterReconciler, storagecluste
storageClient.Name = storagecluster.Name
_, err := controllerutil.CreateOrUpdate(r.ctx, r.Client, storageClient, func() error {
if storageClient.Status.ConsumerID == "" {
token, err := util.GenerateClientOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, nil, storagecluster.UID)
privateKey, err := util.LoadOnboardingValidationPrivateKey(r.ctx, r.Client, r.OperatorNamespace)
if err != nil {
return fmt.Errorf("unable to get Parsed Private Key: %v", err)
}
token, err := util.GenerateClientOnboardingToken(tokenLifetimeInHours, privateKey, nil, storagecluster.UID)
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{})).
Owns(&routev1.Route{}).
Owns(&templatev1.Template{}).
// Using builder.OnlyMetadata as we are only interested in the presence and not getting this resource anywhere
Expand Down
39 changes: 24 additions & 15 deletions controllers/util/provider.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
Expand All @@ -10,19 +11,22 @@ import (
"encoding/json"
"encoding/pem"
"fmt"
"os"
"time"

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

"github.com/google/uuid"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const onboardingValidationPrivateKeySecretName = "onboarding-private-key"

// GenerateClientOnboardingToken generates a ocs-client 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".
// The storageQuotaInGiB is optional, and it is used to limit the storage of PVC in the application cluster.
func GenerateClientOnboardingToken(tokenLifetimeInHours int, privateKeyPath string, storageQuotainGib *uint, storageClusterUID types.UID) (string, error) {
func GenerateClientOnboardingToken(tokenLifetimeInHours int, privateKey *rsa.PrivateKey, storageQuotainGib *uint, storageClusterUID types.UID) (string, error) {
tokenExpirationDate := time.Now().
Add(time.Duration(tokenLifetimeInHours) * time.Hour).
Unix()
Expand All @@ -35,7 +39,7 @@ func GenerateClientOnboardingToken(tokenLifetimeInHours int, privateKeyPath stri
StorageCluster: storageClusterUID,
}

token, err := encodeAndSignOnboardingToken(privateKeyPath, ticket)
token, err := encodeAndSignOnboardingToken(privateKey, ticket)
if err != nil {
return "", err
}
Expand All @@ -44,7 +48,7 @@ func GenerateClientOnboardingToken(tokenLifetimeInHours int, privateKeyPath stri

// GeneratePeerOnboardingToken generates a ocs-peer 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".
func GeneratePeerOnboardingToken(tokenLifetimeInHours int, privateKeyPath string, storageClusterUID types.UID) (string, error) {
func GeneratePeerOnboardingToken(tokenLifetimeInHours int, privateKey *rsa.PrivateKey, storageClusterUID types.UID) (string, error) {
tokenExpirationDate := time.Now().
Add(time.Duration(tokenLifetimeInHours) * time.Hour).
Unix()
Expand All @@ -55,7 +59,7 @@ func GeneratePeerOnboardingToken(tokenLifetimeInHours int, privateKeyPath string
SubjectRole: services.PeerRole,
StorageCluster: storageClusterUID,
}
token, err := encodeAndSignOnboardingToken(privateKeyPath, ticket)
token, err := encodeAndSignOnboardingToken(privateKey, ticket)
if err != nil {
return "", err
}
Expand All @@ -64,7 +68,7 @@ func GeneratePeerOnboardingToken(tokenLifetimeInHours int, privateKeyPath string

// encodeAndSignOnboardingToken generates a token from the ticket.
// The token content is predefined and signed by the private key which'll be read from supplied "privateKeyPath".
func encodeAndSignOnboardingToken(privateKeyPath string, ticket services.OnboardingTicket) (string, error) {
func encodeAndSignOnboardingToken(privateKey *rsa.PrivateKey, ticket services.OnboardingTicket) (string, error) {
payload, err := json.Marshal(ticket)
if err != nil {
return "", fmt.Errorf("failed to marshal the payload: %v", err)
Expand All @@ -79,11 +83,6 @@ func encodeAndSignOnboardingToken(privateKeyPath string, ticket services.Onboard
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 @@ -97,16 +96,26 @@ func encodeAndSignOnboardingToken(privateKeyPath string, ticket services.Onboard
return fmt.Sprintf("%s.%s", encodedPayload, encodedSignature), nil
}

func readAndDecodePrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) {
pemString, err := os.ReadFile(privateKeyPath)
func LoadOnboardingValidationPrivateKey(ctx context.Context, cl client.Client, namespace string) (*rsa.PrivateKey, error) {
privateSecret := &corev1.Secret{}
privateSecret.Name = onboardingValidationPrivateKeySecretName
privateSecret.Namespace = namespace

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

privateSecretKey, ok := privateSecret.Data["key"]
if !ok {
return nil, fmt.Errorf("No data found in secret")
}

Block, _ := pem.Decode(pemString)
Block, _ := pem.Decode(privateSecretKey)
privateKey, err := x509.ParsePKCS1PrivateKey(Block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}

return privateKey, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,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 @@ -678,10 +676,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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 19 additions & 10 deletions services/ux-backend/handlers/onboarding/clienttokens/handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package clienttokens

import (
"context"
"encoding/json"
"fmt"
"math"
Expand All @@ -14,36 +15,32 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

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

var unitToGib = map[string]uint{
"Gi": 1,
"Ti": 1024,
"Pi": 1024 * 1024,
}

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

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

if r.ContentLength != 0 {
var quota = struct {
Value uint `json:"value"`
Unit string `json:"unit"`
}{}
if err = json.NewDecoder(r.Body).Decode(&quota); err != nil {
if err := json.NewDecoder(r.Body).Decode(&quota); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
Expand All @@ -66,7 +63,19 @@ func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int
return
}

if onboardingToken, err := util.GenerateClientOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, storageQuotaInGiB, storageCluster.UID); err != nil {
privateKey, err := util.LoadOnboardingValidationPrivateKey(ctx, cl, namespace)
klog.Info("Getting the Pem key")
if err != nil {
http.Error(w, fmt.Sprintf("Failed to get private key: %v", err), http.StatusBadRequest)
return
}

if onboardingToken, err := util.GenerateClientOnboardingToken(
tokenLifetimeInHours,
privateKey,
storageQuotaInGiB,
storageCluster.UID,
); err != nil {
klog.Errorf("failed to get onboarding token: %v", err)
w.WriteHeader(http.StatusInternalServerError)
w.Header().Set("Content-Type", handlers.ContentTypeTextPlain)
Expand Down
20 changes: 14 additions & 6 deletions services/ux-backend/handlers/onboarding/peertokens/handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package peertokens

import (
"context"
"fmt"
"net/http"

Expand All @@ -11,10 +12,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

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

func HandleMessage(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int, cl client.Client, namespace string) {
switch r.Method {
case "POST":
Expand All @@ -25,14 +22,25 @@ func HandleMessage(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours
}

func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int, cl client.Client, namespace string) {

ctx := context.Background()
storageCluster, err := util.GetStorageClusterInNamespace(r.Context(), cl, namespace)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

if onboardingToken, err := util.GeneratePeerOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, storageCluster.UID); err != nil {
privateKey, err := util.LoadOnboardingValidationPrivateKey(ctx, cl, namespace)
klog.Info("Getting the Pem key")
if err != nil {
http.Error(w, fmt.Sprintf("Failed to get private key: %v", err), http.StatusBadRequest)
return
}

if onboardingToken, err := util.GeneratePeerOnboardingToken(
tokenLifetimeInHours,
privateKey,
storageCluster.UID,
); err != nil {
klog.Errorf("failed to get onboarding token: %v", err)
w.WriteHeader(http.StatusInternalServerError)
w.Header().Set("Content-Type", handlers.ContentTypeTextPlain)
Expand Down
Loading

0 comments on commit bbae1f7

Please sign in to comment.