support host:port in metrics_address (#2042)

This commit is contained in:
wasaga 2021-03-30 18:54:33 -04:00 committed by GitHub
parent 4218f49741
commit c27cd9030d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 25 deletions

View file

@ -690,7 +690,7 @@ func (o *Options) Validate() error {
} }
if o.MetricsAddr != "" { if o.MetricsAddr != "" {
if err := ValidateListenerAddress(o.MetricsAddr); err != nil { if err := ValidateMetricsAddress(o.MetricsAddr); err != nil {
return fmt.Errorf("config: invalid metrics_addr: %w", err) return fmt.Errorf("config: invalid metrics_addr: %w", err)
} }
} }

View file

@ -3,6 +3,7 @@ package config
import ( import (
"fmt" "fmt"
"net" "net"
"strconv"
"strings" "strings"
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
@ -39,15 +40,19 @@ func GetEnvoyDNSLookupFamily(value string) envoy_config_cluster_v3.Cluster_DnsLo
return envoy_config_cluster_v3.Cluster_AUTO return envoy_config_cluster_v3.Cluster_AUTO
} }
// ValidateListenerAddress validates that a listener address is ip:port, not host:port. // ValidateMetricsAddress validates address for the metrics
func ValidateListenerAddress(addr string) error { func ValidateMetricsAddress(addr string) error {
host, _, err := net.SplitHostPort(addr) _, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil || port == "" {
return fmt.Errorf("invalid address, expected host:port") return fmt.Errorf("expected host:port")
} }
if host != "" && net.ParseIP(host) == nil { p, err := strconv.Atoi(port)
return fmt.Errorf("invalid address, expected ip for host") if err != nil {
return fmt.Errorf("port must be a number")
}
if p <= 0 {
return fmt.Errorf("expected positive port number")
} }
return nil return nil

View file

@ -224,9 +224,22 @@ func (srv *Server) buildMetricsListener(cfg *config.Config) (*envoy_config_liste
} }
} }
// we ignore the host part of the address, only binding to
host, port, err := net.SplitHostPort(cfg.Options.MetricsAddr)
if err != nil {
return nil, fmt.Errorf("metrics_addr %s: %w", cfg.Options.MetricsAddr, err)
}
if port == "" {
return nil, fmt.Errorf("metrics_addr %s: port is required", cfg.Options.MetricsAddr)
}
// unless an explicit IP address was provided, and bind to all interfaces if hostname was provided
if net.ParseIP(host) == nil {
host = ""
}
li := &envoy_config_listener_v3.Listener{ li := &envoy_config_listener_v3.Listener{
Name: "metrics-ingress", Name: "metrics-ingress",
Address: buildAddress(cfg.Options.MetricsAddr, 9902), Address: buildAddress(fmt.Sprintf("%s:%s", host, port), 9902),
FilterChains: []*envoy_config_listener_v3.FilterChain{filterChain}, FilterChains: []*envoy_config_listener_v3.FilterChain{filterChain},
} }
return li, nil return li, nil

View file

@ -19,4 +19,5 @@ const (
var ( var (
errNoMetricsAddr = errors.New("no metrics address provided") errNoMetricsAddr = errors.New("no metrics address provided")
errNoMetricsPort = errors.New("no metrics port provided") errNoMetricsPort = errors.New("no metrics port provided")
errNoMetricsHost = errors.New("no metrics host provided")
) )

View file

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"os" "strings"
"time" "time"
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
@ -30,7 +30,7 @@ func (r *Reporter) OnConfigChange(cfg *config.Config) {
services, err := getReportedServices(cfg) services, err := getReportedServices(cfg)
if err != nil { if err != nil {
log.Error().Err(err).Msg("applying config") log.Error().Err(err).Msg("service registry reporter")
return return
} }
@ -69,7 +69,7 @@ func (r *Reporter) OnConfigChange(cfg *config.Config) {
} }
func getReportedServices(cfg *config.Config) ([]*pb.Service, error) { func getReportedServices(cfg *config.Config) ([]*pb.Service, error) {
mu, err := metricsURL(cfg.Options.MetricsAddr) mu, err := metricsURL(*cfg.Options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -79,12 +79,34 @@ func getReportedServices(cfg *config.Config) ([]*pb.Service, error) {
}, nil }, nil
} }
func metricsURL(addr string) (*url.URL, error) { func metricsURL(o config.Options) (*url.URL, error) {
if addr == "" { u := url.URL{
Scheme: "http",
Host: o.MetricsAddr,
Path: defaultMetricsPath,
}
if o.MetricsBasicAuth != "" {
txt, err := base64.StdEncoding.DecodeString(o.MetricsBasicAuth)
if err != nil {
return nil, fmt.Errorf("metrics basic auth: %w", err)
}
parts := strings.SplitN(string(txt), ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("expected username:password for basic auth")
}
u.User = url.UserPassword(parts[0], parts[1])
}
if o.MetricsCertificate != "" || o.MetricsCertificateFile != "" {
u.Scheme = "https"
}
if o.MetricsAddr == "" {
return nil, errNoMetricsAddr return nil, errNoMetricsAddr
} }
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(o.MetricsAddr)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid metrics address: %w", err) return nil, fmt.Errorf("invalid metrics address: %w", err)
} }
@ -94,18 +116,10 @@ func metricsURL(addr string) (*url.URL, error) {
} }
if host == "" { if host == "" {
host, err = os.Hostname() return nil, errNoMetricsHost
if err != nil {
return nil, fmt.Errorf("metrics address is missing hostname, error obtaining it from OS: %w", err)
}
} }
return &url.URL{ return &u, nil
// TODO: TLS selector https://github.com/pomerium/internal/issues/272
Scheme: "http",
Path: defaultMetricsPath,
Host: net.JoinHostPort(host, port),
}, nil
} }
func runReporter( func runReporter(

View file

@ -0,0 +1,33 @@
package registry
import (
"testing"
"github.com/pomerium/pomerium/config"
"github.com/stretchr/testify/assert"
)
func TestMetricsURL(t *testing.T) {
for opt, expect := range map[*config.Options]string{
{MetricsAddr: "my.host:9090"}: "http://my.host:9090/metrics",
{MetricsAddr: "my.host:9090", MetricsBasicAuth: "bXl1c2VyOm15cGFzc3dvcmQ="}: "http://myuser:mypassword@my.host:9090/metrics",
{MetricsAddr: "my.host:9090", MetricsCertificate: "CERT"}: "https://my.host:9090/metrics",
{MetricsAddr: "my.host:9090", MetricsCertificateFile: "CERT"}: "https://my.host:9090/metrics",
} {
u, err := metricsURL(*opt)
if assert.NoError(t, err, opt) {
assert.Equal(t, expect, u.String())
}
}
for _, opt := range []config.Options{
{MetricsAddr: "my.host:"},
{MetricsAddr: "my.host:9090", MetricsBasicAuth: "SMTH"},
{MetricsAddr: ":9090"},
{MetricsAddr: "my.host"},
} {
_, err := metricsURL(opt)
assert.Error(t, err, opt)
}
}