vitess.io/vitess@v0.16.2/go/vt/dbconfigs/dbconfigs.go (about) 1 /* 2 Copyright 2019 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 dbconfigs provides the registration for command line options 18 // to collect db connection parameters. Once registered and collected, 19 // it provides variables and functions to build connection parameters 20 // for connecting to the database. 21 package dbconfigs 22 23 import ( 24 "context" 25 "encoding/json" 26 27 "github.com/spf13/pflag" 28 29 "vitess.io/vitess/go/vt/servenv" 30 "vitess.io/vitess/go/vt/vttls" 31 32 "vitess.io/vitess/go/mysql" 33 "vitess.io/vitess/go/vt/log" 34 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 35 "vitess.io/vitess/go/vt/vterrors" 36 "vitess.io/vitess/go/yaml2" 37 ) 38 39 // config flags 40 const ( 41 App = "app" 42 AppDebug = "appdebug" 43 // AllPrivs user should have more privileges than App (should include possibility to do 44 // schema changes and write to internal Vitess tables), but it shouldn't have SUPER 45 // privilege like Dba has. 46 AllPrivs = "allprivs" 47 Dba = "dba" 48 Filtered = "filtered" 49 Repl = "repl" 50 ExternalRepl = "erepl" 51 ) 52 53 var ( 54 // GlobalDBConfigs contains the initial values of dbconfigs from flags. 55 GlobalDBConfigs DBConfigs 56 57 // All can be used to register all flags: RegisterFlags(All...) 58 All = []string{App, AppDebug, AllPrivs, Dba, Filtered, Repl, ExternalRepl} 59 ) 60 61 // DBConfigs stores all the data needed to build various connection 62 // parameters for the db. It stores credentials for app, appdebug, 63 // allprivs, dba, filtered and repl users. 64 // It contains other connection parameters like socket, charset, etc. 65 // It also stores the default db name, which it can combine with the 66 // rest of the data to build db-sepcific connection parameters. 67 // 68 // The legacy way of initializing is as follows: 69 // App must call RegisterFlags to request the types of connections 70 // it wants support for. This must be done before invoking flags.Parse. 71 // After flag parsing, app invokes the Init function, which will return 72 // a DBConfigs object. 73 // The app must store the DBConfigs object internally, and use it to 74 // build connection parameters as needed. 75 type DBConfigs struct { 76 Socket string `json:"socket,omitempty"` 77 Host string `json:"host,omitempty"` 78 Port int `json:"port,omitempty"` 79 Charset string `json:"charset,omitempty"` 80 Flags uint64 `json:"flags,omitempty"` 81 Flavor string `json:"flavor,omitempty"` 82 SslMode vttls.SslMode `json:"sslMode,omitempty"` 83 SslCa string `json:"sslCa,omitempty"` 84 SslCaPath string `json:"sslCaPath,omitempty"` 85 SslCert string `json:"sslCert,omitempty"` 86 SslKey string `json:"sslKey,omitempty"` 87 TLSMinVersion string `json:"tlsMinVersion,omitempty"` 88 ServerName string `json:"serverName,omitempty"` 89 ConnectTimeoutMilliseconds int `json:"connectTimeoutMilliseconds,omitempty"` 90 DBName string `json:"dbName,omitempty"` 91 EnableQueryInfo bool `json:"enableQueryInfo,omitempty"` 92 93 App UserConfig `json:"app,omitempty"` 94 Dba UserConfig `json:"dba,omitempty"` 95 Filtered UserConfig `json:"filtered,omitempty"` 96 Repl UserConfig `json:"repl,omitempty"` 97 Appdebug UserConfig `json:"appdebug,omitempty"` 98 Allprivs UserConfig `json:"allprivs,omitempty"` 99 externalRepl UserConfig 100 101 appParams mysql.ConnParams 102 dbaParams mysql.ConnParams 103 filteredParams mysql.ConnParams 104 replParams mysql.ConnParams 105 appdebugParams mysql.ConnParams 106 allprivsParams mysql.ConnParams 107 externalReplParams mysql.ConnParams 108 } 109 110 // UserConfig contains user-specific configs. 111 type UserConfig struct { 112 User string `json:"user,omitempty"` 113 Password string `json:"password,omitempty"` 114 UseSSL bool `json:"useSsl,omitempty"` 115 UseTCP bool `json:"useTcp,omitempty"` 116 } 117 118 // RegisterFlags registers the base DBFlags, credentials flags, and the user 119 // specific ones for the specified system users for the requesting command. 120 // For instance, the vttablet command will register flags for all users 121 // as defined in the dbconfigs.All variable. 122 func RegisterFlags(userKeys ...string) { 123 servenv.OnParse(func(fs *pflag.FlagSet) { 124 registerBaseFlags(fs) 125 for _, userKey := range userKeys { 126 uc, cp := GlobalDBConfigs.getParams(userKey, &GlobalDBConfigs) 127 registerPerUserFlags(fs, userKey, uc, cp) 128 } 129 }) 130 } 131 132 func registerBaseFlags(fs *pflag.FlagSet) { 133 fs.StringVar(&GlobalDBConfigs.Socket, "db_socket", "", "The unix socket to connect on. If this is specified, host and port will not be used.") 134 fs.StringVar(&GlobalDBConfigs.Host, "db_host", "", "The host name for the tcp connection.") 135 fs.IntVar(&GlobalDBConfigs.Port, "db_port", 0, "tcp port") 136 fs.StringVar(&GlobalDBConfigs.Charset, "db_charset", "utf8mb4", "Character set used for this tablet.") 137 fs.Uint64Var(&GlobalDBConfigs.Flags, "db_flags", 0, "Flag values as defined by MySQL.") 138 fs.StringVar(&GlobalDBConfigs.Flavor, "db_flavor", "", "Flavor overrid. Valid value is FilePos.") 139 fs.Var(&GlobalDBConfigs.SslMode, "db_ssl_mode", "SSL mode to connect with. One of disabled, preferred, required, verify_ca & verify_identity.") 140 fs.StringVar(&GlobalDBConfigs.SslCa, "db_ssl_ca", "", "connection ssl ca") 141 fs.StringVar(&GlobalDBConfigs.SslCaPath, "db_ssl_ca_path", "", "connection ssl ca path") 142 fs.StringVar(&GlobalDBConfigs.SslCert, "db_ssl_cert", "", "connection ssl certificate") 143 fs.StringVar(&GlobalDBConfigs.SslKey, "db_ssl_key", "", "connection ssl key") 144 fs.StringVar(&GlobalDBConfigs.TLSMinVersion, "db_tls_min_version", "", "Configures the minimal TLS version negotiated when SSL is enabled. Defaults to TLSv1.2. Options: TLSv1.0, TLSv1.1, TLSv1.2, TLSv1.3.") 145 fs.StringVar(&GlobalDBConfigs.ServerName, "db_server_name", "", "server name of the DB we are connecting to.") 146 fs.IntVar(&GlobalDBConfigs.ConnectTimeoutMilliseconds, "db_connect_timeout_ms", 0, "connection timeout to mysqld in milliseconds (0 for no timeout)") 147 fs.BoolVar(&GlobalDBConfigs.EnableQueryInfo, "db_conn_query_info", false, "enable parsing and processing of QUERY_OK info fields") 148 } 149 150 // The flags will change the global singleton 151 func registerPerUserFlags(fs *pflag.FlagSet, userKey string, uc *UserConfig, cp *mysql.ConnParams) { 152 newUserFlag := "db_" + userKey + "_user" 153 fs.StringVar(&uc.User, newUserFlag, "vt_"+userKey, "db "+userKey+" user userKey") 154 155 newPasswordFlag := "db_" + userKey + "_password" 156 fs.StringVar(&uc.Password, newPasswordFlag, "", "db "+userKey+" password") 157 158 fs.BoolVar(&uc.UseSSL, "db_"+userKey+"_use_ssl", true, "Set this flag to false to make the "+userKey+" connection to not use ssl") 159 } 160 161 // Connector contains Connection Parameters for mysql connection 162 type Connector struct { 163 connParams *mysql.ConnParams 164 } 165 166 // New initializes a ConnParams from mysql connection parameters 167 func New(mcp *mysql.ConnParams) Connector { 168 return Connector{ 169 connParams: mcp, 170 } 171 } 172 173 // Connect will invoke the mysql.connect method and return a connection 174 func (c *Connector) Connect(ctx context.Context) (*mysql.Conn, error) { 175 params, err := c.MysqlParams() 176 if err != nil { 177 return nil, err 178 } 179 conn, err := mysql.Connect(ctx, params) 180 if err != nil { 181 return nil, err 182 } 183 return conn, nil 184 } 185 186 // MysqlParams returns the connections params 187 func (c Connector) MysqlParams() (*mysql.ConnParams, error) { 188 if c.connParams == nil { 189 // This is only possible during tests. 190 return nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "parameters are empty") 191 } 192 params, err := withCredentials(c.connParams) 193 if err != nil { 194 return nil, err 195 } 196 return params, nil 197 } 198 199 // DBName gets the dbname from mysql.ConnParams 200 func (c Connector) DBName() string { 201 return c.connParams.DbName 202 } 203 204 // Host gets the host from mysql.ConnParams 205 func (c Connector) Host() string { 206 return c.connParams.Host 207 } 208 209 // AppWithDB returns connection parameters for app with dbname set. 210 func (dbcfgs *DBConfigs) AppWithDB() Connector { 211 return dbcfgs.makeParams(&dbcfgs.appParams, true) 212 } 213 214 // AppDebugWithDB returns connection parameters for appdebug with dbname set. 215 func (dbcfgs *DBConfigs) AppDebugWithDB() Connector { 216 return dbcfgs.makeParams(&dbcfgs.appdebugParams, true) 217 } 218 219 // AllPrivsConnector returns connection parameters for appdebug with no dbname set. 220 func (dbcfgs *DBConfigs) AllPrivsConnector() Connector { 221 return dbcfgs.makeParams(&dbcfgs.allprivsParams, false) 222 } 223 224 // AllPrivsWithDB returns connection parameters for appdebug with dbname set. 225 func (dbcfgs *DBConfigs) AllPrivsWithDB() Connector { 226 return dbcfgs.makeParams(&dbcfgs.allprivsParams, true) 227 } 228 229 // DbaConnector returns connection parameters for dba with no dbname set. 230 func (dbcfgs *DBConfigs) DbaConnector() Connector { 231 return dbcfgs.makeParams(&dbcfgs.dbaParams, false) 232 } 233 234 // DbaWithDB returns connection parameters for appdebug with dbname set. 235 func (dbcfgs *DBConfigs) DbaWithDB() Connector { 236 return dbcfgs.makeParams(&dbcfgs.dbaParams, true) 237 } 238 239 // FilteredWithDB returns connection parameters for filtered with dbname set. 240 func (dbcfgs *DBConfigs) FilteredWithDB() Connector { 241 return dbcfgs.makeParams(&dbcfgs.filteredParams, true) 242 } 243 244 // ReplConnector returns connection parameters for repl with no dbname set. 245 func (dbcfgs *DBConfigs) ReplConnector() Connector { 246 return dbcfgs.makeParams(&dbcfgs.replParams, false) 247 } 248 249 // ExternalRepl returns connection parameters for repl with no dbname set. 250 func (dbcfgs *DBConfigs) ExternalRepl() Connector { 251 return dbcfgs.makeParams(&dbcfgs.externalReplParams, true) 252 } 253 254 // ExternalReplWithDB returns connection parameters for repl with dbname set. 255 func (dbcfgs *DBConfigs) ExternalReplWithDB() Connector { 256 params := dbcfgs.makeParams(&dbcfgs.externalReplParams, true) 257 return params 258 } 259 260 // AppWithDB returns connection parameters for app with dbname set. 261 func (dbcfgs *DBConfigs) makeParams(cp *mysql.ConnParams, withDB bool) Connector { 262 result := *cp 263 if withDB { 264 result.DbName = dbcfgs.DBName 265 } 266 return Connector{ 267 connParams: &result, 268 } 269 } 270 271 // IsZero returns true if DBConfigs was uninitialized. 272 func (dbcfgs *DBConfigs) IsZero() bool { 273 return *dbcfgs == DBConfigs{} 274 } 275 276 // HasGlobalSettings returns true if DBConfigs contains values 277 // for global configs. 278 func (dbcfgs *DBConfigs) HasGlobalSettings() bool { 279 return dbcfgs.Host != "" || dbcfgs.Socket != "" 280 } 281 282 func (dbcfgs *DBConfigs) String() string { 283 out, err := yaml2.Marshal(dbcfgs.Redacted()) 284 if err != nil { 285 return err.Error() 286 } 287 return string(out) 288 } 289 290 // MarshalJSON marshals after redacting passwords. 291 func (dbcfgs *DBConfigs) MarshalJSON() ([]byte, error) { 292 type nonCustom DBConfigs 293 return json.Marshal((*nonCustom)(dbcfgs.Redacted())) 294 } 295 296 // Redacted redacts passwords from DBConfigs. 297 func (dbcfgs *DBConfigs) Redacted() *DBConfigs { 298 dbcfgs = dbcfgs.Clone() 299 dbcfgs.App.Password = "****" 300 dbcfgs.Dba.Password = "****" 301 dbcfgs.Filtered.Password = "****" 302 dbcfgs.Repl.Password = "****" 303 dbcfgs.Appdebug.Password = "****" 304 dbcfgs.Allprivs.Password = "****" 305 return dbcfgs 306 } 307 308 // Clone returns a clone of the DBConfig. 309 func (dbcfgs *DBConfigs) Clone() *DBConfigs { 310 result := *dbcfgs 311 return &result 312 } 313 314 // InitWithSocket will initialize all the necessary connection parameters. 315 // Precedence is as follows: if UserConfig settings are set, 316 // they supersede all other settings. 317 // The next priority is with per-user connection 318 // parameters. This is only for legacy support. 319 // If no per-user parameters are supplied, then the defaultSocketFile 320 // is used to initialize the per-user conn params. 321 func (dbcfgs *DBConfigs) InitWithSocket(defaultSocketFile string) { 322 for _, userKey := range All { 323 uc, cp := dbcfgs.getParams(userKey, dbcfgs) 324 // TODO @rafael: For ExternalRepl we need to respect the provided host / port 325 // At the moment this is an snowflake user connection type that it used by 326 // vreplication to connect to external mysql hosts that are not part of a vitess 327 // cluster. In the future we need to refactor all dbconfig to support custom users 328 // in a more flexible way. 329 if dbcfgs.HasGlobalSettings() && userKey != ExternalRepl { 330 cp.Host = dbcfgs.Host 331 cp.Port = dbcfgs.Port 332 if !uc.UseTCP { 333 cp.UnixSocket = dbcfgs.Socket 334 } 335 } else if cp.UnixSocket == "" && cp.Host == "" { 336 cp.UnixSocket = defaultSocketFile 337 } 338 339 // If the connection params has a charset defined, it will not be overridden by the 340 // global configuration. 341 if dbcfgs.Charset != "" && cp.Charset == "" { 342 cp.Charset = dbcfgs.Charset 343 } 344 345 if dbcfgs.Flags != 0 { 346 cp.Flags = dbcfgs.Flags 347 } 348 if userKey != ExternalRepl { 349 cp.Flavor = dbcfgs.Flavor 350 } 351 cp.ConnectTimeoutMs = uint64(dbcfgs.ConnectTimeoutMilliseconds) 352 cp.EnableQueryInfo = dbcfgs.EnableQueryInfo 353 354 cp.Uname = uc.User 355 cp.Pass = uc.Password 356 if uc.UseSSL { 357 cp.SslMode = dbcfgs.SslMode 358 cp.SslCa = dbcfgs.SslCa 359 cp.SslCaPath = dbcfgs.SslCaPath 360 cp.SslCert = dbcfgs.SslCert 361 cp.SslKey = dbcfgs.SslKey 362 cp.TLSMinVersion = dbcfgs.TLSMinVersion 363 cp.ServerName = dbcfgs.ServerName 364 } 365 } 366 367 log.Infof("DBConfigs: %v\n", dbcfgs.String()) 368 } 369 370 func (dbcfgs *DBConfigs) getParams(userKey string, dbc *DBConfigs) (*UserConfig, *mysql.ConnParams) { 371 var uc *UserConfig 372 var cp *mysql.ConnParams 373 switch userKey { 374 case App: 375 uc = &dbcfgs.App 376 cp = &dbcfgs.appParams 377 case AppDebug: 378 uc = &dbcfgs.Appdebug 379 cp = &dbcfgs.appdebugParams 380 case AllPrivs: 381 uc = &dbcfgs.Allprivs 382 cp = &dbcfgs.allprivsParams 383 case Dba: 384 uc = &dbcfgs.Dba 385 cp = &dbcfgs.dbaParams 386 case Filtered: 387 uc = &dbcfgs.Filtered 388 cp = &dbcfgs.filteredParams 389 case Repl: 390 uc = &dbcfgs.Repl 391 cp = &dbcfgs.replParams 392 case ExternalRepl: 393 uc = &dbcfgs.externalRepl 394 cp = &dbcfgs.externalReplParams 395 default: 396 log.Exitf("Invalid db user key requested: %s", userKey) 397 } 398 return uc, cp 399 } 400 401 // SetDbParams sets the dba and app params 402 func (dbcfgs *DBConfigs) SetDbParams(dbaParams, appParams, filteredParams mysql.ConnParams) { 403 dbcfgs.dbaParams = dbaParams 404 dbcfgs.appParams = appParams 405 dbcfgs.filteredParams = filteredParams 406 } 407 408 // NewTestDBConfigs returns a DBConfigs meant for testing. 409 func NewTestDBConfigs(genParams, appDebugParams mysql.ConnParams, dbname string) *DBConfigs { 410 return &DBConfigs{ 411 appParams: genParams, 412 appdebugParams: appDebugParams, 413 allprivsParams: genParams, 414 dbaParams: genParams, 415 filteredParams: genParams, 416 replParams: genParams, 417 externalReplParams: genParams, 418 DBName: dbname, 419 Charset: "utf8mb4_general_ci", 420 } 421 }