envoy: Initial changes

This commit is contained in:
Travis Groth 2020-05-18 16:34:31 -04:00
parent 8f78497e99
commit 99e788a9b4
107 changed files with 2542 additions and 3322 deletions

View file

@ -0,0 +1,39 @@
package controlplane
import (
envoy_service_accesslog_v2 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v2"
"github.com/pomerium/pomerium/internal/log"
)
func (srv *Server) registerAccessLogHandlers() {
envoy_service_accesslog_v2.RegisterAccessLogServiceServer(srv.GRPCServer, srv)
}
// StreamAccessLogs receives logs from envoy and prints them to stdout.
func (srv *Server) StreamAccessLogs(stream envoy_service_accesslog_v2.AccessLogService_StreamAccessLogsServer) error {
for {
msg, err := stream.Recv()
if err != nil {
log.Error().Err(err).Msg("access log stream error, disconnecting")
return err
}
for _, entry := range msg.GetHttpLogs().LogEntry {
evt := log.Info().Str("service", "envoy")
// common properties
evt = evt.Str("upstream-cluster", entry.GetCommonProperties().GetUpstreamCluster())
// request properties
evt = evt.Str("method", entry.GetRequest().GetRequestMethod().String())
evt = evt.Str("authority", entry.GetRequest().GetAuthority())
evt = evt.Str("path", entry.GetRequest().GetPath())
evt = evt.Str("user-agent", entry.GetRequest().GetUserAgent())
evt = evt.Str("referer", entry.GetRequest().GetReferer())
evt = evt.Str("forwarded-for", entry.GetRequest().GetForwardedFor())
evt = evt.Str("request-id", entry.GetRequest().GetRequestId())
// response properties
evt = evt.Uint32("response-code", entry.GetResponse().GetResponseCode().GetValue())
evt = evt.Str("response-code-details", entry.GetResponse().GetResponseCodeDetails())
evt.Msg("http-request")
}
}
}

View file

@ -0,0 +1,135 @@
package controlplane
import (
"context"
"encoding/json"
"errors"
"fmt"
envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"github.com/pomerium/pomerium/internal/log"
"golang.org/x/sync/errgroup"
)
func (srv *Server) registerXDSHandlers() {
envoy_service_discovery_v3.RegisterAggregatedDiscoveryServiceServer(srv.GRPCServer, srv)
}
// StreamAggregatedResources streams xDS resources based on incoming discovery requests.
//
// This is setup as 3 concurrent goroutines:
// - The first retrieves the requests from the client.
// - The third sends responses back to the client.
// - The second waits for either the client to request a new resource type
// or for the config to have been updated
// - in either case, we loop over all of the current client versions
// and if any of them are different from the current version, we send
// the updated resource
func (srv *Server) StreamAggregatedResources(stream envoy_service_discovery_v3.AggregatedDiscoveryService_StreamAggregatedResourcesServer) error {
incoming := make(chan *envoy_service_discovery_v3.DiscoveryRequest)
outgoing := make(chan *envoy_service_discovery_v3.DiscoveryResponse)
eg, ctx := errgroup.WithContext(stream.Context())
// receive requests
eg.Go(func() error {
return srv.streamAggregatedResourcesIncomingStep(ctx, stream, incoming)
})
eg.Go(func() error {
return srv.streamAggregatedResourcesProcessStep(ctx, incoming, outgoing)
})
// send responses
eg.Go(func() error {
return srv.streamAggregatedResourcesOutgoingStep(ctx, stream, outgoing)
})
return eg.Wait()
}
func (srv *Server) streamAggregatedResourcesIncomingStep(
ctx context.Context,
stream envoy_service_discovery_v3.AggregatedDiscoveryService_StreamAggregatedResourcesServer,
incoming chan<- *envoy_service_discovery_v3.DiscoveryRequest,
) error {
for {
req, err := stream.Recv()
if err != nil {
return err
}
select {
case incoming <- req:
case <-ctx.Done():
return ctx.Err()
}
}
}
func (srv *Server) streamAggregatedResourcesProcessStep(
ctx context.Context,
incoming <-chan *envoy_service_discovery_v3.DiscoveryRequest,
outgoing chan<- *envoy_service_discovery_v3.DiscoveryResponse,
) error {
versions := map[string]string{}
for {
select {
case req := <-incoming:
if req.ErrorDetail != nil {
bs, _ := json.Marshal(req.ErrorDetail.Details)
log.Error().
Err(errors.New(req.ErrorDetail.Message)).
Int32("code", req.ErrorDetail.Code).
RawJSON("details", bs).Msg("error applying configuration")
continue
}
// update the currently stored version
// if this version is different from the current version
// we will send the response below
versions[req.TypeUrl] = req.VersionInfo
case <-srv.configUpdated:
case <-ctx.Done():
return ctx.Err()
}
current := srv.currentConfig.Load().(versionedOptions)
for typeURL, version := range versions {
// the versions are different, so the envoy config needs to be updated
if version != fmt.Sprint(current.version) {
res, err := srv.buildDiscoveryResponse(fmt.Sprint(current.version), typeURL, current.Options)
if err != nil {
return err
}
select {
case outgoing <- res:
case <-ctx.Done():
return ctx.Err()
}
}
}
}
}
func (srv *Server) streamAggregatedResourcesOutgoingStep(
ctx context.Context,
stream envoy_service_discovery_v3.AggregatedDiscoveryService_StreamAggregatedResourcesServer,
outgoing <-chan *envoy_service_discovery_v3.DiscoveryResponse,
) error {
for {
var res *envoy_service_discovery_v3.DiscoveryResponse
select {
case res = <-outgoing:
case <-ctx.Done():
return ctx.Err()
}
err := stream.Send(res)
if err != nil {
return err
}
}
}
// DeltaAggregatedResources is not implemented.
func (srv *Server) DeltaAggregatedResources(in envoy_service_discovery_v3.AggregatedDiscoveryService_DeltaAggregatedResourcesServer) error {
return fmt.Errorf("DeltaAggregatedResources not implemented")
}

View file

@ -0,0 +1,39 @@
// Package controlplane contains the HTTP and gRPC base servers and the xDS gRPC implementation for envoy.
package controlplane
import (
"net/http"
"time"
"github.com/gorilla/handlers"
"github.com/pomerium/pomerium/internal/frontend"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/middleware"
"github.com/pomerium/pomerium/internal/version"
)
func (srv *Server) addHTTPMiddleware() {
root := srv.HTTPRouter
root.Use(log.NewHandler(log.Logger))
root.Use(log.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
log.FromRequest(r).Debug().
Dur("duration", duration).
Int("size", size).
Int("status", status).
Str("method", r.Method).
Str("host", r.Host).
Str("path", r.URL.String()).
Msg("http-request")
}))
root.Use(handlers.RecoveryHandler())
root.Use(log.HeadersHandler(httputil.HeadersXForwarded))
root.Use(log.RemoteAddrHandler("ip"))
root.Use(log.UserAgentHandler("user_agent"))
root.Use(log.RefererHandler("referer"))
root.Use(log.RequestIDHandler("req_id", "Request-Id"))
root.Use(middleware.Healthcheck("/ping", version.UserAgent()))
root.HandleFunc("/healthz", httputil.HealthCheck)
root.HandleFunc("/ping", httputil.HealthCheck)
root.PathPrefix("/.pomerium/assets/").Handler(http.StripPrefix("/.pomerium/assets/", frontend.MustAssetHandler()))
}

View file

@ -0,0 +1,139 @@
package controlplane
import (
"context"
"net"
"net/http"
"sync/atomic"
"time"
"github.com/gorilla/mux"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
)
type versionedOptions struct {
config.Options
version int64
}
// A Server is the control-plane gRPC and HTTP servers.
type Server struct {
GRPCListener net.Listener
GRPCServer *grpc.Server
HTTPListener net.Listener
HTTPRouter *mux.Router
currentConfig atomic.Value
configUpdated chan struct{}
}
// NewServer creates a new Server. Listener ports are chosen by the OS.
func NewServer() (*Server, error) {
srv := &Server{
configUpdated: make(chan struct{}, 1),
}
srv.currentConfig.Store(versionedOptions{})
var err error
// setup gRPC
srv.GRPCListener, err = net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
return nil, err
}
srv.GRPCServer = grpc.NewServer()
reflection.Register(srv.GRPCServer)
srv.registerXDSHandlers()
srv.registerAccessLogHandlers()
// setup HTTP
srv.HTTPListener, err = net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
_ = srv.GRPCListener.Close()
return nil, err
}
srv.HTTPRouter = mux.NewRouter()
srv.addHTTPMiddleware()
return srv, nil
}
// Run runs the control-plane gRPC and HTTP servers.
func (srv *Server) Run(ctx context.Context) error {
eg, ctx := errgroup.WithContext(ctx)
// start the gRPC server
eg.Go(func() error {
log.Info().Str("addr", srv.GRPCListener.Addr().String()).Msg("starting control-plane gRPC server")
return srv.GRPCServer.Serve(srv.GRPCListener)
})
// gracefully stop the gRPC server on context cancellation
eg.Go(func() error {
<-ctx.Done()
ctx, cancel := context.WithCancel(ctx)
ctx, cleanup := context.WithTimeout(ctx, time.Second*5)
defer cleanup()
go func() {
srv.GRPCServer.GracefulStop()
cancel()
}()
go func() {
<-ctx.Done()
srv.GRPCServer.Stop()
cancel()
}()
<-ctx.Done()
return nil
})
hsrv := (&http.Server{
BaseContext: func(li net.Listener) context.Context {
return ctx
},
Handler: srv.HTTPRouter,
})
// start the HTTP server
eg.Go(func() error {
log.Info().Str("addr", srv.HTTPListener.Addr().String()).Msg("starting control-plane HTTP server")
return hsrv.Serve(srv.HTTPListener)
})
// gracefully stop the HTTP server on context cancellation
eg.Go(func() error {
<-ctx.Done()
ctx, cleanup := context.WithTimeout(ctx, time.Second*5)
defer cleanup()
return hsrv.Shutdown(ctx)
})
return eg.Wait()
}
// UpdateOptions updates the pomerium config options.
func (srv *Server) UpdateOptions(options config.Options) error {
select {
case <-srv.configUpdated:
default:
}
prev := srv.currentConfig.Load().(versionedOptions)
srv.currentConfig.Store(versionedOptions{
Options: options,
version: prev.version + 1,
})
srv.configUpdated <- struct{}{}
return nil
}

View file

@ -0,0 +1,105 @@
package controlplane
import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"github.com/pomerium/pomerium/config"
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_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (srv *Server) buildDiscoveryResponse(version string, typeURL string, options config.Options) (*envoy_service_discovery_v3.DiscoveryResponse, error) {
switch typeURL {
case "type.googleapis.com/envoy.config.listener.v3.Listener":
listeners := srv.buildListeners(options)
anys := make([]*any.Any, len(listeners))
for i, listener := range listeners {
a, err := ptypes.MarshalAny(listener)
if err != nil {
return nil, status.Errorf(codes.Internal, "error marshaling type to any: %v", err)
}
anys[i] = a
}
return &envoy_service_discovery_v3.DiscoveryResponse{
VersionInfo: version,
Resources: anys,
TypeUrl: typeURL,
}, nil
case "type.googleapis.com/envoy.config.cluster.v3.Cluster":
clusters := srv.buildClusters(options)
anys := make([]*any.Any, len(clusters))
for i, cluster := range clusters {
a, err := ptypes.MarshalAny(cluster)
if err != nil {
return nil, status.Errorf(codes.Internal, "error marshaling type to any: %v", err)
}
anys[i] = a
}
return &envoy_service_discovery_v3.DiscoveryResponse{
VersionInfo: version,
Resources: anys,
TypeUrl: typeURL,
}, nil
default:
return nil, status.Errorf(codes.Internal, "received request for unknown discovery request type: %s", typeURL)
}
}
func (srv *Server) buildAccessLog() *envoy_config_accesslog_v3.AccessLog {
tc, _ := ptypes.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",
},
},
},
},
})
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 == "" {
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)},
}},
}
}
func getAbsoluteFilePath(filename string) string {
if filepath.IsAbs(filename) {
return filename
}
wd, _ := os.Getwd()
return filepath.Join(wd, filename)
}

View file

@ -0,0 +1,105 @@
package controlplane
import (
"net"
"net/url"
"strings"
"time"
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_config_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
"github.com/golang/protobuf/ptypes"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/urlutil"
)
func (srv *Server) buildClusters(options config.Options) []*envoy_config_cluster_v3.Cluster {
grpcURL := &url.URL{
Scheme: "grpc",
Host: srv.GRPCListener.Addr().String(),
}
httpURL := &url.URL{
Scheme: "http",
Host: srv.HTTPListener.Addr().String(),
}
authzURL := &url.URL{
Scheme: strings.Replace(options.AuthorizeURL.Scheme, "http", "grpc", -1),
Host: options.AuthorizeURL.Host,
}
clusters := []*envoy_config_cluster_v3.Cluster{
srv.buildCluster("pomerium-control-plane-grpc", grpcURL),
srv.buildCluster("pomerium-control-plane-http", httpURL),
srv.buildCluster("pomerium-authz", authzURL),
}
if config.IsProxy(options.Services) {
type clusterDestination struct {
name, scheme, hostport string
}
clusterDestinations := map[clusterDestination]struct{}{}
for _, policy := range options.Policies {
name, scheme, hostport := srv.getClusterDetails(policy.Destination)
clusterDestinations[clusterDestination{name, scheme, hostport}] = struct{}{}
}
for dst := range clusterDestinations {
name, scheme, hostport := dst.name, dst.scheme, dst.hostport
clusters = append(clusters, srv.buildCluster(name, &url.URL{
Scheme: scheme,
Host: hostport,
}))
}
}
return clusters
}
func (srv *Server) getClusterDetails(endpoint *url.URL) (name, scheme, hostport string) {
name = endpoint.Scheme + "-" + strings.Replace(endpoint.Host, ":", "--", -1)
return name, endpoint.Scheme, endpoint.Host
}
func (srv *Server) buildCluster(name string, endpoint *url.URL) *envoy_config_cluster_v3.Cluster {
defaultPort := 80
if endpoint.Scheme == "https" || endpoint.Scheme == "grpcs" {
defaultPort = 443
}
cluster := &envoy_config_cluster_v3.Cluster{
Name: name,
ConnectTimeout: ptypes.DurationProto(time.Second * 10),
LoadAssignment: &envoy_config_endpoint_v3.ClusterLoadAssignment{
ClusterName: name,
Endpoints: []*envoy_config_endpoint_v3.LocalityLbEndpoints{{
LbEndpoints: []*envoy_config_endpoint_v3.LbEndpoint{{
HostIdentifier: &envoy_config_endpoint_v3.LbEndpoint_Endpoint{
Endpoint: &envoy_config_endpoint_v3.Endpoint{
Address: buildAddress(endpoint.Host, defaultPort),
},
},
}},
}},
},
RespectDnsTtl: true,
}
if endpoint.Scheme == "grpc" {
cluster.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{}
}
if endpoint.Scheme == "https" || endpoint.Scheme == "grpcs" {
cluster.TransportSocket = &envoy_config_core_v3.TransportSocket{
Name: "tls",
}
}
if net.ParseIP(urlutil.StripPort(endpoint.Host)) == nil {
cluster.ClusterDiscoveryType = &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_LOGICAL_DNS}
} else {
cluster.ClusterDiscoveryType = &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_STATIC}
}
return cluster
}

View file

@ -0,0 +1,269 @@
package controlplane
import (
"encoding/base64"
"sort"
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_config_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_extensions_filters_http_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/urlutil"
)
var disableExtAuthz *any.Any
func init() {
disableExtAuthz, _ = ptypes.MarshalAny(&envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute{
Override: &envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute_Disabled{
Disabled: true,
},
})
}
func (srv *Server) buildListeners(options config.Options) []*envoy_config_listener_v3.Listener {
var listeners []*envoy_config_listener_v3.Listener
if config.IsAuthenticate(options.Services) || config.IsProxy(options.Services) {
listeners = append(listeners, srv.buildHTTPListener(options))
}
if config.IsAuthorize(options.Services) || config.IsCache(options.Services) {
listeners = append(listeners, srv.buildGRPCListener(options))
}
return listeners
}
func (srv *Server) buildHTTPListener(options config.Options) *envoy_config_listener_v3.Listener {
defaultPort := 80
var transportSocket *envoy_config_core_v3.TransportSocket
if !options.InsecureServer {
defaultPort = 443
tlsConfig, _ := ptypes.MarshalAny(srv.buildDownstreamTLSContext(options))
transportSocket = &envoy_config_core_v3.TransportSocket{
Name: "tls",
ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{
TypedConfig: tlsConfig,
},
}
}
var virtualHosts []*envoy_config_route_v3.VirtualHost
for _, domain := range srv.getAllRouteableDomains(options, options.Addr) {
vh := &envoy_config_route_v3.VirtualHost{
Name: domain,
Domains: []string{domain},
}
if options.Addr == options.GRPCAddr {
// if this is a gRPC service domain and we're supposed to handle that, add those routes
if (config.IsAuthorize(options.Services) && domain == urlutil.StripPort(options.AuthorizeURL.Host)) ||
(config.IsCache(options.Services) && domain == urlutil.StripPort(options.CacheURL.Host)) {
vh.Routes = append(vh.Routes, srv.buildGRPCRoutes()...)
}
}
// these routes match /.pomerium/... and similar paths
vh.Routes = append(vh.Routes, srv.buildPomeriumHTTPRoutes(options, domain)...)
// if we're the proxy, add all the policy routes
if config.IsProxy(options.Services) {
vh.Routes = append(vh.Routes, srv.buildPolicyRoutes(options, domain)...)
}
if len(vh.Routes) > 0 {
virtualHosts = append(virtualHosts, vh)
}
}
extAuthZ, _ := ptypes.MarshalAny(&envoy_extensions_filters_http_ext_authz_v3.ExtAuthz{
StatusOnError: &envoy_type_v3.HttpStatus{
Code: envoy_type_v3.StatusCode_InternalServerError,
},
Services: &envoy_extensions_filters_http_ext_authz_v3.ExtAuthz_GrpcService{
GrpcService: &envoy_config_core_v3.GrpcService{
TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{
ClusterName: "pomerium-authz",
},
},
},
},
})
tc, _ := ptypes.MarshalAny(&envoy_http_connection_manager.HttpConnectionManager{
CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO,
StatPrefix: "ingress",
RouteSpecifier: &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{
RouteConfig: &envoy_config_route_v3.RouteConfiguration{
Name: "main",
VirtualHosts: virtualHosts,
},
},
HttpFilters: []*envoy_http_connection_manager.HttpFilter{
{
Name: "envoy.filters.http.ext_authz",
ConfigType: &envoy_http_connection_manager.HttpFilter_TypedConfig{
TypedConfig: extAuthZ,
},
},
{
Name: "envoy.filters.http.router",
},
},
AccessLog: []*envoy_config_accesslog_v3.AccessLog{srv.buildAccessLog()},
})
li := &envoy_config_listener_v3.Listener{
Name: "http-ingress",
Address: buildAddress(options.Addr, defaultPort),
FilterChains: []*envoy_config_listener_v3.FilterChain{{
Filters: []*envoy_config_listener_v3.Filter{
{
Name: "envoy.filters.network.http_connection_manager",
ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{
TypedConfig: tc,
},
},
},
TransportSocket: transportSocket,
}},
}
return li
}
func (srv *Server) buildGRPCListener(options config.Options) *envoy_config_listener_v3.Listener {
defaultPort := 80
var transportSocket *envoy_config_core_v3.TransportSocket
if !options.GRPCInsecure {
defaultPort = 443
tlsConfig, _ := ptypes.MarshalAny(srv.buildDownstreamTLSContext(options))
transportSocket = &envoy_config_core_v3.TransportSocket{
Name: "tls",
ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{
TypedConfig: tlsConfig,
},
}
}
tc, _ := ptypes.MarshalAny(&envoy_http_connection_manager.HttpConnectionManager{
CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO,
StatPrefix: "grpc_ingress",
RouteSpecifier: &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{
RouteConfig: &envoy_config_route_v3.RouteConfiguration{
Name: "grpc",
VirtualHosts: []*envoy_config_route_v3.VirtualHost{{
Name: "grpc",
Domains: []string{"*"},
Routes: []*envoy_config_route_v3.Route{{
Name: "grpc",
Match: &envoy_config_route_v3.RouteMatch{
PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"},
Grpc: &envoy_config_route_v3.RouteMatch_GrpcRouteMatchOptions{},
},
Action: &envoy_config_route_v3.Route_Route{
Route: &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{Cluster: "pomerium-control-plane-grpc"},
},
},
}},
}},
},
},
HttpFilters: []*envoy_http_connection_manager.HttpFilter{{
Name: "envoy.filters.http.router",
}},
})
return &envoy_config_listener_v3.Listener{
Name: "grpc-ingress",
Address: buildAddress(options.GRPCAddr, defaultPort),
FilterChains: []*envoy_config_listener_v3.FilterChain{{
Filters: []*envoy_config_listener_v3.Filter{{
Name: "envoy.filters.network.http_connection_manager",
ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{
TypedConfig: tc,
},
}},
TransportSocket: transportSocket,
}},
}
}
func (srv *Server) buildDownstreamTLSContext(options config.Options) *envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext {
var cert envoy_extensions_transport_sockets_tls_v3.TlsCertificate
if options.Cert != "" {
bs, _ := base64.StdEncoding.DecodeString(options.Cert)
cert.CertificateChain = &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
InlineBytes: bs,
},
}
} else {
cert.CertificateChain = &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_Filename{
Filename: getAbsoluteFilePath(options.CertFile),
},
}
}
if options.Key != "" {
bs, _ := base64.StdEncoding.DecodeString(options.Key)
cert.PrivateKey = &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
InlineBytes: bs,
},
}
} else {
cert.PrivateKey = &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_Filename{
Filename: getAbsoluteFilePath(options.KeyFile),
},
}
}
return &envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext{
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
TlsCertificates: []*envoy_extensions_transport_sockets_tls_v3.TlsCertificate{
&cert,
},
AlpnProtocols: []string{"h2", "http/1.1"},
},
}
}
func (srv *Server) getAllRouteableDomains(options config.Options, addr string) []string {
lookup := map[string]struct{}{}
if config.IsAuthenticate(options.Services) && addr == options.Addr {
lookup[urlutil.StripPort(options.AuthenticateURL.Host)] = struct{}{}
}
if config.IsAuthorize(options.Services) && addr == options.GRPCAddr {
lookup[urlutil.StripPort(options.AuthorizeURL.Host)] = struct{}{}
}
if config.IsCache(options.Services) && addr == options.GRPCAddr {
lookup[urlutil.StripPort(options.CacheURL.Host)] = struct{}{}
}
if config.IsProxy(options.Services) && addr == options.Addr {
for _, policy := range options.Policies {
lookup[urlutil.StripPort(policy.Source.Host)] = struct{}{}
}
if options.ForwardAuthURL != nil {
lookup[urlutil.StripPort(options.ForwardAuthURL.Host)] = struct{}{}
}
}
domains := make([]string, 0, len(lookup))
for domain := range lookup {
domains = append(domains, domain)
}
sort.Strings(domains)
return domains
}

View file

@ -0,0 +1,136 @@
package controlplane
import (
"fmt"
envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"github.com/golang/protobuf/ptypes/any"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/urlutil"
)
func (srv *Server) buildGRPCRoutes() []*envoy_config_route_v3.Route {
action := &envoy_config_route_v3.Route_Route{
Route: &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
Cluster: "pomerium-control-plane-grpc",
},
},
}
return []*envoy_config_route_v3.Route{{
Name: "pomerium-grpc",
Match: &envoy_config_route_v3.RouteMatch{
PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{
Prefix: "/",
},
Grpc: &envoy_config_route_v3.RouteMatch_GrpcRouteMatchOptions{},
},
Action: action,
TypedPerFilterConfig: map[string]*any.Any{
"envoy.filters.http.ext_authz": disableExtAuthz,
},
}}
}
func (srv *Server) buildPomeriumHTTPRoutes(options config.Options, domain string) []*envoy_config_route_v3.Route {
routes := []*envoy_config_route_v3.Route{
srv.buildControlPlanePathRoute("/ping"),
srv.buildControlPlanePathRoute("/healthz"),
srv.buildControlPlanePathRoute("/.pomerium"),
srv.buildControlPlanePrefixRoute("/.pomerium/"),
}
// if we're handling authentication, add the oauth2 callback url
if config.IsAuthenticate(options.Services) && domain == urlutil.StripPort(options.AuthenticateURL.Host) {
routes = append(routes,
srv.buildControlPlanePathRoute(options.AuthenticateCallbackPath))
}
// if we're the proxy and this is the forward-auth url
if config.IsProxy(options.Services) && options.ForwardAuthURL != nil && domain == urlutil.StripPort(options.ForwardAuthURL.Host) {
routes = append(routes,
srv.buildControlPlanePrefixRoute("/"))
}
return routes
}
func (srv *Server) buildControlPlanePathRoute(path string) *envoy_config_route_v3.Route {
return &envoy_config_route_v3.Route{
Name: "pomerium-path-" + path,
Match: &envoy_config_route_v3.RouteMatch{
PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{Path: path},
},
Action: &envoy_config_route_v3.Route_Route{
Route: &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
Cluster: "pomerium-control-plane-http",
},
},
},
TypedPerFilterConfig: map[string]*any.Any{
"envoy.filters.http.ext_authz": disableExtAuthz,
},
}
}
func (srv *Server) buildControlPlanePrefixRoute(prefix string) *envoy_config_route_v3.Route {
return &envoy_config_route_v3.Route{
Name: "pomerium-prefix-" + prefix,
Match: &envoy_config_route_v3.RouteMatch{
PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: prefix},
},
Action: &envoy_config_route_v3.Route_Route{
Route: &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
Cluster: "pomerium-control-plane-http",
},
},
},
TypedPerFilterConfig: map[string]*any.Any{
"envoy.filters.http.ext_authz": disableExtAuthz,
},
}
}
func (srv *Server) buildPolicyRoutes(options config.Options, domain string) []*envoy_config_route_v3.Route {
var routes []*envoy_config_route_v3.Route
for i, policy := range options.Policies {
if policy.Source.Hostname() != domain {
continue
}
match := &envoy_config_route_v3.RouteMatch{}
switch {
case policy.Regex != "":
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_SafeRegex{
SafeRegex: &envoy_type_matcher_v3.RegexMatcher{
EngineType: &envoy_type_matcher_v3.RegexMatcher_GoogleRe2{
GoogleRe2: &envoy_type_matcher_v3.RegexMatcher_GoogleRE2{},
},
Regex: policy.Regex,
},
}
case policy.Path != "":
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Path{Path: policy.Path}
case policy.Prefix != "":
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: policy.Prefix}
default:
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"}
}
clusterName, _, _ := srv.getClusterDetails(policy.Destination)
routes = append(routes, &envoy_config_route_v3.Route{
Name: fmt.Sprintf("policy-%d", i),
Match: match,
Action: &envoy_config_route_v3.Route_Route{
Route: &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
Cluster: clusterName,
},
},
},
})
}
return routes
}