From a37c544461ca45b612c477235658e7a2973dca80 Mon Sep 17 00:00:00 2001 From: carvalhe Date: Mon, 17 Jul 2023 11:56:49 -0700 Subject: [PATCH] Add static preflight validation (#2996) * Add static preflight validation * ResourceId for path --------- Co-authored-by: BCarvalheira --- pkg/api/preflight.go | 1 + .../openshiftcluster_preflightvalidation.go | 71 +++++++++++-------- ...enshiftcluster_preflightvalidation_test.go | 48 ++++++++++++- pkg/frontend/validate.go | 12 ++-- 4 files changed, 95 insertions(+), 37 deletions(-) diff --git a/pkg/api/preflight.go b/pkg/api/preflight.go index 47c0177b2d5..34fb53f4c97 100644 --- a/pkg/api/preflight.go +++ b/pkg/api/preflight.go @@ -31,6 +31,7 @@ type ManagementErrorWithDetails struct { // ResourceTypeMeta is the Typemeta inside request body of preflight type ResourceTypeMeta struct { + Id string `json:"id"` Name string `json:"name"` Type string `json:"type"` Location string `json:"location"` diff --git a/pkg/frontend/openshiftcluster_preflightvalidation.go b/pkg/frontend/openshiftcluster_preflightvalidation.go index b5ee1e6ae66..83c76f26e12 100644 --- a/pkg/frontend/openshiftcluster_preflightvalidation.go +++ b/pkg/frontend/openshiftcluster_preflightvalidation.go @@ -13,6 +13,7 @@ import ( "github.com/sirupsen/logrus" "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" "github.com/Azure/ARO-RP/pkg/frontend/middleware" ) @@ -32,29 +33,7 @@ func (f *frontend) preflightValidation(w http.ResponseWriter, r *http.Request) { body := r.Context().Value(middleware.ContextKeyBody).([]byte) resources, err := unmarshalRequest(body) - if err == nil { - for _, raw := range resources.Resources { - // get typeMeta from the raw data - typeMeta := api.ResourceTypeMeta{} - if err := json.Unmarshal(raw, &typeMeta); err != nil { - // failing to parse the preflight body is not considered a validation failure. continue - log.Warningf("bad request. Failed to unmarshal ResourceTypeMeta: %s", err) - continue - } - if strings.EqualFold(typeMeta.Type, "Microsoft.RedHatOpenShift/openShiftClusters") { - res := f._preflightValidation(ctx, log, raw, typeMeta.APIVersion) - if res.Status == api.ValidationStatusFailed { - log.Warningf("preflight validation failed") - b = marshalValidationResult(res) - reply(log, w, header, b, statusCodeError(http.StatusOK)) - return - } - } - } - log.Info("preflight validation succeeded") - b = marshalValidationResult(validationSuccess) - reply(log, w, header, b, statusCodeError(http.StatusOK)) - } else { + if err != nil { b = marshalValidationResult(api.ValidationResult{ Status: api.ValidationStatusFailed, Error: &api.ManagementErrorWithDetails{ @@ -63,19 +42,43 @@ func (f *frontend) preflightValidation(w http.ResponseWriter, r *http.Request) { }) reply(log, w, header, b, statusCodeError(http.StatusOK)) } + + for _, raw := range resources.Resources { + // get typeMeta from the raw data + typeMeta := api.ResourceTypeMeta{} + if err := json.Unmarshal(raw, &typeMeta); err != nil { + // failing to parse the preflight body is not considered a validation failure. continue + log.Warningf("bad request. Failed to unmarshal ResourceTypeMeta: %s", err) + continue + } + if strings.EqualFold(typeMeta.Type, "Microsoft.RedHatOpenShift/openShiftClusters") { + res := f._preflightValidation(ctx, log, raw, typeMeta.APIVersion, typeMeta.Id) + if res.Status == api.ValidationStatusFailed { + log.Warningf("preflight validation failed") + b = marshalValidationResult(res) + reply(log, w, header, b, statusCodeError(http.StatusOK)) + return + } + } + } + + log.Info("preflight validation succeeded") + b = marshalValidationResult(validationSuccess) + reply(log, w, header, b, statusCodeError(http.StatusOK)) } -func (f *frontend) _preflightValidation(ctx context.Context, log *logrus.Entry, raw json.RawMessage, apiVersion string) api.ValidationResult { +func (f *frontend) _preflightValidation(ctx context.Context, log *logrus.Entry, raw json.RawMessage, apiVersion string, resourceID string) api.ValidationResult { // unmarshal raw to OpenShiftCluster type - doc := &api.OpenShiftCluster{} - doc.Properties.ProvisioningState = api.ProvisioningStateSucceeded + oc := &api.OpenShiftCluster{} + oc.Properties.ProvisioningState = api.ProvisioningStateSucceeded if !f.env.IsLocalDevelopmentMode() /* not local dev or CI */ { - doc.Properties.FeatureProfile.GatewayEnabled = true + oc.Properties.FeatureProfile.GatewayEnabled = true } converter := f.apis[apiVersion].OpenShiftClusterConverter - ext := converter.ToExternal(doc) + staticValidator := f.apis[apiVersion].OpenShiftClusterStaticValidator + ext := converter.ToExternal(oc) if err := json.Unmarshal(raw, &ext); err != nil { return api.ValidationResult{ Status: api.ValidationStatusFailed, @@ -85,9 +88,17 @@ func (f *frontend) _preflightValidation(ctx context.Context, log *logrus.Entry, } } - converter.ToInternal(ext, doc) + converter.ToInternal(ext, oc) + if err := staticValidator.Static(ext, nil, f.env.Location(), f.env.Domain(), f.env.FeatureIsSet(env.FeatureRequireD2sV3Workers), resourceID); err != nil { + return api.ValidationResult{ + Status: api.ValidationStatusFailed, + Error: &api.ManagementErrorWithDetails{ + Message: to.StringPtr(err.Error()), + }, + } + } - if err := f.validateInstallVersion(ctx, doc); err != nil { + if err := f.validateInstallVersion(ctx, oc); err != nil { return api.ValidationResult{ Status: api.ValidationStatusFailed, Error: &api.ManagementErrorWithDetails{ diff --git a/pkg/frontend/openshiftcluster_preflightvalidation_test.go b/pkg/frontend/openshiftcluster_preflightvalidation_test.go index 34af9116e32..d2a5247af29 100644 --- a/pkg/frontend/openshiftcluster_preflightvalidation_test.go +++ b/pkg/frontend/openshiftcluster_preflightvalidation_test.go @@ -9,6 +9,8 @@ import ( "net/http" "testing" + "github.com/Azure/go-autorest/autorest/to" + "github.com/Azure/ARO-RP/pkg/api" "github.com/Azure/ARO-RP/pkg/metrics/noop" "github.com/Azure/ARO-RP/pkg/util/version" @@ -47,13 +49,14 @@ func TestPreflightValidation(t *testing.T) { []byte(` { "apiVersion": "2022-04-01", + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcename/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", "name": "resourceName", "type": "microsoft.redhatopenshift/openshiftclusters", "location": "eastus", "properties": { "clusterProfile": { "domain": "example.aroapp.io", - "resourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcename", + "resourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcenameTest", "fipsValidatedModules": "Enabled" }, "consoleProfile": {}, @@ -102,6 +105,49 @@ func TestPreflightValidation(t *testing.T) { Status: api.ValidationStatusSucceeded, }, }, + { + name: "Failed Preflight Static", + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: "11111111-1111-1111-1111-111111111111", + }, + }, + }) + }, + preflightRequest: func() *api.PreflightRequest { + return &api.PreflightRequest{ + Resources: []json.RawMessage{ + []byte(` + { + "apiVersion": "2022-04-01", + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcename/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", + "name": "resourceName", + "type": "microsoft.redhatopenshift/openshiftclusters", + "location": "eastus", + "properties": { + "clusterProfile": { + "domain": "example.aroapp.io", + "resourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/resourcenameTest", + "fipsValidatedModules": "Enabled" + } + } + } + `), + }, + } + }, + wantStatusCode: http.StatusOK, + wantResponse: &api.ValidationResult{ + Status: api.ValidationStatusFailed, + Error: &api.ManagementErrorWithDetails{ + Message: to.StringPtr("400: InvalidParameter: properties.clusterProfile.resourceGroupId: The provided resource group '/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/resourcenameTest' is invalid: must be in same subscription as cluster."), + }, + }, + }, } { t.Run(tt.name, func(t *testing.T) { ti := newTestInfra(t). diff --git a/pkg/frontend/validate.go b/pkg/frontend/validate.go index b46d6f6bd4c..51f81057cdc 100644 --- a/pkg/frontend/validate.go +++ b/pkg/frontend/validate.go @@ -202,20 +202,20 @@ func validateAdminMasterVMSize(vmSize string) error { // validateInstallVersion validates the install version set in the clusterprofile.version // TODO convert this into static validation instead of this receiver function in the validation for frontend. -func (f *frontend) validateInstallVersion(ctx context.Context, doc *api.OpenShiftCluster) error { +func (f *frontend) validateInstallVersion(ctx context.Context, oc *api.OpenShiftCluster) error { // If this request is from an older API or the user never specified // the version to install we default to the DefaultInstallStream.Version // TODO: We should set default version in cosmosdb instead of hardcoding it in golang code - if doc.Properties.ClusterProfile.Version == "" { - doc.Properties.ClusterProfile.Version = version.DefaultInstallStream.Version.String() + if oc.Properties.ClusterProfile.Version == "" { + oc.Properties.ClusterProfile.Version = version.DefaultInstallStream.Version.String() } f.mu.RLock() - _, ok := f.enabledOcpVersions[doc.Properties.ClusterProfile.Version] + _, ok := f.enabledOcpVersions[oc.Properties.ClusterProfile.Version] f.mu.RUnlock() - if !ok || !validate.RxInstallVersion.MatchString(doc.Properties.ClusterProfile.Version) { - return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.clusterProfile.version", "The requested OpenShift version '%s' is invalid.", doc.Properties.ClusterProfile.Version) + if !ok || !validate.RxInstallVersion.MatchString(oc.Properties.ClusterProfile.Version) { + return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.clusterProfile.version", "The requested OpenShift version '%s' is invalid.", oc.Properties.ClusterProfile.Version) } return nil