github.com/decred/politeia@v1.4.0/politeiad/politeiad.go (about) 1 // Copyright (c) 2017-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "crypto/elliptic" 9 "crypto/x509" 10 "encoding/json" 11 "fmt" 12 "net/http" 13 "net/http/httputil" 14 "os" 15 "os/signal" 16 "regexp" 17 "runtime/debug" 18 "strings" 19 "syscall" 20 "time" 21 22 "github.com/decred/dcrd/chaincfg/v3" 23 v1 "github.com/decred/politeia/politeiad/api/v1" 24 "github.com/decred/politeia/politeiad/api/v1/identity" 25 v2 "github.com/decred/politeia/politeiad/api/v2" 26 "github.com/decred/politeia/politeiad/backend" 27 "github.com/decred/politeia/politeiad/backend/gitbe" 28 "github.com/decred/politeia/politeiad/backendv2" 29 "github.com/decred/politeia/politeiad/backendv2/tstorebe" 30 "github.com/decred/politeia/util" 31 "github.com/gorilla/mux" 32 "github.com/pkg/errors" 33 ) 34 35 type permission uint 36 37 const ( 38 permissionPublic permission = iota 39 permissionAuth 40 ) 41 42 // politeia application context. 43 type politeia struct { 44 backend backend.Backend 45 backendv2 backendv2.Backend 46 cfg *config 47 router *mux.Router 48 identity *identity.FullIdentity 49 } 50 51 func remoteAddr(r *http.Request) string { 52 via := r.RemoteAddr 53 xff := r.Header.Get(v1.Forward) 54 if xff != "" { 55 return fmt.Sprintf("%v via %v", xff, r.RemoteAddr) 56 } 57 return via 58 } 59 60 // handleNotFound is a generic handler for an invalid route. 61 func (p *politeia) handleNotFound(w http.ResponseWriter, r *http.Request) { 62 // Log incoming connection 63 log.Debugf("Invalid route: %v %v %v %v", remoteAddr(r), r.Method, r.URL, 64 r.Proto) 65 66 // Trace incoming request 67 log.Tracef("%v", newLogClosure(func() string { 68 trace, err := httputil.DumpRequest(r, true) 69 if err != nil { 70 trace = []byte(fmt.Sprintf("logging: "+ 71 "DumpRequest %v", err)) 72 } 73 return string(trace) 74 })) 75 76 util.RespondWithJSON(w, http.StatusNotFound, v1.ServerErrorReply{}) 77 } 78 79 func (p *politeia) respondWithUserError(w http.ResponseWriter, errorCode v1.ErrorStatusT, errorContext []string) { 80 util.RespondWithJSON(w, http.StatusBadRequest, v1.UserErrorReply{ 81 ErrorCode: errorCode, 82 ErrorContext: errorContext, 83 }) 84 } 85 86 func (p *politeia) respondWithServerError(w http.ResponseWriter, errorCode int64, err error) { 87 // If this is a pkg/errors error then we can pull the 88 // stack trace out of the error, otherwise, we use the 89 // stack trace for this function. 90 stack, ok := util.StackTrace(err) 91 if !ok { 92 stack = string(debug.Stack()) 93 } 94 95 log.Errorf("Stacktrace (NOT A REAL CRASH): %v", stack) 96 97 util.RespondWithJSON(w, http.StatusInternalServerError, v1.ServerErrorReply{ 98 ErrorCode: errorCode, 99 }) 100 } 101 102 func (p *politeia) check(user, pass string) bool { 103 if user != p.cfg.RPCUser || pass != p.cfg.RPCPass { 104 return false 105 } 106 return true 107 } 108 109 func (p *politeia) auth(fn http.HandlerFunc) http.HandlerFunc { 110 return func(w http.ResponseWriter, r *http.Request) { 111 user, pass, ok := r.BasicAuth() 112 if !ok || !p.check(user, pass) { 113 log.Infof("%v Unauthorized access for: %v", 114 remoteAddr(r), user) 115 w.Header().Set("WWW-Authenticate", 116 `Basic realm="Politeiad"`) 117 w.WriteHeader(401) 118 p.respondWithUserError(w, v1.ErrorStatusInvalidRPCCredentials, nil) 119 return 120 } 121 log.Infof("%v Authorized access for: %v", 122 remoteAddr(r), user) 123 fn(w, r) 124 } 125 } 126 127 func (p *politeia) addRoute(method string, route string, handler http.HandlerFunc, perm permission) { 128 if perm == permissionAuth { 129 handler = p.auth(handler) 130 } 131 p.router.StrictSlash(true).HandleFunc(route, handler).Methods(method) 132 } 133 134 func (p *politeia) addRouteV2(method string, route string, handler http.HandlerFunc, perm permission) { 135 route = v2.APIRoute + route 136 p.addRoute(method, route, handler, perm) 137 } 138 139 func (p *politeia) setupBackendGit(anp *chaincfg.Params) error { 140 if p.router == nil { 141 return errors.Errorf("router must be initialized") 142 } 143 144 b, err := gitbe.New(anp, p.cfg.DataDir, p.cfg.DcrtimeHost, 145 "", p.identity, p.cfg.GitTrace, p.cfg.DcrdataHost) 146 if err != nil { 147 return fmt.Errorf("new gitbe: %v", err) 148 } 149 p.backend = b 150 151 // Not found 152 p.router.NotFoundHandler = http.HandlerFunc(p.handleNotFound) 153 154 // Unprivileged routes 155 p.addRoute(http.MethodPost, v1.IdentityRoute, p.getIdentity, 156 permissionPublic) 157 p.addRoute(http.MethodPost, v1.NewRecordRoute, p.newRecord, 158 permissionPublic) 159 p.addRoute(http.MethodPost, v1.UpdateUnvettedRoute, p.updateUnvetted, 160 permissionPublic) 161 p.addRoute(http.MethodPost, v1.UpdateVettedRoute, p.updateVetted, 162 permissionPublic) 163 p.addRoute(http.MethodPost, v1.GetUnvettedRoute, p.getUnvetted, 164 permissionPublic) 165 p.addRoute(http.MethodPost, v1.GetVettedRoute, p.getVetted, 166 permissionPublic) 167 168 // Routes that require auth 169 p.addRoute(http.MethodPost, v1.InventoryRoute, p.inventory, 170 permissionAuth) 171 p.addRoute(http.MethodPost, v1.SetUnvettedStatusRoute, 172 p.setUnvettedStatus, permissionAuth) 173 p.addRoute(http.MethodPost, v1.SetVettedStatusRoute, 174 p.setVettedStatus, permissionAuth) 175 p.addRoute(http.MethodPost, v1.UpdateVettedMetadataRoute, 176 p.updateVettedMetadata, permissionAuth) 177 178 // Set plugin routes. Requires auth. 179 p.addRoute(http.MethodPost, v1.PluginCommandRoute, p.pluginCommand, 180 permissionAuth) 181 p.addRoute(http.MethodPost, v1.PluginInventoryRoute, p.pluginInventory, 182 permissionAuth) 183 184 return nil 185 } 186 187 var ( 188 // regexpPluginSettingMulti matches against the plugin setting 189 // value when it contains multiple values. 190 // 191 // pluginID,key,["value1","value2"] matches ["value1","value2"] 192 regexpPluginSettingMulti = regexp.MustCompile(`(\[.*\]$)`) 193 ) 194 195 // parsePluginSetting parses a politeiad config plugin setting. Plugin settings 196 // will be in following format. The value may be a single value or an array of 197 // values. 198 // 199 // pluginID,key,value 200 // pluginID,key,["value1","value2","value3"...] 201 // 202 // When multiple values are provided, the values must be formatted as a JSON 203 // encoded []string. 204 func parsePluginSetting(setting string) (string, *backendv2.PluginSetting, error) { 205 formatMsg := `expected plugin setting format is ` + 206 `pluginID,key,value OR pluginID,key,["value1","value2","value3"]` 207 208 // Parse the plugin setting 209 var ( 210 parsed = strings.Split(setting, ",") 211 212 // isMulti indicates whether the plugin setting contains 213 // multiple values. If the setting only contains a single 214 // value then isMulti will be false. 215 isMulti = regexpPluginSettingMulti.MatchString(setting) 216 ) 217 switch { 218 case len(parsed) < 3: 219 return "", nil, errors.Errorf("missing csv entry '%v'; %v", 220 setting, formatMsg) 221 case len(parsed) == 3: 222 // This is expected; continue 223 case len(parsed) > 3 && isMulti: 224 // This is expected; continue 225 default: 226 return "", nil, errors.Errorf("invalid format '%v'; %v", 227 setting, formatMsg) 228 } 229 230 var ( 231 pluginID = parsed[0] 232 settingKey = parsed[1] 233 settingValue = parsed[2] 234 ) 235 236 // Clean the strings. The setting value is allowed to be case 237 // sensitive. 238 pluginID = strings.ToLower(strings.TrimSpace(pluginID)) 239 settingKey = strings.ToLower(strings.TrimSpace(settingKey)) 240 settingValue = strings.TrimSpace(settingValue) 241 242 // Handle multiple values 243 if isMulti { 244 // Parse values 245 values := regexpPluginSettingMulti.FindString(setting) 246 247 // Verify the values are formatted as valid JSON 248 var s []string 249 err := json.Unmarshal([]byte(values), &s) 250 if err != nil { 251 return "", nil, err 252 } 253 254 // Re-encode the JSON. This will remove any funny 255 // formatting like whitespaces. 256 b, err := json.Marshal(s) 257 if err != nil { 258 return "", nil, err 259 } 260 261 // Save the value 262 settingValue = string(b) 263 } 264 265 return pluginID, &backendv2.PluginSetting{ 266 Key: settingKey, 267 Value: settingValue, 268 }, nil 269 } 270 271 func (p *politeia) setupBackendTstore(anp *chaincfg.Params) error { 272 if p.router == nil { 273 return errors.Errorf("router must be initialized") 274 } 275 276 b, err := tstorebe.New(p.cfg.HomeDir, p.cfg.DataDir, 277 anp, p.cfg.TlogHost, p.cfg.DBHost, p.cfg.DBPass, 278 p.cfg.DcrtimeHost, p.cfg.DcrtimeCert) 279 if err != nil { 280 return fmt.Errorf("new tstorebe: %v", err) 281 } 282 p.backendv2 = b 283 284 // Setup not found handler 285 p.router.NotFoundHandler = http.HandlerFunc(p.handleNotFound) 286 287 // Setup v1 routes 288 p.addRoute(http.MethodPost, v1.IdentityRoute, 289 p.getIdentity, permissionPublic) 290 291 // Setup v2 routes 292 p.addRouteV2(http.MethodPost, v2.RouteRecordNew, 293 p.handleRecordNew, permissionPublic) 294 p.addRouteV2(http.MethodPost, v2.RouteRecordEdit, 295 p.handleRecordEdit, permissionPublic) 296 p.addRouteV2(http.MethodPost, v2.RouteRecordEditMetadata, 297 p.handleRecordEditMetadata, permissionPublic) 298 p.addRouteV2(http.MethodPost, v2.RouteRecordSetStatus, 299 p.handleRecordSetStatus, permissionPublic) 300 p.addRouteV2(http.MethodPost, v2.RouteRecords, 301 p.handleRecords, permissionPublic) 302 p.addRouteV2(http.MethodPost, v2.RouteRecordTimestamps, 303 p.handleRecordTimestamps, permissionPublic) 304 p.addRouteV2(http.MethodPost, v2.RouteInventory, 305 p.handleInventory, permissionPublic) 306 p.addRouteV2(http.MethodPost, v2.RouteInventoryOrdered, 307 p.handleInventoryOrdered, permissionPublic) 308 p.addRouteV2(http.MethodPost, v2.RoutePluginWrite, 309 p.handlePluginWrite, permissionPublic) 310 p.addRouteV2(http.MethodPost, v2.RoutePluginReads, 311 p.handlePluginReads, permissionPublic) 312 p.addRouteV2(http.MethodPost, v2.RoutePluginInventory, 313 p.handlePluginInventory, permissionPublic) 314 315 p.addRouteV2(http.MethodPost, v2.RoutePluginInventory, 316 p.handlePluginInventory, permissionPublic) 317 318 // Setup plugins 319 if len(p.cfg.Plugins) > 0 { 320 // Parse plugin settings 321 settings := make(map[string][]backendv2.PluginSetting) 322 for _, v := range p.cfg.PluginSettings { 323 // Parse plugin setting 324 pluginID, ps, err := parsePluginSetting(v) 325 if err != nil { 326 return err 327 } 328 329 // Add to settings list 330 pss, ok := settings[pluginID] 331 if !ok { 332 pss = make([]backendv2.PluginSetting, 0, 16) 333 } 334 pss = append(pss, *ps) 335 336 // Save settings list 337 settings[pluginID] = pss 338 } 339 340 // Register plugins 341 for _, v := range p.cfg.Plugins { 342 // Setup plugin 343 ps, ok := settings[v] 344 if !ok { 345 ps = make([]backendv2.PluginSetting, 0) 346 } 347 plugin := backendv2.Plugin{ 348 ID: v, 349 Settings: ps, 350 Identity: p.identity, 351 } 352 353 // Register plugin 354 log.Infof("Register plugin: %v", v) 355 err = p.backendv2.PluginRegister(plugin) 356 if err != nil { 357 return fmt.Errorf("PluginRegister %v: %v", v, err) 358 } 359 } 360 361 // Setup plugins 362 for _, v := range p.backendv2.PluginInventory() { 363 log.Infof("Setup plugin: %v", v.ID) 364 err = p.backendv2.PluginSetup(v.ID) 365 if err != nil { 366 return fmt.Errorf("plugin setup %v: %v", v.ID, err) 367 } 368 } 369 } 370 371 // Perform filesytem check 372 if p.cfg.Fsck { 373 err = p.backendv2.Fsck() 374 if err != nil { 375 return err 376 } 377 } 378 379 return nil 380 } 381 382 func _main() error { 383 // Load configuration and parse command line. This function also 384 // initializes logging and configures it accordingly. 385 cfg, _, err := loadConfig() 386 if err != nil { 387 return fmt.Errorf("Could not load configuration file: %v", err) 388 } 389 defer func() { 390 if logRotator != nil { 391 logRotator.Close() 392 } 393 }() 394 395 log.Infof("Version : %v", cfg.Version) 396 log.Infof("Network : %v", activeNetParams.Params.Name) 397 log.Infof("Home dir: %v", cfg.HomeDir) 398 399 // Create the data directory in case it does not exist. 400 err = os.MkdirAll(cfg.DataDir, 0700) 401 if err != nil { 402 return err 403 } 404 405 // Generate the TLS cert and key file if both don't already 406 // exist. 407 if !util.FileExists(cfg.HTTPSKey) && 408 !util.FileExists(cfg.HTTPSCert) { 409 log.Infof("Generating HTTPS keypair...") 410 411 err := util.GenCertPair(elliptic.P521(), "politeiad", 412 cfg.HTTPSCert, cfg.HTTPSKey) 413 if err != nil { 414 return fmt.Errorf("unable to create https keypair: %v", 415 err) 416 } 417 418 log.Infof("HTTPS keypair created...") 419 } 420 421 // Generate ed25519 identity to save messages, tokens etc. 422 if !util.FileExists(cfg.Identity) { 423 log.Infof("Generating signing identity...") 424 id, err := identity.New() 425 if err != nil { 426 return err 427 } 428 err = id.Save(cfg.Identity) 429 if err != nil { 430 return err 431 } 432 log.Infof("Signing identity created...") 433 } 434 435 // Setup the router. Middleware is executed in 436 // the same order that they are registered in. 437 router := mux.NewRouter() 438 m := middleware{ 439 reqBodySizeLimit: cfg.ReqBodySizeLimit, 440 } 441 router.Use(closeBodyMiddleware) // MUST be registered first 442 router.Use(m.reqBodySizeLimitMiddleware) 443 router.Use(loggingMiddleware) 444 router.Use(recoverMiddleware) 445 446 // Setup application context. 447 p := &politeia{ 448 cfg: cfg, 449 router: router, 450 } 451 452 // Load identity. 453 p.identity, err = identity.LoadFullIdentity(cfg.Identity) 454 if err != nil { 455 return err 456 } 457 log.Infof("Public key: %x", p.identity.Public.Key) 458 459 // Load certs, if there. If they aren't there assume OS is used to 460 // resolve cert validity. 461 if len(cfg.DcrtimeCert) != 0 { 462 var certPool *x509.CertPool 463 if !util.FileExists(cfg.DcrtimeCert) { 464 return fmt.Errorf("unable to find dcrtime cert %v", 465 cfg.DcrtimeCert) 466 } 467 dcrtimeCert, err := os.ReadFile(cfg.DcrtimeCert) 468 if err != nil { 469 return fmt.Errorf("unable to read dcrtime cert %v: %v", 470 cfg.DcrtimeCert, err) 471 } 472 certPool = x509.NewCertPool() 473 if !certPool.AppendCertsFromPEM(dcrtimeCert) { 474 return fmt.Errorf("unable to load cert") 475 } 476 } 477 478 // Setup backend 479 log.Infof("Backend: %v", cfg.Backend) 480 switch cfg.Backend { 481 case backendGit: 482 err := p.setupBackendGit(activeNetParams.Params) 483 if err != nil { 484 return err 485 } 486 case backendTstore: 487 err := p.setupBackendTstore(activeNetParams.Params) 488 if err != nil { 489 return err 490 } 491 default: 492 return fmt.Errorf("invalid backend selected: %v", cfg.Backend) 493 } 494 495 // Bind to a port and pass our router in 496 listenC := make(chan error) 497 for _, listener := range cfg.Listeners { 498 listen := listener 499 go func() { 500 s := &http.Server{ 501 Handler: p.router, 502 Addr: listen, 503 ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, 504 WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, 505 } 506 507 log.Infof("Listen: %v", listen) 508 listenC <- s.ListenAndServeTLS(cfg.HTTPSCert, cfg.HTTPSKey) 509 }() 510 } 511 512 // Tell user we are ready to go. 513 log.Infof("Start of day") 514 515 // Setup OS signals 516 sigs := make(chan os.Signal, 1) 517 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 518 signal.Notify(sigs, syscall.SIGINT, syscall.SIGINT) 519 for { 520 select { 521 case sig := <-sigs: 522 log.Infof("Terminating with %v", sig) 523 goto done 524 case err := <-listenC: 525 log.Errorf("%v", err) 526 goto done 527 } 528 } 529 done: 530 switch p.cfg.Backend { 531 case backendGit: 532 p.backend.Close() 533 case backendTstore: 534 p.backendv2.Close() 535 } 536 537 log.Infof("Exiting") 538 539 return nil 540 } 541 542 func main() { 543 err := _main() 544 if err != nil { 545 fmt.Fprintf(os.Stderr, "%v\n", err) 546 os.Exit(1) 547 } 548 }