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 }