Skip to content

Commit

Permalink
changes to onboarding token to include subjectRole in body
Browse files Browse the repository at this point in the history
Signed-off-by: Rewant Soni <resoni@redhat.com>
  • Loading branch information
rewantsoni committed Sep 9, 2024
1 parent 27b0ae9 commit 1c07f07
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 38 deletions.
4 changes: 3 additions & 1 deletion controllers/storagecluster/storageclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
ocsclientv1a1 "github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"
ocsv1 "github.com/red-hat-storage/ocs-operator/api/v4/v1"
"github.com/red-hat-storage/ocs-operator/v4/controllers/util"
"github.com/red-hat-storage/ocs-operator/v4/services"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand All @@ -31,7 +32,8 @@ 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.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, nil)
roleSpec := services.OnboardingSubjectRoleSpec{Role: services.ClientRole}
token, err := util.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, roleSpec)
if err != nil {
return fmt.Errorf("unable to generate onboarding token: %v", err)
}
Expand Down
9 changes: 4 additions & 5 deletions controllers/util/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,18 @@ import (

// 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".
// 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) {
// The roleSpec contains the SubjectRole and the RoleOptions for the ticket.
func GenerateOnboardingToken(tokenLifetimeInHours int, privateKeyPath string, roleSpec services.OnboardingSubjectRoleSpec) (string, error) {
tokenExpirationDate := time.Now().
Add(time.Duration(tokenLifetimeInHours) * time.Hour).
Unix()

ticket := services.OnboardingTicket{
ID: uuid.New().String(),
ExpirationDate: tokenExpirationDate,
SubjectRole: roleSpec,
}
if storageQuotaInGiB != nil {
ticket.StorageQuotaInGiB = *storageQuotaInGiB
}

payload, err := json.Marshal(ticket)
if err != nil {
return "", fmt.Errorf("failed to marshal the payload: %v", err)
Expand Down
13 changes: 8 additions & 5 deletions services/provider/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ func (s *OCSProviderServer) OnboardConsumer(ctx context.Context, req *pb.Onboard
return nil, status.Errorf(codes.Internal, "failed to get public key to validate onboarding ticket for consumer %q. %v", req.ConsumerName, err)
}

onboardingTicket, err := decodeAndValidateTicket(req.OnboardingTicket, pubKey)
onboardingTicket, err := decodeAndValidateTicket(req.OnboardingTicket, pubKey, services.ClientRole)
if err != nil {
klog.Errorf("failed to validate onboarding ticket for consumer %q. %v", req.ConsumerName, err)
return nil, status.Errorf(codes.InvalidArgument, "onboarding ticket is not valid. %v", err)
}

storageConsumerUUID, err := s.consumerManager.Create(ctx, req, int(onboardingTicket.StorageQuotaInGiB))
storageConsumerUUID, err := s.consumerManager.Create(ctx, req, int(onboardingTicket.SubjectRole.ClientOptions.StorageQuotaInGiB))
if err != nil {
if !kerrors.IsAlreadyExists(err) && err != errTicketAlreadyExists {
return nil, status.Errorf(codes.Internal, "failed to create storageConsumer %q. %v", req.ConsumerName, err)
Expand Down Expand Up @@ -470,7 +470,7 @@ func getSubVolumeGroupClusterID(subVolumeGroup *rookCephv1.CephFilesystemSubVolu
return hex.EncodeToString(hash[:16])
}

func decodeAndValidateTicket(ticket string, pubKey *rsa.PublicKey) (*services.OnboardingTicket, error) {
func decodeAndValidateTicket(ticket string, pubKey *rsa.PublicKey, expectedRole services.OnboardingSubjectRole) (*services.OnboardingTicket, error) {
ticketArr := strings.Split(string(ticket), ".")
if len(ticketArr) != 2 {
return nil, fmt.Errorf("invalid ticket")
Expand All @@ -487,8 +487,11 @@ func decodeAndValidateTicket(ticket string, pubKey *rsa.PublicKey) (*services.On
return nil, fmt.Errorf("failed to unmarshal onboarding ticket message. %v", err)
}

if ticketData.StorageQuotaInGiB > math.MaxInt {
return nil, fmt.Errorf("invalid value sent in onboarding ticket, storage quota should be greater than 0 and less than %v: %v", math.MaxInt, ticketData.StorageQuotaInGiB)
if expectedRole == services.ClientRole {
clientOptions := ticketData.SubjectRole.ClientOptions
if ticketData.SubjectRole.ClientOptions.StorageQuotaInGiB > math.MaxInt {
return nil, fmt.Errorf("invalid value sent in onboarding ticket, storage quota should be greater than 0 and less than %v: %v", math.MaxInt, clientOptions.StorageQuotaInGiB)
}
}

signature, err := base64.StdEncoding.DecodeString(ticketArr[1])
Expand Down
27 changes: 24 additions & 3 deletions services/types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
package services

type OnboardingSubjectRole string

type OnboardingSubjectRoleSpec struct {
Role OnboardingSubjectRole `json:"role"`
ClientOptions ClientRoleOptions `json:"clientRoleOptions,omitempty"`
PeerOptions PeerRoleOptions `json:"peerRoleOptions,omitempty"`
}

const (
ClientRole OnboardingSubjectRole = "ocs-client"
PeerRole OnboardingSubjectRole = "ocs-peer"
)

type ClientRoleOptions struct {
StorageQuotaInGiB uint `json:"storageQuotaInGiB,omitempty"`
}

type PeerRoleOptions struct{}

type OnboardingTicket struct {
ID string `json:"id"`
ExpirationDate int64 `json:"expirationDate,string"`
StorageQuotaInGiB uint `json:"storageQuotaInGiB,omitempty"`
ID string `json:"id"`
ExpirationDate int64 `json:"expirationDate,string"`

// SubjectRole specifies the role and options for the role
SubjectRole OnboardingSubjectRoleSpec `json:"subjectRole"`
}
78 changes: 54 additions & 24 deletions services/ux-backend/handlers/onboardingtokens/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
"net/http"

"github.com/red-hat-storage/ocs-operator/v4/controllers/util"
"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"
"k8s.io/utils/ptr"
)

const (
Expand All @@ -32,33 +33,62 @@ func HandleMessage(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours
}

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 roleSpec services.OnboardingSubjectRoleSpec
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 {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if quota.Value == 0 || quota.Value > math.MaxInt {
http.Error(w, fmt.Sprintf("invalid value sent in request body, value should be greater than 0 and less than %v: %v", math.MaxInt, quota.Value), http.StatusBadRequest)
return
}
unitAsGiB, ok := unitToGib[quota.Unit]
if !ok {
http.Error(w, fmt.Sprintf("invalid Unit type sent in request body, Valid types are [Gi,Ti,Pi]: %v", quota.Unit), http.StatusBadRequest)
return
if r.ContentLength == 0 {
http.Error(w, "empty body sent in request, subjectRole is required", http.StatusBadRequest)
return
}
var body = struct {
SubjectRole string `json:"subjectRole"`
Options map[string]interface{} `json:"options"`
}{}
if err = json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

switch services.OnboardingSubjectRole(body.SubjectRole) {
case services.PeerRole:
roleSpec.Role = services.PeerRole
case services.ClientRole:
roleSpec.Role = services.ClientRole
// When options length is 0 that means client options body is empty and storage quota is unlimited
if len(body.Options) != 0 {
var clientOptions = struct {
Value uint `json:"value"`
Unit string `json:"unit"`
}{}
configBytes, err := json.Marshal(body.Options)
if err != nil {
http.Error(w, fmt.Sprintf("failed to marshal options for ocs-client role: %v", err), http.StatusBadRequest)
return
}
if err = json.Unmarshal(configBytes, &clientOptions); err != nil {
http.Error(w, fmt.Sprintf("failed to unmarshal options for ocs-client role: %v", err), http.StatusBadRequest)
return
}
if clientOptions.Value == 0 || clientOptions.Value > math.MaxInt {
http.Error(w, fmt.Sprintf("invalid value sent in request body, value should be greater than 0 and less than %v: %v", math.MaxInt, clientOptions.Value), http.StatusBadRequest)
return
}
unitAsGiB, ok := unitToGib[clientOptions.Unit]
if !ok {
http.Error(w, fmt.Sprintf("invalid Unit type sent in request body, Valid types are [Gi,Ti,Pi]: %v", clientOptions.Unit), http.StatusBadRequest)
return
}
storageQuotaInGiB := unitAsGiB * clientOptions.Value
roleSpec.ClientOptions = services.ClientRoleOptions{StorageQuotaInGiB: storageQuotaInGiB}
}
storageQuotaInGiB = ptr.To(unitAsGiB * quota.Value)
default:
http.Error(w, fmt.Sprintf("invalid subjectRole sent in request body, Valid roles are [ocs-peer,ocs-client]: %v", body.SubjectRole), http.StatusBadRequest)
return
}
if onboardingToken, err := util.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, storageQuotaInGiB); err != nil {
klog.Errorf("failed to get onboardig token: %v", err)

if onboardingToken, err := util.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath, roleSpec); err != nil {
klog.Errorf("failed to get onboarding token: %v", err)
w.WriteHeader(http.StatusInternalServerError)
w.Header().Set("Content-Type", handlers.ContentTypeTextPlain)

Expand Down

0 comments on commit 1c07f07

Please sign in to comment.