github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/pgwire/hba_conf.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package pgwire 12 13 import ( 14 "context" 15 "net" 16 "net/http" 17 "sort" 18 "strings" 19 20 "github.com/cockroachdb/cockroach/pkg/clusterversion" 21 "github.com/cockroachdb/cockroach/pkg/security" 22 "github.com/cockroachdb/cockroach/pkg/settings" 23 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 24 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/hba" 25 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 26 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 27 "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" 28 "github.com/cockroachdb/cockroach/pkg/util/log" 29 "github.com/cockroachdb/errors" 30 ) 31 32 // This file contains the logic for the configuration of HBA rules. 33 // 34 // In a nutshell, administrators customize the cluster setting 35 // `server.host_based_authentication.configuration`; each time they 36 // do so, all the nodes parse this configuration and re-initialize 37 // their authentication rules (a list of entries) from the setting. 38 // 39 // If the cluster setting is not initialized, or when it is assigned 40 // the empty string, a special "default" configuration is used 41 // instead: 42 // 43 // host all root all cert # require certs for root 44 // host all all all cert-password # require certs or password for everyone else 45 // 46 // (In fact, the first line `host all root all cert` is always 47 // inserted at the start of any custom configuration, as a safeguard.) 48 // 49 // The HBA configuration is an ordered list of rules. Each time 50 // a client attempts to connect, the server scans the 51 // rules from the beginning of the list. The first rule that 52 // matches the connection decides how to authenticate. 53 // 54 // The syntax is inspired/derived from that of PostgreSQL's pg_hba.conf: 55 // https://www.postgresql.org/docs/12/auth-pg-hba-conf.html 56 // 57 // For now, CockroachDB only supports the following syntax: 58 // 59 // host all <user[,user]...> <IP-address/mask-length> <auth-method> 60 // 61 // The matching rules are as follows: 62 // - A rule matches if the connecting username matches either of the 63 // usernames listed in the rule, or if the pseudo-user 'all' is 64 // present in the user column. 65 // - A rule matches if the connecting client's IP address is included 66 // in the network address specified in the CIDR notation. 67 // 68 69 // serverHBAConfSetting is the name of the cluster setting that holds 70 // the HBA configuration. 71 const serverHBAConfSetting = "server.host_based_authentication.configuration" 72 73 // connAuthConf is the cluster setting that holds the HBA 74 // configuration. 75 var connAuthConf = func() *settings.StringSetting { 76 s := settings.RegisterValidatedStringSetting( 77 serverHBAConfSetting, 78 "host-based authentication configuration to use during connection authentication", 79 "", 80 checkHBASyntaxBeforeUpdatingSetting, 81 ) 82 s.SetVisibility(settings.Public) 83 return s 84 }() 85 86 // loadLocalAuthConfigUponRemoteSettingChange initializes the local 87 // node's cache of the auth configuration each time the cluster 88 // setting is updated. 89 func loadLocalAuthConfigUponRemoteSettingChange( 90 ctx context.Context, server *Server, st *cluster.Settings, 91 ) { 92 val := connAuthConf.Get(&st.SV) 93 94 // An empty HBA configuration is special and means "use the 95 // default". 96 conf := DefaultHBAConfig 97 if val != "" { 98 var err error 99 conf, err = ParseAndNormalize(val) 100 if err != nil { 101 // The default is also used if the node is unable to load the 102 // config from the cluster setting. 103 log.Warningf(ctx, "invalid %s: %v", serverHBAConfSetting, err) 104 conf = DefaultHBAConfig 105 } 106 } 107 server.auth.Lock() 108 defer server.auth.Unlock() 109 server.auth.conf = conf 110 } 111 112 // checkHBASyntaxBeforeUpdatingSetting is run by the SQL gateway each 113 // time a SQL client attempts to update the cluster setting. 114 // It is also used when initially loading the default value. 115 func checkHBASyntaxBeforeUpdatingSetting(values *settings.Values, s string) error { 116 if s == "" { 117 // An empty configuration is always valid. 118 return nil 119 } 120 // Note: we parse, but do not normalize, here, so as to 121 // check for unsupported features in the input. 122 conf, err := hba.Parse(s) 123 if err != nil { 124 return err 125 } 126 127 if len(conf.Entries) == 0 { 128 // If the string was not empty, the user likely intended to have 129 // *something* in the configuration, so us not finding anything 130 // likely indicates either a parsing bug, or that the user 131 // mistakenly put only comments in their config. 132 return errors.WithHint(errors.New("no entries"), 133 "To use the default configuration, assign the empty string ('').") 134 } 135 136 // Retrieve the cluster version handle. We'll need to check the current cluster version. 137 var vh clusterversion.Handle 138 if values != nil { 139 vh = values.Opaque().(clusterversion.Handle) 140 } 141 142 for _, entry := range conf.Entries { 143 switch entry.ConnType { 144 case hba.ConnHostAny: 145 case hba.ConnLocal: 146 if vh != nil && 147 !vh.IsActive(context.TODO(), clusterversion.VersionAuthLocalAndTrustRejectMethods) { 148 return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, 149 `authentication rule type 'local' requires all nodes to be upgraded to %s`, 150 clusterversion.VersionByKey(clusterversion.VersionAuthLocalAndTrustRejectMethods), 151 ) 152 } 153 default: 154 return unimplemented.Newf("hba-type-"+entry.ConnType.String(), 155 "unsupported connection type: %s", entry.ConnType) 156 } 157 for _, db := range entry.Database { 158 if !db.IsKeyword("all") { 159 return errors.WithHint( 160 unimplemented.New("hba-per-db", "per-database HBA rules are not supported"), 161 "Use the special value 'all' (without quotes) to match all databases.") 162 } 163 } 164 165 if entry.ConnType != hba.ConnLocal { 166 // Verify the user is not requesting hostname-based validation, 167 // which is not yet implemented. 168 addrOk := true 169 switch t := entry.Address.(type) { 170 case *net.IPNet: 171 case hba.AnyAddr: 172 case hba.String: 173 addrOk = t.IsKeyword("all") 174 default: 175 addrOk = false 176 } 177 if !addrOk { 178 return errors.WithHint( 179 unimplemented.New("hba-hostnames", "hostname-based HBA rules are not supported"), 180 "List the numeric CIDR notation instead, for example: 127.0.0.1/8.\n"+ 181 "Alternatively, use 'all' (without quotes) for any IPv4/IPv6 address.") 182 } 183 } 184 185 // Verify that the auth method is supported. 186 method, ok := hbaAuthMethods[entry.Method.Value] 187 if !ok || method.fn == nil { 188 return errors.WithHintf(unimplemented.Newf("hba-method-"+entry.Method.Value, 189 "unknown auth method %q", entry.Method.Value), 190 "Supported methods: %s", listRegisteredMethods()) 191 } 192 // Verify that the cluster setting is at least the required version. 193 if vh != nil && !vh.IsActive(context.TODO(), method.minReqVersion) { 194 return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, 195 `authentication method '%s' requires all nodes to be upgraded to %s`, 196 entry.Method.Value, 197 clusterversion.VersionByKey(method.minReqVersion)) 198 } 199 // Run the per-method validation. 200 if check := hbaCheckHBAEntries[entry.Method.Value]; check != nil { 201 if err := check(entry); err != nil { 202 return err 203 } 204 } 205 } 206 return nil 207 } 208 209 // ParseAndNormalize calls hba.ParseAndNormalize and also ensures the 210 // configuration starts with a rule that authenticates the root user 211 // with client certificates. 212 // 213 // This prevents users from shooting themselves in the foot and making 214 // root not able to login, thus disallowing anyone from fixing the HBA 215 // configuration. 216 func ParseAndNormalize(val string) (*hba.Conf, error) { 217 conf, err := hba.ParseAndNormalize(val) 218 if err != nil { 219 return conf, err 220 } 221 222 if len(conf.Entries) == 0 || !conf.Entries[0].Equivalent(rootEntry) { 223 entries := make([]hba.Entry, 1, len(conf.Entries)+1) 224 entries[0] = rootEntry 225 entries = append(entries, conf.Entries...) 226 conf.Entries = entries 227 } 228 229 // Lookup and cache the auth methods. 230 for i := range conf.Entries { 231 method := conf.Entries[i].Method.Value 232 methodEntry, ok := hbaAuthMethods[method] 233 if !ok { 234 // TODO(knz): Determine if an error should be reported 235 // upon unknown auth methods. 236 // See: https://github.com/cockroachdb/cockroach/issues/43716 237 return nil, errors.Errorf("unknown auth method %s", method) 238 } 239 conf.Entries[i].MethodFn = methodEntry.methodInfo 240 } 241 242 return conf, nil 243 } 244 245 var insecureEntry = hba.Entry{ 246 ConnType: hba.ConnHostAny, 247 User: []hba.String{{Value: "all", Quoted: false}}, 248 Address: hba.AnyAddr{}, 249 Method: hba.String{Value: "--insecure"}, 250 } 251 252 var rootEntry = hba.Entry{ 253 ConnType: hba.ConnHostAny, 254 User: []hba.String{{Value: security.RootUser, Quoted: false}}, 255 Address: hba.AnyAddr{}, 256 Method: hba.String{Value: "cert-password"}, 257 Input: "host all root all cert-password # CockroachDB mandatory rule", 258 } 259 260 // DefaultHBAConfig is used when the stored HBA configuration string 261 // is empty or invalid. 262 var DefaultHBAConfig = func() *hba.Conf { 263 loadDefaultMethods() 264 conf, err := ParseAndNormalize(` 265 host all all all cert-password # built-in CockroachDB default 266 local all all password # built-in CockroachDB default 267 `) 268 if err != nil { 269 panic(err) 270 } 271 return conf 272 }() 273 274 // GetAuthenticationConfiguration retrieves the current applicable 275 // authentication configuration. 276 // 277 // This is guaranteed to return a valid configuration. Additionally, 278 // the various setters for the configuration also pass through 279 // ParseAndNormalize(), whereby an entry is always present at the start, 280 // to enable root to log in with a valid client cert. 281 // 282 // The data returned by this method is also observable via the debug 283 // endpoint /debug/hba_conf. 284 func (s *Server) GetAuthenticationConfiguration() *hba.Conf { 285 s.auth.RLock() 286 auth := s.auth.conf 287 s.auth.RUnlock() 288 289 if auth == nil { 290 // This can happen when using the value for the first time before 291 // the cluster setting has ever been set. 292 auth = DefaultHBAConfig 293 } 294 return auth 295 } 296 297 // RegisterAuthMethod registers an AuthMethod for pgwire 298 // authentication and for use in HBA configuration. 299 // 300 // The minReqVersion is checked upon configuration to verify whether 301 // the current active cluster version is at least the version 302 // specified. 303 // 304 // The validConnTypes is checked during rule matching when accepting 305 // connections: if the connection type is not accepted by the auth 306 // method, authentication is refused upfront. For example, the "cert" 307 // method requires SSL; if a rule specifies "host .... cert" and the 308 // client connects without SSL, the authentication is refused. 309 // (To express "cert on SSL, password on non-SSL", the HBA conf 310 // can list 'hostssl ... cert; hostnossl .... password' instead.) 311 // 312 // The checkEntry method, if provided, is called upon configuration 313 // the cluster setting in the SQL client which attempts to change the 314 // configuration. It can block the configuration if e.g. the syntax is 315 // invalid. 316 func RegisterAuthMethod( 317 method string, 318 fn AuthMethod, 319 minReqVersion clusterversion.VersionKey, 320 validConnTypes hba.ConnType, 321 checkEntry CheckHBAEntry, 322 ) { 323 hbaAuthMethods[method] = authMethodEntry{methodInfo{validConnTypes, fn}, minReqVersion} 324 if checkEntry != nil { 325 hbaCheckHBAEntries[method] = checkEntry 326 } 327 } 328 329 // listsupportedMethods returns a sorted, comma-delimited list 330 // of registered AuthMethods. 331 func listRegisteredMethods() string { 332 methods := make([]string, 0, len(hbaAuthMethods)) 333 for method := range hbaAuthMethods { 334 methods = append(methods, method) 335 } 336 sort.Strings(methods) 337 return strings.Join(methods, ", ") 338 } 339 340 var ( 341 hbaAuthMethods = map[string]authMethodEntry{} 342 hbaCheckHBAEntries = map[string]CheckHBAEntry{} 343 ) 344 345 type authMethodEntry struct { 346 methodInfo 347 minReqVersion clusterversion.VersionKey 348 } 349 350 type methodInfo struct { 351 validConnTypes hba.ConnType 352 fn AuthMethod 353 } 354 355 // CheckHBAEntry defines a method for validating an hba Entry upon 356 // configuration of the cluster setting by a SQL client. 357 type CheckHBAEntry func(hba.Entry) error 358 359 // HBADebugFn exposes the computed HBA configuration via the debug 360 // interface, for inspection by tests. 361 func (s *Server) HBADebugFn() http.HandlerFunc { 362 return func(w http.ResponseWriter, req *http.Request) { 363 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 364 365 auth := s.GetAuthenticationConfiguration() 366 367 _, _ = w.Write([]byte("# Active authentication configuration on this node:\n")) 368 _, _ = w.Write([]byte(auth.String())) 369 } 370 }