diff --git a/api.go b/api.go index dd57abc4..1b4f00ae 100644 --- a/api.go +++ b/api.go @@ -31,10 +31,10 @@ import ( func APICommand() *cobra.Command { roles := []string{} usernamePrefix := "" - var allowEmptyBillingEntity bool + var allowEmptyBillingEntity, skipBillingEntityValidation bool ob := &odooStorageBuilder{} - ost := orgStore.New(&roles, &usernamePrefix, &allowEmptyBillingEntity) + ost := orgStore.New(&roles, &usernamePrefix, &allowEmptyBillingEntity, &skipBillingEntityValidation) ib := &invitationStorageBuilder{usernamePrefix: &usernamePrefix} cmd, err := builder.APIServer. @@ -55,6 +55,7 @@ func APICommand() *cobra.Command { cmd.Flags().StringSliceVar(&roles, "cluster-roles", []string{}, "Cluster Roles to bind when creating an organization") cmd.Flags().StringVar(&usernamePrefix, "username-prefix", "", "Prefix prepended to username claims. Usually the same as \"--oidc-username-prefix\" of the Kubernetes API server") cmd.Flags().BoolVar(&allowEmptyBillingEntity, "allow-empty-billing-entity", true, "Allow empty billing entity references") + cmd.Flags().BoolVar(&skipBillingEntityValidation, "organization-skip-billing-entity-validation", false, "Skip validation of billing entity references") cmd.Flags().StringVar(&ob.billingEntityStorage, "billing-entity-storage", "fake", "Storage backend for billing entities. Supported values: fake, odoo8, odoo16") diff --git a/apiserver/organization/billingentity_validate.go b/apiserver/organization/billingentity_validate.go index 4cedf7ce..c04ac6f6 100644 --- a/apiserver/organization/billingentity_validate.go +++ b/apiserver/organization/billingentity_validate.go @@ -3,6 +3,7 @@ package organization import ( "context" "fmt" + "os" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/endpoints/request" @@ -45,29 +46,38 @@ func (c impersonatorFromRestconf) Impersonate(u user.Info) (client.Client, error // billingEntityValidator validates that the billing entity exists and the requesting user has access to it. // it does so by impersonating the user and trying to get the billing entity. func (s *organizationStorage) billingEntityValidator(ctx context.Context, org, oldOrg *orgv1.Organization) error { - // check if changed - if oldOrg != nil && oldOrg.Spec.BillingEntityRef == org.Spec.BillingEntityRef { - return nil - } - // check if we allow empty billing entities - if org.Spec.BillingEntityRef == "" && s.allowEmptyBillingEntity { - return nil - } + validate := func() error { + // check if changed + if oldOrg != nil && oldOrg.Spec.BillingEntityRef == org.Spec.BillingEntityRef { + return nil + } + // check if we allow empty billing entities + if org.Spec.BillingEntityRef == "" && s.allowEmptyBillingEntity { + return nil + } - user, ok := request.UserFrom(ctx) - if !ok { - return fmt.Errorf("no user in context") - } + user, ok := request.UserFrom(ctx) + if !ok { + return fmt.Errorf("no user in context") + } - var be billingv1.BillingEntity - c, err := s.impersonator.Impersonate(user) - if err != nil { - return fmt.Errorf("failed to impersonate user: %w", err) - } + var be billingv1.BillingEntity + c, err := s.impersonator.Impersonate(user) + if err != nil { + return fmt.Errorf("failed to impersonate user: %w", err) + } + + if err := c.Get(ctx, client.ObjectKey{Name: org.Spec.BillingEntityRef}, &be); err != nil { + return err + } - if err := c.Get(ctx, client.ObjectKey{Name: org.Spec.BillingEntityRef}, &be); err != nil { - return err + return nil } - return nil + err := validate() + if err != nil && s.skipBillingEntityValidation { + fmt.Fprintf(os.Stderr, "Warning: billing entity validation for %q (ref: %q) would have failed: %v\n", org.Name, org.Spec.BillingEntityRef, err) + return nil + } + return err } diff --git a/apiserver/organization/organization.go b/apiserver/organization/organization.go index 35d17634..c27bb604 100644 --- a/apiserver/organization/organization.go +++ b/apiserver/organization/organization.go @@ -28,7 +28,7 @@ import ( // +kubebuilder:rbac:groups="flowcontrol.apiserver.k8s.io",resources=prioritylevelconfigurations;flowschemas,verbs=get;list;watch // New returns a new storage provider for Organizations -func New(clusterRoles *[]string, usernamePrefix *string, allowEmptyBillingEntity *bool) restbuilder.ResourceHandlerProvider { +func New(clusterRoles *[]string, usernamePrefix *string, allowEmptyBillingEntity, skipBillingEntityValidation *bool) restbuilder.ResourceHandlerProvider { return func(s *runtime.Scheme, g genericregistry.RESTOptionsGetter) (rest.Storage, error) { masterConfig := loopback.GetLoopbackMasterClientConfig() @@ -54,9 +54,10 @@ func New(clusterRoles *[]string, usernamePrefix *string, allowEmptyBillingEntity members: kubeMemberProvider{ Client: c, }, - usernamePrefix: *usernamePrefix, - impersonator: impersonatorFromRestconf{masterConfig, client.Options{Scheme: c.Scheme()}}, - allowEmptyBillingEntity: *allowEmptyBillingEntity, + usernamePrefix: *usernamePrefix, + impersonator: impersonatorFromRestconf{masterConfig, client.Options{Scheme: c.Scheme()}}, + allowEmptyBillingEntity: *allowEmptyBillingEntity, + skipBillingEntityValidation: *skipBillingEntityValidation, } return authwrapper.NewAuthorizedStorage(stor, metav1.GroupVersionResource{ @@ -78,7 +79,8 @@ type organizationStorage struct { impersonator impersonator - allowEmptyBillingEntity bool + skipBillingEntityValidation bool + allowEmptyBillingEntity bool } func (s organizationStorage) New() runtime.Object {