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  }