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 }