소스 검색

Add HTTP Basic authentication for /metrics endpoint

Frédéric Guillot 3 년 전
부모
커밋
877dbed5e8
4개의 변경된 파일65개의 추가작업 그리고 3개의 파일을 삭제
  1. 17 1
      config/options.go
  2. 8 0
      config/parser.go
  3. 20 0
      miniflux.1
  4. 20 2
      service/httpd/httpd.go

+ 17 - 1
config/options.go

@@ -72,6 +72,8 @@ const (
 	defaultMetricsCollector                   = false
 	defaultMetricsRefreshInterval             = 60
 	defaultMetricsAllowedNetworks             = "127.0.0.1/8"
+	defaultMetricsUsername                    = ""
+	defaultMetricsPassword                    = ""
 	defaultWatchdog                           = true
 	defaultInvidiousInstance                  = "yewtu.be"
 )
@@ -144,6 +146,8 @@ type Options struct {
 	metricsCollector                   bool
 	metricsRefreshInterval             int
 	metricsAllowedNetworks             []string
+	metricsUsername                    string
+	metricsPassword                    string
 	watchdog                           bool
 	invidiousInstance                  string
 	proxyPrivateKey                    []byte
@@ -211,6 +215,8 @@ func NewOptions() *Options {
 		metricsCollector:                   defaultMetricsCollector,
 		metricsRefreshInterval:             defaultMetricsRefreshInterval,
 		metricsAllowedNetworks:             []string{defaultMetricsAllowedNetworks},
+		metricsUsername:                    defaultMetricsUsername,
+		metricsPassword:                    defaultMetricsPassword,
 		watchdog:                           defaultWatchdog,
 		invidiousInstance:                  defaultInvidiousInstance,
 		proxyPrivateKey:                    randomKey,
@@ -513,6 +519,14 @@ func (o *Options) MetricsAllowedNetworks() []string {
 	return o.metricsAllowedNetworks
 }
 
+func (o *Options) MetricsUsername() string {
+	return o.metricsUsername
+}
+
+func (o *Options) MetricsPassword() string {
+	return o.metricsPassword
+}
+
 // HTTPClientUserAgent returns the global User-Agent header for miniflux.
 func (o *Options) HTTPClientUserAgent() string {
 	return o.httpClientUserAgent
@@ -576,6 +590,8 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
 		"METRICS_ALLOWED_NETWORKS":               strings.Join(o.metricsAllowedNetworks, ","),
 		"METRICS_COLLECTOR":                      o.metricsCollector,
 		"METRICS_REFRESH_INTERVAL":               o.metricsRefreshInterval,
+		"METRICS_USERNAME":                       o.metricsUsername,
+		"METRICS_PASSWORD":                       redactSecretValue(o.metricsPassword, redactSecret),
 		"OAUTH2_CLIENT_ID":                       o.oauth2ClientID,
 		"OAUTH2_CLIENT_SECRET":                   redactSecretValue(o.oauth2ClientSecret, redactSecret),
 		"OAUTH2_OIDC_DISCOVERY_ENDPOINT":         o.oauth2OidcDiscoveryEndpoint,
@@ -626,7 +642,7 @@ func (o *Options) String() string {
 
 func redactSecretValue(value string, redactSecret bool) string {
 	if redactSecret && value != "" {
-		return "******"
+		return "<secret>"
 	}
 	return value
 }

+ 8 - 0
config/parser.go

@@ -207,6 +207,14 @@ func (p *Parser) parseLines(lines []string) (err error) {
 			p.opts.metricsRefreshInterval = parseInt(value, defaultMetricsRefreshInterval)
 		case "METRICS_ALLOWED_NETWORKS":
 			p.opts.metricsAllowedNetworks = parseStringList(value, []string{defaultMetricsAllowedNetworks})
+		case "METRICS_USERNAME":
+			p.opts.metricsUsername = parseString(value, defaultMetricsUsername)
+		case "METRICS_USERNAME_FILE":
+			p.opts.metricsUsername = readSecretFile(value, defaultMetricsUsername)
+		case "METRICS_PASSWORD":
+			p.opts.metricsPassword = parseString(value, defaultMetricsPassword)
+		case "METRICS_PASSWORD_FILE":
+			p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword)
 		case "FETCH_YOUTUBE_WATCH_TIME":
 			p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime)
 		case "WATCHDOG":

+ 20 - 0
miniflux.1

@@ -283,6 +283,26 @@ List of networks allowed to access the metrics endpoint (comma-separated values)
 .br
 Default is 127.0.0.1/8\&.
 .TP
+.B METRICS_USERNAME
+Metrics endpoint username for basic HTTP authentication\&.
+.br
+Default is emtpty\&.
+.TP
+.B METRICS_USERNAME_FILE
+Path to a file that contains the username for the metrics endpoint HTTP authentication\&.
+.br
+Default is emtpty\&.
+.TP
+.B METRICS_PASSWORD
+Metrics endpoint password for basic HTTP authentication\&.
+.br
+Default is emtpty\&.
+.TP
+.B METRICS_PASSWORD_FILE
+Path to a file that contains the password for the metrics endpoint HTTP authentication\&.
+.br
+Default is emtpty\&.
+.TP
 .B OAUTH2_PROVIDER
 Possible values are "google" or "oidc"\&.
 .br

+ 20 - 2
service/httpd/httpd.go

@@ -222,7 +222,25 @@ func setupHandler(store *storage.Storage, pool *worker.Pool) *mux.Router {
 }
 
 func isAllowedToAccessMetricsEndpoint(r *http.Request) bool {
-	clientIP := net.ParseIP(request.ClientIP(r))
+	clientIP := request.ClientIP(r)
+
+	if config.Opts.MetricsUsername() != "" && config.Opts.MetricsPassword() != "" {
+		username, password, authOK := r.BasicAuth()
+		if !authOK {
+			logger.Info("[Metrics] [ClientIP=%s] No authentication header sent", clientIP)
+			return false
+		}
+
+		if username == "" || password == "" {
+			logger.Info("[Metrics] [ClientIP=%s] Empty username or password", clientIP)
+			return false
+		}
+
+		if username != config.Opts.MetricsUsername() || password != config.Opts.MetricsPassword() {
+			logger.Error("[Metrics] [ClientIP=%s] Invalid username or password", clientIP)
+			return false
+		}
+	}
 
 	for _, cidr := range config.Opts.MetricsAllowedNetworks() {
 		_, network, err := net.ParseCIDR(cidr)
@@ -230,7 +248,7 @@ func isAllowedToAccessMetricsEndpoint(r *http.Request) bool {
 			logger.Fatal(`[Metrics] Unable to parse CIDR %v`, err)
 		}
 
-		if network.Contains(clientIP) {
+		if network.Contains(net.ParseIP(clientIP)) {
 			return true
 		}
 	}