Skip to content

Commit

Permalink
Dynamic validation of subscription level registrations at the RP
Browse files Browse the repository at this point in the history
  • Loading branch information
schiruma committed Aug 29, 2023
1 parent 3b370ae commit 230be57
Show file tree
Hide file tree
Showing 40 changed files with 3,393 additions and 60 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ require (

require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures v1.1.0
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 h1:uqM+VoHjVH6zdlkLF2b6O
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2/go.mod h1:twTKAa1E6hLmSDjLhaCkbTMQKc7p/rNLU40rLxGEOCI=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures v1.1.0 h1:NhvID5juwkPxMUD8hdV3no0nugxk9QM8d5OSLskjOLM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures v1.1.0/go.mod h1:hDdPReNCfyh7kmZm6uKm3uH3OQkGn8gbeb1c/JkmEdE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
Expand Down
90 changes: 90 additions & 0 deletions pkg/frontend/encryptionathost_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package frontend

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"context"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures"
"github.com/golang/mock/gomock"

"github.com/Azure/ARO-RP/pkg/api"
mock_features "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/features"
utilerror "github.com/Azure/ARO-RP/test/util/error"
)

func TestValidateEncryptionAtHost(t *testing.T) {
EncryptionAtHostEnabledOrDisabled := func(MasterProfile api.MasterProfile, WorkerProfiles []api.WorkerProfile) *api.OpenShiftCluster {
return &api.OpenShiftCluster{
Properties: api.OpenShiftClusterProperties{
MasterProfile: MasterProfile,
WorkerProfiles: WorkerProfiles,
},
}
}

EncryptionAtHostFeatureEnabledOrDisabled := func(state armfeatures.SubscriptionFeatureRegistrationState) *armfeatures.SubscriptionFeatureRegistrationsClientGetResponse {
return &armfeatures.SubscriptionFeatureRegistrationsClientGetResponse{
SubscriptionFeatureRegistration: armfeatures.SubscriptionFeatureRegistration{
Properties: &armfeatures.SubscriptionFeatureRegistrationProperties{
State: &state,
},
},
}
}

for _, tt := range []struct {
name string
oc *api.OpenShiftCluster
mockResponse *armfeatures.SubscriptionFeatureRegistrationsClientGetResponse
mockErr error
wantErr string
}{
{
name: "encryption at host disabled - feature isn't registered",
oc: EncryptionAtHostEnabledOrDisabled(api.MasterProfile{EncryptionAtHost: api.EncryptionAtHostDisabled}, []api.WorkerProfile{{EncryptionAtHost: api.EncryptionAtHostDisabled}}),
mockResponse: EncryptionAtHostFeatureEnabledOrDisabled(armfeatures.SubscriptionFeatureRegistrationStateNotRegistered),
},
{
name: "encryption at host disabled - feature is registered",
oc: EncryptionAtHostEnabledOrDisabled(api.MasterProfile{EncryptionAtHost: api.EncryptionAtHostDisabled}, []api.WorkerProfile{{EncryptionAtHost: api.EncryptionAtHostDisabled}}),
mockResponse: EncryptionAtHostFeatureEnabledOrDisabled(armfeatures.SubscriptionFeatureRegistrationStateRegistered),
},
{
name: "encryption at host enabled - feature is registered",
oc: EncryptionAtHostEnabledOrDisabled(api.MasterProfile{EncryptionAtHost: api.EncryptionAtHostEnabled}, []api.WorkerProfile{{EncryptionAtHost: api.EncryptionAtHostEnabled}}),
mockResponse: EncryptionAtHostFeatureEnabledOrDisabled(armfeatures.SubscriptionFeatureRegistrationStateRegistered),
},
{
name: "encryption at host enabled - feature isn't registered",
oc: EncryptionAtHostEnabledOrDisabled(api.MasterProfile{EncryptionAtHost: api.EncryptionAtHostEnabled}, []api.WorkerProfile{{EncryptionAtHost: api.EncryptionAtHostEnabled}}),
mockResponse: EncryptionAtHostFeatureEnabledOrDisabled(armfeatures.SubscriptionFeatureRegistrationStateNotRegistered),
wantErr: "400: InvalidParameter: armfeatures.SubscriptionFeatureRegistrationProperties: Microsoft.Compute/EncryptionAtHost feature is not enabled for this subscription. Register the feature using 'az feature register --namespace Microsoft.Compute --name EncryptionAtHost'",
},
{
name: "MasterProfile encryption at host enabled - feature isn't registered",
oc: EncryptionAtHostEnabledOrDisabled(api.MasterProfile{EncryptionAtHost: api.EncryptionAtHostEnabled}, []api.WorkerProfile{{EncryptionAtHost: api.EncryptionAtHostDisabled}}),
mockResponse: EncryptionAtHostFeatureEnabledOrDisabled(armfeatures.SubscriptionFeatureRegistrationStateNotRegistered),
wantErr: "400: InvalidParameter: armfeatures.SubscriptionFeatureRegistrationProperties: Microsoft.Compute/EncryptionAtHost feature is not enabled for this subscription. Register the feature using 'az feature register --namespace Microsoft.Compute --name EncryptionAtHost'",
},
{
name: "WorkerProfile encryption at host enabled - feature isn't registered",
oc: EncryptionAtHostEnabledOrDisabled(api.MasterProfile{EncryptionAtHost: api.EncryptionAtHostDisabled}, []api.WorkerProfile{{EncryptionAtHost: api.EncryptionAtHostEnabled}}),
mockResponse: EncryptionAtHostFeatureEnabledOrDisabled(armfeatures.SubscriptionFeatureRegistrationStateNotRegistered),
wantErr: "400: InvalidParameter: armfeatures.SubscriptionFeatureRegistrationProperties: Microsoft.Compute/EncryptionAtHost feature is not enabled for this subscription. Register the feature using 'az feature register --namespace Microsoft.Compute --name EncryptionAtHost'",
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()

subFeatureRegistrationsClient := mock_features.NewMockSubscriptionFeatureRegistrationsClient(controller)
subFeatureRegistrationsClient.EXPECT().Get(gomock.Any(), "Microsoft.Compute", "EncryptionAtHost", gomock.Any()).Return(*tt.mockResponse, tt.mockErr).AnyTimes()

err := validateEncryptionAtHost(context.Background(), subFeatureRegistrationsClient, tt.oc)
utilerror.AssertErrorMessage(t, err, tt.wantErr)
})
}
}
73 changes: 73 additions & 0 deletions pkg/frontend/encryptionathost_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package frontend

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"context"
"net/http"

"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features"
)

type EncryptionAtHostValidator interface {
ValidateEncryptionAtHost(ctx context.Context, subscriptionID string, oc *api.OpenShiftCluster) error
}

type encryptionAtHostValidator struct{}

func (e encryptionAtHostValidator) ValidateEncryptionAtHost(ctx context.Context, subscriptionID string, oc *api.OpenShiftCluster) error {
credential, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return err
}

subFeatureRegistrationsClient, err := armfeatures.NewSubscriptionFeatureRegistrationsClient(subscriptionID, credential, nil)
if err != nil {
return err
}
return validateEncryptionAtHost(ctx, subFeatureRegistrationsClient, oc)
}

func validateEncryptionAtHost(ctx context.Context, subFeatureRegistrationsClient features.SubscriptionFeatureRegistrationsClient, oc *api.OpenShiftCluster) error {
encryptionSettings := make(map[api.EncryptionAtHost]bool)

encryptionSettings[oc.Properties.MasterProfile.EncryptionAtHost] = true

for _, wp := range oc.Properties.WorkerProfiles {
encryptionSettings[wp.EncryptionAtHost] = true
}

for setting := range encryptionSettings {
if setting == api.EncryptionAtHostEnabled {
if err := IsRegisteredForEncryptionAtHostFeature(ctx, subFeatureRegistrationsClient); err != nil {
return err
}

Check failure on line 50 in pkg/frontend/encryptionathost_validation.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unnecessary trailing newline (whitespace)
}
}

return nil
}

func IsRegisteredForEncryptionAtHostFeature(ctx context.Context, subFeatureRegistrationsClient features.SubscriptionFeatureRegistrationsClient) error {
response, err := subFeatureRegistrationsClient.Get(ctx, "Microsoft.Compute", "EncryptionAtHost", nil)
if err != nil {
return err
}
if *response.Properties.State == armfeatures.SubscriptionFeatureRegistrationStateRegistered {
return nil
}
return &api.CloudError{
StatusCode: http.StatusBadRequest,
CloudErrorBody: &api.CloudErrorBody{
Code: api.CloudErrorCodeInvalidParameter,
Message: "Microsoft.Compute/EncryptionAtHost feature is not enabled for this subscription. Register the feature using 'az feature register --namespace Microsoft.Compute --name EncryptionAtHost'",
Target: "armfeatures.SubscriptionFeatureRegistrationProperties",
},
}
}
14 changes: 8 additions & 6 deletions pkg/frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ type frontend struct {
kubeActionsFactory kubeActionsFactory
azureActionsFactory azureActionsFactory

skuValidator SkuValidator
quotaValidator QuotaValidator
providersValidator ProvidersValidator
skuValidator SkuValidator
quotaValidator QuotaValidator
providersValidator ProvidersValidator
encryptionathostValidator EncryptionAtHostValidator

clusterEnricher clusterdata.BestEffortEnricher

Expand Down Expand Up @@ -160,9 +161,10 @@ func NewFrontend(ctx context.Context,
kubeActionsFactory: kubeActionsFactory,
azureActionsFactory: azureActionsFactory,

quotaValidator: quotaValidator{},
skuValidator: skuValidator{},
providersValidator: providersValidator{},
quotaValidator: quotaValidator{},
skuValidator: skuValidator{},
providersValidator: providersValidator{},
encryptionathostValidator: encryptionAtHostValidator{},

clusterEnricher: enricher,

Expand Down
2 changes: 1 addition & 1 deletion pkg/frontend/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ package frontend
// Licensed under the Apache License 2.0.

//go:generate rm -rf ../../util/mocks/$GOPACKAGE
//go:generate go run ../../vendor/github.com/golang/mock/mockgen -destination=../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend StreamResponder,QuotaValidator,SkuValidator,ProvidersValidator
//go:generate go run ../../vendor/github.com/golang/mock/mockgen -destination=../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend StreamResponder,QuotaValidator,SkuValidator,ProvidersValidator,EncryptionAtHostValidator
//go:generate go run ../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../util/mocks/$GOPACKAGE/$GOPACKAGE.go
5 changes: 5 additions & 0 deletions pkg/frontend/openshiftcluster_putorpatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,11 @@ func (f *frontend) ValidateNewCluster(ctx context.Context, subscription *api.Sub
return err
}

err = f.encryptionathostValidator.ValidateEncryptionAtHost(ctx, subscription.ID, cluster)
if err != nil {
return err
}

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/util/azureclient/mgmt/features/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ package features
// Licensed under the Apache License 2.0.

//go:generate rm -rf ../../../../util/mocks/$GOPACKAGE
//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/$GOPACKAGE DeploymentsClient,ProvidersClient,ResourceGroupsClient,ResourcesClient
//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/$GOPACKAGE DeploymentsClient,ProvidersClient,ResourceGroupsClient,ResourcesClient,SubscriptionFeatureRegistrationsClient
//go:generate go run ../../../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package features

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"context"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures"
)

// SubscriptionFeatureRegistrationsClient is a minimal interface for azure SubscriptionFeatureRegistrationsClient
type SubscriptionFeatureRegistrationsClient interface {
Get(ctx context.Context, providerNamespace string, featureName string, options *armfeatures.SubscriptionFeatureRegistrationsClientGetOptions) (armfeatures.SubscriptionFeatureRegistrationsClientGetResponse, error)
}
41 changes: 40 additions & 1 deletion pkg/util/mocks/azureclient/mgmt/features/features.go

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

41 changes: 40 additions & 1 deletion pkg/util/mocks/frontend/frontend.go

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

Loading

0 comments on commit 230be57

Please sign in to comment.