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  }