diff --git a/api/everest-server.gen.go b/api/everest-server.gen.go index a9ae296a6..67991d9ff 100644 --- a/api/everest-server.gen.go +++ b/api/everest-server.gen.go @@ -2652,40 +2652,40 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/everest.go b/api/everest.go index 43d005dc5..dd5103321 100644 --- a/api/everest.go +++ b/api/everest.go @@ -39,11 +39,11 @@ import ( "golang.org/x/time/rate" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/percona/everest/api/rbac" "github.com/percona/everest/cmd/config" "github.com/percona/everest/pkg/common" "github.com/percona/everest/pkg/kubernetes" "github.com/percona/everest/pkg/oidc" + "github.com/percona/everest/pkg/rbac" "github.com/percona/everest/pkg/session" "github.com/percona/everest/public" ) diff --git a/api/permissions.go b/api/permissions.go index bf972eb28..6527f6dd7 100644 --- a/api/permissions.go +++ b/api/permissions.go @@ -8,7 +8,7 @@ import ( "github.com/labstack/echo/v4" "go.uber.org/zap" - "github.com/percona/everest/api/rbac" + "github.com/percona/everest/pkg/rbac" ) // GetUserPermissions returns the permissions for the currently logged in user. diff --git a/client/everest-client.gen.go b/client/everest-client.gen.go index 42bb5005f..1b23eceb9 100644 --- a/client/everest-client.gen.go +++ b/client/everest-client.gen.go @@ -7323,40 +7323,40 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/commands/settings.go b/commands/settings.go index 1cbb6083a..629f0ec1e 100644 --- a/commands/settings.go +++ b/commands/settings.go @@ -30,6 +30,6 @@ func newSettingsCommand(l *zap.SugaredLogger) *cobra.Command { Short: "Configure Everest settings", } cmd.AddCommand(settings.NewOIDCCmd(l)) - + cmd.AddCommand(settings.NewRBACCmd(l)) return cmd } diff --git a/commands/settings/rbac.go b/commands/settings/rbac.go new file mode 100644 index 000000000..4a3c47f5b --- /dev/null +++ b/commands/settings/rbac.go @@ -0,0 +1,20 @@ +// Package settings ... +package settings + +import ( + "github.com/spf13/cobra" + "go.uber.org/zap" + + "github.com/percona/everest/commands/settings/rbac" +) + +// NewRBACCmd returns an new RBAC sub-command. +func NewRBACCmd(l *zap.SugaredLogger) *cobra.Command { + cmd := &cobra.Command{ + Use: "rbac", + Long: "Manage RBAC settings", + Short: "Manage RBAC settings", + } + cmd.AddCommand(rbac.NewValidateCommand(l)) + return cmd +} diff --git a/commands/settings/rbac/validate.go b/commands/settings/rbac/validate.go new file mode 100644 index 000000000..8f1dabda8 --- /dev/null +++ b/commands/settings/rbac/validate.go @@ -0,0 +1,89 @@ +// everest +// Copyright (C) 2023 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package rbac ... +package rbac + +import ( + "errors" + "fmt" + "net/url" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.uber.org/zap" + "k8s.io/client-go/tools/clientcmd" + + "github.com/percona/everest/pkg/kubernetes" + "github.com/percona/everest/pkg/output" + "github.com/percona/everest/pkg/rbac" +) + +// NewValidateCommand returns a new command for validating RBAC. +func NewValidateCommand(l *zap.SugaredLogger) *cobra.Command { + cmd := &cobra.Command{ + Use: "validate", + Long: "Validate RBAC settings", + Short: "Validate RBAC settings", + Run: func(cmd *cobra.Command, args []string) { //nolint:revive + initValidateViperFlags(cmd) + + kubeconfigPath := viper.GetString("kubeconfig") + policyFilepath := viper.GetString("policy-file") + if kubeconfigPath == "" && policyFilepath == "" { + l.Error("Either --kubeconfig or --policy-file must be set") + os.Exit(1) + } + + var k *kubernetes.Kubernetes + if kubeconfigPath != "" && policyFilepath == "" { + client, err := kubernetes.New(kubeconfigPath, l) + if err != nil { + var u *url.Error + if errors.As(err, &u) || errors.Is(err, clientcmd.ErrEmptyConfig) { + l.Error("Could not connect to Kubernetes. " + + "Make sure Kubernetes is running and is accessible from this computer/server.") + } + os.Exit(1) + } + k = client + } + + err := rbac.ValidatePolicy(cmd.Context(), k, policyFilepath) + if err != nil { + fmt.Fprint(os.Stdout, output.Failure("Invalid")) + msg := err.Error() + msg = strings.Join(strings.Split(msg, "\n"), " - ") + fmt.Fprintln(os.Stdout, msg) + os.Exit(1) + } + fmt.Fprintln(os.Stdout, output.Success("Valid")) + }, + } + initValidateFlags(cmd) + return cmd +} + +func initValidateFlags(cmd *cobra.Command) { + cmd.Flags().String("policy-file", "", "Path to the policy file to use") +} + +func initValidateViperFlags(cmd *cobra.Command) { + viper.BindEnv("kubeconfig") //nolint:errcheck,gosec + viper.BindPFlag("kubeconfig", cmd.Flags().Lookup("kubeconfig")) //nolint:errcheck,gosec + viper.BindPFlag("policy-file", cmd.Flags().Lookup("policy-file")) //nolint:errcheck,gosec +} diff --git a/docs/spec/openapi.yml b/docs/spec/openapi.yml index ef5d7d6d2..b06d0868c 100644 --- a/docs/spec/openapi.yml +++ b/docs/spec/openapi.yml @@ -152,6 +152,7 @@ paths: schema: $ref: '#/components/schemas/Error' '/namespaces': + x-everest-resource-name: namespaces get: tags: - General info diff --git a/api/rbac/configmap-adapter/adapter.go b/pkg/rbac/configmap-adapter/adapter.go similarity index 83% rename from api/rbac/configmap-adapter/adapter.go rename to pkg/rbac/configmap-adapter/adapter.go index 767bf0edf..a00f2f44c 100644 --- a/api/rbac/configmap-adapter/adapter.go +++ b/pkg/rbac/configmap-adapter/adapter.go @@ -22,10 +22,11 @@ import ( "strings" "github.com/casbin/casbin/v2/model" - "github.com/casbin/casbin/v2/persist" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/types" "github.com/percona/everest/pkg/kubernetes" + rbacutils "github.com/percona/everest/pkg/rbac/utils" ) // Adapter is the ConfigMap adapter for Casbin. @@ -33,13 +34,19 @@ import ( type Adapter struct { kubeClient *kubernetes.Kubernetes namespacedName types.NamespacedName + l *zap.SugaredLogger } -// NewAdapter is the constructor for Adapter. -func NewAdapter(kubeClient *kubernetes.Kubernetes, namespacedName types.NamespacedName) *Adapter { +// New constructs a new adapter that manages a policy inside a ConfigMap. +func New( + l *zap.SugaredLogger, + kubeClient *kubernetes.Kubernetes, + namespacedName types.NamespacedName, +) *Adapter { return &Adapter{ kubeClient: kubeClient, namespacedName: namespacedName, + l: l, } } @@ -68,9 +75,11 @@ func (a *Adapter) LoadPolicy(model model.Model) error { if str == "" { continue } - _ = persist.LoadPolicyLine(str, model) + if err := rbacutils.LoadPolicyLine(str, model); err != nil { + a.l.Error("failed to load policy", zap.Error(err)) + return err + } } - return nil } diff --git a/pkg/rbac/fileadapter/adapter.go b/pkg/rbac/fileadapter/adapter.go new file mode 100644 index 000000000..a2b317974 --- /dev/null +++ b/pkg/rbac/fileadapter/adapter.go @@ -0,0 +1,70 @@ +// Package fileadapter provides a file adapter for Casbin. +package fileadapter + +import ( + "errors" + "io" + "os" + "strings" + + "github.com/casbin/casbin/v2/model" + + rbacutils "github.com/percona/everest/pkg/rbac/utils" +) + +// Adapter is the file adapter for Casbin. +type Adapter struct { + content string +} + +// New returns a new adapter that reads a policy located at the given path. +func New(path string) (*Adapter, error) { + f, err := os.Open(path) //nolint:gosec + if err != nil { + return nil, err + } + defer f.Close() //nolint:errcheck + + content, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + return &Adapter{ + content: string(content), + }, nil +} + +// LoadPolicy loads all policy rules from the storage. +func (a *Adapter) LoadPolicy(model model.Model) error { + strs := strings.Split(a.content, "\n") + for _, str := range strs { + if str == "" { + continue + } + if err := rbacutils.LoadPolicyLine(str, model); err != nil { + return err + } + } + return nil +} + +// SavePolicy saves all policy rules to the storage. +func (a *Adapter) SavePolicy(_ model.Model) error { + return errors.New("not implemented") +} + +// AddPolicy adds a policy rule to the storage. +func (a *Adapter) AddPolicy(_ string, _ string, _ []string) error { + return errors.New("not implemented") +} + +// RemovePolicy removes a policy rule from the storage. +func (a *Adapter) RemovePolicy(_ string, _ string, _ []string) error { + return errors.New("not implemented") +} + +// RemoveFilteredPolicy removes policy rules that match the filter from the storage. +func (a *Adapter) RemoveFilteredPolicy(_ string, _ string, _ int, _ ...string) error { + return errors.New("not implemented") +} diff --git a/api/rbac/rbac.go b/pkg/rbac/rbac.go similarity index 88% rename from api/rbac/rbac.go rename to pkg/rbac/rbac.go index 9e0605edd..a1c346315 100644 --- a/api/rbac/rbac.go +++ b/pkg/rbac/rbac.go @@ -31,12 +31,13 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - configmapadapter "github.com/percona/everest/api/rbac/configmap-adapter" everestclient "github.com/percona/everest/client" "github.com/percona/everest/data" "github.com/percona/everest/pkg/common" "github.com/percona/everest/pkg/kubernetes" "github.com/percona/everest/pkg/kubernetes/informer" + configmapadapter "github.com/percona/everest/pkg/rbac/configmap-adapter" + "github.com/percona/everest/pkg/rbac/fileadapter" "github.com/percona/everest/pkg/session" ) @@ -68,32 +69,47 @@ func refreshEnforcerInBackground( return nil } -// NewEnforcer creates a new Casbin enforcer with the RBAC model and ConfigMap adapter. -func NewEnforcer(ctx context.Context, kubeClient *kubernetes.Kubernetes, l *zap.SugaredLogger) (*casbin.Enforcer, error) { +func getModel() (model.Model, error) { modelData, err := fs.ReadFile(data.RBAC, "rbac/model.conf") if err != nil { return nil, errors.Join(err, errors.New("could not read casbin model")) } + return model.NewModelFromString(string(modelData)) +} - model, err := model.NewModelFromString(string(modelData)) +// NewEnforcer creates a new Casbin enforcer with the RBAC model and ConfigMap adapter. +func NewEnforcer(ctx context.Context, kubeClient *kubernetes.Kubernetes, l *zap.SugaredLogger) (*casbin.Enforcer, error) { + model, err := getModel() if err != nil { - return nil, errors.Join(err, errors.New("could not create casbin model")) + return nil, err } cmReq := types.NamespacedName{ - Namespace: kubeClient.Namespace(), + Namespace: common.SystemNamespace, Name: common.EverestRBACConfigMapName, } - adapter := configmapadapter.NewAdapter(kubeClient, cmReq) + adapter := configmapadapter.New(l, kubeClient, cmReq) - enforcer, err := casbin.NewEnforcer(model, adapter, true) + enforcer, err := casbin.NewEnforcer(model, adapter, false) if err != nil { return nil, errors.Join(err, errors.New("could not create casbin enforcer")) } - return enforcer, refreshEnforcerInBackground(ctx, kubeClient, enforcer, l) } +// NewEnforcerFromFilePath creates a new Casbin enforcer with the policy stored at the given filePath. +func NewEnforcerFromFilePath(filePath string) (*casbin.Enforcer, error) { + model, err := getModel() + if err != nil { + return nil, err + } + adapter, err := fileadapter.New(filePath) + if err != nil { + return nil, err + } + return casbin.NewEnforcer(model, adapter) +} + // GetUser extracts the user from the JWT token in the context. func GetUser(c echo.Context) (string, error) { token, ok := c.Get("user").(*jwt.Token) // by default token is stored under `user` key @@ -183,6 +199,9 @@ func NewEnforceHandler(basePath string, enforcer *casbin.Enforcer) func(c echo.C namespace := c.Param("namespace") name := c.Param("name") object = namespace + "/" + name + case "namespaces": + name := c.Param("name") + object = name } action, ok := actionMethodMap[c.Request().Method] diff --git a/pkg/rbac/testdata/policy-1-good.csv b/pkg/rbac/testdata/policy-1-good.csv new file mode 100644 index 000000000..4f297cc1a --- /dev/null +++ b/pkg/rbac/testdata/policy-1-good.csv @@ -0,0 +1,4 @@ +p, adminrole:role, namespaces, read, * +p, adminrole:role, database-engines, *, */* +p, adminrole:role, monitoring-instances, *, */* +g, admin, adminrole:role diff --git a/pkg/rbac/testdata/policy-2-bad.csv b/pkg/rbac/testdata/policy-2-bad.csv new file mode 100644 index 000000000..f695c41a5 --- /dev/null +++ b/pkg/rbac/testdata/policy-2-bad.csv @@ -0,0 +1,4 @@ +p, adminrole:role, namespaces, read, * +p, adminrole:role, database-engines, *, */* +this is a bad policy +xxxxxxxxxx diff --git a/pkg/rbac/testdata/policy-3-bad.csv b/pkg/rbac/testdata/policy-3-bad.csv new file mode 100644 index 000000000..6fc52d942 --- /dev/null +++ b/pkg/rbac/testdata/policy-3-bad.csv @@ -0,0 +1,6 @@ +p, adminrole:role, namespaces, read, * +p, adminrole:role, database-engines, *, */* +p, adminrole:role, monitoring-instances, *, */* +p, adminrole:role, monitoring instances, *, */* +g, admin, adminrole:role + diff --git a/pkg/rbac/testdata/policy-4-bad.csv b/pkg/rbac/testdata/policy-4-bad.csv new file mode 100644 index 000000000..8420eabbc --- /dev/null +++ b/pkg/rbac/testdata/policy-4-bad.csv @@ -0,0 +1,5 @@ +p, adminrole:role, namespaces, read, * +p, adminrole:role, database-engines, *, */* +p, adminrole:role, monitoring-instances, *, */* +p, notexists:role, monitoring-instances, *, */* +g, admin, adminrole:role diff --git a/pkg/rbac/testdata/policy-5-bad.csv b/pkg/rbac/testdata/policy-5-bad.csv new file mode 100644 index 000000000..f931c944a --- /dev/null +++ b/pkg/rbac/testdata/policy-5-bad.csv @@ -0,0 +1,4 @@ +p, adminrole:role, namespaces, read, * +p, adminrole:role, database-engines, *, */* +p, adminrole:role, monitoring-instances, *, */*, asdfasdf +g, admin, adminrole:role diff --git a/pkg/rbac/testdata/policy-6-bad.csv b/pkg/rbac/testdata/policy-6-bad.csv new file mode 100644 index 000000000..963a60936 --- /dev/null +++ b/pkg/rbac/testdata/policy-6-bad.csv @@ -0,0 +1,4 @@ +p, adminrole:role, namespaces, read, * +p, adminrole:role, database-engines, *, */* +p, alice, non-existent-resource, *, */* +g, admin, adminrole:role diff --git a/pkg/rbac/utils/utils.go b/pkg/rbac/utils/utils.go new file mode 100644 index 000000000..bd55db77a --- /dev/null +++ b/pkg/rbac/utils/utils.go @@ -0,0 +1,37 @@ +// Package utils contains utility functions for RBAC. +package utils + +import ( + "encoding/csv" + "fmt" + "strings" + + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" +) + +// LoadPolicyLine loads a text line as a policy rule to model. +// +// This function is copied (and modified) from https://github.com/casbin/casbin/blob/71c8c84e300cf8b276f28e21e555a39ad793d65c/persist/adapter.go#L25. +// The original function is missing certain validations that leads to panics. +func LoadPolicyLine(line string, m model.Model) error { + if line == "" || strings.HasPrefix(line, "#") { + return nil + } + + r := csv.NewReader(strings.NewReader(line)) + r.Comma = ',' + r.Comment = '#' + r.TrimLeadingSpace = true + + tokens, err := r.Read() + if err != nil { + return err + } + + if len(tokens) < 2 { + return fmt.Errorf("invalid policy line '%s'", line) + } + + return persist.LoadPolicyArray(tokens, m) +} diff --git a/pkg/rbac/validate.go b/pkg/rbac/validate.go new file mode 100644 index 000000000..db6ca3553 --- /dev/null +++ b/pkg/rbac/validate.go @@ -0,0 +1,109 @@ +package rbac + +import ( + "context" + "errors" + "fmt" + "regexp" + "slices" + "strings" + + "github.com/casbin/casbin/v2" + "go.uber.org/zap" + + "github.com/percona/everest/pkg/kubernetes" +) + +// ErrPolicySyntax is returned when a policy has a syntax error. +var errPolicySyntax = errors.New("policy syntax error") + +// ValidatePolicy validates a policy from either Kubernetes or local file. +func ValidatePolicy(ctx context.Context, k *kubernetes.Kubernetes, filepath string) error { + enforcer, err := newKubeOrFileEnforcer(ctx, k, filepath) + if err != nil { + return errors.Join(errPolicySyntax, err) + } + + // check basic policy syntax. + policy := enforcer.GetPolicy() + for _, policy := range policy { + if err := validateTerms(policy); err != nil { + return errors.Join(errPolicySyntax, err) + } + } + + // ensure that non-existent roles are not used. + roles := enforcer.GetAllRoles() + if err := checkRoles(roles, policy); err != nil { + return errors.Join(errPolicySyntax, err) + } + + // ensure that non-existent resources are not used. + if err := checkResourceNames(policy); err != nil { + return errors.Join(errPolicySyntax, err) + } + return nil +} + +func checkResourceNames(policies [][]string) error { + resourcePathMap, _, err := buildPathResourceMap("") + if err != nil { + return fmt.Errorf("failed to get resource path map: %w", err) + } + knownResources := make(map[string]struct{}) + for _, resource := range resourcePathMap { + knownResources[resource] = struct{}{} + } + for _, policy := range policies { + resourceName := policy[1] + if resourceName == "*" { + continue + } + if _, ok := knownResources[resourceName]; !ok { + return fmt.Errorf("unknown resource name '%s'", resourceName) + } + } + return nil +} + +func checkRoles(roles []string, policies [][]string) error { + for _, policy := range policies { + roleName := policy[0] + if !strings.HasSuffix(roleName, ":role") { + continue + } + if !slices.Contains(roles, roleName) { + return fmt.Errorf("role '%s' does not exist", roleName) + } + } + return nil +} + +func validateTerms(terms []string) error { + pattern := `^[/*-_:a-zA-Z0-9]+$` + compiled := regexp.MustCompile(pattern) + for _, term := range terms { + if !compiled.MatchString(term) { + return fmt.Errorf("invalid policy term '%s'", term) + } + } + return nil +} + +//nolint:nonamedreturns +func newKubeOrFileEnforcer( + ctx context.Context, + kubeClient *kubernetes.Kubernetes, + filePath string, +) (e *casbin.Enforcer, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("cannot create enforcer: %v", r) + e = nil + } + }() + if filePath != "" { + return NewEnforcerFromFilePath(filePath) + } + return NewEnforcer(ctx, kubeClient, zap.NewNop().Sugar()) +} diff --git a/pkg/rbac/validate_test.go b/pkg/rbac/validate_test.go new file mode 100644 index 000000000..79dfd5907 --- /dev/null +++ b/pkg/rbac/validate_test.go @@ -0,0 +1,169 @@ +package rbac + +import ( + "context" + "errors" + "fmt" + "testing" +) + +func TestValidatePolicy(t *testing.T) { + t.Parallel() + testcases := []struct { + path string + err error + }{ + { + path: "./testdata/policy-1-good.csv", + err: nil, + }, + { + path: "./testdata/policy-2-bad.csv", + err: errPolicySyntax, + }, + { + path: "./testdata/policy-3-bad.csv", + err: errPolicySyntax, + }, + { + path: "./testdata/policy-4-bad.csv", + err: errPolicySyntax, + }, + { + path: "./testdata/policy-5-bad.csv", + err: errPolicySyntax, + }, + } + + ctx := context.Background() + for i, tc := range testcases { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + t.Parallel() + err := ValidatePolicy(ctx, nil, tc.path) + if err != nil && tc.err == nil { + t.Fatalf("expected no error, got %v", err) + } + if err == nil && tc.err != nil { + t.Fatalf("expected error %v, got nil", tc.err) + } + if !errors.Is(err, tc.err) { + t.Fatalf("unexpected error %v", err) + } + }) + } +} + +func TestCheckResourceNames(t *testing.T) { + t.Parallel() + testcases := []struct { + policies [][]string + valid bool + }{ + { + policies: [][]string{ + {"admin:role", "database-clusters", "create", "*"}, + {"admin:role", "monitoring-instances", "*", "*"}, + }, + valid: true, + }, + { + policies: [][]string{ + {"admin:role", "database-clusters", "create", "*"}, + {"admin:role", "monitoring-instances", "*", "*"}, + {"admin:role", "does-not-exist", "*", "*"}, + }, + valid: false, + }, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + t.Parallel() + err := checkResourceNames(tc.policies) + if err != nil && tc.valid { + t.Fatalf("expected no error, got %v", err) + } + if err == nil && !tc.valid { + t.Fatalf("expected error, got nil") + } + }) + } +} + +func TestCheckRoles(t *testing.T) { + t.Parallel() + testcases := []struct { + roles []string + policies [][]string + valid bool + }{ + { + roles: []string{"admin:role", "viewer:role"}, + policies: [][]string{ + {"admin:role", "database-clusters", "create", "*"}, + {"admin:role", "monitoring-instances", "*", "*"}, + }, + valid: true, + }, + { + roles: []string{"admin:role", "viewer:role"}, + policies: [][]string{ + {"admin:role", "database-clusters", "create", "*"}, + {"admin:role", "monitoring-instances", "*", "*"}, + {"does-not-exist:role", "monitoring-instances", "*", "*"}, + }, + valid: false, + }, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + t.Parallel() + err := checkRoles(tc.roles, tc.policies) + if err != nil && tc.valid { + t.Fatalf("expected no error, got %v", err) + } + if err == nil && !tc.valid { + t.Fatalf("expected error, got nil") + } + }) + } +} + +func TestValidateTerms(t *testing.T) { + t.Parallel() + testcases := []struct { + terms []string + valid bool + }{ + { + terms: []string{"admin:role", "database-clusters", "create", "*"}, + valid: true, + }, + { + terms: []string{"admin!!:role", "database-clusters", "create", "*"}, + valid: false, + }, + { + terms: []string{"admin!!:role", "database clusters", "create", "*"}, + valid: false, + }, + { + terms: []string{"admin!!:role", "", "create", "*"}, + valid: false, + }, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + t.Parallel() + err := validateTerms(tc.terms) + if err != nil && tc.valid { + t.Fatalf("expected no error, got %v", err) + } + if err == nil && !tc.valid { + t.Fatalf("expected error, got nil") + } + }) + } +}