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 client-onboarding-token-validation #2827

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
45 changes: 29 additions & 16 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"
mrudraia1 marked this conversation as resolved.
Show resolved Hide resolved

mrudraia1 marked this conversation as resolved.
Show resolved Hide resolved
// 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,30 @@ 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)
}

Block, _ := pem.Decode(pemString)
privateKey, err := x509.ParsePKCS1PrivateKey(Block.Bytes)
privateSecretKey := privateSecret.Data["key"]
if len(privateSecretKey) == 0 {
return nil, fmt.Errorf("No data found in secret")
}

block, rest := pem.Decode(privateSecretKey)
if len(rest) > 0 {
return nil, fmt.Errorf("PEM block not found in private key: %s", rest)
}

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 @@ -647,8 +647,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 @@ -684,10 +682,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
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ rules:
verbs:
- get
- list
- apiGroups:
- ocs.openshift.io
resources:
- storageclusters/finalizers
verbs:
- update

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

4 changes: 2 additions & 2 deletions onboarding-validation-keys-generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func main() {
klog.Exitf("failed to delete public secret: %v", err)
}

err = controllerutil.SetOwnerReference(storageCluster, privateSecret, cl.Scheme())
err = controllerutil.SetControllerReference(storageCluster, privateSecret, cl.Scheme())
Copy link
Member

Choose a reason for hiding this comment

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

You would need additional permissions inorder to add a ControllerReference, please make sure that you add the required permissions

Copy link
Author

Choose a reason for hiding this comment

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

Added the permissions for ControllerReference

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe Rewant mentioned adding additional permissions, not removing existing ones, which suggests Owner and Controller are needed. @rewantsoni am I right here?

if err != nil {
klog.Exitf("failed to set owner reference for private secret: %v", err)
}
Expand All @@ -95,7 +95,7 @@ func main() {
klog.Exitf("failed to create private secret: %v", err)
}

err = controllerutil.SetOwnerReference(storageCluster, publicSecret, cl.Scheme())
err = controllerutil.SetControllerReference(storageCluster, publicSecret, cl.Scheme())
if err != nil {
klog.Exitf("failed to set owner reference for public secret: %v", err)
}
Expand Down
7 changes: 7 additions & 0 deletions rbac/onboarding-validation-keys-generator-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ rules:
verbs:
- get
- list
- apiGroups:
- ocs.openshift.io
resources:
- storageclusters/finalizers
verbs:
- update

22 changes: 15 additions & 7 deletions services/ux-backend/handlers/onboarding/clienttokens/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

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

var unitToGib = map[string]uint{
"Gi": 1,
"Ti": 1024,
Expand All @@ -37,13 +33,13 @@ func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int
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 +62,19 @@ func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int
return
}

if onboardingToken, err := util.GenerateClientOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, storageQuotaInGiB, storageCluster.UID); err != nil {
klog.Info("Loading onboarding validation private Key")
privateKey, err := util.LoadOnboardingValidationPrivateKey(r.Context(), cl, namespace)
if err != nil {
http.Error(w, fmt.Sprintf("Failed loading onboarding validation 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
Loading
Loading