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
}

View file

@ -1,103 +0,0 @@
package cryptutil
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/caddyserver/certmagic"
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
)
// NewAutocert automatically retrieves public certificates from the free
// certificate authority Let's Encrypt using HTTP-01 and TLS-ALPN-01 challenges.
// To complete the challenges, the server must be accessible from the internet
// by port 80 or 443 .
//
// https://letsencrypt.org/docs/challenge-types/#http-01-challenge
// https://letsencrypt.org/docs/challenge-types/#tls-alpn-01
func NewAutocert(tlsConfig *tls.Config, hostnames []string, useStaging bool, path string) (*tls.Config, func(h http.Handler) http.Handler, error) {
certmagic.DefaultACME.Agreed = true
if useStaging {
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
}
cm := certmagic.NewDefault()
tlsConfig = newTLSConfigIfEmpty(tlsConfig)
// add existing certs to the cache, and staple OCSP
for _, cert := range tlsConfig.Certificates {
if err := cm.CacheUnmanagedTLSCertificate(cert, nil); err != nil {
return nil, nil, fmt.Errorf("cryptutil: failed caching cert: %w", err)
}
}
cm.Storage = &certmagic.FileStorage{Path: path}
acmeConfig := certmagic.NewACMEManager(cm, certmagic.DefaultACME)
cm.Issuer = acmeConfig
// todo(bdd) : add cancellation context?
if err := cm.ManageAsync(context.TODO(), hostnames); err != nil {
return nil, nil, fmt.Errorf("cryptutil: sync failed: %w", err)
}
tlsConfig.GetCertificate = cm.GetCertificate
tlsConfig.NextProtos = append(tlsConfig.NextProtos, tlsalpn01.ACMETLS1Protocol)
tlsConfig.BuildNameToCertificate()
return tlsConfig, acmeConfig.HTTPChallengeHandler, nil
}
// TLSConfigFromBase64 returns an tls configuration from a base64 encoded blob.
func TLSConfigFromBase64(tlsConfig *tls.Config, cert, key string) (*tls.Config, error) {
tlsConfig = newTLSConfigIfEmpty(tlsConfig)
c, err := CertifcateFromBase64(cert, key)
if err != nil {
return nil, err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, *c)
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
// TLSConfigFromFile returns an tls configuration from a certificate and
// key file .
func TLSConfigFromFile(tlsConfig *tls.Config, cert, key string) (*tls.Config, error) {
tlsConfig = newTLSConfigIfEmpty(tlsConfig)
c, err := CertificateFromFile(cert, key)
if err != nil {
return nil, err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, *c)
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
// newTLSConfigIfEmpty returns an opinionated TLS configuration if config is nil.
// See :
// https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations
// https://blog.cloudflare.com/exposing-go-on-the-internet/
// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
// https://github.com/golang/go/blob/df91b8044dbe790c69c16058330f545be069cc1f/src/crypto/tls/common.go#L919
func newTLSConfigIfEmpty(tlsConfig *tls.Config) *tls.Config {
if tlsConfig != nil {
return tlsConfig
}
return &tls.Config{
MinVersion: tls.VersionTLS12,
// Prioritize cipher suites sped up by AES-NI (AES-GCM)
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
PreferServerCipherSuites: true,
// Use curves which have assembly implementations
CurvePreferences: []tls.CurveID{
tls.X25519,
tls.CurveP256,
},
// HTTP/2 must be enabled manually when using http.Serve
NextProtos: []string{"h2", "http/1.1"},
}
}

View file

@ -1,49 +0,0 @@
package cryptutil
import (
"crypto/tls"
"testing"
)
func TestTLSConfigFromBase64(t *testing.T) {
tests := []struct {
name string
cert string
key string
wantErr bool
}{
{"good",
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
false},
{"bad cert",
"!=",
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
true},
{"bad key",
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"!=",
true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := TLSConfigFromBase64(nil, tt.cert, tt.key)
if (err != nil) != tt.wantErr {
t.Errorf("TLSConfigFromBase64() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestTLSConfigFromFile(t *testing.T) {
cfg, err := TLSConfigFromFile(nil, "testdata/example-cert.pem", "testdata/example-key.pem")
if err != nil {
t.Fatal(err)
}
listener, err := tls.Listen("tcp", ":0", cfg)
if err != nil {
t.Fatal(err)
}
_ = listener
}

64
internal/envoy/embed.go Normal file
View file

@ -0,0 +1,64 @@
package envoy
import (
"fmt"
"os"
"path/filepath"
"github.com/natefinch/atomic"
resources "gopkg.in/cookieo9/resources-go.v2"
)
var embeddedFilesDirectory = filepath.Join(os.TempDir(), "pomerium-embedded-files")
func extractEmbeddedEnvoy() (outPath string, err error) {
exePath, err := resources.ExecutablePath()
if err != nil {
return "", fmt.Errorf("error finding executable path: %w", err)
}
bundle, err := resources.OpenZip(exePath)
if err != nil {
return "", fmt.Errorf("error opening binary zip file: %w", err)
}
defer bundle.Close()
rc, err := bundle.Open("envoy")
if err != nil {
return "", fmt.Errorf("error opening embedded envoy binary: %w", err)
}
defer rc.Close()
err = os.MkdirAll(embeddedFilesDirectory, 0755)
if err != nil {
return "", fmt.Errorf("error creating embedded file directory: (directory=%s): %w", embeddedFilesDirectory, err)
}
outPath = filepath.Join(embeddedFilesDirectory, "envoy")
// skip extraction if we already have it
var zfi os.FileInfo
if zf, ok := rc.(interface{ FileInfo() os.FileInfo }); ok {
zfi = zf.FileInfo()
if fi, e := os.Stat(outPath); e == nil {
if fi.Size() == zfi.Size() && fi.ModTime() == zfi.ModTime() {
return outPath, nil
}
}
}
err = atomic.WriteFile(outPath, rc)
if err != nil {
return "", fmt.Errorf("error extracting embedded envoy binary to temporary directory (path=%s): %w", outPath, err)
}
err = os.Chmod(outPath, 0755)
if err != nil {
return "", fmt.Errorf("error chmoding embedded envoy binary: %w", err)
}
if zfi != nil {
_ = os.Chtimes(outPath, zfi.ModTime(), zfi.ModTime())
}
return outPath, nil
}

158
internal/envoy/envoy.go Normal file
View file

@ -0,0 +1,158 @@
// Package envoy creates and configures an envoy server.
package envoy
import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/natefinch/atomic"
"github.com/pomerium/pomerium/internal/log"
"github.com/rs/zerolog"
)
const (
workingDirectoryName = ".pomerium-envoy"
configFileName = "envoy-config.yaml"
)
// A Server is a pomerium proxy implemented via envoy.
type Server struct {
wd string
cmd *exec.Cmd
grpcPort, httpPort string
}
// NewServer creates a new server with traffic routed by envoy.
func NewServer(grpcPort, httpPort string) (*Server, error) {
wd := filepath.Join(os.TempDir(), workingDirectoryName)
err := os.MkdirAll(wd, 0755)
if err != nil {
return nil, fmt.Errorf("error creating temporary working directory for envoy: %w", err)
}
srv := &Server{
wd: wd,
grpcPort: grpcPort,
httpPort: httpPort,
}
err = srv.writeConfig()
if err != nil {
return nil, fmt.Errorf("error writing initial envoy configuration: %w", err)
}
return srv, nil
}
// Run runs the server by extracting the embedded envoy and then executing it.
func (srv *Server) Run(ctx context.Context) error {
envoyPath, err := extractEmbeddedEnvoy()
if err != nil {
log.Warn().Err(err).Send()
envoyPath = "envoy"
}
srv.cmd = exec.CommandContext(ctx, envoyPath,
"-c", configFileName,
"--log-level", log.Logger.GetLevel().String(),
"--log-format", "%l--%n--%v",
"--log-format-escaped",
)
srv.cmd.Dir = srv.wd
stderr, err := srv.cmd.StderrPipe()
if err != nil {
return fmt.Errorf("error creating stderr pipe for envoy: %w", err)
}
go srv.handleLogs(stderr)
stdout, err := srv.cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("error creating stderr pipe for envoy: %w", err)
}
go srv.handleLogs(stdout)
// make sure envoy is killed if we're killed
srv.cmd.SysProcAttr = sysProcAttr
return srv.cmd.Run()
}
func (srv *Server) writeConfig() error {
return atomic.WriteFile(filepath.Join(srv.wd, configFileName), strings.NewReader(`
node:
id: pomerium-envoy
cluster: pomerium-envoy
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 127.0.0.1, port_value: 9901 }
dynamic_resources:
cds_config:
ads: {}
resource_api_version: V3
lds_config:
ads: {}
resource_api_version: V3
ads_config:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: pomerium-control-plane-grpc
static_resources:
clusters:
- name: pomerium-control-plane-grpc
connect_timeout: { seconds: 5 }
type: STATIC
hosts:
- socket_address:
address: 127.0.0.1
port_value: `+srv.grpcPort+`
http2_protocol_options: {}
`))
}
func (srv *Server) handleLogs(stdout io.ReadCloser) {
fileNameAndNumberRE := regexp.MustCompile(`^(\[[^:]+:[0-9]+\])\s(.*)$`)
s := bufio.NewScanner(stdout)
for s.Scan() {
ln := s.Text()
// format: level--name--message
// message is c-escaped
lvl := zerolog.TraceLevel
if pos := strings.Index(ln, "--"); pos >= 0 {
lvlstr := ln[:pos]
ln = ln[pos+2:]
if x, err := zerolog.ParseLevel(lvlstr); err == nil {
lvl = x
}
}
name := ""
if pos := strings.Index(ln, "--"); pos >= 0 {
name = ln[:pos]
ln = ln[pos+2:]
}
msg := fileNameAndNumberRE.ReplaceAllString(ln, "\"$2\"")
if s, err := strconv.Unquote(msg); err == nil {
msg = s
}
log.WithLevel(lvl).Str("service", "envoy").Str("name", name).Msg(msg)
}
}

View file

@ -0,0 +1,9 @@
// +build linux
package envoy
import "syscall"
var sysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGTERM,
}

View file

@ -0,0 +1,7 @@
// +build !linux
package envoy
import "syscall"
var sysProcAttr = &syscall.SysProcAttr{}

View file

@ -2,7 +2,6 @@ package grpc
import (
"crypto/tls"
"errors"
"net"
"os"
"os/signal"
@ -34,21 +33,17 @@ func NewServer(opt *ServerOptions, registrationFn func(s *grpc.Server), wg *sync
grpc.KeepaliveParams(opt.KeepaliveParams),
}
if len(opt.TLSCertificate) == 1 {
cert := credentials.NewServerTLSFromCert(&opt.TLSCertificate[0])
if opt.TLSCertificate != nil {
log.Debug().Str("addr", opt.Addr).Msg("internal/grpc: serving over TLS")
cert := credentials.NewServerTLSFromCert(opt.TLSCertificate)
grpcOpts = append(grpcOpts, grpc.Creds(cert))
} else if !opt.InsecureServer {
return nil, errors.New("internal/grpc: unexpected number of certificates")
} else {
log.Warn().Str("addr", opt.Addr).Msg("internal/grpc: serving without TLS")
}
srv := grpc.NewServer(grpcOpts...)
registrationFn(srv)
log.Info().
Str("addr", opt.Addr).
Bool("insecure", opt.InsecureServer).
Str("service", opt.ServiceName).
Interface("grpc-service-info", srv.GetServiceInfo()).
Msg("internal/grpc: registered")
log.Info().Interface("grpc-service-info", srv.GetServiceInfo()).Msg("internal/grpc: registered")
wg.Add(1)
go func() {
@ -68,7 +63,7 @@ type ServerOptions struct {
Addr string
// TLS certificates to use, if any.
TLSCertificate []tls.Certificate
TLSCertificate *tls.Certificate
// InsecureServer when enabled disables all transport security.
// In this mode, Pomerium is susceptible to man-in-the-middle attacks.

View file

@ -1,7 +1,6 @@
package grpc
import (
"crypto/tls"
"encoding/base64"
"os"
"os/signal"
@ -33,8 +32,6 @@ BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiBHbhVnGbwXqaMZ1dB8eBAK56jyeWDZ
-----END CERTIFICATE-----`
func TestNewServer(t *testing.T) {
// to make friendly to testing environments where 443 requires root
defaultServerOptions.Addr = ":0"
certb64, err := cryptutil.CertifcateFromBase64(
base64.StdEncoding.EncodeToString([]byte(pubKey)),
base64.StdEncoding.EncodeToString([]byte(privKey)))
@ -50,12 +47,10 @@ func TestNewServer(t *testing.T) {
wantNil bool
wantErr bool
}{
{"simple", &ServerOptions{Addr: ":0", InsecureServer: true}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
{"simple keepalive options", &ServerOptions{Addr: ":0", InsecureServer: true, KeepaliveParams: keepalive.ServerParameters{MaxConnectionAge: 5 * time.Minute}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
{"simple", &ServerOptions{Addr: ":0"}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
{"simple keepalive options", &ServerOptions{Addr: ":0", KeepaliveParams: keepalive.ServerParameters{MaxConnectionAge: 5 * time.Minute}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
{"bad tcp port", &ServerOptions{Addr: ":9999999"}, func(s *grpc.Server) {}, &sync.WaitGroup{}, true, true},
{"with cert", &ServerOptions{Addr: ":0", TLSCertificate: []tls.Certificate{*certb64}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
{"with multiple certs", &ServerOptions{Addr: ":0", TLSCertificate: []tls.Certificate{*certb64, *certb64}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, true, true},
{"with no certs or insecure", &ServerOptions{Addr: ":0", TLSCertificate: []tls.Certificate{}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, true, true},
{"with certs", &ServerOptions{Addr: ":0", TLSCertificate: certb64}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View file

@ -11,17 +11,8 @@ type ServerOptions struct {
// HTTPS requests. If empty, ":443" is used.
Addr string
// TLSConfig is the tls configuration used to setup the HTTPS server.
TLSConfig *tls.Config
// InsecureServer when enabled disables all transport security.
// In this mode, Pomerium is susceptible to man-in-the-middle attacks.
// This should be used only for testing.
Insecure bool
// Service is an optional field that helps define what the server's role is.
Service string
// TLS certificates to use.
TLSCertificate *tls.Certificate
// Timeouts
ReadHeaderTimeout time.Duration
ReadTimeout time.Duration

View file

@ -3,7 +3,6 @@ package httputil
import (
"context"
"crypto/tls"
"errors"
"fmt"
stdlog "log"
"net"
@ -26,24 +25,15 @@ func NewServer(opt *ServerOptions, h http.Handler, wg *sync.WaitGroup) (*http.Se
} else {
opt.applyServerDefaults()
}
sublogger := log.With().
Str("service", opt.Service).
Bool("insecure", opt.Insecure).
Str("addr", opt.Addr).
Logger()
if !opt.Insecure && opt.TLSConfig == nil {
return nil, errors.New("internal/httputil: server must run in insecure mode or have a valid tls config")
}
ln, err := net.Listen("tcp", opt.Addr)
if err != nil {
return nil, err
}
if !opt.Insecure {
ln = tls.NewListener(ln, opt.TLSConfig)
if opt.TLSCertificate != nil {
ln = tls.NewListener(ln, newDefaultTLSConfig(opt.TLSCertificate))
}
sublogger := log.With().Str("addr", opt.Addr).Logger()
// Set up the main server.
srv := &http.Server{
@ -66,6 +56,38 @@ func NewServer(opt *ServerOptions, h http.Handler, wg *sync.WaitGroup) (*http.Se
return srv, nil
}
// newDefaultTLSConfig creates a new TLS config based on the certificate files given.
// See :
// https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations
// https://blog.cloudflare.com/exposing-go-on-the-internet/
// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
// https://github.com/golang/go/blob/df91b8044dbe790c69c16058330f545be069cc1f/src/crypto/tls/common.go#L919
func newDefaultTLSConfig(cert *tls.Certificate) *tls.Config {
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
// Prioritize cipher suites sped up by AES-NI (AES-GCM)
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
PreferServerCipherSuites: true,
// Use curves which have assembly implementations
CurvePreferences: []tls.CurveID{
tls.X25519,
tls.CurveP256,
},
Certificates: []tls.Certificate{*cert},
// HTTP/2 must be enabled manually when using http.Serve
NextProtos: []string{"h2"},
}
tlsConfig.BuildNameToCertificate()
return tlsConfig
}
// RedirectHandler takes an incoming request and redirects to its HTTPS counterpart
func RedirectHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View file

@ -1,7 +1,7 @@
package httputil
import (
"crypto/tls"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
@ -15,13 +15,32 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/pomerium/pomerium/internal/cryptutil"
)
const privKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMQiDy26/R4ca/OdnjIf8OEDeHcw8yB5SDV9FD500CW5oAoGCCqGSM49
AwEHoUQDQgAEFumdSrEe9dnPEUU3LuyC8l6MM6PefNgpSsRL4GrD22XITMjqDKFr
jqJTf0Fo1ZWm4v+Eds6s88rsLzEC+cKLRQ==
-----END EC PRIVATE KEY-----`
const pubKey = `-----BEGIN CERTIFICATE-----
MIIBeDCCAR+gAwIBAgIUUGE8w2S7XzpkVLbNq5QUxyVOwqEwCgYIKoZIzj0EAwIw
ETEPMA0GA1UEAwwGdW51c2VkMCAXDTE5MDcxNTIzNDQyOVoYDzQ3NTcwNjExMjM0
NDI5WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAQW6Z1KsR712c8RRTcu7ILyXowzo9582ClKxEvgasPbZchMyOoMoWuOolN/QWjV
labi/4R2zqzzyuwvMQL5wotFo1MwUTAdBgNVHQ4EFgQURYdcaniRqBHXeaM79LtV
pyJ4EwAwHwYDVR0jBBgwFoAURYdcaniRqBHXeaM79LtVpyJ4EwAwDwYDVR0TAQH/
BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiBHbhVnGbwXqaMZ1dB8eBAK56jyeWDZ
2PWXmFMTu7+RywIgaZ7UwVNB2k7KjEEBiLm0PIRcpJmczI2cP9+ZMIkPHHw=
-----END CERTIFICATE-----`
func TestNewServer(t *testing.T) {
// to support envs that won't let us use 443 without root
defaultServerOptions.Addr = ":0"
certb64, err := cryptutil.CertifcateFromBase64(
base64.StdEncoding.EncodeToString([]byte(pubKey)),
base64.StdEncoding.EncodeToString([]byte(privKey)))
if err != nil {
t.Fatal(err)
}
t.Parallel()
tests := []struct {
name string
@ -33,51 +52,37 @@ func TestNewServer(t *testing.T) {
{"good basic http handler",
&ServerOptions{
Addr: ":0",
Insecure: true,
Addr: "127.0.0.1:0",
TLSCertificate: certb64,
},
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, http")
}),
false},
{"bad neither insecure nor certs set",
&ServerOptions{
Addr: ":0",
},
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, http")
}),
true},
{"good no address",
&ServerOptions{
Insecure: true,
},
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, http")
}),
false},
{"empty handler",
nil,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, http")
}),
true},
// todo(bdd): fails travis-ci
// {"good no address",
// &ServerOptions{
// TLSCertificate: certb64,
// },
// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// fmt.Fprintln(w, "Hello, http")
// }),
// false},
// todo(bdd): fails travis-ci
// {"empty handler",
// nil,
// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// fmt.Fprintln(w, "Hello, http")
// }),
// false},
{"bad port - invalid port range ",
&ServerOptions{
Addr: ":65536",
Insecure: true,
Addr: "127.0.0.1:65536",
TLSCertificate: certb64,
}, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, http")
}),
true},
{"good tls set",
&ServerOptions{
TLSConfig: &tls.Config{},
},
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, http")
}),
false},
}
for _, tt := range tests {

View file

@ -116,7 +116,7 @@ func New(o *Options) (*Store, error) {
if err != nil {
return nil, err
}
serverOpts := &httputil.ServerOptions{Addr: o.Addr, Insecure: true, Service: Name}
serverOpts := &httputil.ServerOptions{Addr: o.Addr}
var wg sync.WaitGroup
s.srv, err = httputil.NewServer(serverOpts, metrics.HTTPMetricsHandler("groupcache")(QueryParamToCtx(s.cluster)), &wg)
if err != nil {