github.com/argoproj/argo-cd/v3@v3.2.1/util/rbac/rbac.go (about)

     1  package rbac
     2  
     3  import (
     4  	"context"
     5  	"encoding/csv"
     6  	"errors"
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/argoproj/argo-cd/v3/util/assets"
    14  	"github.com/argoproj/argo-cd/v3/util/glob"
    15  	jwtutil "github.com/argoproj/argo-cd/v3/util/jwt"
    16  
    17  	"github.com/casbin/casbin/v2"
    18  	"github.com/casbin/casbin/v2/model"
    19  	"github.com/casbin/casbin/v2/util"
    20  	"github.com/casbin/govaluate"
    21  	"github.com/golang-jwt/jwt/v5"
    22  	gocache "github.com/patrickmn/go-cache"
    23  	log "github.com/sirupsen/logrus"
    24  	"google.golang.org/grpc/codes"
    25  	"google.golang.org/grpc/status"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/fields"
    30  	informersv1 "k8s.io/client-go/informers/core/v1"
    31  	"k8s.io/client-go/kubernetes"
    32  	"k8s.io/client-go/tools/cache"
    33  )
    34  
    35  const (
    36  	ConfigMapPolicyCSVKey     = "policy.csv"
    37  	ConfigMapPolicyDefaultKey = "policy.default"
    38  	ConfigMapScopesKey        = "scopes"
    39  	ConfigMapMatchModeKey     = "policy.matchMode"
    40  	GlobMatchMode             = "glob"
    41  	RegexMatchMode            = "regex"
    42  
    43  	defaultRBACSyncPeriod = 10 * time.Minute
    44  )
    45  
    46  // CasbinEnforcer represents methods that must be implemented by a Casbin enforces
    47  type CasbinEnforcer interface {
    48  	EnableLog(bool)
    49  	Enforce(rvals ...any) (bool, error)
    50  	LoadPolicy() error
    51  	EnableEnforce(bool)
    52  	AddFunction(name string, function govaluate.ExpressionFunction)
    53  	GetGroupingPolicy() ([][]string, error)
    54  	GetAllRoles() ([]string, error)
    55  	GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error)
    56  }
    57  
    58  const (
    59  	// please add new items to Resources
    60  	ResourceClusters          = "clusters"
    61  	ResourceProjects          = "projects"
    62  	ResourceApplications      = "applications"
    63  	ResourceApplicationSets   = "applicationsets"
    64  	ResourceRepositories      = "repositories"
    65  	ResourceWriteRepositories = "write-repositories"
    66  	ResourceCertificates      = "certificates"
    67  	ResourceAccounts          = "accounts"
    68  	ResourceGPGKeys           = "gpgkeys"
    69  	ResourceLogs              = "logs"
    70  	ResourceExec              = "exec"
    71  	ResourceExtensions        = "extensions"
    72  
    73  	// please add new items to Actions
    74  	ActionGet      = "get"
    75  	ActionCreate   = "create"
    76  	ActionUpdate   = "update"
    77  	ActionDelete   = "delete"
    78  	ActionSync     = "sync"
    79  	ActionOverride = "override"
    80  	ActionAction   = "action"
    81  	ActionInvoke   = "invoke"
    82  )
    83  
    84  var (
    85  	DefaultScopes = []string{"groups"}
    86  	Resources     = []string{
    87  		ResourceClusters,
    88  		ResourceProjects,
    89  		ResourceApplications,
    90  		ResourceApplicationSets,
    91  		ResourceRepositories,
    92  		ResourceWriteRepositories,
    93  		ResourceCertificates,
    94  		ResourceAccounts,
    95  		ResourceGPGKeys,
    96  		ResourceLogs,
    97  		ResourceExec,
    98  		ResourceExtensions,
    99  	}
   100  	Actions = []string{
   101  		ActionGet,
   102  		ActionCreate,
   103  		ActionUpdate,
   104  		ActionDelete,
   105  		ActionSync,
   106  		ActionOverride,
   107  		ActionAction,
   108  		ActionInvoke,
   109  	}
   110  )
   111  
   112  var ProjectScoped = map[string]bool{
   113  	ResourceApplications:    true,
   114  	ResourceApplicationSets: true,
   115  	ResourceLogs:            true,
   116  	ResourceExec:            true,
   117  	ResourceClusters:        true,
   118  	ResourceRepositories:    true,
   119  }
   120  
   121  // Enforcer is a wrapper around an Casbin enforcer that:
   122  // * is backed by a kubernetes config map
   123  // * has a predefined RBAC model
   124  // * supports a built-in policy
   125  // * supports a user-defined policy
   126  // * supports a custom JWT claims enforce function
   127  type Enforcer struct {
   128  	lock               sync.Mutex
   129  	enforcerCache      *gocache.Cache
   130  	adapter            *argocdAdapter
   131  	enableLog          bool
   132  	enabled            bool
   133  	clientset          kubernetes.Interface
   134  	namespace          string
   135  	configmap          string
   136  	claimsEnforcerFunc ClaimsEnforcerFunc
   137  	model              model.Model
   138  	defaultRole        string
   139  	matchMode          string
   140  }
   141  
   142  // cachedEnforcer holds the Casbin enforcer instances and optional custom project policy
   143  type cachedEnforcer struct {
   144  	enforcer CasbinEnforcer
   145  	policy   string
   146  }
   147  
   148  func (e *Enforcer) invalidateCache(actions ...func()) {
   149  	e.lock.Lock()
   150  	defer e.lock.Unlock()
   151  
   152  	for _, action := range actions {
   153  		action()
   154  	}
   155  	e.enforcerCache.Flush()
   156  }
   157  
   158  func (e *Enforcer) getCasbinEnforcer(project string, policy string) CasbinEnforcer {
   159  	res, err := e.tryGetCasbinEnforcer(project, policy)
   160  	if err != nil {
   161  		panic(err)
   162  	}
   163  	return res
   164  }
   165  
   166  // tryGetCasbinEnforcer returns the cached enforcer for the given optional project and project policy.
   167  func (e *Enforcer) tryGetCasbinEnforcer(project string, policy string) (CasbinEnforcer, error) {
   168  	e.lock.Lock()
   169  	defer e.lock.Unlock()
   170  	var cached *cachedEnforcer
   171  	val, ok := e.enforcerCache.Get(project)
   172  	if ok {
   173  		if c, ok := val.(*cachedEnforcer); ok && c.policy == policy {
   174  			cached = c
   175  		}
   176  	}
   177  	if cached != nil {
   178  		return cached.enforcer, nil
   179  	}
   180  	matchFunc := globMatchFunc
   181  	if e.matchMode == RegexMatchMode {
   182  		matchFunc = util.RegexMatchFunc
   183  	}
   184  
   185  	var err error
   186  	var enforcer CasbinEnforcer
   187  	if policy != "" {
   188  		if enforcer, err = newEnforcerSafe(matchFunc, e.model, newAdapter(e.adapter.builtinPolicy, e.adapter.userDefinedPolicy, policy)); err != nil {
   189  			// fallback to default policy if project policy is invalid
   190  			log.Errorf("Failed to load project '%s' policy", project)
   191  			enforcer, err = newEnforcerSafe(matchFunc, e.model, e.adapter)
   192  		}
   193  	} else {
   194  		enforcer, err = newEnforcerSafe(matchFunc, e.model, e.adapter)
   195  	}
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	enforcer.AddFunction("globOrRegexMatch", matchFunc)
   201  	enforcer.EnableLog(e.enableLog)
   202  	enforcer.EnableEnforce(e.enabled)
   203  	e.enforcerCache.SetDefault(project, &cachedEnforcer{enforcer: enforcer, policy: policy})
   204  	return enforcer, nil
   205  }
   206  
   207  // ClaimsEnforcerFunc is func template to enforce a JWT claims. The subject is replaced
   208  type ClaimsEnforcerFunc func(claims jwt.Claims, rvals ...any) bool
   209  
   210  func newEnforcerSafe(matchFunction govaluate.ExpressionFunction, params ...any) (e CasbinEnforcer, err error) {
   211  	defer func() {
   212  		if r := recover(); r != nil {
   213  			err = fmt.Errorf("%v", r)
   214  			e = nil
   215  		}
   216  	}()
   217  	enfs, err := casbin.NewCachedEnforcer(params...)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	enfs.AddFunction("globOrRegexMatch", matchFunction)
   222  	return enfs, nil
   223  }
   224  
   225  func NewEnforcer(clientset kubernetes.Interface, namespace, configmap string, claimsEnforcer ClaimsEnforcerFunc) *Enforcer {
   226  	adapter := newAdapter("", "", "")
   227  	builtInModel := newBuiltInModel()
   228  	return &Enforcer{
   229  		enforcerCache:      gocache.New(time.Hour, time.Hour),
   230  		adapter:            adapter,
   231  		clientset:          clientset,
   232  		namespace:          namespace,
   233  		configmap:          configmap,
   234  		model:              builtInModel,
   235  		claimsEnforcerFunc: claimsEnforcer,
   236  		enabled:            true,
   237  	}
   238  }
   239  
   240  // EnableLog executes casbin.Enforcer functionality.
   241  func (e *Enforcer) EnableLog(s bool) {
   242  	e.invalidateCache(func() {
   243  		e.enableLog = s
   244  	})
   245  }
   246  
   247  // EnableEnforce executes casbin.Enforcer functionality and will invalidate cache if required.
   248  func (e *Enforcer) EnableEnforce(s bool) {
   249  	e.invalidateCache(func() {
   250  		e.enabled = s
   251  	})
   252  }
   253  
   254  // LoadPolicy executes casbin.Enforcer functionality and will invalidate cache if required.
   255  func (e *Enforcer) LoadPolicy() error {
   256  	_, err := e.tryGetCasbinEnforcer("", "")
   257  	return err
   258  }
   259  
   260  // CheckUserDefinedRoleReferentialIntegrity iterates over roles and policies to validate the existence of a matching policy subject for every defined role
   261  func CheckUserDefinedRoleReferentialIntegrity(e CasbinEnforcer) error {
   262  	allRoles, err := e.GetAllRoles()
   263  	if err != nil {
   264  		return err
   265  	}
   266  	notFound := make([]string, 0)
   267  	for _, roleName := range allRoles {
   268  		permissions, err := e.GetImplicitPermissionsForUser(roleName)
   269  		if err != nil {
   270  			return err
   271  		}
   272  		if len(permissions) == 0 {
   273  			notFound = append(notFound, roleName)
   274  		}
   275  	}
   276  	if len(notFound) > 0 {
   277  		return fmt.Errorf("user defined roles not found in policies: %s", strings.Join(notFound, ","))
   278  	}
   279  	return nil
   280  }
   281  
   282  // Glob match func
   283  func globMatchFunc(args ...any) (any, error) {
   284  	if len(args) < 2 {
   285  		return false, nil
   286  	}
   287  	val, ok := args[0].(string)
   288  	if !ok {
   289  		return false, nil
   290  	}
   291  
   292  	pattern, ok := args[1].(string)
   293  	if !ok {
   294  		return false, nil
   295  	}
   296  
   297  	return glob.Match(pattern, val), nil
   298  }
   299  
   300  // SetMatchMode set match mode on runtime, glob match or regex match
   301  func (e *Enforcer) SetMatchMode(mode string) {
   302  	e.invalidateCache(func() {
   303  		if mode == RegexMatchMode {
   304  			e.matchMode = RegexMatchMode
   305  		} else {
   306  			e.matchMode = GlobMatchMode
   307  		}
   308  	})
   309  }
   310  
   311  // SetDefaultRole sets a default role to use during enforcement. Will fall back to this role if
   312  // normal enforcement fails
   313  func (e *Enforcer) SetDefaultRole(roleName string) {
   314  	e.defaultRole = roleName
   315  }
   316  
   317  // SetClaimsEnforcerFunc sets a claims enforce function during enforcement. The claims enforce function
   318  // can extract claims from JWT token and do the proper enforcement based on user, group or any information
   319  // available in the input parameter list
   320  func (e *Enforcer) SetClaimsEnforcerFunc(claimsEnforcer ClaimsEnforcerFunc) {
   321  	e.claimsEnforcerFunc = claimsEnforcer
   322  }
   323  
   324  // Enforce is a wrapper around casbin.Enforce to additionally enforce a default role and a custom
   325  // claims function
   326  func (e *Enforcer) Enforce(rvals ...any) bool {
   327  	return enforce(e.getCasbinEnforcer("", ""), e.defaultRole, e.claimsEnforcerFunc, rvals...)
   328  }
   329  
   330  // EnforceErr is a convenience helper to wrap a failed enforcement with a detailed error about the request
   331  func (e *Enforcer) EnforceErr(rvals ...any) error {
   332  	if !e.Enforce(rvals...) {
   333  		errMsg := "permission denied"
   334  
   335  		if len(rvals) > 0 {
   336  			rvalsStrs := make([]string, len(rvals)-1)
   337  			for i, rval := range rvals[1:] {
   338  				rvalsStrs[i] = fmt.Sprintf("%s", rval)
   339  			}
   340  			if s, ok := rvals[0].(jwt.Claims); ok {
   341  				claims, err := jwtutil.MapClaims(s)
   342  				if err == nil {
   343  					userId := jwtutil.GetUserIdentifier(claims)
   344  					if userId != "" {
   345  						rvalsStrs = append(rvalsStrs, "sub: "+userId)
   346  					}
   347  					if issuedAtTime, err := jwtutil.IssuedAtTime(claims); err == nil {
   348  						rvalsStrs = append(rvalsStrs, "iat: "+issuedAtTime.Format(time.RFC3339))
   349  					}
   350  				}
   351  			}
   352  			errMsg = fmt.Sprintf("%s: %s", errMsg, strings.Join(rvalsStrs, ", "))
   353  		}
   354  
   355  		return status.Error(codes.PermissionDenied, errMsg)
   356  	}
   357  	return nil
   358  }
   359  
   360  // EnforceRuntimePolicy enforces a policy defined at run-time which augments the built-in and
   361  // user-defined policy. This allows any explicit denies of the built-in, and user-defined policies
   362  // to override the run-time policy. Runs normal enforcement if run-time policy is empty.
   363  func (e *Enforcer) EnforceRuntimePolicy(project string, policy string, rvals ...any) bool {
   364  	enf := e.CreateEnforcerWithRuntimePolicy(project, policy)
   365  	return e.EnforceWithCustomEnforcer(enf, rvals...)
   366  }
   367  
   368  // CreateEnforcerWithRuntimePolicy creates an enforcer with a policy defined at run-time which augments the built-in and
   369  // user-defined policy. This allows any explicit denies of the built-in, and user-defined policies
   370  // to override the run-time policy. Runs normal enforcement if run-time policy is empty.
   371  func (e *Enforcer) CreateEnforcerWithRuntimePolicy(project string, policy string) CasbinEnforcer {
   372  	return e.getCasbinEnforcer(project, policy)
   373  }
   374  
   375  // EnforceWithCustomEnforcer wraps enforce with an custom enforcer
   376  func (e *Enforcer) EnforceWithCustomEnforcer(enf CasbinEnforcer, rvals ...any) bool {
   377  	return enforce(enf, e.defaultRole, e.claimsEnforcerFunc, rvals...)
   378  }
   379  
   380  // enforce is a helper to additionally check a default role and invoke a custom claims enforcement function
   381  func enforce(enf CasbinEnforcer, defaultRole string, claimsEnforcerFunc ClaimsEnforcerFunc, rvals ...any) bool {
   382  	// check the default role
   383  	if defaultRole != "" && len(rvals) >= 2 {
   384  		if ok, err := enf.Enforce(append([]any{defaultRole}, rvals[1:]...)...); ok && err == nil {
   385  			return true
   386  		}
   387  	}
   388  	if len(rvals) == 0 {
   389  		return false
   390  	}
   391  	// check if subject is jwt.Claims vs. a normal subject string and run custom claims
   392  	// enforcement func (if set)
   393  	sub := rvals[0]
   394  	switch s := sub.(type) {
   395  	case string:
   396  		// noop
   397  	case jwt.Claims:
   398  		if claimsEnforcerFunc != nil && claimsEnforcerFunc(s, rvals...) {
   399  			return true
   400  		}
   401  		rvals = append([]any{""}, rvals[1:]...)
   402  	default:
   403  		rvals = append([]any{""}, rvals[1:]...)
   404  	}
   405  	ok, err := enf.Enforce(rvals...)
   406  	return ok && err == nil
   407  }
   408  
   409  // SetBuiltinPolicy sets a built-in policy, which augments any user defined policies
   410  func (e *Enforcer) SetBuiltinPolicy(policy string) error {
   411  	e.invalidateCache(func() {
   412  		e.adapter.builtinPolicy = policy
   413  	})
   414  	return e.LoadPolicy()
   415  }
   416  
   417  // SetUserPolicy sets a user policy, augmenting the built-in policy
   418  func (e *Enforcer) SetUserPolicy(policy string) error {
   419  	e.invalidateCache(func() {
   420  		e.adapter.userDefinedPolicy = policy
   421  	})
   422  	return e.LoadPolicy()
   423  }
   424  
   425  // newInformer returns an informer which watches updates on the rbac configmap
   426  func (e *Enforcer) newInformer() cache.SharedIndexInformer {
   427  	tweakConfigMap := func(options *metav1.ListOptions) {
   428  		cmFieldSelector := fields.ParseSelectorOrDie("metadata.name=" + e.configmap)
   429  		options.FieldSelector = cmFieldSelector.String()
   430  	}
   431  	indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
   432  	return informersv1.NewFilteredConfigMapInformer(e.clientset, e.namespace, defaultRBACSyncPeriod, indexers, tweakConfigMap)
   433  }
   434  
   435  // RunPolicyLoader runs the policy loader which watches policy updates from the configmap and reloads them
   436  func (e *Enforcer) RunPolicyLoader(ctx context.Context, onUpdated func(cm *corev1.ConfigMap) error) error {
   437  	cm, err := e.clientset.CoreV1().ConfigMaps(e.namespace).Get(ctx, e.configmap, metav1.GetOptions{})
   438  	if err != nil {
   439  		if !apierrors.IsNotFound(err) {
   440  			return err
   441  		}
   442  	} else {
   443  		err = e.syncUpdate(cm, onUpdated)
   444  		if err != nil {
   445  			return err
   446  		}
   447  	}
   448  	e.runInformer(ctx, onUpdated)
   449  	return nil
   450  }
   451  
   452  func (e *Enforcer) runInformer(ctx context.Context, onUpdated func(cm *corev1.ConfigMap) error) {
   453  	cmInformer := e.newInformer()
   454  	_, err := cmInformer.AddEventHandler(
   455  		cache.ResourceEventHandlerFuncs{
   456  			AddFunc: func(obj any) {
   457  				if cm, ok := obj.(*corev1.ConfigMap); ok {
   458  					err := e.syncUpdate(cm, onUpdated)
   459  					if err != nil {
   460  						log.Error(err)
   461  					} else {
   462  						log.Infof("RBAC ConfigMap '%s' added", e.configmap)
   463  					}
   464  				}
   465  			},
   466  			UpdateFunc: func(old, new any) {
   467  				oldCM := old.(*corev1.ConfigMap)
   468  				newCM := new.(*corev1.ConfigMap)
   469  				if oldCM.ResourceVersion == newCM.ResourceVersion {
   470  					return
   471  				}
   472  				err := e.syncUpdate(newCM, onUpdated)
   473  				if err != nil {
   474  					log.Error(err)
   475  				} else {
   476  					log.Infof("RBAC ConfigMap '%s' updated", e.configmap)
   477  				}
   478  			},
   479  		},
   480  	)
   481  	if err != nil {
   482  		log.Error(err)
   483  	}
   484  	log.Info("Starting rbac config informer")
   485  	cmInformer.Run(ctx.Done())
   486  	log.Info("rbac configmap informer cancelled")
   487  }
   488  
   489  // PolicyCSV will generate the final policy csv to be used
   490  // by Argo CD RBAC. It will find entries in the given data
   491  // that matches the policy key name convention:
   492  //
   493  //	policy[.overlay].csv
   494  func PolicyCSV(data map[string]string) string {
   495  	var strBuilder strings.Builder
   496  	// add the main policy first
   497  	if p, ok := data[ConfigMapPolicyCSVKey]; ok {
   498  		strBuilder.WriteString(p)
   499  	}
   500  
   501  	keys := make([]string, 0, len(data))
   502  	for k := range data {
   503  		keys = append(keys, k)
   504  	}
   505  	sort.Strings(keys)
   506  
   507  	// append additional policies at the end of the csv
   508  	for _, key := range keys {
   509  		value := data[key]
   510  		if strings.HasPrefix(key, "policy.") &&
   511  			strings.HasSuffix(key, ".csv") &&
   512  			key != ConfigMapPolicyCSVKey {
   513  			strBuilder.WriteString("\n")
   514  			strBuilder.WriteString(value)
   515  		}
   516  	}
   517  	return strBuilder.String()
   518  }
   519  
   520  // syncUpdate updates the enforcer
   521  func (e *Enforcer) syncUpdate(cm *corev1.ConfigMap, onUpdated func(cm *corev1.ConfigMap) error) error {
   522  	e.SetDefaultRole(cm.Data[ConfigMapPolicyDefaultKey])
   523  	e.SetMatchMode(cm.Data[ConfigMapMatchModeKey])
   524  	policyCSV := PolicyCSV(cm.Data)
   525  	if err := onUpdated(cm); err != nil {
   526  		return err
   527  	}
   528  	return e.SetUserPolicy(policyCSV)
   529  }
   530  
   531  // ValidatePolicy verifies a policy string is acceptable to casbin
   532  func ValidatePolicy(policy string) error {
   533  	casbinEnforcer, err := newEnforcerSafe(globMatchFunc, newBuiltInModel(), newAdapter("", "", policy))
   534  	if err != nil {
   535  		return fmt.Errorf("policy syntax error: %s", policy)
   536  	}
   537  
   538  	// Check for referential integrity
   539  	if err := CheckUserDefinedRoleReferentialIntegrity(casbinEnforcer); err != nil {
   540  		log.Warning(err.Error())
   541  	}
   542  	return nil
   543  }
   544  
   545  // newBuiltInModel is a helper to return a brand new casbin model from the built-in model string.
   546  // This is needed because it is not safe to re-use the same casbin Model when instantiating new
   547  // casbin enforcers.
   548  func newBuiltInModel() model.Model {
   549  	m, err := model.NewModelFromString(assets.ModelConf)
   550  	if err != nil {
   551  		panic(err)
   552  	}
   553  	return m
   554  }
   555  
   556  // Casbin adapter which satisfies persist.Adapter interface
   557  type argocdAdapter struct {
   558  	builtinPolicy     string
   559  	userDefinedPolicy string
   560  	runtimePolicy     string
   561  }
   562  
   563  func newAdapter(builtinPolicy, userDefinedPolicy, runtimePolicy string) *argocdAdapter {
   564  	return &argocdAdapter{
   565  		builtinPolicy:     builtinPolicy,
   566  		userDefinedPolicy: userDefinedPolicy,
   567  		runtimePolicy:     runtimePolicy,
   568  	}
   569  }
   570  
   571  func (a *argocdAdapter) LoadPolicy(model model.Model) error {
   572  	for _, policyStr := range []string{a.builtinPolicy, a.userDefinedPolicy, a.runtimePolicy} {
   573  		for _, line := range strings.Split(policyStr, "\n") {
   574  			if err := loadPolicyLine(strings.TrimSpace(line), model); err != nil {
   575  				return err
   576  			}
   577  		}
   578  	}
   579  	return nil
   580  }
   581  
   582  // The modified version of LoadPolicyLine function defined in "persist" package of github.com/casbin/casbin.
   583  // Uses CVS parser to correctly handle quotes in policy line.
   584  func loadPolicyLine(line string, model model.Model) error {
   585  	if line == "" || strings.HasPrefix(line, "#") {
   586  		return nil
   587  	}
   588  
   589  	reader := csv.NewReader(strings.NewReader(line))
   590  	reader.TrimLeadingSpace = true
   591  	tokens, err := reader.Read()
   592  	if err != nil {
   593  		return err
   594  	}
   595  
   596  	tokenLen := len(tokens)
   597  
   598  	if tokenLen < 1 ||
   599  		tokens[0] == "" ||
   600  		(tokens[0] == "g" && tokenLen != 3) ||
   601  		(tokens[0] == "p" && tokenLen != 6) {
   602  		return fmt.Errorf("invalid RBAC policy: %s", line)
   603  	}
   604  
   605  	key := tokens[0]
   606  	sec := key[:1]
   607  	if _, ok := model[sec]; !ok {
   608  		return fmt.Errorf("invalid RBAC policy: %s", line)
   609  	}
   610  	if _, ok := model[sec][key]; !ok {
   611  		return fmt.Errorf("invalid RBAC policy: %s", line)
   612  	}
   613  	model[sec][key].Policy = append(model[sec][key].Policy, tokens[1:])
   614  	return nil
   615  }
   616  
   617  func (a *argocdAdapter) SavePolicy(_ model.Model) error {
   618  	return errors.New("not implemented")
   619  }
   620  
   621  func (a *argocdAdapter) AddPolicy(_ string, _ string, _ []string) error {
   622  	return errors.New("not implemented")
   623  }
   624  
   625  func (a *argocdAdapter) RemovePolicy(_ string, _ string, _ []string) error {
   626  	return errors.New("not implemented")
   627  }
   628  
   629  func (a *argocdAdapter) RemoveFilteredPolicy(_ string, _ string, _ int, _ ...string) error {
   630  	return errors.New("not implemented")
   631  }