envoy: refactor controlplane xds to new envoyconfig package (#2086)

This commit is contained in:
Caleb Doxsey 2021-04-13 13:51:44 -06:00 committed by GitHub
parent 0e66619081
commit 1dcccf2b56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 421 additions and 378 deletions

View file

@ -0,0 +1,29 @@
package envoyconfig
import (
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
"github.com/pomerium/pomerium/internal/httputil/reproxy"
)
// A Builder builds envoy config from pomerium config.
type Builder struct {
localGRPCAddress string
localHTTPAddress string
filemgr *filemgr.Manager
reproxy *reproxy.Handler
}
// New creates a new Builder.
func New(
localGRPCAddress string,
localHTTPAddress string,
fileManager *filemgr.Manager,
reproxyHandler *reproxy.Handler,
) *Builder {
return &Builder{
localGRPCAddress: localGRPCAddress,
localHTTPAddress: localHTTPAddress,
filemgr: fileManager,
reproxy: reproxyHandler,
}
}

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"encoding/base64"
@ -13,65 +13,38 @@ import (
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/martinlindhe/base36"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
)
// An Endpoint is a URL with its corresponding Transport Socket.
type Endpoint struct {
url url.URL
transportSocket *envoy_config_core_v3.TransportSocket
loadBalancerWeight *wrappers.UInt32Value
}
// NewEndpoint creates a new Endpoint.
func NewEndpoint(u *url.URL, ts *envoy_config_core_v3.TransportSocket, weight uint32) Endpoint {
var w *wrappers.UInt32Value
if weight > 0 {
w = &wrappers.UInt32Value{Value: weight}
}
return Endpoint{url: *u, transportSocket: ts, loadBalancerWeight: w}
}
// TransportSocketName return the name for this endpoint.
func (e Endpoint) TransportSocketName() string {
if e.transportSocket == nil {
return ""
}
h := cryptutil.HashProto(e.transportSocket)
return "ts-" + base36.EncodeBytes(h)
}
func (srv *Server) buildClusters(options *config.Options) ([]*envoy_config_cluster_v3.Cluster, error) {
// BuildClusters builds envoy clusters from the given config.
func (b *Builder) BuildClusters(cfg *config.Config) ([]*envoy_config_cluster_v3.Cluster, error) {
grpcURL := &url.URL{
Scheme: "http",
Host: srv.GRPCListener.Addr().String(),
Host: b.localGRPCAddress,
}
httpURL := &url.URL{
Scheme: "http",
Host: srv.HTTPListener.Addr().String(),
Host: b.localHTTPAddress,
}
authzURLs, err := options.GetAuthorizeURLs()
authzURLs, err := cfg.Options.GetAuthorizeURLs()
if err != nil {
return nil, err
}
controlGRPC, err := srv.buildInternalCluster(options, "pomerium-control-plane-grpc", []*url.URL{grpcURL}, true)
controlGRPC, err := b.buildInternalCluster(cfg.Options, "pomerium-control-plane-grpc", []*url.URL{grpcURL}, true)
if err != nil {
return nil, err
}
controlHTTP, err := srv.buildInternalCluster(options, "pomerium-control-plane-http", []*url.URL{httpURL}, false)
controlHTTP, err := b.buildInternalCluster(cfg.Options, "pomerium-control-plane-http", []*url.URL{httpURL}, false)
if err != nil {
return nil, err
}
authZ, err := srv.buildInternalCluster(options, "pomerium-authorize", authzURLs, true)
authZ, err := b.buildInternalCluster(cfg.Options, "pomerium-authorize", authzURLs, true)
if err != nil {
return nil, err
}
@ -82,14 +55,14 @@ func (srv *Server) buildClusters(options *config.Options) ([]*envoy_config_clust
authZ,
}
if config.IsProxy(options.Services) {
for i, p := range options.GetAllPolicies() {
if config.IsProxy(cfg.Options.Services) {
for i, p := range cfg.Options.GetAllPolicies() {
policy := p
if policy.EnvoyOpts == nil {
policy.EnvoyOpts = newDefaultEnvoyClusterConfig()
}
if len(policy.To) > 0 {
cluster, err := srv.buildPolicyCluster(options, &policy)
cluster, err := b.buildPolicyCluster(cfg.Options, &policy)
if err != nil {
return nil, fmt.Errorf("policy #%d: %w", i, err)
}
@ -105,31 +78,31 @@ func (srv *Server) buildClusters(options *config.Options) ([]*envoy_config_clust
return clusters, nil
}
func (srv *Server) buildInternalCluster(options *config.Options, name string, dsts []*url.URL, forceHTTP2 bool) (*envoy_config_cluster_v3.Cluster, error) {
func (b *Builder) buildInternalCluster(options *config.Options, name string, dsts []*url.URL, forceHTTP2 bool) (*envoy_config_cluster_v3.Cluster, error) {
cluster := newDefaultEnvoyClusterConfig()
cluster.DnsLookupFamily = config.GetEnvoyDNSLookupFamily(options.DNSLookupFamily)
var endpoints []Endpoint
for _, dst := range dsts {
ts, err := srv.buildInternalTransportSocket(options, dst)
ts, err := b.buildInternalTransportSocket(options, dst)
if err != nil {
return nil, err
}
endpoints = append(endpoints, NewEndpoint(dst, ts, 1))
}
if err := srv.buildCluster(cluster, name, endpoints, forceHTTP2); err != nil {
if err := b.buildCluster(cluster, name, endpoints, forceHTTP2); err != nil {
return nil, err
}
return cluster, nil
}
func (srv *Server) buildPolicyCluster(options *config.Options, policy *config.Policy) (*envoy_config_cluster_v3.Cluster, error) {
func (b *Builder) buildPolicyCluster(options *config.Options, policy *config.Policy) (*envoy_config_cluster_v3.Cluster, error) {
cluster := new(envoy_config_cluster_v3.Cluster)
proto.Merge(cluster, policy.EnvoyOpts)
cluster.AltStatName = getClusterStatsName(policy)
name := getClusterID(policy)
endpoints, err := srv.buildPolicyEndpoints(policy)
endpoints, err := b.buildPolicyEndpoints(policy)
if err != nil {
return nil, err
}
@ -142,17 +115,17 @@ func (srv *Server) buildPolicyCluster(options *config.Options, policy *config.Po
cluster.DnsLookupFamily = envoy_config_cluster_v3.Cluster_V4_ONLY
}
if err := srv.buildCluster(cluster, name, endpoints, false); err != nil {
if err := b.buildCluster(cluster, name, endpoints, false); err != nil {
return nil, err
}
return cluster, nil
}
func (srv *Server) buildPolicyEndpoints(policy *config.Policy) ([]Endpoint, error) {
func (b *Builder) buildPolicyEndpoints(policy *config.Policy) ([]Endpoint, error) {
var endpoints []Endpoint
for _, dst := range policy.To {
ts, err := srv.buildPolicyTransportSocket(policy, dst.URL)
ts, err := b.buildPolicyTransportSocket(policy, dst.URL)
if err != nil {
return nil, err
}
@ -161,7 +134,7 @@ func (srv *Server) buildPolicyEndpoints(policy *config.Policy) ([]Endpoint, erro
return endpoints, nil
}
func (srv *Server) buildInternalTransportSocket(options *config.Options, endpoint *url.URL) (*envoy_config_core_v3.TransportSocket, error) {
func (b *Builder) buildInternalTransportSocket(options *config.Options, endpoint *url.URL) (*envoy_config_core_v3.TransportSocket, error) {
if endpoint.Scheme != "https" {
return nil, nil
}
@ -177,19 +150,19 @@ func (srv *Server) buildInternalTransportSocket(options *config.Options, endpoin
}},
}
if options.CAFile != "" {
validationContext.TrustedCa = srv.filemgr.FileDataSource(options.CAFile)
validationContext.TrustedCa = b.filemgr.FileDataSource(options.CAFile)
} else if options.CA != "" {
bs, err := base64.StdEncoding.DecodeString(options.CA)
if err != nil {
log.Error().Err(err).Msg("invalid custom CA certificate")
}
validationContext.TrustedCa = srv.filemgr.BytesDataSource("custom-ca.pem", bs)
validationContext.TrustedCa = b.filemgr.BytesDataSource("custom-ca.pem", bs)
} else {
rootCA, err := getRootCertificateAuthority()
if err != nil {
log.Error().Err(err).Msg("unable to enable certificate verification because no root CAs were found")
} else {
validationContext.TrustedCa = srv.filemgr.FileDataSource(rootCA)
validationContext.TrustedCa = b.filemgr.FileDataSource(rootCA)
}
}
tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{
@ -210,12 +183,12 @@ func (srv *Server) buildInternalTransportSocket(options *config.Options, endpoin
}, nil
}
func (srv *Server) buildPolicyTransportSocket(policy *config.Policy, dst url.URL) (*envoy_config_core_v3.TransportSocket, error) {
func (b *Builder) buildPolicyTransportSocket(policy *config.Policy, dst url.URL) (*envoy_config_core_v3.TransportSocket, error) {
if dst.Scheme != "https" {
return nil, nil
}
vc, err := srv.buildPolicyValidationContext(policy, dst)
vc, err := b.buildPolicyValidationContext(policy, dst)
if err != nil {
return nil, err
}
@ -243,7 +216,7 @@ func (srv *Server) buildPolicyTransportSocket(policy *config.Policy, dst url.URL
}
if policy.ClientCertificate != nil {
tlsContext.CommonTlsContext.TlsCertificates = append(tlsContext.CommonTlsContext.TlsCertificates,
srv.envoyTLSCertificateFromGoTLSCertificate(policy.ClientCertificate))
b.envoyTLSCertificateFromGoTLSCertificate(policy.ClientCertificate))
}
tlsConfig := marshalAny(tlsContext)
@ -255,7 +228,7 @@ func (srv *Server) buildPolicyTransportSocket(policy *config.Policy, dst url.URL
}, nil
}
func (srv *Server) buildPolicyValidationContext(
func (b *Builder) buildPolicyValidationContext(
policy *config.Policy, dst url.URL,
) (*envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext, error) {
sni := dst.Hostname()
@ -270,19 +243,19 @@ func (srv *Server) buildPolicyValidationContext(
}},
}
if policy.TLSCustomCAFile != "" {
validationContext.TrustedCa = srv.filemgr.FileDataSource(policy.TLSCustomCAFile)
validationContext.TrustedCa = b.filemgr.FileDataSource(policy.TLSCustomCAFile)
} else if policy.TLSCustomCA != "" {
bs, err := base64.StdEncoding.DecodeString(policy.TLSCustomCA)
if err != nil {
log.Error().Err(err).Msg("invalid custom CA certificate")
}
validationContext.TrustedCa = srv.filemgr.BytesDataSource("custom-ca.pem", bs)
validationContext.TrustedCa = b.filemgr.BytesDataSource("custom-ca.pem", bs)
} else {
rootCA, err := getRootCertificateAuthority()
if err != nil {
log.Error().Err(err).Msg("unable to enable certificate verification because no root CAs were found")
} else {
validationContext.TrustedCa = srv.filemgr.FileDataSource(rootCA)
validationContext.TrustedCa = b.filemgr.FileDataSource(rootCA)
}
}
@ -293,7 +266,7 @@ func (srv *Server) buildPolicyValidationContext(
return validationContext, nil
}
func (srv *Server) buildCluster(
func (b *Builder) buildCluster(
cluster *envoy_config_cluster_v3.Cluster,
name string,
endpoints []Endpoint,
@ -307,7 +280,7 @@ func (srv *Server) buildCluster(
cluster.ConnectTimeout = defaultConnectionTimeout
}
cluster.RespectDnsTtl = true
lbEndpoints, err := srv.buildLbEndpoints(endpoints)
lbEndpoints, err := b.buildLbEndpoints(endpoints)
if err != nil {
return err
}
@ -318,7 +291,7 @@ func (srv *Server) buildCluster(
LbEndpoints: lbEndpoints,
}},
}
cluster.TransportSocketMatches, err = srv.buildTransportSocketMatches(endpoints)
cluster.TransportSocketMatches, err = b.buildTransportSocketMatches(endpoints)
if err != nil {
return err
}
@ -348,7 +321,7 @@ func (srv *Server) buildCluster(
return cluster.Validate()
}
func (srv *Server) buildLbEndpoints(endpoints []Endpoint) ([]*envoy_config_endpoint_v3.LbEndpoint, error) {
func (b *Builder) buildLbEndpoints(endpoints []Endpoint) ([]*envoy_config_endpoint_v3.LbEndpoint, error) {
var lbes []*envoy_config_endpoint_v3.LbEndpoint
for _, e := range endpoints {
defaultPort := 80
@ -386,7 +359,7 @@ func (srv *Server) buildLbEndpoints(endpoints []Endpoint) ([]*envoy_config_endpo
return lbes, nil
}
func (srv *Server) buildTransportSocketMatches(endpoints []Endpoint) ([]*envoy_config_cluster_v3.Cluster_TransportSocketMatch, error) {
func (b *Builder) buildTransportSocketMatches(endpoints []Endpoint) ([]*envoy_config_cluster_v3.Cluster_TransportSocketMatch, error) {
var tsms []*envoy_config_cluster_v3.Cluster_TransportSocketMatch
seen := map[string]struct{}{}
for _, e := range endpoints {

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"encoding/base64"
@ -12,6 +12,7 @@ import (
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
"github.com/pomerium/pomerium/internal/testutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
)
@ -20,19 +21,19 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
cacheDir, _ := os.UserCacheDir()
customCA := filepath.Join(cacheDir, "pomerium", "envoy", "files", "custom-ca-32484c314b584447463735303142374c31414145374650305a525539554938594d524855353757313942494d473847535231.pem")
srv, _ := NewServer("TEST", nil)
b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
rootCAPath, _ := getRootCertificateAuthority()
rootCA := srv.filemgr.FileDataSource(rootCAPath).GetFilename()
rootCA := b.filemgr.FileDataSource(rootCAPath).GetFilename()
t.Run("insecure", func(t *testing.T) {
ts, err := srv.buildPolicyTransportSocket(&config.Policy{
ts, err := b.buildPolicyTransportSocket(&config.Policy{
To: mustParseWeightedURLs(t, "http://example.com"),
}, *mustParseURL(t, "http://example.com"))
require.NoError(t, err)
assert.Nil(t, ts)
})
t.Run("host as sni", func(t *testing.T) {
ts, err := srv.buildPolicyTransportSocket(&config.Policy{
ts, err := b.buildPolicyTransportSocket(&config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"),
}, *mustParseURL(t, "https://example.com"))
require.NoError(t, err)
@ -66,7 +67,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
`, ts)
})
t.Run("tls_server_name as sni", func(t *testing.T) {
ts, err := srv.buildPolicyTransportSocket(&config.Policy{
ts, err := b.buildPolicyTransportSocket(&config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"),
TLSServerName: "use-this-name.example.com",
}, *mustParseURL(t, "https://example.com"))
@ -101,7 +102,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
`, ts)
})
t.Run("tls_skip_verify", func(t *testing.T) {
ts, err := srv.buildPolicyTransportSocket(&config.Policy{
ts, err := b.buildPolicyTransportSocket(&config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"),
TLSSkipVerify: true,
}, *mustParseURL(t, "https://example.com"))
@ -137,7 +138,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
`, ts)
})
t.Run("custom ca", func(t *testing.T) {
ts, err := srv.buildPolicyTransportSocket(&config.Policy{
ts, err := b.buildPolicyTransportSocket(&config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"),
TLSCustomCA: base64.StdEncoding.EncodeToString([]byte{0, 0, 0, 0}),
}, *mustParseURL(t, "https://example.com"))
@ -173,7 +174,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
})
t.Run("client certificate", func(t *testing.T) {
clientCert, _ := cryptutil.CertificateFromBase64(aExampleComCert, aExampleComKey)
ts, err := srv.buildPolicyTransportSocket(&config.Policy{
ts, err := b.buildPolicyTransportSocket(&config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"),
ClientCertificate: clientCert,
}, *mustParseURL(t, "https://example.com"))
@ -218,17 +219,17 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
}
func Test_buildCluster(t *testing.T) {
srv, _ := NewServer("TEST", nil)
b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
rootCAPath, _ := getRootCertificateAuthority()
rootCA := srv.filemgr.FileDataSource(rootCAPath).GetFilename()
rootCA := b.filemgr.FileDataSource(rootCAPath).GetFilename()
t.Run("insecure", func(t *testing.T) {
endpoints, err := srv.buildPolicyEndpoints(&config.Policy{
endpoints, err := b.buildPolicyEndpoints(&config.Policy{
To: mustParseWeightedURLs(t, "http://example.com", "http://1.2.3.4"),
})
require.NoError(t, err)
cluster := newDefaultEnvoyClusterConfig()
cluster.DnsLookupFamily = envoy_config_cluster_v3.Cluster_V4_ONLY
err = srv.buildCluster(cluster, "example", endpoints, true)
err = b.buildCluster(cluster, "example", endpoints, true)
require.NoErrorf(t, err, "cluster %+v", cluster)
testutil.AssertProtoJSONEqual(t, `
{
@ -277,7 +278,7 @@ func Test_buildCluster(t *testing.T) {
`, cluster)
})
t.Run("secure", func(t *testing.T) {
endpoints, err := srv.buildPolicyEndpoints(&config.Policy{
endpoints, err := b.buildPolicyEndpoints(&config.Policy{
To: mustParseWeightedURLs(t,
"https://example.com",
"https://example.com",
@ -285,7 +286,7 @@ func Test_buildCluster(t *testing.T) {
})
require.NoError(t, err)
cluster := newDefaultEnvoyClusterConfig()
err = srv.buildCluster(cluster, "example", endpoints, true)
err = b.buildCluster(cluster, "example", endpoints, true)
require.NoErrorf(t, err, "cluster %+v", cluster)
testutil.AssertProtoJSONEqual(t, `
{
@ -405,12 +406,12 @@ func Test_buildCluster(t *testing.T) {
`, cluster)
})
t.Run("ip addresses", func(t *testing.T) {
endpoints, err := srv.buildPolicyEndpoints(&config.Policy{
endpoints, err := b.buildPolicyEndpoints(&config.Policy{
To: mustParseWeightedURLs(t, "http://127.0.0.1", "http://127.0.0.2"),
})
require.NoError(t, err)
cluster := newDefaultEnvoyClusterConfig()
err = srv.buildCluster(cluster, "example", endpoints, true)
err = b.buildCluster(cluster, "example", endpoints, true)
require.NoErrorf(t, err, "cluster %+v", cluster)
testutil.AssertProtoJSONEqual(t, `
{
@ -458,12 +459,12 @@ func Test_buildCluster(t *testing.T) {
`, cluster)
})
t.Run("weights", func(t *testing.T) {
endpoints, err := srv.buildPolicyEndpoints(&config.Policy{
endpoints, err := b.buildPolicyEndpoints(&config.Policy{
To: mustParseWeightedURLs(t, "http://127.0.0.1:8080,1", "http://127.0.0.2,2"),
})
require.NoError(t, err)
cluster := newDefaultEnvoyClusterConfig()
err = srv.buildCluster(cluster, "example", endpoints, true)
err = b.buildCluster(cluster, "example", endpoints, true)
require.NoErrorf(t, err, "cluster %+v", cluster)
testutil.AssertProtoJSONEqual(t, `
{
@ -513,12 +514,12 @@ func Test_buildCluster(t *testing.T) {
`, cluster)
})
t.Run("localhost", func(t *testing.T) {
endpoints, err := srv.buildPolicyEndpoints(&config.Policy{
endpoints, err := b.buildPolicyEndpoints(&config.Policy{
To: mustParseWeightedURLs(t, "http://localhost"),
})
require.NoError(t, err)
cluster := newDefaultEnvoyClusterConfig()
err = srv.buildCluster(cluster, "example", endpoints, true)
err = b.buildCluster(cluster, "example", endpoints, true)
require.NoErrorf(t, err, "cluster %+v", cluster)
testutil.AssertProtoJSONEqual(t, `
{
@ -556,7 +557,7 @@ func Test_buildCluster(t *testing.T) {
`, cluster)
})
t.Run("outlier", func(t *testing.T) {
endpoints, err := srv.buildPolicyEndpoints(&config.Policy{
endpoints, err := b.buildPolicyEndpoints(&config.Policy{
To: mustParseWeightedURLs(t, "http://example.com"),
})
require.NoError(t, err)
@ -566,7 +567,7 @@ func Test_buildCluster(t *testing.T) {
EnforcingConsecutive_5Xx: wrapperspb.UInt32(17),
SplitExternalLocalOriginErrors: true,
}
err = srv.buildCluster(cluster, "example", endpoints, true)
err = b.buildCluster(cluster, "example", endpoints, true)
require.NoErrorf(t, err, "cluster %+v", cluster)
testutil.AssertProtoJSONEqual(t, `
{

View file

@ -0,0 +1,207 @@
// Package envoyconfig contains a Builder for building Envoy configuration from Pomerium configuration.
package envoyconfig
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net"
"net/url"
"os"
"strconv"
"sync"
"time"
envoy_config_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_extensions_access_loggers_grpc_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3"
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/martinlindhe/base36"
"golang.org/x/net/nettest"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/pkg/cryptutil"
)
var (
errNoEndpoints = errors.New("cluster must have endpoints")
defaultConnectionTimeout = durationpb.New(time.Second * 10)
)
// An Endpoint is a URL with its corresponding Transport Socket.
type Endpoint struct {
url url.URL
transportSocket *envoy_config_core_v3.TransportSocket
loadBalancerWeight *wrappers.UInt32Value
}
// NewEndpoint creates a new Endpoint.
func NewEndpoint(u *url.URL, ts *envoy_config_core_v3.TransportSocket, weight uint32) Endpoint {
var w *wrappers.UInt32Value
if weight > 0 {
w = &wrappers.UInt32Value{Value: weight}
}
return Endpoint{url: *u, transportSocket: ts, loadBalancerWeight: w}
}
// TransportSocketName return the name for this endpoint.
func (e Endpoint) TransportSocketName() string {
if e.transportSocket == nil {
return ""
}
h := cryptutil.HashProto(e.transportSocket)
return "ts-" + base36.EncodeBytes(h)
}
// newDefaultEnvoyClusterConfig creates envoy cluster with certain default values
func newDefaultEnvoyClusterConfig() *envoy_config_cluster_v3.Cluster {
return &envoy_config_cluster_v3.Cluster{
ConnectTimeout: defaultConnectionTimeout,
RespectDnsTtl: true,
DnsLookupFamily: envoy_config_cluster_v3.Cluster_AUTO,
}
}
func buildAccessLogs(options *config.Options) []*envoy_config_accesslog_v3.AccessLog {
lvl := options.ProxyLogLevel
if lvl == "" {
lvl = options.LogLevel
}
if lvl == "" {
lvl = "debug"
}
switch lvl {
case "trace", "debug", "info":
default:
// don't log access requests for levels > info
return nil
}
tc := marshalAny(&envoy_extensions_access_loggers_grpc_v3.HttpGrpcAccessLogConfig{
CommonConfig: &envoy_extensions_access_loggers_grpc_v3.CommonGrpcAccessLogConfig{
LogName: "ingress-http",
GrpcService: &envoy_config_core_v3.GrpcService{
TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{
ClusterName: "pomerium-control-plane-grpc",
},
},
},
TransportApiVersion: envoy_config_core_v3.ApiVersion_V3,
},
})
return []*envoy_config_accesslog_v3.AccessLog{{
Name: "envoy.access_loggers.http_grpc",
ConfigType: &envoy_config_accesslog_v3.AccessLog_TypedConfig{TypedConfig: tc},
}}
}
func buildAddress(hostport string, defaultPort int) *envoy_config_core_v3.Address {
host, strport, err := net.SplitHostPort(hostport)
if err != nil {
host = hostport
strport = fmt.Sprint(defaultPort)
}
port, err := strconv.Atoi(strport)
if err != nil {
port = defaultPort
}
if host == "" {
if nettest.SupportsIPv6() {
host = "::"
} else {
host = "0.0.0.0"
}
}
return &envoy_config_core_v3.Address{
Address: &envoy_config_core_v3.Address_SocketAddress{SocketAddress: &envoy_config_core_v3.SocketAddress{
Address: host,
PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{PortValue: uint32(port)},
Ipv4Compat: true,
}},
}
}
func (b *Builder) envoyTLSCertificateFromGoTLSCertificate(cert *tls.Certificate) *envoy_extensions_transport_sockets_tls_v3.TlsCertificate {
envoyCert := &envoy_extensions_transport_sockets_tls_v3.TlsCertificate{}
var chain bytes.Buffer
for _, cbs := range cert.Certificate {
_ = pem.Encode(&chain, &pem.Block{
Type: "CERTIFICATE",
Bytes: cbs,
})
}
envoyCert.CertificateChain = b.filemgr.BytesDataSource("tls-crt.pem", chain.Bytes())
if cert.OCSPStaple != nil {
envoyCert.OcspStaple = b.filemgr.BytesDataSource("ocsp-staple", cert.OCSPStaple)
}
if bs, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey); err == nil {
envoyCert.PrivateKey = b.filemgr.BytesDataSource("tls-key.pem", pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: bs,
},
))
} else {
log.Warn().Err(err).Msg("failed to marshal private key for tls config")
}
for _, scts := range cert.SignedCertificateTimestamps {
envoyCert.SignedCertificateTimestamp = append(envoyCert.SignedCertificateTimestamp,
b.filemgr.BytesDataSource("signed-certificate-timestamp", scts))
}
return envoyCert
}
var rootCABundle struct {
sync.Once
value string
}
func getRootCertificateAuthority() (string, error) {
rootCABundle.Do(func() {
// from https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ssl#arch-overview-ssl-enabling-verification
knownRootLocations := []string{
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem",
"/usr/local/etc/ssl/cert.pem",
"/etc/ssl/cert.pem",
}
for _, path := range knownRootLocations {
if _, err := os.Stat(path); err == nil {
rootCABundle.value = path
break
}
}
if rootCABundle.value == "" {
log.Error().Strs("known-locations", knownRootLocations).
Msgf("no root certificates were found in any of the known locations")
} else {
log.Info().Msgf("using %s as the system root certificate authority bundle", rootCABundle.value)
}
})
if rootCABundle.value == "" {
return "", fmt.Errorf("root certificates not found")
}
return rootCABundle.value, nil
}
func marshalAny(msg proto.Message) *anypb.Any {
any := new(anypb.Any)
_ = anypb.MarshalFrom(any, msg, proto.MarshalOptions{
AllowPartial: true,
Deterministic: true,
})
return any
}

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"encoding/base64"
@ -53,11 +53,12 @@ func init() {
})
}
func (srv *Server) buildListeners(cfg *config.Config) ([]*envoy_config_listener_v3.Listener, error) {
// BuildListeners builds envoy listeners from the given config.
func (b *Builder) BuildListeners(cfg *config.Config) ([]*envoy_config_listener_v3.Listener, error) {
var listeners []*envoy_config_listener_v3.Listener
if config.IsAuthenticate(cfg.Options.Services) || config.IsProxy(cfg.Options.Services) {
li, err := srv.buildMainListener(cfg)
li, err := b.buildMainListener(cfg)
if err != nil {
return nil, err
}
@ -65,7 +66,7 @@ func (srv *Server) buildListeners(cfg *config.Config) ([]*envoy_config_listener_
}
if config.IsAuthorize(cfg.Options.Services) || config.IsDataBroker(cfg.Options.Services) {
li, err := srv.buildGRPCListener(cfg)
li, err := b.buildGRPCListener(cfg)
if err != nil {
return nil, err
}
@ -73,7 +74,7 @@ func (srv *Server) buildListeners(cfg *config.Config) ([]*envoy_config_listener_
}
if cfg.Options.MetricsAddr != "" {
li, err := srv.buildMetricsListener(cfg)
li, err := b.buildMetricsListener(cfg)
if err != nil {
return nil, err
}
@ -83,7 +84,7 @@ func (srv *Server) buildListeners(cfg *config.Config) ([]*envoy_config_listener_
return listeners, nil
}
func (srv *Server) buildMainListener(cfg *config.Config) (*envoy_config_listener_v3.Listener, error) {
func (b *Builder) buildMainListener(cfg *config.Config) (*envoy_config_listener_v3.Listener, error) {
listenerFilters := []*envoy_config_listener_v3.ListenerFilter{}
if cfg.Options.UseProxyProtocol {
proxyCfg := marshalAny(&envoy_extensions_filters_listener_proxy_protocol_v3.ProxyProtocol{})
@ -101,7 +102,7 @@ func (srv *Server) buildMainListener(cfg *config.Config) (*envoy_config_listener
return nil, err
}
filter, err := srv.buildMainHTTPConnectionManagerFilter(cfg.Options, allDomains, "")
filter, err := b.buildMainHTTPConnectionManagerFilter(cfg.Options, allDomains, "")
if err != nil {
return nil, err
}
@ -126,9 +127,9 @@ func (srv *Server) buildMainListener(cfg *config.Config) (*envoy_config_listener
},
})
chains, err := srv.buildFilterChains(cfg.Options, cfg.Options.Addr,
chains, err := b.buildFilterChains(cfg.Options, cfg.Options.Addr,
func(tlsDomain string, httpDomains []string) (*envoy_config_listener_v3.FilterChain, error) {
filter, err := srv.buildMainHTTPConnectionManagerFilter(cfg.Options, httpDomains, tlsDomain)
filter, err := b.buildMainHTTPConnectionManagerFilter(cfg.Options, httpDomains, tlsDomain)
if err != nil {
return nil, err
}
@ -140,7 +141,7 @@ func (srv *Server) buildMainListener(cfg *config.Config) (*envoy_config_listener
ServerNames: []string{tlsDomain},
}
}
tlsContext := srv.buildDownstreamTLSContext(cfg, tlsDomain)
tlsContext := b.buildDownstreamTLSContext(cfg, tlsDomain)
if tlsContext != nil {
tlsConfig := marshalAny(tlsContext)
filterChain.TransportSocket = &envoy_config_core_v3.TransportSocket{
@ -165,8 +166,8 @@ func (srv *Server) buildMainListener(cfg *config.Config) (*envoy_config_listener
return li, nil
}
func (srv *Server) buildMetricsListener(cfg *config.Config) (*envoy_config_listener_v3.Listener, error) {
filter, err := srv.buildMetricsHTTPConnectionManagerFilter()
func (b *Builder) buildMetricsListener(cfg *config.Config) (*envoy_config_listener_v3.Listener, error) {
filter, err := b.buildMetricsHTTPConnectionManagerFilter()
if err != nil {
return nil, err
}
@ -186,7 +187,7 @@ func (srv *Server) buildMetricsListener(cfg *config.Config) (*envoy_config_liste
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
TlsParams: tlsParams,
TlsCertificates: []*envoy_extensions_transport_sockets_tls_v3.TlsCertificate{
srv.envoyTLSCertificateFromGoTLSCertificate(cert),
b.envoyTLSCertificateFromGoTLSCertificate(cert),
},
AlpnProtocols: []string{"h2", "http/1.1"},
},
@ -202,7 +203,7 @@ func (srv *Server) buildMetricsListener(cfg *config.Config) (*envoy_config_liste
dtc.CommonTlsContext.ValidationContextType = &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
ValidationContext: &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
TrustChainVerification: envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext_VERIFY_TRUST_CHAIN,
TrustedCa: srv.filemgr.BytesDataSource("metrics_client_ca.pem", bs),
TrustedCa: b.filemgr.BytesDataSource("metrics_client_ca.pem", bs),
},
}
} else if cfg.Options.MetricsClientCAFile != "" {
@ -210,7 +211,7 @@ func (srv *Server) buildMetricsListener(cfg *config.Config) (*envoy_config_liste
dtc.CommonTlsContext.ValidationContextType = &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
ValidationContext: &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
TrustChainVerification: envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext_VERIFY_TRUST_CHAIN,
TrustedCa: srv.filemgr.FileDataSource(cfg.Options.MetricsClientCAFile),
TrustedCa: b.filemgr.FileDataSource(cfg.Options.MetricsClientCAFile),
},
}
}
@ -245,7 +246,7 @@ func (srv *Server) buildMetricsListener(cfg *config.Config) (*envoy_config_liste
return li, nil
}
func (srv *Server) buildFilterChains(
func (b *Builder) buildFilterChains(
options *config.Options, addr string,
callback func(tlsDomain string, httpDomains []string) (*envoy_config_listener_v3.FilterChain, error),
) ([]*envoy_config_listener_v3.FilterChain, error) {
@ -283,7 +284,7 @@ func (srv *Server) buildFilterChains(
return chains, nil
}
func (srv *Server) buildMainHTTPConnectionManagerFilter(
func (b *Builder) buildMainHTTPConnectionManagerFilter(
options *config.Options,
domains []string,
tlsDomain string,
@ -309,7 +310,7 @@ func (srv *Server) buildMainHTTPConnectionManagerFilter(
// if this is a gRPC service domain and we're supposed to handle that, add those routes
if (config.IsAuthorize(options.Services) && hostsMatchDomain(authorizeURLs, domain)) ||
(config.IsDataBroker(options.Services) && hostsMatchDomain(dataBrokerURLs, domain)) {
rs, err := srv.buildGRPCRoutes()
rs, err := b.buildGRPCRoutes()
if err != nil {
return nil, err
}
@ -318,7 +319,7 @@ func (srv *Server) buildMainHTTPConnectionManagerFilter(
}
// these routes match /.pomerium/... and similar paths
rs, err := srv.buildPomeriumHTTPRoutes(options, domain)
rs, err := b.buildPomeriumHTTPRoutes(options, domain)
if err != nil {
return nil, err
}
@ -326,7 +327,7 @@ func (srv *Server) buildMainHTTPConnectionManagerFilter(
// if we're the proxy, add all the policy routes
if config.IsProxy(options.Services) {
rs, err := srv.buildPolicyRoutes(options, domain)
rs, err := b.buildPolicyRoutes(options, domain)
if err != nil {
return nil, err
}
@ -343,7 +344,7 @@ func (srv *Server) buildMainHTTPConnectionManagerFilter(
}
}
rs, err := srv.buildPomeriumHTTPRoutes(options, "*")
rs, err := b.buildPomeriumHTTPRoutes(options, "*")
if err != nil {
return nil, err
}
@ -443,11 +444,11 @@ func (srv *Server) buildMainHTTPConnectionManagerFilter(
maxStreamDuration = ptypes.DurationProto(options.WriteTimeout)
}
rc, err := srv.buildRouteConfiguration("main", virtualHosts)
rc, err := b.buildRouteConfiguration("main", virtualHosts)
if err != nil {
return nil, err
}
tracingProvider, err := srv.buildTracingProvider(options)
tracingProvider, err := b.buildTracingProvider(options)
if err != nil {
return nil, err
}
@ -482,8 +483,8 @@ func (srv *Server) buildMainHTTPConnectionManagerFilter(
}, nil
}
func (srv *Server) buildMetricsHTTPConnectionManagerFilter() (*envoy_config_listener_v3.Filter, error) {
rc, err := srv.buildRouteConfiguration("metrics", []*envoy_config_route_v3.VirtualHost{{
func (b *Builder) buildMetricsHTTPConnectionManagerFilter() (*envoy_config_listener_v3.Filter, error) {
rc, err := b.buildRouteConfiguration("metrics", []*envoy_config_route_v3.VirtualHost{{
Name: "metrics",
Domains: []string{"*"},
Routes: []*envoy_config_route_v3.Route{{
@ -523,8 +524,8 @@ func (srv *Server) buildMetricsHTTPConnectionManagerFilter() (*envoy_config_list
}, nil
}
func (srv *Server) buildGRPCListener(cfg *config.Config) (*envoy_config_listener_v3.Listener, error) {
filter, err := srv.buildGRPCHTTPConnectionManagerFilter()
func (b *Builder) buildGRPCListener(cfg *config.Config) (*envoy_config_listener_v3.Listener, error) {
filter, err := b.buildGRPCHTTPConnectionManagerFilter()
if err != nil {
return nil, err
}
@ -541,7 +542,7 @@ func (srv *Server) buildGRPCListener(cfg *config.Config) (*envoy_config_listener
}, nil
}
chains, err := srv.buildFilterChains(cfg.Options, cfg.Options.Addr,
chains, err := b.buildFilterChains(cfg.Options, cfg.Options.Addr,
func(tlsDomain string, httpDomains []string) (*envoy_config_listener_v3.FilterChain, error) {
filterChain := &envoy_config_listener_v3.FilterChain{
Filters: []*envoy_config_listener_v3.Filter{filter},
@ -551,7 +552,7 @@ func (srv *Server) buildGRPCListener(cfg *config.Config) (*envoy_config_listener
ServerNames: []string{tlsDomain},
}
}
tlsContext := srv.buildDownstreamTLSContext(cfg, tlsDomain)
tlsContext := b.buildDownstreamTLSContext(cfg, tlsDomain)
if tlsContext != nil {
tlsConfig := marshalAny(tlsContext)
filterChain.TransportSocket = &envoy_config_core_v3.TransportSocket{
@ -582,8 +583,8 @@ func (srv *Server) buildGRPCListener(cfg *config.Config) (*envoy_config_listener
return li, nil
}
func (srv *Server) buildGRPCHTTPConnectionManagerFilter() (*envoy_config_listener_v3.Filter, error) {
rc, err := srv.buildRouteConfiguration("grpc", []*envoy_config_route_v3.VirtualHost{{
func (b *Builder) buildGRPCHTTPConnectionManagerFilter() (*envoy_config_listener_v3.Filter, error) {
rc, err := b.buildRouteConfiguration("grpc", []*envoy_config_route_v3.VirtualHost{{
Name: "grpc",
Domains: []string{"*"},
Routes: []*envoy_config_route_v3.Route{{
@ -634,7 +635,7 @@ func (srv *Server) buildGRPCHTTPConnectionManagerFilter() (*envoy_config_listene
}, nil
}
func (srv *Server) buildRouteConfiguration(name string, virtualHosts []*envoy_config_route_v3.VirtualHost) (*envoy_config_route_v3.RouteConfiguration, error) {
func (b *Builder) buildRouteConfiguration(name string, virtualHosts []*envoy_config_route_v3.VirtualHost) (*envoy_config_route_v3.RouteConfiguration, error) {
return &envoy_config_route_v3.RouteConfiguration{
Name: name,
VirtualHosts: virtualHosts,
@ -643,7 +644,7 @@ func (srv *Server) buildRouteConfiguration(name string, virtualHosts []*envoy_co
}, nil
}
func (srv *Server) buildDownstreamTLSContext(cfg *config.Config, domain string) *envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext {
func (b *Builder) buildDownstreamTLSContext(cfg *config.Config, domain string) *envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext {
certs, err := cfg.AllCertificates()
if err != nil {
log.Warn().Str("domain", domain).Err(err).Msg("failed to get all certificates from config")
@ -656,7 +657,7 @@ func (srv *Server) buildDownstreamTLSContext(cfg *config.Config, domain string)
return nil
}
envoyCert := srv.envoyTLSCertificateFromGoTLSCertificate(cert)
envoyCert := b.envoyTLSCertificateFromGoTLSCertificate(cert)
return &envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext{
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
TlsParams: tlsParams,

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"os"
@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/controlplane/filemgr"
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
"github.com/pomerium/pomerium/internal/testutil"
)
@ -24,8 +24,8 @@ func Test_buildMetricsHTTPConnectionManagerFilter(t *testing.T) {
certFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-crt-354e49305a5a39414a545530374e58454e48334148524c4e324258463837364355564c4e4532464b54355139495547514a38.pem")
keyFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-key-3350415a38414e4e4a4655424e55393430474147324651433949384e485341334b5157364f424b4c5856365a545937383735.pem")
srv, _ := NewServer("TEST", nil)
li, err := srv.buildMetricsListener(&config.Config{
b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
li, err := b.buildMetricsListener(&config.Config{
Options: &config.Options{
MetricsAddr: "127.0.0.1:9902",
MetricsCertificate: aExampleComCert,
@ -106,12 +106,12 @@ func Test_buildMetricsHTTPConnectionManagerFilter(t *testing.T) {
}
func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
srv, _ := NewServer("TEST", nil)
b := New("local-grpc", "local-http", nil, nil)
options := config.NewDefaultOptions()
options.SkipXffAppend = true
options.XffNumTrustedHops = 1
filter, err := srv.buildMainHTTPConnectionManagerFilter(options, []string{"example.com"}, "*")
filter, err := b.buildMainHTTPConnectionManagerFilter(options, []string{"example.com"}, "*")
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `{
"name": "envoy.filters.network.http_connection_manager",
@ -467,14 +467,14 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
}
func Test_buildDownstreamTLSContext(t *testing.T) {
srv, _ := NewServer("TEST", nil)
b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
cacheDir, _ := os.UserCacheDir()
certFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-crt-354e49305a5a39414a545530374e58454e48334148524c4e324258463837364355564c4e4532464b54355139495547514a38.pem")
keyFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-key-3350415a38414e4e4a4655424e55393430474147324651433949384e485341334b5157364f424b4c5856365a545937383735.pem")
t.Run("no-validation", func(t *testing.T) {
downstreamTLSContext := srv.buildDownstreamTLSContext(&config.Config{Options: &config.Options{
downstreamTLSContext := b.buildDownstreamTLSContext(&config.Config{Options: &config.Options{
Cert: aExampleComCert,
Key: aExampleComKey,
}}, "a.example.com")
@ -507,7 +507,7 @@ func Test_buildDownstreamTLSContext(t *testing.T) {
}`, downstreamTLSContext)
})
t.Run("client-ca", func(t *testing.T) {
downstreamTLSContext := srv.buildDownstreamTLSContext(&config.Config{Options: &config.Options{
downstreamTLSContext := b.buildDownstreamTLSContext(&config.Config{Options: &config.Options{
Cert: aExampleComCert,
Key: aExampleComKey,
ClientCA: "TEST",
@ -544,7 +544,7 @@ func Test_buildDownstreamTLSContext(t *testing.T) {
}`, downstreamTLSContext)
})
t.Run("policy-client-ca", func(t *testing.T) {
downstreamTLSContext := srv.buildDownstreamTLSContext(&config.Config{Options: &config.Options{
downstreamTLSContext := b.buildDownstreamTLSContext(&config.Config{Options: &config.Options{
Cert: aExampleComCert,
Key: aExampleComKey,
Policies: []config.Policy{
@ -662,9 +662,9 @@ func Test_hostMatchesDomain(t *testing.T) {
}
func Test_buildRouteConfiguration(t *testing.T) {
srv := &Server{filemgr: filemgr.NewManager()}
b := New("local-grpc", "local-http", nil, nil)
virtualHosts := make([]*envoy_config_route_v3.VirtualHost, 10)
routeConfig, err := srv.buildRouteConfiguration("test-route-configuration", virtualHosts)
routeConfig, err := b.buildRouteConfiguration("test-route-configuration", virtualHosts)
require.NoError(t, err)
assert.Equal(t, "test-route-configuration", routeConfig.GetName())
assert.Equal(t, virtualHosts, routeConfig.GetVirtualHosts())
@ -672,11 +672,9 @@ func Test_buildRouteConfiguration(t *testing.T) {
}
func Test_requireProxyProtocol(t *testing.T) {
srv := &Server{
filemgr: filemgr.NewManager(),
}
b := New("local-grpc", "local-http", nil, nil)
t.Run("required", func(t *testing.T) {
li, err := srv.buildMainListener(&config.Config{Options: &config.Options{
li, err := b.buildMainListener(&config.Config{Options: &config.Options{
UseProxyProtocol: true,
InsecureServer: true,
}})
@ -691,7 +689,7 @@ func Test_requireProxyProtocol(t *testing.T) {
]`, li.GetListenerFilters())
})
t.Run("not required", func(t *testing.T) {
li, err := srv.buildMainListener(&config.Config{Options: &config.Options{
li, err := b.buildMainListener(&config.Config{Options: &config.Options{
UseProxyProtocol: false,
InsecureServer: true,
}})

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"embed"

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"encoding/json"

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"encoding/json"
@ -25,7 +25,7 @@ const (
httpCluster = "pomerium-control-plane-http"
)
func (srv *Server) buildGRPCRoutes() ([]*envoy_config_route_v3.Route, error) {
func (b *Builder) buildGRPCRoutes() ([]*envoy_config_route_v3.Route, error) {
action := &envoy_config_route_v3.Route_Route{
Route: &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
@ -48,7 +48,7 @@ func (srv *Server) buildGRPCRoutes() ([]*envoy_config_route_v3.Route, error) {
}}, nil
}
func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain string) ([]*envoy_config_route_v3.Route, error) {
func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, domain string) ([]*envoy_config_route_v3.Route, error) {
var routes []*envoy_config_route_v3.Route
// if this is the pomerium proxy in front of the the authenticate service, don't add
@ -59,46 +59,46 @@ func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain strin
}
if !isFrontingAuthenticate {
// enable ext_authz
r, err := srv.buildControlPlanePathRoute("/.pomerium/jwt", true)
r, err := b.buildControlPlanePathRoute("/.pomerium/jwt", true)
if err != nil {
return nil, err
}
routes = append(routes, r)
// disable ext_authz and passthrough to proxy handlers
r, err = srv.buildControlPlanePathRoute("/ping", false)
r, err = b.buildControlPlanePathRoute("/ping", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/healthz", false)
r, err = b.buildControlPlanePathRoute("/healthz", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/.pomerium", false)
r, err = b.buildControlPlanePathRoute("/.pomerium", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePrefixRoute("/.pomerium/", false)
r, err = b.buildControlPlanePrefixRoute("/.pomerium/", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/.well-known/pomerium", false)
r, err = b.buildControlPlanePathRoute("/.well-known/pomerium", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePrefixRoute("/.well-known/pomerium/", false)
r, err = b.buildControlPlanePrefixRoute("/.well-known/pomerium/", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
// per #837, only add robots.txt if there are no unauthenticated routes
if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: domain, Path: "/robots.txt"}) {
r, err := srv.buildControlPlanePathRoute("/robots.txt", false)
r, err := b.buildControlPlanePathRoute("/robots.txt", false)
if err != nil {
return nil, err
}
@ -111,7 +111,7 @@ func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain strin
return nil, err
}
if config.IsAuthenticate(options.Services) && hostMatchesDomain(authenticateURL, domain) {
r, err := srv.buildControlPlanePathRoute(options.AuthenticateCallbackPath, false)
r, err := b.buildControlPlanePathRoute(options.AuthenticateCallbackPath, false)
if err != nil {
return nil, err
}
@ -124,17 +124,17 @@ func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain strin
}
if config.IsProxy(options.Services) && hostMatchesDomain(forwardAuthURL, domain) {
// disable ext_authz and pass request to proxy handlers that enable authN flow
r, err := srv.buildControlPlanePathAndQueryRoute("/verify", []string{urlutil.QueryForwardAuthURI, urlutil.QuerySessionEncrypted, urlutil.QueryRedirectURI})
r, err := b.buildControlPlanePathAndQueryRoute("/verify", []string{urlutil.QueryForwardAuthURI, urlutil.QuerySessionEncrypted, urlutil.QueryRedirectURI})
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathAndQueryRoute("/", []string{urlutil.QueryForwardAuthURI, urlutil.QuerySessionEncrypted, urlutil.QueryRedirectURI})
r, err = b.buildControlPlanePathAndQueryRoute("/", []string{urlutil.QueryForwardAuthURI, urlutil.QuerySessionEncrypted, urlutil.QueryRedirectURI})
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathAndQueryRoute("/", []string{urlutil.QueryForwardAuthURI})
r, err = b.buildControlPlanePathAndQueryRoute("/", []string{urlutil.QueryForwardAuthURI})
if err != nil {
return nil, err
}
@ -143,7 +143,7 @@ func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain strin
// otherwise, enforce ext_authz; pass all other requests through to an upstream
// handler that will simply respond with http status 200 / OK indicating that
// the fronting forward-auth proxy can continue.
r, err = srv.buildControlPlaneProtectedPrefixRoute("/")
r, err = b.buildControlPlaneProtectedPrefixRoute("/")
if err != nil {
return nil, err
}
@ -152,7 +152,7 @@ func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain strin
return routes, nil
}
func (srv *Server) buildControlPlaneProtectedPrefixRoute(prefix string) (*envoy_config_route_v3.Route, error) {
func (b *Builder) buildControlPlaneProtectedPrefixRoute(prefix string) (*envoy_config_route_v3.Route, error) {
return &envoy_config_route_v3.Route{
Name: "pomerium-protected-prefix-" + prefix,
Match: &envoy_config_route_v3.RouteMatch{
@ -168,7 +168,7 @@ func (srv *Server) buildControlPlaneProtectedPrefixRoute(prefix string) (*envoy_
}, nil
}
func (srv *Server) buildControlPlanePathAndQueryRoute(path string, queryparams []string) (*envoy_config_route_v3.Route, error) {
func (b *Builder) buildControlPlanePathAndQueryRoute(path string, queryparams []string) (*envoy_config_route_v3.Route, error) {
var queryParameterMatchers []*envoy_config_route_v3.QueryParameterMatcher
for _, q := range queryparams {
queryParameterMatchers = append(queryParameterMatchers,
@ -197,7 +197,7 @@ func (srv *Server) buildControlPlanePathAndQueryRoute(path string, queryparams [
}, nil
}
func (srv *Server) buildControlPlanePathRoute(path string, protected bool) (*envoy_config_route_v3.Route, error) {
func (b *Builder) buildControlPlanePathRoute(path string, protected bool) (*envoy_config_route_v3.Route, error) {
r := &envoy_config_route_v3.Route{
Name: "pomerium-path-" + path,
Match: &envoy_config_route_v3.RouteMatch{
@ -219,7 +219,7 @@ func (srv *Server) buildControlPlanePathRoute(path string, protected bool) (*env
return r, nil
}
func (srv *Server) buildControlPlanePrefixRoute(prefix string, protected bool) (*envoy_config_route_v3.Route, error) {
func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) (*envoy_config_route_v3.Route, error) {
r := &envoy_config_route_v3.Route{
Name: "pomerium-prefix-" + prefix,
Match: &envoy_config_route_v3.RouteMatch{
@ -260,7 +260,7 @@ func getClusterStatsName(policy *config.Policy) string {
return ""
}
func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) ([]*envoy_config_route_v3.Route, error) {
func (b *Builder) buildPolicyRoutes(options *config.Options, domain string) ([]*envoy_config_route_v3.Route, error) {
var routes []*envoy_config_route_v3.Route
for i, p := range options.GetAllPolicies() {
@ -278,13 +278,13 @@ func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) ([]
RequestHeadersToRemove: getRequestHeadersToRemove(options, &policy),
}
if policy.Redirect != nil {
action, err := srv.buildPolicyRouteRedirectAction(policy.Redirect)
action, err := b.buildPolicyRouteRedirectAction(policy.Redirect)
if err != nil {
return nil, err
}
envoyRoute.Action = &envoy_config_route_v3.Route_Redirect{Redirect: action}
} else {
action, err := srv.buildPolicyRouteRouteAction(options, &policy)
action, err := b.buildPolicyRouteRouteAction(options, &policy)
if err != nil {
return nil, err
}
@ -324,7 +324,7 @@ func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) ([]
if policy.IsForKubernetes() {
policyID, _ := policy.RouteID()
for _, hdr := range srv.reproxy.GetPolicyIDHeaders(policyID) {
for _, hdr := range b.reproxy.GetPolicyIDHeaders(policyID) {
envoyRoute.RequestHeadersToAdd = append(envoyRoute.RequestHeadersToAdd,
&envoy_config_core_v3.HeaderValueOption{
Header: &envoy_config_core_v3.HeaderValue{
@ -345,7 +345,7 @@ func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) ([]
return routes, nil
}
func (srv *Server) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*envoy_config_route_v3.RedirectAction, error) {
func (b *Builder) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*envoy_config_route_v3.RedirectAction, error) {
action := &envoy_config_route_v3.RedirectAction{}
switch {
case r.HTTPSRedirect != nil:
@ -382,7 +382,7 @@ func (srv *Server) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*en
return action, nil
}
func (srv *Server) buildPolicyRouteRouteAction(options *config.Options, policy *config.Policy) (*envoy_config_route_v3.RouteAction, error) {
func (b *Builder) buildPolicyRouteRouteAction(options *config.Options, policy *config.Policy) (*envoy_config_route_v3.RouteAction, error) {
clusterName := getClusterID(policy)
// kubernetes requests are sent to the http control plane to be reproxied
if policy.IsForKubernetes() {

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"fmt"
@ -14,7 +14,7 @@ import (
"google.golang.org/protobuf/proto"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/controlplane/filemgr"
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
"github.com/pomerium/pomerium/internal/testutil"
)
@ -27,8 +27,8 @@ func policyNameFunc() func(*config.Policy) string {
}
func Test_buildGRPCRoutes(t *testing.T) {
srv := &Server{filemgr: filemgr.NewManager()}
routes, err := srv.buildGRPCRoutes()
b := &Builder{filemgr: filemgr.NewManager()}
routes, err := b.buildGRPCRoutes()
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `
[
@ -53,7 +53,7 @@ func Test_buildGRPCRoutes(t *testing.T) {
}
func Test_buildPomeriumHTTPRoutes(t *testing.T) {
srv := &Server{filemgr: filemgr.NewManager()}
b := &Builder{filemgr: filemgr.NewManager()}
routeString := func(typ, name string, protected bool) string {
str := `{
"name": "pomerium-` + typ + `-` + name + `",
@ -84,7 +84,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
AuthenticateCallbackPath: "/oauth2/callback",
ForwardAuthURLString: "https://forward-auth.example.com",
}
routes, err := srv.buildPomeriumHTTPRoutes(options, "authenticate.example.com")
routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com")
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `[
@ -105,7 +105,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
AuthenticateURLString: "https://authenticate.example.com",
AuthenticateCallbackPath: "/oauth2/callback",
}
routes, err := srv.buildPomeriumHTTPRoutes(options, "authenticate.example.com")
routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com")
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, "null", routes)
})
@ -122,7 +122,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
}},
}
_ = options.Policies[0].Validate()
routes, err := srv.buildPomeriumHTTPRoutes(options, "from.example.com")
routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com")
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `[
@ -150,7 +150,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
}},
}
_ = options.Policies[0].Validate()
routes, err := srv.buildPomeriumHTTPRoutes(options, "from.example.com")
routes, err := b.buildPomeriumHTTPRoutes(options, "from.example.com")
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `[
@ -166,8 +166,8 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
}
func Test_buildControlPlanePathRoute(t *testing.T) {
srv := &Server{filemgr: filemgr.NewManager()}
route, err := srv.buildControlPlanePathRoute("/hello/world", false)
b := &Builder{filemgr: filemgr.NewManager()}
route, err := b.buildControlPlanePathRoute("/hello/world", false)
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `
{
@ -189,8 +189,8 @@ func Test_buildControlPlanePathRoute(t *testing.T) {
}
func Test_buildControlPlanePrefixRoute(t *testing.T) {
srv := &Server{filemgr: filemgr.NewManager()}
route, err := srv.buildControlPlanePrefixRoute("/hello/world/", false)
b := &Builder{filemgr: filemgr.NewManager()}
route, err := b.buildControlPlanePrefixRoute("/hello/world/", false)
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `
{
@ -217,8 +217,8 @@ func Test_buildPolicyRoutes(t *testing.T) {
}(getClusterID)
getClusterID = policyNameFunc()
srv := &Server{filemgr: filemgr.NewManager()}
routes, err := srv.buildPolicyRoutes(&config.Options{
b := &Builder{filemgr: filemgr.NewManager()}
routes, err := b.buildPolicyRoutes(&config.Options{
CookieName: "pomerium",
DefaultUpstreamTimeout: time.Second * 3,
Policies: []config.Policy{
@ -535,7 +535,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
`, routes)
t.Run("fronting-authenticate", func(t *testing.T) {
routes, err := srv.buildPolicyRoutes(&config.Options{
routes, err := b.buildPolicyRoutes(&config.Options{
AuthenticateURLString: "https://authenticate.example.com",
Services: "proxy",
CookieName: "pomerium",
@ -588,7 +588,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
})
t.Run("tcp", func(t *testing.T) {
routes, err := srv.buildPolicyRoutes(&config.Options{
routes, err := b.buildPolicyRoutes(&config.Options{
CookieName: "pomerium",
DefaultUpstreamTimeout: time.Second * 3,
Policies: []config.Policy{
@ -679,8 +679,8 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
getClusterID = f
}(getClusterID)
getClusterID = policyNameFunc()
srv := &Server{filemgr: filemgr.NewManager()}
routes, err := srv.buildPolicyRoutes(&config.Options{
b := &Builder{filemgr: filemgr.NewManager()}
routes, err := b.buildPolicyRoutes(&config.Options{
CookieName: "pomerium",
DefaultUpstreamTimeout: time.Second * 3,
Policies: []config.Policy{
@ -924,9 +924,9 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
}
func Test_buildPolicyRouteRedirectAction(t *testing.T) {
srv := &Server{filemgr: filemgr.NewManager()}
b := &Builder{filemgr: filemgr.NewManager()}
t.Run("HTTPSRedirect", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
HTTPSRedirect: proto.Bool(true),
})
require.NoError(t, err)
@ -936,7 +936,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
},
}, action)
action, err = srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err = b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
HTTPSRedirect: proto.Bool(false),
})
require.NoError(t, err)
@ -947,7 +947,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
}, action)
})
t.Run("SchemeRedirect", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
SchemeRedirect: proto.String("https"),
})
require.NoError(t, err)
@ -958,7 +958,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
}, action)
})
t.Run("HostRedirect", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
HostRedirect: proto.String("HOST"),
})
require.NoError(t, err)
@ -967,7 +967,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
}, action)
})
t.Run("PortRedirect", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
PortRedirect: proto.Uint32(1234),
})
require.NoError(t, err)
@ -976,7 +976,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
}, action)
})
t.Run("PathRedirect", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
PathRedirect: proto.String("PATH"),
})
require.NoError(t, err)
@ -987,7 +987,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
}, action)
})
t.Run("PrefixRewrite", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
PrefixRewrite: proto.String("PREFIX_REWRITE"),
})
require.NoError(t, err)
@ -998,7 +998,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
}, action)
})
t.Run("ResponseCode", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
ResponseCode: proto.Int32(301),
})
require.NoError(t, err)
@ -1007,7 +1007,7 @@ func Test_buildPolicyRouteRedirectAction(t *testing.T) {
}, action)
})
t.Run("StripQuery", func(t *testing.T) {
action, err := srv.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
action, err := b.buildPolicyRouteRedirectAction(&config.PolicyRedirect{
StripQuery: proto.Bool(true),
})
require.NoError(t, err)

View file

@ -1,4 +1,4 @@
package controlplane
package envoyconfig
import (
"fmt"
@ -10,7 +10,7 @@ import (
"github.com/pomerium/pomerium/internal/telemetry/trace"
)
func (srv *Server) buildTracingProvider(options *config.Options) (*envoy_config_trace_v3.Tracing_Http, error) {
func (b *Builder) buildTracingProvider(options *config.Options) (*envoy_config_trace_v3.Tracing_Http, error) {
tracingOptions, err := config.NewTracingOptions(options)
if err != nil {
return nil, fmt.Errorf("invalid tracing config: %w", err)

View file

@ -1,23 +0,0 @@
package controlplane
import (
"errors"
"time"
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
"github.com/golang/protobuf/ptypes"
)
var (
errNoEndpoints = errors.New("cluster must have endpoints")
defaultConnectionTimeout = ptypes.DurationProto(time.Second * 10)
)
// newDefaultEnvoyClusterConfig creates envoy cluster with certain default values
func newDefaultEnvoyClusterConfig() *envoy_config_cluster_v3.Cluster {
return &envoy_config_cluster_v3.Cluster{
ConnectTimeout: defaultConnectionTimeout,
RespectDnsTtl: true,
DnsLookupFamily: envoy_config_cluster_v3.Cluster_AUTO,
}
}

View file

@ -15,7 +15,8 @@ import (
"google.golang.org/grpc/reflection"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/controlplane/filemgr"
"github.com/pomerium/pomerium/config/envoyconfig"
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
"github.com/pomerium/pomerium/internal/controlplane/xdsmgr"
"github.com/pomerium/pomerium/internal/httputil/reproxy"
"github.com/pomerium/pomerium/internal/log"
@ -55,6 +56,7 @@ type Server struct {
filemgr *filemgr.Manager
metricsMgr *config.MetricsManager
reproxy *reproxy.Handler
builder *envoyconfig.Builder
}
// NewServer creates a new Server. Listener ports are chosen by the OS.
@ -94,6 +96,16 @@ func NewServer(name string, metricsMgr *config.MetricsManager) (*Server, error)
srv.HTTPRouter = mux.NewRouter()
srv.addHTTPMiddleware()
srv.filemgr = filemgr.NewManager()
srv.filemgr.ClearCache()
srv.builder = envoyconfig.New(
srv.GRPCListener.Addr().String(),
srv.HTTPListener.Addr().String(),
srv.filemgr,
srv.reproxy,
)
res, err := srv.buildDiscoveryResources()
if err != nil {
return nil, err
@ -102,9 +114,6 @@ func NewServer(name string, metricsMgr *config.MetricsManager) (*Server, error)
srv.xdsmgr = xdsmgr.NewManager(res)
envoy_service_discovery_v3.RegisterAggregatedDiscoveryServiceServer(srv.GRPCServer, srv.xdsmgr)
srv.filemgr = filemgr.NewManager()
srv.filemgr.ClearCache()
return srv, nil
}

View file

@ -1,28 +1,11 @@
package controlplane
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"net"
"os"
"strconv"
"sync"
envoy_config_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_extensions_access_loggers_grpc_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3"
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"golang.org/x/net/nettest"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/pkg/cryptutil"
)
@ -35,7 +18,7 @@ func (srv *Server) buildDiscoveryResources() (map[string][]*envoy_service_discov
resources := map[string][]*envoy_service_discovery_v3.Resource{}
cfg := srv.currentConfig.Load()
clusters, err := srv.buildClusters(cfg.Options)
clusters, err := srv.builder.BuildClusters(cfg.Config)
if err != nil {
return nil, err
}
@ -48,7 +31,7 @@ func (srv *Server) buildDiscoveryResources() (map[string][]*envoy_service_discov
})
}
listeners, err := srv.buildListeners(cfg.Config)
listeners, err := srv.builder.BuildListeners(cfg.Config)
if err != nil {
return nil, err
}
@ -62,138 +45,3 @@ func (srv *Server) buildDiscoveryResources() (map[string][]*envoy_service_discov
}
return resources, nil
}
func buildAccessLogs(options *config.Options) []*envoy_config_accesslog_v3.AccessLog {
lvl := options.ProxyLogLevel
if lvl == "" {
lvl = options.LogLevel
}
if lvl == "" {
lvl = "debug"
}
switch lvl {
case "trace", "debug", "info":
default:
// don't log access requests for levels > info
return nil
}
tc := marshalAny(&envoy_extensions_access_loggers_grpc_v3.HttpGrpcAccessLogConfig{
CommonConfig: &envoy_extensions_access_loggers_grpc_v3.CommonGrpcAccessLogConfig{
LogName: "ingress-http",
GrpcService: &envoy_config_core_v3.GrpcService{
TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{
ClusterName: "pomerium-control-plane-grpc",
},
},
},
TransportApiVersion: envoy_config_core_v3.ApiVersion_V3,
},
})
return []*envoy_config_accesslog_v3.AccessLog{{
Name: "envoy.access_loggers.http_grpc",
ConfigType: &envoy_config_accesslog_v3.AccessLog_TypedConfig{TypedConfig: tc},
}}
}
func buildAddress(hostport string, defaultPort int) *envoy_config_core_v3.Address {
host, strport, err := net.SplitHostPort(hostport)
if err != nil {
host = hostport
strport = fmt.Sprint(defaultPort)
}
port, err := strconv.Atoi(strport)
if err != nil {
port = defaultPort
}
if host == "" {
if nettest.SupportsIPv6() {
host = "::"
} else {
host = "0.0.0.0"
}
}
return &envoy_config_core_v3.Address{
Address: &envoy_config_core_v3.Address_SocketAddress{SocketAddress: &envoy_config_core_v3.SocketAddress{
Address: host,
PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{PortValue: uint32(port)},
Ipv4Compat: true,
}},
}
}
func (srv *Server) envoyTLSCertificateFromGoTLSCertificate(cert *tls.Certificate) *envoy_extensions_transport_sockets_tls_v3.TlsCertificate {
envoyCert := &envoy_extensions_transport_sockets_tls_v3.TlsCertificate{}
var chain bytes.Buffer
for _, cbs := range cert.Certificate {
_ = pem.Encode(&chain, &pem.Block{
Type: "CERTIFICATE",
Bytes: cbs,
})
}
envoyCert.CertificateChain = srv.filemgr.BytesDataSource("tls-crt.pem", chain.Bytes())
if cert.OCSPStaple != nil {
envoyCert.OcspStaple = srv.filemgr.BytesDataSource("ocsp-staple", cert.OCSPStaple)
}
if bs, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey); err == nil {
envoyCert.PrivateKey = srv.filemgr.BytesDataSource("tls-key.pem", pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: bs,
},
))
} else {
log.Warn().Err(err).Msg("failed to marshal private key for tls config")
}
for _, scts := range cert.SignedCertificateTimestamps {
envoyCert.SignedCertificateTimestamp = append(envoyCert.SignedCertificateTimestamp,
srv.filemgr.BytesDataSource("signed-certificate-timestamp", scts))
}
return envoyCert
}
var rootCABundle struct {
sync.Once
value string
}
func getRootCertificateAuthority() (string, error) {
rootCABundle.Do(func() {
// from https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ssl#arch-overview-ssl-enabling-verification
knownRootLocations := []string{
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem",
"/usr/local/etc/ssl/cert.pem",
"/etc/ssl/cert.pem",
}
for _, path := range knownRootLocations {
if _, err := os.Stat(path); err == nil {
rootCABundle.value = path
break
}
}
if rootCABundle.value == "" {
log.Error().Strs("known-locations", knownRootLocations).
Msgf("no root certificates were found in any of the known locations")
} else {
log.Info().Msgf("using %s as the system root certificate authority bundle", rootCABundle.value)
}
})
if rootCABundle.value == "" {
return "", fmt.Errorf("root certificates not found")
}
return rootCABundle.value, nil
}
func marshalAny(msg proto.Message) *anypb.Any {
any := new(anypb.Any)
_ = anypb.MarshalFrom(any, msg, proto.MarshalOptions{
AllowPartial: true,
Deterministic: true,
})
return any
}