mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-30 19:06:33 +02:00
* xds: use plain functions, add unit tests for control plane routes * xds: add test for grpc routes * xds: add test for pomerium http routes * xds: add test for policy routes * xds: use plain functions * xds: test get all routeable domains * xds: add build downstream tls context test * more tests * test for client cert * more tests
235 lines
7.1 KiB
Go
235 lines
7.1 KiB
Go
package controlplane
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
xxhash "github.com/cespare/xxhash/v2"
|
|
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"
|
|
"github.com/golang/protobuf/ptypes"
|
|
"github.com/golang/protobuf/ptypes/any"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/pomerium/pomerium/config"
|
|
"github.com/pomerium/pomerium/internal/log"
|
|
)
|
|
|
|
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 := 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 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, _ := 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 = "::"
|
|
}
|
|
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 inlineBytes(bs []byte) *envoy_config_core_v3.DataSource {
|
|
return &envoy_config_core_v3.DataSource{
|
|
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
|
|
InlineBytes: bs,
|
|
},
|
|
}
|
|
}
|
|
|
|
func inlineBytesAsFilename(name string, bs []byte) *envoy_config_core_v3.DataSource {
|
|
ext := filepath.Ext(name)
|
|
name = fmt.Sprintf("%s-%x%s", name[:len(name)-len(ext)], xxhash.Sum64(bs), ext)
|
|
|
|
cacheDir, err := os.UserCacheDir()
|
|
if err != nil {
|
|
cacheDir = filepath.Join(os.TempDir())
|
|
}
|
|
cacheDir = filepath.Join(cacheDir, "pomerium", "envoy", "files")
|
|
if err = os.MkdirAll(cacheDir, 0755); err != nil {
|
|
log.Error().Err(err).Msg("error creating cache directory, falling back to inline bytes")
|
|
return inlineBytes(bs)
|
|
}
|
|
|
|
fp := filepath.Join(cacheDir, name)
|
|
if _, err = os.Stat(fp); os.IsNotExist(err) {
|
|
err = ioutil.WriteFile(fp, bs, 0600)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("error writing cache file, falling back to inline bytes")
|
|
return inlineBytes(bs)
|
|
}
|
|
} else if err != nil {
|
|
log.Error().Err(err).Msg("error reading cache file, falling back to inline bytes")
|
|
return inlineBytes(bs)
|
|
}
|
|
|
|
return inlineFilename(fp)
|
|
}
|
|
|
|
func inlineFilename(name string) *envoy_config_core_v3.DataSource {
|
|
return &envoy_config_core_v3.DataSource{
|
|
Specifier: &envoy_config_core_v3.DataSource_Filename{
|
|
Filename: name,
|
|
},
|
|
}
|
|
}
|
|
|
|
func getPolicyName(policy *config.Policy) string {
|
|
return fmt.Sprintf("policy-%x", policy.Checksum())
|
|
}
|
|
|
|
func 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,
|
|
})
|
|
break
|
|
}
|
|
envoyCert.CertificateChain = inlineBytesAsFilename("tls-crt.pem", chain.Bytes())
|
|
if cert.OCSPStaple != nil {
|
|
envoyCert.OcspStaple = inlineBytes(cert.OCSPStaple)
|
|
}
|
|
if bs, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey); err == nil {
|
|
envoyCert.PrivateKey = inlineBytesAsFilename("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,
|
|
inlineBytes(scts))
|
|
}
|
|
return envoyCert
|
|
}
|
|
|
|
var rootCABundle string
|
|
|
|
func init() {
|
|
// 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 = path
|
|
break
|
|
}
|
|
}
|
|
if rootCABundle == "" {
|
|
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)
|
|
}
|
|
}
|
|
|
|
func getRootCertificateAuthority() (string, error) {
|
|
if rootCABundle == "" {
|
|
return "", fmt.Errorf("root certificates not found")
|
|
}
|
|
return rootCABundle, nil
|
|
}
|