github.com/decred/politeia@v1.4.0/politeiawww/config/legacy.go (about)

     1  // Copyright (c) 2013-2014 The btcsuite developers
     2  // Copyright (c) 2015-2021 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package config
     7  
     8  import (
     9  	"crypto/tls"
    10  	"crypto/x509"
    11  	"encoding/pem"
    12  	"fmt"
    13  	"net/url"
    14  	"os"
    15  	"path/filepath"
    16  	"time"
    17  
    18  	"github.com/decred/dcrd/hdkeychain/v3"
    19  	"github.com/decred/politeia/util"
    20  )
    21  
    22  const (
    23  	// Currently available modes to run politeia, by default piwww, is used.
    24  	PiWWWMode  = "piwww"
    25  	CMSWWWMode = "cmswww"
    26  
    27  	defaultDcrdataMainnet = "dcrdata.decred.org:443"
    28  	defaultDcrdataTestnet = "testnet.decred.org:443"
    29  
    30  	defaultPaywallMinConfirmations = uint64(2)
    31  	defaultPaywallAmount           = uint64(0)
    32  
    33  	defaultMailAddressCMS = "Contractor Management System <noreply@example.org>"
    34  	defaultMailRateLimit  = 100 // Email limit per user
    35  
    36  	defaultVoteDurationMin = uint32(2016)
    37  	defaultVoteDurationMax = uint32(4032)
    38  
    39  	// dust value can be found increasing the amount value until we get false
    40  	// from IsDustAmount function. Amounts can not be lower than dust
    41  	// func IsDustAmount(amount int64, relayFeePerKb int64) bool {
    42  	//     totalSize := 8 + 2 + 1 + 25 + 165
    43  	// 	   return int64(amount)*1000/(3*int64(totalSize)) < int64(relayFeePerKb)
    44  	// }
    45  	dust = 60300
    46  )
    47  
    48  var (
    49  	// Default start date to start pulling code statistics if none specified.
    50  	// 6 months in minutes 60min * 24h * 7days * 26 weeks
    51  	defaultCodeStatStart = time.Now().Add(-1 * time.Minute * 60 * 24 * 7 * 26)
    52  
    53  	// Default end date to stop pull code statistics if none specified.
    54  	// Use today as the default end code stat date.
    55  	defaultCodeStatEnd = time.Now()
    56  
    57  	// Check to make sure code stat start time is sane 2 years from today.
    58  	// 2 years in minutes 60min * 24h * 7days * 52weeks * 2years
    59  	codeStatCheck = time.Now().Add(-1 * time.Minute * 60 * 24 * 7 * 52 * 2)
    60  )
    61  
    62  // LegacyConfig represents the config options for legacy API.
    63  //
    64  // Everything in this config is DEPRECATED and will be removed in the near
    65  // future.
    66  type LegacyConfig struct {
    67  	// Legacy user database settings
    68  	DBRootCert       string `long:"dbrootcert" description:"File containing the CA certificate for the database"`
    69  	DBCert           string `long:"dbcert" description:"File containing the politeiawww client certificate for the database"`
    70  	DBKey            string `long:"dbkey" description:"File containing the politeiawww client certificate key for the database"`
    71  	EncryptionKey    string `long:"encryptionkey" description:"File containing encryption key used for encrypting user data at rest"`
    72  	OldEncryptionKey string `long:"oldencryptionkey" description:"File containing old encryption key (only set when rotating keys)"`
    73  
    74  	// Settings the need to be turned into plugin settings.
    75  	MailRateLimit    int    `long:"mailratelimit" description:"Limits the amount of emails a user can receive in 24h"`
    76  	WebServerAddress string `long:"webserveraddress" description:"Web server address used to create email links (format: <scheme>://<host>[:<port>])"`
    77  
    78  	// Legacy API settings
    79  	Mode        string `long:"mode" description:"Mode www runs as. Supported values: piwww, cmswww"`
    80  	DcrdataHost string `long:"dcrdatahost" description:"Dcrdata ip:port"`
    81  
    82  	// Legacy pi settings
    83  	PaywallAmount            uint64 `long:"paywallamount" description:"Amount of DCR (in atoms) required for a user to register or submit a proposal."`
    84  	PaywallXpub              string `long:"paywallxpub" description:"Extended public key for deriving paywall addresses."`
    85  	MinConfirmationsRequired uint64 `long:"minconfirmations" description:"Minimum blocks confirmation for accepting paywall as paid. Only works in TestNet."`
    86  
    87  	// Legacy cmswww settings
    88  	BuildCMSDB           bool     `long:"buildcmsdb" description:"Build the cmsdb from scratch"`
    89  	GithubAPIToken       string   `long:"githubapitoken" description:"API Token used to communicate with github API.  When populated in cmswww mode, github-tracker is enabled."`
    90  	CodeStatRepos        []string `long:"codestatrepos" description:"Org/Repositories to crawl for code statistics"`
    91  	CodeStatOrganization string   `long:"codestatorg" description:"Organization to crawl for code statistics"`
    92  	CodeStatStart        int64    `long:"codestatstart" description:"Date in which to look back to for code stat crawl (default 6 months back)"`
    93  	CodeStatEnd          int64    `long:"codestatend" description:"Date in which to end look back to for code stat crawl (default today)"`
    94  	CodeStatSkipSync     bool     `long:"codestatskipsync" description:"Skip pull request crawl on startup"`
    95  	VoteDurationMin      uint32   `long:"votedurationmin" description:"Minimum duration of a dcc vote in blocks"`
    96  	VoteDurationMax      uint32   `long:"votedurationmax" description:"Maximum duration of a dcc vote in blocks"`
    97  }
    98  
    99  // setupLegacyConfig sets up the legacy config settings.
   100  func setupLegacyConfig(cfg *Config) error {
   101  	// Setup mode specific settings
   102  	switch cfg.Mode {
   103  	case CMSWWWMode:
   104  		// Setup CMS specific settings
   105  		if cfg.MailAddress == defaultMailAddress {
   106  			cfg.MailAddress = defaultMailAddressCMS
   107  		}
   108  		err := setupLegacyCMSSettings(cfg)
   109  		if err != nil {
   110  			return err
   111  		}
   112  
   113  	case PiWWWMode:
   114  		// Setup pi specific settings
   115  		err := setupLegacyPiSettings(cfg)
   116  		if err != nil {
   117  			return err
   118  		}
   119  
   120  	default:
   121  		return fmt.Errorf("invalid mode '%v'", cfg.Mode)
   122  	}
   123  
   124  	// Verify the various config settings
   125  	err := setupLegacyUserDBSettings(cfg)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	// Verify the SMTP mail settings
   131  	switch {
   132  	case cfg.MailHost == "" && cfg.MailUser == "" &&
   133  		cfg.MailPass == "" && cfg.WebServerAddress == "":
   134  		// Email is disabled; this is ok
   135  	case cfg.MailHost != "" && cfg.MailUser != "" &&
   136  		cfg.MailPass != "" && cfg.WebServerAddress != "":
   137  		// All mail settings have been set; this is ok
   138  	default:
   139  		return fmt.Errorf("either all or none of the following config" +
   140  			"options should be supplied: mailhost, mailuser, mailpass, " +
   141  			"webserveraddress")
   142  	}
   143  
   144  	// Verify the webserver address
   145  	_, err = url.Parse(cfg.WebServerAddress)
   146  	if err != nil {
   147  		return fmt.Errorf("invalid webserveraddress setting '%v': %v",
   148  			cfg.WebServerAddress, err)
   149  	}
   150  
   151  	// Verify the dcrdata host
   152  	if cfg.DcrdataHost == "" {
   153  		if cfg.TestNet {
   154  			cfg.DcrdataHost = defaultDcrdataTestnet
   155  		} else {
   156  			cfg.DcrdataHost = defaultDcrdataMainnet
   157  		}
   158  	}
   159  	_, err = url.Parse(cfg.DcrdataHost)
   160  	if err != nil {
   161  		return fmt.Errorf("invalid dcrdata setting '%v': %v",
   162  			cfg.DcrdataHost, err)
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  // setupLegacyUserDBSettings sets up the legacy user database config settings.
   169  func setupLegacyUserDBSettings(cfg *Config) error {
   170  	switch cfg.UserDB {
   171  	case LevelDB:
   172  		// Leveldb implementation does not require any database settings
   173  		// and does support encrypting data at rest. Return an error if
   174  		// the user has the encryption settings set to prevent them from
   175  		// thinking their data is being encrypted.
   176  		switch {
   177  		case cfg.DBHost != "":
   178  			log.Warnf("leveldb does not use --dbhost")
   179  		case cfg.DBRootCert != "":
   180  			log.Warnf("leveldb does not use --dbrootcert")
   181  		case cfg.DBCert != "":
   182  			log.Warnf("leveldb does not use --dbcert")
   183  		case cfg.DBKey != "":
   184  			log.Warnf("leveldb does not use --dbkey")
   185  		case cfg.EncryptionKey != "":
   186  			return fmt.Errorf("leveldb --encryptionkey not supported")
   187  		case cfg.OldEncryptionKey != "":
   188  			return fmt.Errorf("leveldb --oldencryptionkey not supported")
   189  		}
   190  
   191  	case CockroachDB:
   192  		// Verify database host
   193  		if cfg.DBHost == "" {
   194  			cfg.DBHost = defaultCockroachDBHost
   195  		}
   196  		_, err := url.Parse(cfg.DBHost)
   197  		if err != nil {
   198  			return fmt.Errorf("invalid dbhost '%v': %v",
   199  				cfg.DBHost, err)
   200  		}
   201  
   202  		// Verify certs and encryption key. Cockroachdb requires
   203  		// these settings.
   204  		switch {
   205  		case cfg.DBRootCert == "":
   206  			return fmt.Errorf("dbrootcert param is required")
   207  		case cfg.DBCert == "":
   208  			return fmt.Errorf("dbcert param is required")
   209  		case cfg.DBKey == "":
   210  			return fmt.Errorf("dbkey param is required")
   211  		}
   212  		if cfg.EncryptionKey == "" {
   213  			cfg.EncryptionKey = filepath.Join(cfg.HomeDir, "sbox.key")
   214  		}
   215  
   216  		// Clean file paths
   217  		cfg.DBRootCert = util.CleanAndExpandPath(cfg.DBRootCert)
   218  		cfg.DBCert = util.CleanAndExpandPath(cfg.DBCert)
   219  		cfg.DBKey = util.CleanAndExpandPath(cfg.DBKey)
   220  		cfg.EncryptionKey = util.CleanAndExpandPath(cfg.EncryptionKey)
   221  		cfg.OldEncryptionKey = util.CleanAndExpandPath(cfg.OldEncryptionKey)
   222  
   223  		// Validate user database encryption keys.
   224  		err = validateEncryptionKeys(cfg.EncryptionKey, cfg.OldEncryptionKey)
   225  		if err != nil {
   226  			return fmt.Errorf("validate encryption keys: %v", err)
   227  		}
   228  
   229  		// Validate user database root cert
   230  		b, err := os.ReadFile(cfg.DBRootCert)
   231  		if err != nil {
   232  			return fmt.Errorf("read dbrootcert: %v", err)
   233  		}
   234  		block, _ := pem.Decode(b)
   235  		_, err = x509.ParseCertificate(block.Bytes)
   236  		if err != nil {
   237  			return fmt.Errorf("parse dbrootcert: %v", err)
   238  		}
   239  
   240  		// Validate user database key pair
   241  		_, err = tls.LoadX509KeyPair(cfg.DBCert, cfg.DBKey)
   242  		if err != nil {
   243  			return fmt.Errorf("load key pair dbcert "+
   244  				"and dbkey: %v", err)
   245  		}
   246  
   247  	case MySQL:
   248  		// Set defaults
   249  		if cfg.EncryptionKey == "" {
   250  			cfg.EncryptionKey = filepath.Join(cfg.HomeDir, "sbox.key")
   251  		}
   252  
   253  		// Clean encryption keys paths.
   254  		cfg.EncryptionKey = util.CleanAndExpandPath(cfg.EncryptionKey)
   255  		cfg.OldEncryptionKey = util.CleanAndExpandPath(cfg.OldEncryptionKey)
   256  
   257  		// Validate user database encryption keys.
   258  		err := validateEncryptionKeys(cfg.EncryptionKey, cfg.OldEncryptionKey)
   259  		if err != nil {
   260  			return err
   261  		}
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  // setupLegacyPiSettings sets up the legacy piwww settings.
   268  func setupLegacyPiSettings(cfg *Config) error {
   269  	// Verify paywall settings
   270  	paywallIsEnabled := cfg.PaywallAmount != 0 || cfg.PaywallXpub != ""
   271  	if !paywallIsEnabled {
   272  		return nil
   273  	}
   274  
   275  	// Parse extended public key
   276  	_, err := hdkeychain.NewKeyFromString(cfg.PaywallXpub,
   277  		cfg.ActiveNet.Params)
   278  	if err != nil {
   279  		return fmt.Errorf("invalid extended public key: %v", err)
   280  	}
   281  
   282  	// Verify paywall amount
   283  	if cfg.PaywallAmount < dust {
   284  		return fmt.Errorf("paywall amount needs to be higher than %v", dust)
   285  	}
   286  
   287  	// Verify required paywall confirmations
   288  	if !cfg.TestNet &&
   289  		cfg.MinConfirmationsRequired != defaultPaywallMinConfirmations {
   290  		return fmt.Errorf("cannot set --minconfirmations on mainnet")
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  // setupLegacyCMSSettings sets up the legacy CMS config settings.
   297  func setupLegacyCMSSettings(cfg *Config) error {
   298  	if cfg.CodeStatStart > 0 &&
   299  		(time.Unix(cfg.CodeStatStart, 0).Before(codeStatCheck) ||
   300  			time.Unix(cfg.CodeStatStart, 0).After(time.Now())) {
   301  		return fmt.Errorf("you have entered an invalid code stat start date")
   302  	}
   303  	if cfg.CodeStatEnd > 0 &&
   304  		time.Unix(cfg.CodeStatEnd, 0).Before(time.Unix(cfg.CodeStatStart, 0)) {
   305  		return fmt.Errorf("you have entered an invalid code stat end date")
   306  	}
   307  	if cfg.CodeStatStart <= 0 {
   308  		cfg.CodeStatStart = defaultCodeStatStart.Unix()
   309  	}
   310  	if cfg.CodeStatEnd <= 0 {
   311  		cfg.CodeStatEnd = defaultCodeStatEnd.Unix()
   312  	}
   313  	return nil
   314  }
   315  
   316  // validateEncryptionKeys validates the provided encryption keys.
   317  func validateEncryptionKeys(encKey, oldEncKey string) error {
   318  	if encKey != "" && !util.FileExists(encKey) {
   319  		return fmt.Errorf("file not found %v", encKey)
   320  	}
   321  
   322  	if oldEncKey != "" {
   323  		switch {
   324  		case encKey == "":
   325  			return fmt.Errorf("old encryption key param " +
   326  				"cannot be used without encryption key param")
   327  
   328  		case encKey == oldEncKey:
   329  			return fmt.Errorf("old encryption key param " +
   330  				"and encryption key param must be different")
   331  
   332  		case !util.FileExists(oldEncKey):
   333  			return fmt.Errorf("file not found %v", oldEncKey)
   334  		}
   335  	}
   336  
   337  	return nil
   338  }