Skip to content

Commit

Permalink
user: Add mysql user db implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
amass01 committed Jun 1, 2021
1 parent 76d31f7 commit 32c81dd
Show file tree
Hide file tree
Showing 14 changed files with 1,198 additions and 55 deletions.
5 changes: 4 additions & 1 deletion politeiad/backendv2/tstorebe/store/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/decred/politeia/politeiad/backendv2/tstorebe/store"
"github.com/decred/politeia/util"

// MySQL driver.
_ "github.com/go-sql-driver/mysql"
)

Expand Down Expand Up @@ -278,7 +279,9 @@ func (s *mysql) Close() {
s.db.Close()
}

func New(appDir, host, user, password, dbname string) (*mysql, error) {
// New connects to a mysql instance using the given connection params,
// and returns pointer to the created mysql struct.
func New(host, user, password, dbname string) (*mysql, error) {
// The password is required to derive the encryption key
if password == "" {
return nil, fmt.Errorf("password not provided")
Expand Down
4 changes: 2 additions & 2 deletions politeiad/backendv2/tstorebe/tstore/tstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const (
// store to a leveldb instance.
DBTypeLevelDB = "leveldb"

// DBTypeLevelDB is a config option that sets the backing key-value
// DBTypeMySQL is a config option that sets the backing key-value
// store to a MySQL instance.
DBTypeMySQL = "mysql"

Expand Down Expand Up @@ -227,7 +227,7 @@ func New(appDir, dataDir string, anp *chaincfg.Params, tlogHost, tlogPass, dbTyp
case DBTypeMySQL:
// Example db name: testnet3_unvetted_kv
dbName := fmt.Sprintf("%v_kv", anp.Name)
kvstore, err = mysql.New(appDir, dbHost, dbUser, dbPass, dbName)
kvstore, err = mysql.New(dbHost, dbUser, dbPass, dbName)
if err != nil {
return nil, err
}
Expand Down
76 changes: 61 additions & 15 deletions politeiawww/cmd/politeiawww_dbutil/politeiawww_dbutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/decred/politeia/politeiawww/user"
"github.com/decred/politeia/politeiawww/user/cockroachdb"
"github.com/decred/politeia/politeiawww/user/localdb"
"github.com/decred/politeia/politeiawww/user/mysqldb"
"github.com/decred/politeia/util"
"github.com/google/uuid"
_ "github.com/jinzhu/gorm/dialects/postgres"
Expand Down Expand Up @@ -60,6 +61,7 @@ var (
// Database options
level = flag.Bool("leveldb", false, "")
cockroach = flag.Bool("cockroachdb", false, "")
mysql = flag.Bool("mysqldb", false, "")

// Application options
testnet = flag.Bool("testnet", false, "")
Expand All @@ -69,6 +71,7 @@ var (
clientCert = flag.String("clientcert", defaultClientCert, "")
clientKey = flag.String("clientkey", defaultClientKey, "")
encryptionKey = flag.String("encryptionkey", defaultEncryptionKey, "")
password = flag.String("password", "", "")

// Commands
addCredits = flag.Bool("addcredits", false, "")
Expand All @@ -93,6 +96,8 @@ const usageMsg = `politeiawww_dbutil usage:
Use LevelDB
-cockroachdb
Use CockroachDB
-mysqldb
Use MySQLDB
Application options
-testnet
Expand All @@ -101,7 +106,7 @@ const usageMsg = `politeiawww_dbutil usage:
politeiawww data directory
(default osDataDir/politeiawww/data)
-host string
CockroachDB ip:port
CockroachDB/MySQL ip:port
(default localhost:26257)
-rootcert string
File containing the CockroachDB SSL root cert
Expand All @@ -113,8 +118,10 @@ const usageMsg = `politeiawww_dbutil usage:
File containing the CockroachDB SSL client cert key
(default ~/.cockroachdb/certs/clients/politeiawww/client.politeiawww.key)
-encryptionkey string
File containing the CockroachDB encryption key
File containing the CockroachDB/MySQL encryption key
(default osDataDir/politeiawww/sbox.key)
-password string
MySQL database password.
Commands
-addcredits
Expand Down Expand Up @@ -593,6 +600,27 @@ func validateCockroachParams() error {
return nil
}

func validateMySQLParams() error {
// Validate host.
_, err := url.Parse(*host)
if err != nil {
return fmt.Errorf("parse host '%v': %v", *host, err)
}

// Validate password.
if *password == "" {
return fmt.Errorf("MySQL password missing; use -password flag to" +
" provide it")
}

// Ensure encryption key file exists.
if !util.FileExists(*encryptionKey) {
return fmt.Errorf("file not found %v", *encryptionKey)
}

return nil
}

func cmdVerifyIdentities() error {
args := flag.Args()
if len(args) != 1 {
Expand Down Expand Up @@ -736,36 +764,41 @@ func _main() error {
} else {
network = chaincfg.MainNetParams().Name
}
// Validate database selection
if *level && *cockroach {
return fmt.Errorf("database choice cannot be both " +
"-leveldb and -cockroachdb")

// Validate database selection.
switch {
case *mysql && *cockroach, *level && *mysql, *level && *cockroach,
*level && *cockroach && *mysql:
fmt.Println(mysql, cockroach)
return fmt.Errorf("multiple database flags; must use one of the " +
"following: -leveldb, -mysqldb or -cockroachdb")
}

switch {
case *addCredits || *setAdmin || *stubUsers || *resetTotp:
// These commands must be run with -cockroachdb or -leveldb
if !*level && !*cockroach {
// These commands must be run with -cockroachdb, -mysqldb or -leveldb.
if !*level && !*cockroach && !*mysql {
return fmt.Errorf("missing database flag; must use " +
"either -leveldb or -cockroachdb")
"-leveldb, -cockroachdb or -mysqldb")
}
case *dump:
// These commands must be run with -leveldb
// These commands must be run with -leveldb.
if !*level {
return fmt.Errorf("missing database flag; must use " +
"-leveldb with this command")
}
case *verifyIdentities, *setEmail:
// These commands must be run with -cockroachdb
// These commands must be run with either -cockroachdb or
// -mysqldb.
if !*cockroach || *level {
return fmt.Errorf("invalid database flag; must use " +
"-cockroachdb with this command")
"either -mysqldb or -cockroachdb with this command")
}
case *migrate || *createKey:
// These commands must be run without a database flag
if *level || *cockroach {
// These commands must be run without a database flag.
if *level || *cockroach || *mysql {
return fmt.Errorf("unexpected database flag found; " +
"remove database flag -leveldb and -cockroachdb")
"remove database flag -leveldb, -mysqldb and -cockroachdb")
}
}

Expand Down Expand Up @@ -801,6 +834,19 @@ func _main() error {
}
userDB = db
defer userDB.Close()

case *mysql:
err := validateMySQLParams()
if err != nil {
return err
}
db, err := mysqldb.New(*host, *password, network, *encryptionKey)
if err != nil {
return fmt.Errorf("new mysqldb: %v", err)
}
userDB = db
defer userDB.Close()

}

// Run command
Expand Down
96 changes: 71 additions & 25 deletions politeiawww/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,12 @@ const (
// User database options
userDBLevel = "leveldb"
userDBCockroach = "cockroachdb"
userDBMySQL = "mysqldb"

defaultUserDB = userDBLevel

// Environment variables.
envDBPass = "DBPASS"
)

var (
Expand Down Expand Up @@ -259,6 +263,47 @@ func loadIdentity(cfg *config.Config) error {
return nil
}

// validateEncryptionKey validates the encryption key config.
func validateEncryptionKey(encKey, oldEncKey string) error {
encKey = util.CleanAndExpandPath(encKey)
oldEncKey = util.CleanAndExpandPath(oldEncKey)

if encKey != "" && !util.FileExists(encKey) {
return fmt.Errorf("file not found %v", encKey)
}

if oldEncKey != "" {
switch {
case encKey == "":
return fmt.Errorf("old encryption key param " +
"cannot be used without encryption key param")

case encKey == oldEncKey:
return fmt.Errorf("old encryption key param " +
"and encryption key param must be different")

case !util.FileExists(oldEncKey):
return fmt.Errorf("file not found %v", oldEncKey)
}
}

return nil
}

// validateDBHost validates user database host.
func validateDBHost(host string) error {
if host == "" {
return fmt.Errorf("dbhost param is required")
}

_, err := url.Parse(host)
if err != nil {
return fmt.Errorf("parse dbhost: %v", err)
}

return nil
}

// loadConfig initializes and parses the config using a config file and command
// line options.
//
Expand Down Expand Up @@ -667,10 +712,8 @@ func loadConfig() (*config.Config, []string, error) {
}

case userDBCockroach:
// Cockroachdb required these settings
// Cockroachdb requires these settings.
switch {
case cfg.DBHost == "":
return nil, nil, fmt.Errorf("dbhost param is required")
case cfg.DBRootCert == "":
return nil, nil, fmt.Errorf("dbrootcert param is required")
case cfg.DBCert == "":
Expand All @@ -684,10 +727,10 @@ func loadConfig() (*config.Config, []string, error) {
cfg.DBCert = util.CleanAndExpandPath(cfg.DBCert)
cfg.DBKey = util.CleanAndExpandPath(cfg.DBKey)

// Validate user database host
_, err = url.Parse(cfg.DBHost)
// Validate DB host.
err = validateDBHost(cfg.DBHost)
if err != nil {
return nil, nil, fmt.Errorf("parse dbhost: %v", err)
return nil, nil, err
}

// Validate user database root cert
Expand All @@ -708,36 +751,39 @@ func loadConfig() (*config.Config, []string, error) {
"and dbkey: %v", err)
}

// Validate user database encryption keys
cfg.EncryptionKey = util.CleanAndExpandPath(cfg.EncryptionKey)
cfg.OldEncryptionKey = util.CleanAndExpandPath(cfg.OldEncryptionKey)

if cfg.EncryptionKey != "" && !util.FileExists(cfg.EncryptionKey) {
return nil, nil, fmt.Errorf("file not found %v", cfg.EncryptionKey)
// Validate user database encryption keys.
err = validateEncryptionKey(cfg.EncryptionKey, cfg.OldEncryptionKey)
if err != nil {
return nil, nil, fmt.Errorf("validate encryption key: %v", err)
}

if cfg.OldEncryptionKey != "" {
switch {
case cfg.EncryptionKey == "":
return nil, nil, fmt.Errorf("old encryption key param " +
"cannot be used without encryption key param")
case userDBMySQL:
// The database password is provided in an env variable.
cfg.DBPass = os.Getenv(envDBPass)
if cfg.DBPass == "" {
return nil, nil, fmt.Errorf("dbpass not found; you must provide " +
"the database password for the politeiad user in the env " +
"variable DBPASS")
}

case cfg.EncryptionKey == cfg.OldEncryptionKey:
return nil, nil, fmt.Errorf("old encryption key param " +
"and encryption key param must be different")
// Validate DB host.
err = validateDBHost(cfg.DBHost)
if err != nil {
return nil, nil, err
}

case !util.FileExists(cfg.OldEncryptionKey):
return nil, nil, fmt.Errorf("file not found %v", cfg.OldEncryptionKey)
}
// Validate user database encryption keys.
err = validateEncryptionKey(cfg.EncryptionKey, cfg.OldEncryptionKey)
if err != nil {
return nil, nil, fmt.Errorf("validate encryption key: %v", err)
}

default:
return nil, nil, fmt.Errorf("invalid userdb '%v'; must "+
"be either leveldb or cockroachdb", cfg.UserDB)
"be leveldb, cockroachdb or mysqldb", cfg.UserDB)
}

// Verify paywall settings

paywallIsEnabled := cfg.PaywallAmount != 0 || cfg.PaywallXpub != ""
if paywallIsEnabled {
// Parse extended public key
Expand Down
1 change: 1 addition & 0 deletions politeiawww/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ type Config struct {
DBRootCert string `long:"dbrootcert" description:"File containing the CA certificate for the database"`
DBCert string `long:"dbcert" description:"File containing the politeiawww client certificate for the database"`
DBKey string `long:"dbkey" description:"File containing the politeiawww client certificate key for the database"`
DBPass string // Provided in env variable "DBPASS"
EncryptionKey string `long:"encryptionkey" description:"File containing encryption key used for encrypting user data at rest"`
OldEncryptionKey string `long:"oldencryptionkey" description:"File containing old encryption key (only set when rotating keys)"`

Expand Down
6 changes: 6 additions & 0 deletions politeiawww/user/localdb/localdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,12 @@ func (l *localdb) AllUsers(callbackFn func(u *user.User)) error {
return iter.Error()
}

// RotateKeys is an empty stub to satisfy the user.Database insterface.
// Localdb implementation does not use encryption.
func (l *localdb) RotateKeys(_ string) error {
return nil
}

// PluginExec executes the provided plugin command.
func (l *localdb) PluginExec(pc user.PluginCommand) (*user.PluginCommandReply, error) {
log.Tracef("PluginExec: %v %v", pc.ID, pc.Command)
Expand Down
21 changes: 21 additions & 0 deletions politeiawww/user/mysqldb/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package mysqldb

import "github.com/decred/slog"

// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log = slog.Disabled

// DisableLog disables all library log output. Logging output is disabled
// by default until either UseLogger or SetLogWriter are called.
func DisableLog() {
log = slog.Disabled
}

// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using slog.
func UseLogger(logger slog.Logger) {
log = logger
}
Loading

0 comments on commit 32c81dd

Please sign in to comment.