Skip to content

Commit

Permalink
MGMT-14582: Set OCI platform behind a capability (#5249)
Browse files Browse the repository at this point in the history
* MGMT-14582: Set OCI platform behind a capability

Ensure OCI platform cannot be used if the user's oragnization doesn't
have `bare_metal_installer_platform_oci` capability.

* factorize error message in HasOrgBasedCapability
  • Loading branch information
adriengentil committed May 31, 2023
1 parent 1d92a60 commit ee0971c
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 11 deletions.
10 changes: 7 additions & 3 deletions internal/bminventory/inventory.go
Expand Up @@ -540,6 +540,9 @@ func (b *bareMetalInventory) RegisterClusterInternal(
if err = validations.ValidateDualStackNetworks(params.NewClusterParams, false); err != nil {
return nil, common.NewApiError(http.StatusBadRequest, err)
}
if err = validations.ValidatePlatformCapability(params.NewClusterParams.Platform, ctx, b.authzHandler); err != nil {
return nil, common.NewApiError(http.StatusBadRequest, err)
}

params, err = b.setDefaultRegisterClusterParams(ctx, params, id)
if err != nil {
Expand Down Expand Up @@ -572,9 +575,6 @@ func (b *bareMetalInventory) RegisterClusterInternal(
// with multiarch in order to get access to that release payload.
var multiarchAllowed bool
multiarchAllowed, err = b.authzHandler.HasOrgBasedCapability(ctx, ocm.MultiarchCapabilityName)
if err != nil {
log.WithError(err).Errorf("error getting user %s capability", ocm.MultiarchCapabilityName)
}
if err != nil || !multiarchAllowed {
err = common.NewApiError(http.StatusBadRequest, errors.New("multiarch clusters are not available"))
return nil, err
Expand Down Expand Up @@ -1863,6 +1863,10 @@ func (b *bareMetalInventory) validateAndUpdateClusterParams(ctx context.Context,
return installer.V2UpdateClusterParams{}, err
}

if err := validations.ValidatePlatformCapability(params.ClusterUpdateParams.Platform, ctx, b.authzHandler); err != nil {
return installer.V2UpdateClusterParams{}, err
}

return *params, nil
}

Expand Down
153 changes: 153 additions & 0 deletions internal/bminventory/inventory_test.go
Expand Up @@ -7776,6 +7776,81 @@ var _ = Describe("V2ClusterUpdate cluster", func() {
Expect(swag.BoolValue(actual.Platform.IsExternal)).To(BeFalse())
})
})
Context("Update from none platform to oci platform with RHSSO", func() {
var cluster *common.Cluster
var mockOcmAuthz *ocm.MockOCMAuthorization
var authCtx context.Context
var payload *ocm.AuthPayload
BeforeEach(func() {
clusterID = strfmt.UUID(uuid.New().String())
cluster = &common.Cluster{Cluster: models.Cluster{
ID: &clusterID,
HighAvailabilityMode: swag.String(models.ClusterHighAvailabilityModeFull),
UserManagedNetworking: swag.Bool(true),
Platform: &models.Platform{
Type: common.PlatformTypePtr(models.PlatformTypeNone),
},
CPUArchitecture: common.X86CPUArchitecture,
}}
err := db.Create(cluster).Error
Expect(err).ShouldNot(HaveOccurred())

cfg := auth.GetConfigRHSSO()
cfg.EnableOrgBasedFeatureGates = true
mockOcmAuthz = ocm.NewMockOCMAuthorization(ctrl)
mockOcmClient := &ocm.Client{Cache: cache.New(10*time.Minute, 30*time.Minute), Authorization: mockOcmAuthz}
bm.authHandler = auth.NewRHSSOAuthenticator(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
bm.authzHandler = auth.NewAuthzHandler(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
payload = &ocm.AuthPayload{Role: ocm.UserRole}
payload.Username = "user1"
payload.Organization = "org1"
authCtx = context.WithValue(ctx, restapi.AuthKey, payload)

})

It("Update platform=oci - PlatformOci capability is set - success", func() {
mockSuccess()
mockProviderRegistry.EXPECT().SetPlatformUsages(models.PlatformTypeOci, gomock.Any(), mockUsage)
mockOcmAuthz.EXPECT().CapabilityReview(gomock.Any(), payload.Username, ocm.PlatformOciCapabilityName, ocm.OrganizationCapabilityType).Return(true, nil).Times(1)
mockClusterApi.EXPECT().VerifyClusterUpdatability(createClusterIdMatcher(cluster)).Return(nil).Times(1)

reply := bm.V2UpdateCluster(authCtx, installer.V2UpdateClusterParams{
ClusterID: clusterID,
ClusterUpdateParams: &models.V2ClusterUpdateParams{
Platform: &models.Platform{Type: common.PlatformTypePtr(models.PlatformTypeOci)},
},
})
Expect(reply).Should(BeAssignableToTypeOf(installer.NewV2UpdateClusterCreated()))
actual := reply.(*installer.V2UpdateClusterCreated).Payload
Expect(swag.BoolValue(actual.UserManagedNetworking)).To(Equal(true))
Expect(*actual.Platform.Type).To(Equal(models.PlatformTypeOci))
Expect(swag.BoolValue(actual.Platform.IsExternal)).To(BeTrue())
})

It("Update platform=oci - PlatformOci capability is not set - failure", func() {
mockOcmAuthz.EXPECT().CapabilityReview(gomock.Any(), payload.Username, ocm.PlatformOciCapabilityName, ocm.OrganizationCapabilityType).Return(false, nil).Times(1)

reply := bm.V2UpdateCluster(authCtx, installer.V2UpdateClusterParams{
ClusterID: clusterID,
ClusterUpdateParams: &models.V2ClusterUpdateParams{
Platform: &models.Platform{Type: common.PlatformTypePtr(models.PlatformTypeOci)},
},
})
verifyApiErrorString(reply, http.StatusBadRequest, "Platform oci is not available")
})

It("Update platform=oci - CapabilityReview returns an error - failure", func() {
mockOcmAuthz.EXPECT().CapabilityReview(gomock.Any(), payload.Username, ocm.PlatformOciCapabilityName, ocm.OrganizationCapabilityType).Return(true, errors.Errorf("some error")).Times(1)

reply := bm.V2UpdateCluster(authCtx, installer.V2UpdateClusterParams{
ClusterID: clusterID,
ClusterUpdateParams: &models.V2ClusterUpdateParams{
Platform: &models.Platform{Type: common.PlatformTypePtr(models.PlatformTypeOci)},
},
})
verifyApiErrorString(reply, http.StatusBadRequest, "Platform oci is not available")
})
})
})

Context("Feature compatibility", func() {
Expand Down Expand Up @@ -17576,6 +17651,33 @@ var _ = Describe("Platform tests", func() {
Expect(swag.BoolValue(cluster.Platform.IsExternal)).Should(BeTrue())
Expect(swag.BoolValue(cluster.UserManagedNetworking)).Should(BeTrue())
})

It("OCI platform - RHSSO - PlatformOciCapability is set", func() {
cfg := auth.GetConfigRHSSO()
cfg.EnableOrgBasedFeatureGates = true
mockOcmAuthz := ocm.NewMockOCMAuthorization(ctrl)
mockOcmClient := &ocm.Client{Cache: cache.New(10*time.Minute, 30*time.Minute), Authorization: mockOcmAuthz}
bm.authHandler = auth.NewRHSSOAuthenticator(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
bm.authzHandler = auth.NewAuthzHandler(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
payload := &ocm.AuthPayload{Role: ocm.UserRole}
payload.Username = "user1"
payload.Organization = "org1"
authCtx := context.WithValue(ctx, restapi.AuthKey, payload)
mockOcmAuthz.EXPECT().CapabilityReview(gomock.Any(), payload.Username, ocm.PlatformOciCapabilityName, ocm.OrganizationCapabilityType).Return(true, nil).Times(1)

registerParams.NewClusterParams.Platform = &models.Platform{
Type: common.PlatformTypePtr(models.PlatformTypeOci),
}
reply := bm.V2RegisterCluster(authCtx, *registerParams)

Expect(reply).Should(BeAssignableToTypeOf(installer.NewV2RegisterClusterCreated()))
cluster := reply.(*installer.V2RegisterClusterCreated).Payload
Expect(cluster.Platform).ShouldNot(BeNil())
Expect(common.PlatformTypeValue(cluster.Platform.Type)).Should(BeEquivalentTo(models.PlatformTypeOci))
Expect(swag.BoolValue(cluster.Platform.IsExternal)).Should(BeTrue())
Expect(swag.BoolValue(cluster.UserManagedNetworking)).Should(BeTrue())
})

})

Context("Failed to register cluster", func() {
Expand Down Expand Up @@ -17609,6 +17711,57 @@ var _ = Describe("Platform tests", func() {
verifyApiError(reply, http.StatusBadRequest)
})

It("OCI platform - RHSSO - PlatformOciCapability is not set", func() {
cfg := auth.GetConfigRHSSO()
cfg.EnableOrgBasedFeatureGates = true
mockOcmAuthz := ocm.NewMockOCMAuthorization(ctrl)
mockOcmClient := &ocm.Client{Cache: cache.New(10*time.Minute, 30*time.Minute), Authorization: mockOcmAuthz}
bm.authHandler = auth.NewRHSSOAuthenticator(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
bm.authzHandler = auth.NewAuthzHandler(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
payload := &ocm.AuthPayload{Role: ocm.UserRole}
payload.Username = "user1"
payload.Organization = "org1"
authCtx := context.WithValue(ctx, restapi.AuthKey, payload)
mockOcmAuthz.EXPECT().CapabilityReview(gomock.Any(), payload.Username, ocm.PlatformOciCapabilityName, ocm.OrganizationCapabilityType).Return(false, nil).Times(1)

registerParams.NewClusterParams.Platform = &models.Platform{
Type: common.PlatformTypePtr(models.PlatformTypeOci),
}

mockEvents.EXPECT().SendClusterEvent(gomock.Any(), eventstest.NewEventMatcher(
eventstest.WithNameMatcher(eventgen.ClusterRegistrationFailedEventName),
eventstest.WithMessageContainsMatcher("Platform oci is not available"),
eventstest.WithSeverityMatcher(models.EventSeverityError))).Times(1)

reply := bm.V2RegisterCluster(authCtx, *registerParams)
verifyApiError(reply, http.StatusBadRequest)
})

It("OCI platform - RHSSO - CapabilityReview returns an error", func() {
cfg := auth.GetConfigRHSSO()
cfg.EnableOrgBasedFeatureGates = true
mockOcmAuthz := ocm.NewMockOCMAuthorization(ctrl)
mockOcmClient := &ocm.Client{Cache: cache.New(10*time.Minute, 30*time.Minute), Authorization: mockOcmAuthz}
bm.authHandler = auth.NewRHSSOAuthenticator(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
bm.authzHandler = auth.NewAuthzHandler(cfg, mockOcmClient, common.GetTestLog().WithField("pkg", "auth"), db)
payload := &ocm.AuthPayload{Role: ocm.UserRole}
payload.Username = "user1"
payload.Organization = "org1"
authCtx := context.WithValue(ctx, restapi.AuthKey, payload)
mockOcmAuthz.EXPECT().CapabilityReview(gomock.Any(), payload.Username, ocm.PlatformOciCapabilityName, ocm.OrganizationCapabilityType).Return(true, errors.Errorf("some error")).Times(1)

registerParams.NewClusterParams.Platform = &models.Platform{
Type: common.PlatformTypePtr(models.PlatformTypeOci),
}

mockEvents.EXPECT().SendClusterEvent(gomock.Any(), eventstest.NewEventMatcher(
eventstest.WithNameMatcher(eventgen.ClusterRegistrationFailedEventName),
eventstest.WithMessageContainsMatcher("Platform oci is not available"),
eventstest.WithSeverityMatcher(models.EventSeverityError))).Times(1)

reply := bm.V2RegisterCluster(authCtx, *registerParams)
verifyApiError(reply, http.StatusBadRequest)
})
})
})

Expand Down
9 changes: 1 addition & 8 deletions internal/bminventory/inventory_v2_handlers.go
Expand Up @@ -572,15 +572,8 @@ func (b *bareMetalInventory) V2ImportCluster(ctx context.Context, params install
}

func (b *bareMetalInventory) allowedToIgnoreValidations(ctx context.Context) bool {
log := logutil.FromContext(ctx, b.log)
allowedToIgnoreValidations, err := b.authzHandler.HasOrgBasedCapability(ctx, ocm.IgnoreValidationsCapabilityName)
if err != nil {
log.WithError(err).Errorf("error getting user %s capability", ocm.IgnoreValidationsCapabilityName)
}
if err != nil || !allowedToIgnoreValidations {
return false
}
return true
return err == nil && allowedToIgnoreValidations
}

func (b *bareMetalInventory) setIgnoredValidationsBadRequest(message string) *installer.V2SetIgnoredValidationsBadRequest {
Expand Down
24 changes: 24 additions & 0 deletions internal/cluster/validations/validations.go
Expand Up @@ -2,6 +2,7 @@ package validations

import (
"bytes"
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
Expand Down Expand Up @@ -976,3 +977,26 @@ func ValidateArchitectureWithPlatform(architecture *string, platform *models.Pla

return nil
}

func ValidatePlatformCapability(platform *models.Platform, ctx context.Context, authzHandler auth.Authorizer) error {
if platform == nil {
return nil
}

var capabilityName *string
switch *platform.Type {
case models.PlatformTypeOci:
capabilityName = swag.String(ocm.PlatformOciCapabilityName)
}

if capabilityName == nil {
return nil
}

available, err := authzHandler.HasOrgBasedCapability(ctx, *capabilityName)
if err == nil && available {
return nil
}

return common.NewApiError(http.StatusBadRequest, errors.Errorf("Platform %s is not available", *platform.Type))
}
5 changes: 5 additions & 0 deletions pkg/auth/rhsso_authz_handler.go
Expand Up @@ -186,6 +186,11 @@ func (a *AuthzHandler) HasOrgBasedCapability(ctx context.Context, capability str
isAllowed, err := a.client.Authorization.CapabilityReview(context.Background(), fmt.Sprint(username), capability, ocm.OrganizationCapabilityType)
a.log.Debugf("queried AMS API with CapabilityReview for username: %s about capability: %s, capability type: %s. Result: %t",
fmt.Sprint(username), capability, ocm.OrganizationCapabilityType, isAllowed)

if err != nil {
a.log.WithError(err).Errorf("error getting user %s capability", capability)
}

return isAllowed, err
}

Expand Down
1 change: 1 addition & 0 deletions pkg/ocm/utils.go
Expand Up @@ -17,6 +17,7 @@ const (
AMSActionDelete string = "delete"
BareMetalCapabilityName string = "bare_metal_installer_admin"
MultiarchCapabilityName string = "bare_metal_installer_multiarch"
PlatformOciCapabilityName string = "bare_metal_installer_platform_oci"
AccountCapabilityType string = "Account"
OrganizationCapabilityType string = "Organization"
Subscription string = "Subscription"
Expand Down

0 comments on commit ee0971c

Please sign in to comment.