vitess.io/vitess@v0.16.2/go/vt/vtadmin/rbac/config.go (about)

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package rbac
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/spf13/viper"
    24  	"k8s.io/apimachinery/pkg/util/sets"
    25  
    26  	"vitess.io/vitess/go/vt/concurrency"
    27  	"vitess.io/vitess/go/vt/log"
    28  )
    29  
    30  // Config is the RBAC configuration representation. The public fields are
    31  // populated by viper during LoadConfig, and the private fields are set during
    32  // cfg.Reify. A config must be reified before first use.
    33  type Config struct {
    34  	Authenticator string
    35  	Rules         []*struct {
    36  		Resource string
    37  		Actions  []string
    38  		Subjects []string
    39  		Clusters []string
    40  	}
    41  
    42  	reified bool
    43  
    44  	cfg           map[string][]*Rule
    45  	authenticator Authenticator
    46  	authorizer    *Authorizer
    47  }
    48  
    49  // LoadConfig reads the file at path into a Config struct, and then reifies
    50  // the config so its autheticator and authorizer may be used. Errors during
    51  // loading/parsing, or validation errors during reification are returned to the
    52  // caller.
    53  //
    54  // Any file format supported by viper is supported. Currently this is yaml, json
    55  // or toml.
    56  func LoadConfig(path string) (*Config, error) {
    57  	v := viper.New()
    58  	v.SetConfigFile(path)
    59  	if err := v.ReadInConfig(); err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	var cfg Config
    64  	if err := v.UnmarshalExact(&cfg); err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	if err := cfg.Reify(); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return &cfg, nil
    73  }
    74  
    75  // Reify makes a config that was loaded from a file usable, by validating the
    76  // rules and constructing its (optional) authenticator and authorizer. A config
    77  // must be reified before first use. Calling Reify multiple times has no effect
    78  // after the first call. Reify is called by LoadConfig, so a config loaded that
    79  // way does not need to be manually reified.
    80  func (c *Config) Reify() error {
    81  	if c.reified {
    82  		return nil
    83  	}
    84  
    85  	// reify the rules
    86  	byResource := map[string][]*Rule{}
    87  	rec := concurrency.AllErrorRecorder{}
    88  
    89  	for i, rule := range c.Rules {
    90  		resourceRules := byResource[rule.Resource]
    91  
    92  		actions := sets.New[string](rule.Actions...)
    93  		if actions.Has("*") && actions.Len() > 1 {
    94  			// error to have wildcard and something else
    95  			rec.RecordError(fmt.Errorf("rule %d: actions list cannot include wildcard and other actions, have %v", i, sets.List(actions)))
    96  		}
    97  
    98  		subjects := sets.New[string](rule.Subjects...)
    99  		if subjects.Has("*") && subjects.Len() > 1 {
   100  			// error to have wildcard and something else
   101  			rec.RecordError(fmt.Errorf("rule %d: subjects list cannot include wildcard and other subjects, have %v", i, sets.List(subjects)))
   102  		}
   103  
   104  		clusters := sets.New[string](rule.Clusters...)
   105  		if clusters.Has("*") && clusters.Len() > 1 {
   106  			// error to have wildcard and something else
   107  			rec.RecordError(fmt.Errorf("rule %d: clusters list cannot include wildcard and other clusters, have %v", i, sets.List(clusters)))
   108  		}
   109  
   110  		resourceRules = append(resourceRules, &Rule{
   111  			actions:  actions,
   112  			subjects: subjects,
   113  			clusters: clusters,
   114  		})
   115  		byResource[rule.Resource] = resourceRules
   116  	}
   117  
   118  	if rec.HasErrors() {
   119  		return rec.Error()
   120  	}
   121  
   122  	log.Infof("[rbac]: loaded authorizer with %d rules", len(c.Rules))
   123  
   124  	c.cfg = byResource
   125  	c.authorizer = &Authorizer{
   126  		policies: c.cfg,
   127  	}
   128  
   129  	// reify the authenticator
   130  	switch {
   131  	case strings.HasSuffix(c.Authenticator, ".so"):
   132  		authn, err := loadAuthenticatorPlugin(c.Authenticator)
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		c.authenticator = authn
   138  	case c.Authenticator != "":
   139  		factory, ok := authenticators[c.Authenticator]
   140  		if !ok {
   141  			return fmt.Errorf("%w %s", ErrUnregisteredAuthenticationImpl, c.Authenticator)
   142  		}
   143  
   144  		c.authenticator = factory()
   145  	default:
   146  		log.Info("[rbac]: no authenticator implementation specified")
   147  		c.authenticator = nil // Technically a no-op, but being super explicit about it.
   148  	}
   149  
   150  	c.reified = true
   151  	return nil
   152  }
   153  
   154  // GetAuthenticator returns the Authenticator implementation specified by the
   155  // config. It returns nil if the Authenticator string field is the empty string,
   156  // or if a call to Reify has not been made.
   157  func (c *Config) GetAuthenticator() Authenticator {
   158  	return c.authenticator
   159  }
   160  
   161  // GetAuthorizer returns the Authorizer using the rules specified in the config.
   162  // It returns nil if a call to Reify has not been made.
   163  func (c *Config) GetAuthorizer() *Authorizer {
   164  	return c.authorizer
   165  }
   166  
   167  // DefaultConfig returns a default config that allows all actions on all resources
   168  // It is mainly used in the case where users explicitly pass --no-rbac flag.
   169  func DefaultConfig() *Config {
   170  	log.Info("[rbac]: using default rbac configuration")
   171  	actions := []string{
   172  		string(GetAction),
   173  		string(CreateAction),
   174  		string(DeleteAction),
   175  		string(PutAction),
   176  		string(PingAction),
   177  		string(ReloadAction),
   178  		string(EmergencyFailoverShardAction),
   179  		string(PlannedFailoverShardAction),
   180  		string(TabletExternallyPromotedAction),
   181  		string(ManageTabletReplicationAction),
   182  		string(ManageTabletWritabilityAction),
   183  		string(RefreshTabletReplicationSourceAction),
   184  	}
   185  	subjects := []string{"*"}
   186  	clusters := []string{"*"}
   187  
   188  	cfg := map[string][]*Rule{
   189  		"*": {
   190  			{
   191  				clusters: sets.New[string](clusters...),
   192  				actions:  sets.New[string](actions...),
   193  				subjects: sets.New[string](subjects...),
   194  			},
   195  		},
   196  	}
   197  
   198  	return &Config{
   199  		Rules: []*struct {
   200  			Resource string
   201  			Actions  []string
   202  			Subjects []string
   203  			Clusters []string
   204  		}{
   205  			{
   206  				Resource: "*",
   207  				Actions:  actions,
   208  				Subjects: subjects,
   209  				Clusters: clusters,
   210  			},
   211  		},
   212  		cfg: cfg,
   213  		authorizer: &Authorizer{
   214  			policies: cfg,
   215  		},
   216  		authenticator: nil,
   217  	}
   218  }