github.com/m-lab/locate@v0.17.6/locate.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"log"
     7  	"net/http"
     8  	"time"
     9  
    10  	secretmanager "cloud.google.com/go/secretmanager/apiv1"
    11  	"github.com/gomodule/redigo/redis"
    12  	"github.com/justinas/alice"
    13  	promet "github.com/prometheus/client_golang/prometheus"
    14  	"github.com/prometheus/client_golang/prometheus/promhttp"
    15  	"gopkg.in/square/go-jose.v2/jwt"
    16  
    17  	"github.com/m-lab/access/controller"
    18  	"github.com/m-lab/access/token"
    19  	"github.com/m-lab/go/content"
    20  	"github.com/m-lab/go/flagx"
    21  	"github.com/m-lab/go/httpx"
    22  	"github.com/m-lab/go/memoryless"
    23  	"github.com/m-lab/go/prometheusx"
    24  	"github.com/m-lab/go/rtx"
    25  	v2 "github.com/m-lab/locate/api/v2"
    26  	"github.com/m-lab/locate/clientgeo"
    27  	"github.com/m-lab/locate/handler"
    28  	"github.com/m-lab/locate/heartbeat"
    29  	"github.com/m-lab/locate/limits"
    30  	"github.com/m-lab/locate/memorystore"
    31  	"github.com/m-lab/locate/metrics"
    32  	"github.com/m-lab/locate/prometheus"
    33  	"github.com/m-lab/locate/secrets"
    34  	"github.com/m-lab/locate/static"
    35  )
    36  
    37  var (
    38  	listenPort         string
    39  	project            string
    40  	platform           string
    41  	locatorAE          bool
    42  	locatorMM          bool
    43  	legacyServer       string
    44  	signerSecretName   string
    45  	maxmind            = flagx.URL{}
    46  	verifySecretName   string
    47  	redisAddr          string
    48  	promUserSecretName string
    49  	promPassSecretName string
    50  	promURL            string
    51  	limitsPath         string
    52  	keySource          = flagx.Enum{
    53  		Options: []string{"secretmanager", "local"},
    54  		Value:   "secretmanager",
    55  	}
    56  
    57  	rateLimitRedisAddr    string
    58  	rateLimitPrefix       string
    59  	rateLimitIPUAInterval time.Duration
    60  	rateLimitIPUAMax      int
    61  	rateLimitIPInterval   time.Duration
    62  	rateLimitIPMax        int
    63  
    64  	earlyExitClients flagx.StringArray
    65  )
    66  
    67  func init() {
    68  	// PORT and GOOGLE_CLOUD_PROJECT are part of the default App Engine environment.
    69  	flag.StringVar(&listenPort, "port", "8080", "AppEngine port environment variable")
    70  	flag.StringVar(&project, "google-cloud-project", "", "AppEngine project environment variable")
    71  	flag.StringVar(&platform, "platform-project", "", "GCP project for platform machine names")
    72  	flag.StringVar(&signerSecretName, "signer-secret-name", "locate-service-signer-key", "Name of secret for locate signer key in Secret Manager")
    73  	flag.StringVar(&verifySecretName, "verify-secret-name", "locate-monitoring-service-verify-key", "Name of secret for monitoring verifier key in Secret Manager")
    74  	flag.StringVar(&redisAddr, "redis-address", "", "Primary endpoint for Redis instance")
    75  	flag.StringVar(&promUserSecretName, "prometheus-username-secret-name", "prometheus-support-build-prom-auth-user",
    76  		"Name of secret for Prometheus username")
    77  	flag.StringVar(&promPassSecretName, "prometheus-password-secret-name", "prometheus-support-build-prom-auth-pass",
    78  		"Name of secret for Prometheus password")
    79  	flag.StringVar(&promURL, "prometheus-url", "", "Base URL to query prometheus")
    80  	flag.BoolVar(&locatorAE, "locator-appengine", true, "Use the AppEngine clientgeo locator")
    81  	flag.BoolVar(&locatorMM, "locator-maxmind", false, "Use the MaxMind clientgeo locator")
    82  	flag.Var(&maxmind, "maxmind-url", "When -locator-maxmind is true, the tar URL of MaxMind IP database. May be: gs://bucket/file or file:./relativepath/file")
    83  	flag.Var(&keySource, "key-source", "Where to load signer and verifier keys")
    84  	flag.StringVar(&limitsPath, "limits-path", "/go/src/github.com/m-lab/locate/limits/config.yaml", "Path to the limits config file")
    85  	flag.DurationVar(&rateLimitIPUAInterval, "rate-limit-interval", time.Hour, "Time window for IP+UA rate limiting")
    86  	flag.IntVar(&rateLimitIPUAMax, "rate-limit-max", 40, "Max number of events in the time window for IP+UA rate limiting")
    87  	flag.DurationVar(&rateLimitIPInterval, "rate-limit-ip-interval", time.Hour,
    88  		"Time window for IP-only rate limiting")
    89  	flag.IntVar(&rateLimitIPMax, "rate-limit-ip-max", 120,
    90  		"Max number of events in the time window for IP-only rate limiting")
    91  	flag.StringVar(&rateLimitPrefix, "rate-limit-prefix", "locate:ratelimit", "Prefix for Redis keys for IP+UA rate limiting")
    92  	flag.StringVar(&rateLimitRedisAddr, "rate-limit-redis-address", "", "Primary endpoint for Redis instance for rate limiting")
    93  	flag.Var(&earlyExitClients, "early-exit-clients", "Client names that should receive early_exit parameter (can be specified multiple times)")
    94  	// Enable logging with line numbers to trace error locations.
    95  	log.SetFlags(log.LUTC | log.Llongfile)
    96  }
    97  
    98  var mainCtx, mainCancel = context.WithCancel(context.Background())
    99  
   100  type loader interface {
   101  	LoadSigner(ctx context.Context, name string) (*token.Signer, error)
   102  	LoadVerifier(ctx context.Context, name string) (*token.Verifier, error)
   103  	LoadPrometheus(ctx context.Context, user, pass string) (*prometheus.Credentials, error)
   104  }
   105  
   106  func main() {
   107  	flag.Parse()
   108  	rtx.Must(flagx.ArgsFromEnv(flag.CommandLine), "Could not parse env args")
   109  	defer mainCancel()
   110  
   111  	prom := prometheusx.MustServeMetrics()
   112  	defer prom.Close()
   113  
   114  	// Create the Secret Manager client
   115  	var cfg loader
   116  
   117  	switch keySource.Value {
   118  	case "secretmanager":
   119  		client, err := secretmanager.NewClient(mainCtx)
   120  		rtx.Must(err, "Failed to create Secret Manager client")
   121  		cfg = secrets.NewConfig(project, client)
   122  		defer client.Close()
   123  	case "local":
   124  		cfg = secrets.NewLocalConfig()
   125  	}
   126  
   127  	// SIGNER - load the signer key.
   128  	signer, err := cfg.LoadSigner(mainCtx, signerSecretName)
   129  	rtx.Must(err, "Failed to load signer key")
   130  
   131  	locators := clientgeo.MultiLocator{clientgeo.NewUserLocator()}
   132  	if locatorAE {
   133  		aeLocator := clientgeo.NewAppEngineLocator()
   134  		locators = append(locators, aeLocator)
   135  	}
   136  	if locatorMM {
   137  		mm, err := content.FromURL(mainCtx, maxmind.URL)
   138  		rtx.Must(err, "failed to load maxmindurl: %s", maxmind.URL)
   139  		mmLocator := clientgeo.NewMaxmindLocator(mainCtx, mm)
   140  		locators = append(locators, mmLocator)
   141  	}
   142  
   143  	pool := redis.Pool{
   144  		Dial: func() (redis.Conn, error) {
   145  			return redis.Dial("tcp", redisAddr)
   146  		},
   147  	}
   148  	memorystore := memorystore.NewClient[v2.HeartbeatMessage](&pool)
   149  	tracker := heartbeat.NewHeartbeatStatusTracker(memorystore)
   150  	defer tracker.StopImport()
   151  	srvLocatorV2 := heartbeat.NewServerLocator(tracker)
   152  
   153  	// Rate limiter Redis pool.
   154  	rateLimitPool := redis.Pool{
   155  		Dial: func() (redis.Conn, error) {
   156  			return redis.Dial("tcp", rateLimitRedisAddr)
   157  		},
   158  	}
   159  	rateLimitConfig := limits.RateLimitConfig{
   160  		IPConfig: limits.LimitConfig{
   161  			Interval:  rateLimitIPInterval,
   162  			MaxEvents: rateLimitIPMax,
   163  		},
   164  		IPUAConfig: limits.LimitConfig{
   165  			Interval:  rateLimitIPUAInterval,
   166  			MaxEvents: rateLimitIPUAMax,
   167  		},
   168  		KeyPrefix: rateLimitPrefix,
   169  	}
   170  	ipLimiter := limits.NewRateLimiter(&rateLimitPool, rateLimitConfig)
   171  
   172  	creds, err := cfg.LoadPrometheus(mainCtx, promUserSecretName, promPassSecretName)
   173  	rtx.Must(err, "failed to load Prometheus credentials")
   174  	promClient, err := prometheus.NewClient(creds, promURL)
   175  	rtx.Must(err, "failed to create Prometheus client")
   176  
   177  	lmts, err := limits.ParseConfig(limitsPath)
   178  	rtx.Must(err, "failed to parse limits config")
   179  	c := handler.NewClient(project, signer, srvLocatorV2, locators, promClient,
   180  		lmts, ipLimiter, earlyExitClients)
   181  
   182  	go func() {
   183  		// Check and reload db at least once a day.
   184  		reloadConfig := memoryless.Config{
   185  			Min:      time.Hour,
   186  			Max:      24 * time.Hour,
   187  			Expected: 6 * time.Hour,
   188  		}
   189  		tick, err := memoryless.NewTicker(mainCtx, reloadConfig)
   190  		rtx.Must(err, "Could not create ticker for reloading")
   191  		for range tick.C {
   192  			locators.Reload(mainCtx)
   193  		}
   194  	}()
   195  
   196  	// MONITORING VERIFIER - for access tokens provided by monitoring.
   197  	// The `verifier` returned by cfg.LoadVerifier() is a single object, but may
   198  	// possibly itself contain multiple verification keys. The sequence for
   199  	// getting here is something like: flag --verify-secret-name -> var
   200  	// verifySecretName -> fetch all enabled secrets associated with name from
   201  	// the Google Secret Manager -> pass a slice of JWT keys (secrets) to
   202  	// token.NewVerifier(), which results in the `verifier` value assigned
   203  	// below.
   204  	verifier, err := cfg.LoadVerifier(mainCtx, verifySecretName)
   205  	rtx.Must(err, "Failed to create verifier")
   206  	exp := jwt.Expected{
   207  		Issuer:   static.IssuerMonitoring,
   208  		Audience: jwt.Audience{static.AudienceLocate},
   209  	}
   210  	tc, err := controller.NewTokenController(verifier, true, exp)
   211  	rtx.Must(err, "Failed to create token controller")
   212  	monitoringChain := alice.New(tc.Limit).Then(http.HandlerFunc(c.Monitoring))
   213  
   214  	// TODO: add verifier for optional access tokens to support NextRequest.
   215  
   216  	mux := http.NewServeMux()
   217  	// PLATFORM APIs
   218  	// Services report their health to the heartbeat service.
   219  	mux.HandleFunc("/v2/platform/heartbeat", promhttp.InstrumentHandlerDuration(
   220  		metrics.RequestHandlerDuration.MustCurryWith(promet.Labels{"path": "/v2/platform/heartbeat"}),
   221  		http.HandlerFunc(c.Heartbeat)))
   222  	// Collect Prometheus health signals.
   223  	mux.HandleFunc("/v2/platform/prometheus", promhttp.InstrumentHandlerDuration(
   224  		metrics.RequestHandlerDuration.MustCurryWith(promet.Labels{"path": "/v2/platform/prometheus"}),
   225  		http.HandlerFunc(c.Prometheus)))
   226  	// End to end monitoring requests access tokens for specific targets.
   227  	mux.Handle("/v2/platform/monitoring/", promhttp.InstrumentHandlerDuration(
   228  		metrics.RequestHandlerDuration.MustCurryWith(promet.Labels{"path": "/v2/platform/monitoring/"}),
   229  		monitoringChain))
   230  
   231  	// USER APIs
   232  	// Clients request access tokens for specific services.
   233  	mux.HandleFunc("/v2/nearest/", promhttp.InstrumentHandlerDuration(
   234  		metrics.RequestHandlerDuration.MustCurryWith(promet.Labels{"path": "/v2/nearest/"}),
   235  		http.HandlerFunc(c.Nearest)))
   236  	// REQUIRED: API keys parameters required for priority requests.
   237  	mux.HandleFunc("/v2/priority/nearest/", promhttp.InstrumentHandlerDuration(
   238  		metrics.RequestHandlerDuration.MustCurryWith(promet.Labels{"path": "/v2/priority/nearest/"}),
   239  		http.HandlerFunc(c.Nearest)))
   240  
   241  	// Liveness and Readiness checks to support deployments.
   242  	mux.HandleFunc("/v2/live", c.Live)
   243  	mux.HandleFunc("/v2/ready", c.Ready)
   244  
   245  	// Return list of all heartbeat registrations
   246  	mux.HandleFunc("/v2/siteinfo/registrations", c.Registrations)
   247  
   248  	srv := &http.Server{
   249  		Addr:    ":" + listenPort,
   250  		Handler: mux,
   251  	}
   252  	log.Println("Listening for INSECURE access requests on " + listenPort)
   253  	rtx.Must(httpx.ListenAndServeAsync(srv), "Could not start server")
   254  	defer srv.Close()
   255  	<-mainCtx.Done()
   256  }