github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/broker/instance_create.go (about)

     1  package broker
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"net/netip"
    11  	"strings"
    12  
    13  	"github.com/kyma-project/kyma-environment-broker/internal/networking"
    14  
    15  	"github.com/hashicorp/go-multierror"
    16  
    17  	"github.com/kyma-project/kyma-environment-broker/internal/euaccess"
    18  
    19  	"k8s.io/client-go/tools/clientcmd"
    20  
    21  	"github.com/google/uuid"
    22  	"github.com/kyma-incubator/compass/components/director/pkg/jsonschema"
    23  	"github.com/pivotal-cf/brokerapi/v8/domain"
    24  	"github.com/pivotal-cf/brokerapi/v8/domain/apiresponses"
    25  	"github.com/sirupsen/logrus"
    26  
    27  	"github.com/kyma-project/kyma-environment-broker/common/gardener"
    28  	"github.com/kyma-project/kyma-environment-broker/internal"
    29  	"github.com/kyma-project/kyma-environment-broker/internal/dashboard"
    30  	"github.com/kyma-project/kyma-environment-broker/internal/middleware"
    31  	"github.com/kyma-project/kyma-environment-broker/internal/ptr"
    32  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    33  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    34  )
    35  
    36  //go:generate mockery --name=Queue --output=automock --outpkg=automock --case=underscore
    37  //go:generate mockery --name=PlanValidator --output=automock --outpkg=automock --case=underscore
    38  
    39  type (
    40  	Queue interface {
    41  		Add(operationId string)
    42  	}
    43  
    44  	PlanValidator interface {
    45  		IsPlanSupport(planID string) bool
    46  	}
    47  )
    48  
    49  type ProvisionEndpoint struct {
    50  	config            Config
    51  	operationsStorage storage.Operations
    52  	instanceStorage   storage.Instances
    53  	queue             Queue
    54  	builderFactory    PlanValidator
    55  	enabledPlanIDs    map[string]struct{}
    56  	plansConfig       PlansConfig
    57  	kymaVerOnDemand   bool
    58  	planDefaults      PlanDefaults
    59  
    60  	shootDomain       string
    61  	shootProject      string
    62  	shootDnsProviders gardener.DNSProvidersData
    63  
    64  	dashboardConfig dashboard.Config
    65  
    66  	euAccessWhitelist        euaccess.WhitelistSet
    67  	euAccessRejectionMessage string
    68  
    69  	log logrus.FieldLogger
    70  }
    71  
    72  func NewProvision(cfg Config,
    73  	gardenerConfig gardener.Config,
    74  	operationsStorage storage.Operations,
    75  	instanceStorage storage.Instances,
    76  	queue Queue,
    77  	builderFactory PlanValidator,
    78  	plansConfig PlansConfig,
    79  	kvod bool,
    80  	planDefaults PlanDefaults,
    81  	euAccessWhitelist euaccess.WhitelistSet,
    82  	euRejectMessage string,
    83  	log logrus.FieldLogger,
    84  	dashboardConfig dashboard.Config,
    85  ) *ProvisionEndpoint {
    86  	enabledPlanIDs := map[string]struct{}{}
    87  	for _, planName := range cfg.EnablePlans {
    88  		id := PlanIDsMapping[planName]
    89  		enabledPlanIDs[id] = struct{}{}
    90  	}
    91  
    92  	return &ProvisionEndpoint{
    93  		config:                   cfg,
    94  		operationsStorage:        operationsStorage,
    95  		instanceStorage:          instanceStorage,
    96  		queue:                    queue,
    97  		builderFactory:           builderFactory,
    98  		log:                      log.WithField("service", "ProvisionEndpoint"),
    99  		enabledPlanIDs:           enabledPlanIDs,
   100  		plansConfig:              plansConfig,
   101  		kymaVerOnDemand:          kvod,
   102  		shootDomain:              gardenerConfig.ShootDomain,
   103  		shootProject:             gardenerConfig.Project,
   104  		shootDnsProviders:        gardenerConfig.DNSProviders,
   105  		planDefaults:             planDefaults,
   106  		euAccessWhitelist:        euAccessWhitelist,
   107  		euAccessRejectionMessage: euRejectMessage,
   108  		dashboardConfig:          dashboardConfig,
   109  	}
   110  }
   111  
   112  // Provision creates a new service instance
   113  //
   114  //	PUT /v2/service_instances/{instance_id}
   115  func (b *ProvisionEndpoint) Provision(ctx context.Context, instanceID string, details domain.ProvisionDetails, asyncAllowed bool) (domain.ProvisionedServiceSpec, error) {
   116  	operationID := uuid.New().String()
   117  	logger := b.log.WithFields(logrus.Fields{"instanceID": instanceID, "operationID": operationID, "planID": details.PlanID})
   118  	logger.Infof("Provision called with context: %s", marshallRawContext(hideSensitiveDataFromRawContext(details.RawContext)))
   119  
   120  	region, found := middleware.RegionFromContext(ctx)
   121  	if !found {
   122  		err := fmt.Errorf("No region specified in request.")
   123  		return domain.ProvisionedServiceSpec{}, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, "provisioning")
   124  	}
   125  	platformProvider, found := middleware.ProviderFromContext(ctx)
   126  	if !found {
   127  		err := fmt.Errorf("No provider specified in request.")
   128  		return domain.ProvisionedServiceSpec{}, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, "provisioning")
   129  	}
   130  
   131  	// validation of incoming input
   132  	ersContext, parameters, err := b.validateAndExtract(details, platformProvider, ctx, logger)
   133  	if err != nil {
   134  		errMsg := fmt.Sprintf("[instanceID: %s] %s", instanceID, err)
   135  		return domain.ProvisionedServiceSpec{}, apiresponses.NewFailureResponse(err, http.StatusBadRequest, errMsg)
   136  	}
   137  
   138  	provisioningParameters := internal.ProvisioningParameters{
   139  		PlanID:           details.PlanID,
   140  		ServiceID:        details.ServiceID,
   141  		ErsContext:       ersContext,
   142  		Parameters:       parameters,
   143  		PlatformRegion:   region,
   144  		PlatformProvider: platformProvider,
   145  	}
   146  
   147  	logger.Infof("Starting provisioning runtime: Name=%s, GlobalAccountID=%s, SubAccountID=%s PlatformRegion=%s, ProvisioningParameterts.Region=%s, ProvisioningParameterts.MachineType=%s",
   148  		parameters.Name, ersContext.GlobalAccountID, ersContext.SubAccountID, region, valueOfPtr(parameters.Region), valueOfPtr(parameters.MachineType))
   149  	logParametersWithMaskedKubeconfig(parameters, logger)
   150  
   151  	// check if operation with instance ID already created
   152  	existingOperation, errStorage := b.operationsStorage.GetProvisioningOperationByInstanceID(instanceID)
   153  	switch {
   154  	case errStorage != nil && !dberr.IsNotFound(errStorage):
   155  		logger.Errorf("cannot get existing operation from storage %s", errStorage)
   156  		return domain.ProvisionedServiceSpec{}, fmt.Errorf("cannot get existing operation from storage")
   157  	case existingOperation != nil && !dberr.IsNotFound(errStorage):
   158  		return b.handleExistingOperation(existingOperation, provisioningParameters)
   159  	}
   160  
   161  	shootName := gardener.CreateShootName()
   162  	shootDomainSuffix := strings.Trim(b.shootDomain, ".")
   163  
   164  	dashboardURL := b.createDashboardURL(details.PlanID, instanceID)
   165  
   166  	// create and save new operation
   167  	operation, err := internal.NewProvisioningOperationWithID(operationID, instanceID, provisioningParameters)
   168  	if err != nil {
   169  		logger.Errorf("cannot create new operation: %s", err)
   170  		return domain.ProvisionedServiceSpec{}, fmt.Errorf("cannot create new operation")
   171  	}
   172  
   173  	operation.ShootName = shootName
   174  	operation.ShootDomain = fmt.Sprintf("%s.%s", shootName, shootDomainSuffix)
   175  	operation.ShootDNSProviders = b.shootDnsProviders
   176  	operation.DashboardURL = dashboardURL
   177  	// for own cluster plan - KEB uses provided shoot name and shoot domain
   178  	if IsOwnClusterPlan(provisioningParameters.PlanID) {
   179  		operation.ShootName = provisioningParameters.Parameters.ShootName
   180  		operation.ShootDomain = provisioningParameters.Parameters.ShootDomain
   181  	}
   182  	logger.Infof("Runtime ShootDomain: %s", operation.ShootDomain)
   183  
   184  	err = b.operationsStorage.InsertOperation(operation.Operation)
   185  	if err != nil {
   186  		logger.Errorf("cannot save operation: %s", err)
   187  		return domain.ProvisionedServiceSpec{}, fmt.Errorf("cannot save operation")
   188  	}
   189  
   190  	instance := internal.Instance{
   191  		InstanceID:      instanceID,
   192  		GlobalAccountID: ersContext.GlobalAccountID,
   193  		SubAccountID:    ersContext.SubAccountID,
   194  		ServiceID:       provisioningParameters.ServiceID,
   195  		ServiceName:     KymaServiceName,
   196  		ServicePlanID:   provisioningParameters.PlanID,
   197  		ServicePlanName: PlanNamesMapping[provisioningParameters.PlanID],
   198  		DashboardURL:    dashboardURL,
   199  		Parameters:      operation.ProvisioningParameters,
   200  	}
   201  	err = b.instanceStorage.Insert(instance)
   202  	if err != nil {
   203  		logger.Errorf("cannot save instance in storage: %s", err)
   204  		return domain.ProvisionedServiceSpec{}, fmt.Errorf("cannot save instance")
   205  	}
   206  
   207  	logger.Info("Adding operation to provisioning queue")
   208  	b.queue.Add(operation.ID)
   209  
   210  	return domain.ProvisionedServiceSpec{
   211  		IsAsync:       true,
   212  		OperationData: operation.ID,
   213  		DashboardURL:  dashboardURL,
   214  		Metadata: domain.InstanceMetadata{
   215  			Labels: ResponseLabels(operation, instance, b.config.URL, b.config.EnableKubeconfigURLLabel),
   216  		},
   217  	}, nil
   218  }
   219  
   220  func logParametersWithMaskedKubeconfig(parameters internal.ProvisioningParametersDTO, logger *logrus.Entry) {
   221  	parameters.Kubeconfig = "*****"
   222  	logger.Infof("Runtime parameters: %+v", parameters)
   223  }
   224  
   225  func valueOfPtr(ptr *string) string {
   226  	if ptr == nil {
   227  		return ""
   228  	}
   229  	return *ptr
   230  }
   231  
   232  func (b *ProvisionEndpoint) validateAndExtract(details domain.ProvisionDetails, provider internal.CloudProvider, ctx context.Context, l logrus.FieldLogger) (internal.ERSContext, internal.ProvisioningParametersDTO, error) {
   233  	var ersContext internal.ERSContext
   234  	var parameters internal.ProvisioningParametersDTO
   235  
   236  	if details.ServiceID != KymaServiceID {
   237  		return ersContext, parameters, fmt.Errorf("service_id not recognized")
   238  	}
   239  	if _, exists := b.enabledPlanIDs[details.PlanID]; !exists {
   240  		return ersContext, parameters, fmt.Errorf("plan ID %q is not recognized", details.PlanID)
   241  	}
   242  
   243  	ersContext, err := b.extractERSContext(details)
   244  	logger := l.WithField("globalAccountID", ersContext.GlobalAccountID)
   245  	if err != nil {
   246  		return ersContext, parameters, fmt.Errorf("while extracting ers context: %w", err)
   247  	}
   248  
   249  	parameters, err = b.extractInputParameters(details)
   250  	if err != nil {
   251  		return ersContext, parameters, fmt.Errorf("while extracting input parameters: %w", err)
   252  	}
   253  	defaults, err := b.planDefaults(details.PlanID, provider, parameters.Provider)
   254  	if err != nil {
   255  		return ersContext, parameters, fmt.Errorf("while obtaining plan defaults: %w", err)
   256  	}
   257  
   258  	// TODO: remove when the feature (networking params) is completed and tested on prod
   259  	if !b.config.AllowNetworkingParameters && parameters.Networking != nil {
   260  		return ersContext, parameters, fmt.Errorf("providing networking parameters is not allowed")
   261  	}
   262  	if err := b.validateNetworking(parameters); err != nil {
   263  		return ersContext, parameters, err
   264  	}
   265  
   266  	if !b.config.AllowModulesParameters {
   267  		b.log.Infof("modules section passed to API, but AllowModulesParameters is set to false. Parameters will be reset to nil")
   268  		parameters.Modules = nil
   269  	}
   270  
   271  	var autoscalerMin, autoscalerMax int
   272  	if defaults.GardenerConfig != nil {
   273  		p := defaults.GardenerConfig
   274  		autoscalerMin, autoscalerMax = p.AutoScalerMin, p.AutoScalerMax
   275  	}
   276  	if err := parameters.AutoScalerParameters.Validate(autoscalerMin, autoscalerMax); err != nil {
   277  		return ersContext, parameters, apiresponses.NewFailureResponse(err, http.StatusUnprocessableEntity, err.Error())
   278  	}
   279  	if parameters.OIDC.IsProvided() {
   280  		if err := parameters.OIDC.Validate(); err != nil {
   281  			return ersContext, parameters, apiresponses.NewFailureResponse(err, http.StatusUnprocessableEntity, err.Error())
   282  		}
   283  	}
   284  
   285  	planValidator, err := b.validator(&details, provider, ctx)
   286  	if err != nil {
   287  		return ersContext, parameters, fmt.Errorf("while creating plan validator: %w", err)
   288  	}
   289  	result, err := planValidator.ValidateString(string(details.RawParameters))
   290  	if err != nil {
   291  		return ersContext, parameters, fmt.Errorf("while executing JSON schema validator: %w", err)
   292  	}
   293  	if !result.Valid {
   294  		return ersContext, parameters, fmt.Errorf("while validating input parameters: %w", result.Error)
   295  	}
   296  
   297  	// EU Access: reject requests for not whitelisted globalAccountIds
   298  	if isEuRestrictedAccess(ctx) {
   299  		logger.Infof("EU Access restricted instance creation")
   300  		if euaccess.IsNotWhitelisted(ersContext.GlobalAccountID, b.euAccessWhitelist) {
   301  			logger.Infof(b.euAccessRejectionMessage)
   302  			err = fmt.Errorf(b.euAccessRejectionMessage)
   303  			return ersContext, parameters, apiresponses.NewFailureResponse(err, http.StatusBadRequest, "provisioning")
   304  		}
   305  	}
   306  
   307  	if !b.kymaVerOnDemand {
   308  		logger.Infof("Kyma on demand functionality is disabled. Default Kyma version will be used instead %s", parameters.KymaVersion)
   309  		parameters.KymaVersion = ""
   310  		parameters.OverridesVersion = ""
   311  	}
   312  	parameters.LicenceType = b.determineLicenceType(details.PlanID)
   313  
   314  	found := b.builderFactory.IsPlanSupport(details.PlanID)
   315  	if !found {
   316  		return ersContext, parameters, fmt.Errorf("the plan ID not known, planID: %s", details.PlanID)
   317  	}
   318  
   319  	if IsOwnClusterPlan(details.PlanID) {
   320  		decodedKubeconfig, err := base64.StdEncoding.DecodeString(parameters.Kubeconfig)
   321  		if err != nil {
   322  			return ersContext, parameters, fmt.Errorf("while decoding kubeconfig: %w", err)
   323  		}
   324  		parameters.Kubeconfig = string(decodedKubeconfig)
   325  		err = validateKubeconfig(parameters.Kubeconfig)
   326  		if err != nil {
   327  			return ersContext, parameters, fmt.Errorf("while validating kubeconfig: %w", err)
   328  		}
   329  	}
   330  
   331  	if IsTrialPlan(details.PlanID) && parameters.Region != nil && *parameters.Region != "" {
   332  		_, valid := validRegionsForTrial[TrialCloudRegion(*parameters.Region)]
   333  		if !valid {
   334  			return ersContext, parameters, fmt.Errorf("invalid region specified in request for trial")
   335  		}
   336  	}
   337  
   338  	if IsTrialPlan(details.PlanID) && b.config.OnlySingleTrialPerGA {
   339  		count, err := b.instanceStorage.GetNumberOfInstancesForGlobalAccountID(ersContext.GlobalAccountID)
   340  		if err != nil {
   341  			return ersContext, parameters, fmt.Errorf("while checking if a trial Kyma instance exists for given global account: %w", err)
   342  		}
   343  
   344  		if count > 0 {
   345  			logger.Info("Provisioning Trial SKR rejected, such instance was already created for this Global Account")
   346  			return ersContext, parameters, fmt.Errorf("trial Kyma was created for the global account, but there is only one allowed")
   347  		}
   348  	}
   349  
   350  	return ersContext, parameters, nil
   351  }
   352  
   353  func isEuRestrictedAccess(ctx context.Context) bool {
   354  	platformRegion, _ := middleware.RegionFromContext(ctx)
   355  	return euaccess.IsEURestrictedAccess(platformRegion)
   356  }
   357  
   358  // Rudimentary kubeconfig validation
   359  func validateKubeconfig(kubeconfig string) error {
   360  	config, err := clientcmd.Load([]byte(kubeconfig))
   361  	if err != nil {
   362  		return err
   363  	}
   364  	err = clientcmd.Validate(*config)
   365  	if err != nil {
   366  		return err
   367  	}
   368  	return nil
   369  }
   370  
   371  func (b *ProvisionEndpoint) extractERSContext(details domain.ProvisionDetails) (internal.ERSContext, error) {
   372  	var ersContext internal.ERSContext
   373  	err := json.Unmarshal(details.RawContext, &ersContext)
   374  	if err != nil {
   375  		return ersContext, fmt.Errorf("while decoding context: %w", err)
   376  	}
   377  
   378  	if ersContext.GlobalAccountID == "" {
   379  		return ersContext, fmt.Errorf("global accountID parameter cannot be empty")
   380  	}
   381  	if ersContext.SubAccountID == "" {
   382  		return ersContext, fmt.Errorf("subAccountID parameter cannot be empty")
   383  	}
   384  	if ersContext.UserID == "" {
   385  		return ersContext, fmt.Errorf("UserID parameter cannot be empty")
   386  	}
   387  	ersContext.UserID = strings.ToLower(ersContext.UserID)
   388  
   389  	return ersContext, nil
   390  }
   391  
   392  func (b *ProvisionEndpoint) extractInputParameters(details domain.ProvisionDetails) (internal.ProvisioningParametersDTO, error) {
   393  	var parameters internal.ProvisioningParametersDTO
   394  	err := json.Unmarshal(details.RawParameters, &parameters)
   395  	if err != nil {
   396  		return parameters, fmt.Errorf("while unmarshaling raw parameters: %w", err)
   397  	}
   398  
   399  	return parameters, nil
   400  }
   401  
   402  func (b *ProvisionEndpoint) handleExistingOperation(operation *internal.ProvisioningOperation, input internal.ProvisioningParameters) (domain.ProvisionedServiceSpec, error) {
   403  
   404  	if !operation.ProvisioningParameters.IsEqual(input) {
   405  		err := fmt.Errorf("provisioning operation already exist")
   406  		msg := fmt.Sprintf("provisioning operation with InstanceID %s already exist", operation.InstanceID)
   407  		return domain.ProvisionedServiceSpec{}, apiresponses.NewFailureResponse(err, http.StatusConflict, msg)
   408  	}
   409  
   410  	instance, err := b.instanceStorage.GetByID(operation.InstanceID)
   411  	if err != nil {
   412  		err := fmt.Errorf("cannot fetch instance for operation")
   413  		msg := fmt.Sprintf("cannot fetch instance with ID: %s for operation woth ID: %s", operation.InstanceID, operation.ID)
   414  		return domain.ProvisionedServiceSpec{}, apiresponses.NewFailureResponse(err, http.StatusConflict, msg)
   415  	}
   416  
   417  	return domain.ProvisionedServiceSpec{
   418  		IsAsync:       true,
   419  		OperationData: operation.ID,
   420  		DashboardURL:  operation.DashboardURL,
   421  		Metadata: domain.InstanceMetadata{
   422  			Labels: ResponseLabels(*operation, *instance, b.config.URL, b.config.EnableKubeconfigURLLabel),
   423  		},
   424  	}, nil
   425  }
   426  
   427  func (b *ProvisionEndpoint) determineLicenceType(planId string) *string {
   428  	if planId == AzureLitePlanID || IsTrialPlan(planId) {
   429  		return ptr.String(internal.LicenceTypeLite)
   430  	}
   431  
   432  	return nil
   433  }
   434  
   435  func (b *ProvisionEndpoint) validator(details *domain.ProvisionDetails, provider internal.CloudProvider, ctx context.Context) (JSONSchemaValidator, error) {
   436  	platformRegion, _ := middleware.RegionFromContext(ctx)
   437  	plans := Plans(b.plansConfig, provider, b.config.IncludeAdditionalParamsInSchema, euaccess.IsEURestrictedAccess(platformRegion), b.config.RegionParameterIsRequired, b.config.AllowModulesParameters)
   438  	plan := plans[details.PlanID]
   439  	schema := string(Marshal(plan.Schemas.Instance.Create.Parameters))
   440  
   441  	return jsonschema.NewValidatorFromStringSchema(schema)
   442  }
   443  
   444  func (b *ProvisionEndpoint) createDashboardURL(planID, instanceID string) string {
   445  	if IsOwnClusterPlan(planID) {
   446  		return b.dashboardConfig.LandscapeURL
   447  	} else {
   448  		return fmt.Sprintf("%s/?kubeconfigID=%s", b.dashboardConfig.LandscapeURL, instanceID)
   449  	}
   450  }
   451  
   452  func validateCidr(cidr string) (*net.IPNet, error) {
   453  	ip, ipNet, err := net.ParseCIDR(cidr)
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  	// find cases like: 10.250.0.1/19
   458  	if ipNet != nil {
   459  		if !ipNet.IP.Equal(ip) {
   460  			return nil, fmt.Errorf("%s must be valid canonical CIDR", ip)
   461  		}
   462  	}
   463  	return ipNet, nil
   464  }
   465  
   466  func (b *ProvisionEndpoint) validateNetworking(parameters internal.ProvisioningParametersDTO) error {
   467  	var err, e error
   468  	if len(parameters.Zones) > 4 {
   469  		// the algorithm of creating AWS zone CIDRs does not work for more than 4 zones
   470  		err = multierror.Append(err, fmt.Errorf("number of zones must not be greater than 4"))
   471  	}
   472  	if parameters.Networking == nil {
   473  		return nil
   474  	}
   475  
   476  	// currently we do not support Pod's and Service's
   477  	if parameters.Networking.PodsCidr != nil {
   478  		return fmt.Errorf("pod network's CIDR is not supported in the request")
   479  	}
   480  	if parameters.Networking.ServicesCidr != nil {
   481  		return fmt.Errorf("service network's CIDR is not supported in the request")
   482  	}
   483  
   484  	var nodes, services, pods *net.IPNet
   485  	if nodes, e = validateCidr(parameters.Networking.NodesCidr); e != nil {
   486  		err = multierror.Append(err, fmt.Errorf("while parsing nodes CIDR: %w", e))
   487  	}
   488  	// error is handled before, in the validate CIDR
   489  	cidr, _ := netip.ParsePrefix(parameters.Networking.NodesCidr)
   490  	if cidr.Bits() > 23 {
   491  		err = multierror.Append(err, fmt.Errorf("the suffix of the node CIDR must not be greater than 26"))
   492  	}
   493  
   494  	if err != nil {
   495  		return err
   496  	}
   497  
   498  	for _, seed := range networking.GardenerSeedCIDRs {
   499  		_, seedCidr, _ := net.ParseCIDR(seed)
   500  		if e := validateOverlapping(*nodes, *seedCidr); e != nil {
   501  			err = multierror.Append(err, fmt.Errorf("nodes CIDR must not overlap %s", seed))
   502  		}
   503  	}
   504  
   505  	if parameters.Networking.PodsCidr != nil {
   506  		if pods, e = validateCidr(*parameters.Networking.PodsCidr); e != nil {
   507  			err = multierror.Append(err, fmt.Errorf("while parsing pods CIDR: %w", e))
   508  		}
   509  	} else {
   510  		_, pods, _ = net.ParseCIDR(networking.DefaultPodsCIDR)
   511  	}
   512  	if parameters.Networking.ServicesCidr != nil {
   513  		if services, e = validateCidr(*parameters.Networking.ServicesCidr); e != nil {
   514  			err = multierror.Append(err, fmt.Errorf("while parsing services CIDR: %w", e))
   515  		}
   516  	} else {
   517  		_, services, _ = net.ParseCIDR(networking.DefaultServicesCIDR)
   518  	}
   519  	if err != nil {
   520  		return err
   521  	}
   522  
   523  	if e := validateOverlapping(*nodes, *pods); e != nil {
   524  		err = multierror.Append(err, fmt.Errorf("nodes CIDR must not overlap %s", pods.String()))
   525  	}
   526  	if e := validateOverlapping(*nodes, *services); e != nil {
   527  		err = multierror.Append(err, fmt.Errorf("nodes CIDR must not overlap %s", services.String()))
   528  	}
   529  	if e := validateOverlapping(*services, *pods); e != nil {
   530  		err = multierror.Append(err, fmt.Errorf("services CIDR must not overlap pods CIDR"))
   531  	}
   532  
   533  	return err
   534  }
   535  
   536  func validateOverlapping(n1 net.IPNet, n2 net.IPNet) error {
   537  
   538  	if n1.Contains(n2.IP) || n2.Contains(n1.IP) {
   539  		return fmt.Errorf("%s overlaps %s", n1.String(), n2.String())
   540  	}
   541  
   542  	return nil
   543  }