Skip to content

Commit

Permalink
ROX-11640: RHSSO dynamic clients rotation API (#1236)
Browse files Browse the repository at this point in the history
Co-authored-by: dhaus67 <[email protected]>
  • Loading branch information
ivan-degtiarenko and dhaus67 authored Sep 13, 2023
1 parent 3e65804 commit 8220021
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 36 deletions.
38 changes: 38 additions & 0 deletions internal/dinosaur/pkg/api/admin/private/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,44 @@ paths:
security:
- Bearer: []
summary: Update a Central instance by ID
/api/rhacs/v1/admin/centrals/{id}/rotate-secrets:
post:
parameters:
- description: The ID of record
in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: RHSSO client successfully rotated
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Auth token is invalid
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: User is not authorised to access the service
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: No Central found with the specified ID or dynamic clients are
not configured
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Unexpected error occurred
summary: Rotate RHSSO client of a central tenant
/api/rhacs/v1/admin/centrals/{id}/restore:
post:
parameters:
Expand Down
105 changes: 105 additions & 0 deletions internal/dinosaur/pkg/api/admin/private/api_default.go

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

22 changes: 22 additions & 0 deletions internal/dinosaur/pkg/handlers/admin_dinosaur.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type AdminCentralHandler interface {
GetCentralDefaultVersion(w http.ResponseWriter, r *http.Request)
// Restore restores a tenant that was already marked as deleted
Restore(w http.ResponseWriter, r *http.Request)
// RotateSecrets rotates secrets within central
RotateSecrets(w http.ResponseWriter, r *http.Request)
}

type adminCentralHandler struct {
Expand Down Expand Up @@ -427,6 +429,22 @@ func (h adminCentralHandler) GetCentralDefaultVersion(w http.ResponseWriter, r *
handlers.Handle(w, r, cfg, http.StatusOK)
}

func (h adminCentralHandler) RotateSecrets(w http.ResponseWriter, r *http.Request) {
cfg := &handlers.HandlerConfig{
Action: func() (i interface{}, serviceError *errors.ServiceError) {
id := mux.Vars(r)["id"]
ctx := r.Context()
centralRequest, err := h.service.Get(ctx, id)
if err != nil {
return nil, err
}
err = h.service.RotateCentralRHSSOClient(ctx, centralRequest)
return nil, err
},
}
handlers.Handle(w, r, cfg, http.StatusOK)
}

type gitOpsAdminHandler struct{}

var _ AdminCentralHandler = (*gitOpsAdminHandler)(nil)
Expand Down Expand Up @@ -466,3 +484,7 @@ func (g gitOpsAdminHandler) GetCentralDefaultVersion(w http.ResponseWriter, r *h
func (g gitOpsAdminHandler) Restore(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not implemented", http.StatusNotImplemented)
}

func (g gitOpsAdminHandler) RotateSecrets(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not implemented", http.StatusNotImplemented)
}
38 changes: 38 additions & 0 deletions internal/dinosaur/pkg/rhsso/augment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package rhsso

import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/dbapi"
"github.com/stackrox/acs-fleet-manager/pkg/client/iam"
"github.com/stackrox/acs-fleet-manager/pkg/client/redhatsso/api"
"github.com/stackrox/rox/pkg/stringutils"
)

const (
oidcProviderCallbackPath = "/sso/providers/oidc/callback"
dynamicClientsNameMaxLength = 50
)

func AugmentWithDynamicAuthConfig(ctx context.Context, r *dbapi.CentralRequest, realmConfig *iam.IAMRealmConfig, apiClient *api.AcsTenantsApiService) error {
// There is a limit on name length of the dynamic client. To avoid unnecessary errors,
// we truncate name here.
name := stringutils.Truncate(fmt.Sprintf("acscs-%s", r.Name), dynamicClientsNameMaxLength)
orgID := r.OrganisationID
redirectURIs := []string{fmt.Sprintf("https://%s%s", r.GetUIHost(), oidcProviderCallbackPath)}

dynamicClientData, _, err := apiClient.CreateAcsClient(ctx, api.AcsClientRequestData{
Name: name,
OrgId: orgID,
RedirectUris: redirectURIs,
})
if err != nil {
return errors.Wrapf(err, "failed to create RHSSO dynamic client for %s", r.ID)
}

r.AuthConfig.ClientID = dynamicClientData.ClientId
r.AuthConfig.ClientSecret = dynamicClientData.Secret // pragma: allowlist secret
r.AuthConfig.Issuer = realmConfig.ValidIssuerURI
return nil
}
3 changes: 3 additions & 0 deletions internal/dinosaur/pkg/routes/route_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ func (s *options) buildAPIBaseRouter(mainRouter *mux.Router, basePath string, op
adminCentralsRouter.HandleFunc("/{id}/restore", adminCentralHandler.Restore).
Name(logger.NewLogEvent("admin-restore-central", "[admin] restore central by id").ToString()).
Methods(http.MethodPost)
adminCentralsRouter.HandleFunc("/{id}/rotate-secrets", adminCentralHandler.RotateSecrets).
Name(logger.NewLogEvent("admin-rotate-central-secrets", "[admin] rotate central secrets by id").ToString()).
Methods(http.MethodPost)

adminCreateRouter := adminCentralsRouter.NewRoute().Subrouter()
adminCreateRouter.HandleFunc("", adminCentralHandler.Create).Methods(http.MethodPost)
Expand Down
36 changes: 35 additions & 1 deletion internal/dinosaur/pkg/services/dinosaur.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package services
import (
"context"
"fmt"
"github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/rhsso"
"github.com/stackrox/acs-fleet-manager/pkg/client/iam"
dynamicClientAPI "github.com/stackrox/acs-fleet-manager/pkg/client/redhatsso/api"
"github.com/stackrox/acs-fleet-manager/pkg/client/redhatsso/dynamicclients"
"sync"
"time"

Expand Down Expand Up @@ -106,6 +110,7 @@ type DinosaurService interface {
ListCentralsWithoutAuthConfig() ([]*dbapi.CentralRequest, *errors.ServiceError)
VerifyAndUpdateDinosaurAdmin(ctx context.Context, dinosaurRequest *dbapi.CentralRequest) *errors.ServiceError
Restore(ctx context.Context, id string) *errors.ServiceError
RotateCentralRHSSOClient(ctx context.Context, centralRequest *dbapi.CentralRequest) *errors.ServiceError
}

var _ DinosaurService = &dinosaurService{}
Expand All @@ -124,17 +129,20 @@ type dinosaurService struct {
clusterPlacementStrategy ClusterPlacementStrategy
amsClient ocm.AMSClient
centralDefaultVersionService CentralDefaultVersionService
iamConfig *iam.IAMConfig
rhSSODynamicClientsAPI *dynamicClientAPI.AcsTenantsApiService
}

// NewDinosaurService ...
func NewDinosaurService(connectionFactory *db.ConnectionFactory, clusterService ClusterService, iamService sso.IAMService,
dinosaurConfig *config.CentralConfig, dataplaneClusterConfig *config.DataplaneClusterConfig, awsConfig *config.AWSConfig,
iamConfig *iam.IAMConfig, dinosaurConfig *config.CentralConfig, dataplaneClusterConfig *config.DataplaneClusterConfig, awsConfig *config.AWSConfig,
quotaServiceFactory QuotaServiceFactory, awsClientFactory aws.ClientFactory, authorizationService authorization.Authorization,
clusterPlacementStrategy ClusterPlacementStrategy, amsClient ocm.AMSClient, centralDefaultVersionService CentralDefaultVersionService) *dinosaurService {
return &dinosaurService{
connectionFactory: connectionFactory,
clusterService: clusterService,
iamService: iamService,
iamConfig: iamConfig,
dinosaurConfig: dinosaurConfig,
awsConfig: awsConfig,
quotaServiceFactory: quotaServiceFactory,
Expand All @@ -144,9 +152,35 @@ func NewDinosaurService(connectionFactory *db.ConnectionFactory, clusterService
clusterPlacementStrategy: clusterPlacementStrategy,
amsClient: amsClient,
centralDefaultVersionService: centralDefaultVersionService,
rhSSODynamicClientsAPI: dynamicclients.NewDynamicClientsAPI(iamConfig.RedhatSSORealm),
}
}

func (k *dinosaurService) RotateCentralRHSSOClient(ctx context.Context, centralRequest *dbapi.CentralRequest) *errors.ServiceError {
realmConfig := k.iamConfig.RedhatSSORealm
if k.dinosaurConfig.HasStaticAuth() {
return errors.New(errors.ErrorDynamicClientsNotUsed, "RHSSO is configured via static configuration")
}
if !realmConfig.IsConfigured() {
return errors.New(errors.ErrorDynamicClientsNotUsed, "RHSSO dynamic client configuration is not present")
}

previousAuthConfig := centralRequest.AuthConfig
if err := rhsso.AugmentWithDynamicAuthConfig(ctx, centralRequest, k.iamConfig.RedhatSSORealm, k.rhSSODynamicClientsAPI); err != nil {
return errors.NewWithCause(errors.ErrorClientRotationFailed, err, "failed to augment auth config")

}
if err := k.Update(centralRequest); err != nil {
glog.Errorf("Rotating RHSSO client failed: created new RHSSO dynamic client, but failed to update central record, client ID is %s", centralRequest.AuthConfig.ClientID)
return errors.NewWithCause(errors.ErrorClientRotationFailed, err, "failed to update database record")
}
if _, err := k.rhSSODynamicClientsAPI.DeleteAcsClient(ctx, previousAuthConfig.ClientID); err != nil {
glog.Errorf("Rotating RHSSO client failed: failed to delete RHSSO dynamic client, client ID is %s", centralRequest.AuthConfig.ClientID)
return errors.NewWithCause(errors.ErrorClientRotationFailed, err, "failed to delete previous RHSSO dynamic client")
}
return nil
}

// HasAvailableCapacityInRegion ...
func (k *dinosaurService) HasAvailableCapacityInRegion(dinosaurRequest *dbapi.CentralRequest) (bool, *errors.ServiceError) {
regionCapacity := int64(k.dataplaneClusterConfig.ClusterConfig.GetCapacityForRegion(dinosaurRequest.Region))
Expand Down
Loading

0 comments on commit 8220021

Please sign in to comment.