mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 18:36:30 +02:00
244 lines
8.1 KiB
Go
244 lines
8.1 KiB
Go
package controlplane
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"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"
|
|
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
|
envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
|
"github.com/golang/protobuf/ptypes"
|
|
|
|
"github.com/pomerium/pomerium/config"
|
|
"github.com/pomerium/pomerium/internal/log"
|
|
"github.com/pomerium/pomerium/internal/urlutil"
|
|
)
|
|
|
|
func (srv *Server) buildClusters(options *config.Options) []*envoy_config_cluster_v3.Cluster {
|
|
grpcURL := &url.URL{
|
|
Scheme: "http",
|
|
Host: srv.GRPCListener.Addr().String(),
|
|
}
|
|
httpURL := &url.URL{
|
|
Scheme: "http",
|
|
Host: srv.HTTPListener.Addr().String(),
|
|
}
|
|
authzURL := &url.URL{
|
|
Scheme: options.GetAuthorizeURL().Scheme,
|
|
Host: options.GetAuthorizeURL().Host,
|
|
}
|
|
|
|
clusters := []*envoy_config_cluster_v3.Cluster{
|
|
buildInternalCluster(options, "pomerium-control-plane-grpc", grpcURL, true),
|
|
buildInternalCluster(options, "pomerium-control-plane-http", httpURL, false),
|
|
}
|
|
|
|
clusters = append(clusters, buildInternalCluster(options, authzURL.Host, authzURL, true))
|
|
|
|
if config.IsProxy(options.Services) {
|
|
for _, policy := range options.Policies {
|
|
clusters = append(clusters, buildPolicyCluster(options, &policy))
|
|
}
|
|
}
|
|
|
|
return clusters
|
|
}
|
|
|
|
func buildInternalCluster(options *config.Options, name string, endpoint *url.URL, forceHTTP2 bool) *envoy_config_cluster_v3.Cluster {
|
|
dnsLookupFamily := config.GetEnvoyDNSLookupFamily(options.DNSLookupFamily)
|
|
return buildCluster(name, endpoint, buildInternalTransportSocket(options, endpoint), forceHTTP2, dnsLookupFamily)
|
|
}
|
|
|
|
func buildPolicyCluster(options *config.Options, policy *config.Policy) *envoy_config_cluster_v3.Cluster {
|
|
name := getPolicyName(policy)
|
|
dnsLookupFamily := config.GetEnvoyDNSLookupFamily(options.DNSLookupFamily)
|
|
if policy.EnableGoogleCloudServerlessAuthentication {
|
|
dnsLookupFamily = envoy_config_cluster_v3.Cluster_V4_ONLY
|
|
}
|
|
return buildCluster(name, policy.Destination, buildPolicyTransportSocket(policy), false, dnsLookupFamily)
|
|
}
|
|
|
|
func buildInternalTransportSocket(options *config.Options, endpoint *url.URL) *envoy_config_core_v3.TransportSocket {
|
|
if endpoint.Scheme != "https" {
|
|
return nil
|
|
}
|
|
sni := endpoint.Hostname()
|
|
if options.OverrideCertificateName != "" {
|
|
sni = options.OverrideCertificateName
|
|
}
|
|
validationContext := &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
|
|
MatchSubjectAltNames: []*envoy_type_matcher_v3.StringMatcher{{
|
|
MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
|
|
Exact: sni,
|
|
},
|
|
}},
|
|
}
|
|
if options.CAFile != "" {
|
|
validationContext.TrustedCa = inlineFilename(options.CAFile)
|
|
} else if options.CA != "" {
|
|
bs, err := base64.StdEncoding.DecodeString(options.CA)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("invalid custom CA certificate")
|
|
}
|
|
validationContext.TrustedCa = inlineBytesAsFilename("custom-ca.pem", bs)
|
|
} else {
|
|
rootCA, err := getRootCertificateAuthority()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("unable to enable certificate verification because no root CAs were found")
|
|
} else {
|
|
validationContext.TrustedCa = inlineFilename(rootCA)
|
|
}
|
|
}
|
|
tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{
|
|
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
|
|
AlpnProtocols: []string{"h2", "http/1.1"},
|
|
ValidationContextType: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
|
|
ValidationContext: validationContext,
|
|
},
|
|
},
|
|
Sni: sni,
|
|
}
|
|
tlsConfig, _ := ptypes.MarshalAny(tlsContext)
|
|
return &envoy_config_core_v3.TransportSocket{
|
|
Name: "tls",
|
|
ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{
|
|
TypedConfig: tlsConfig,
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildPolicyTransportSocket(policy *config.Policy) *envoy_config_core_v3.TransportSocket {
|
|
if policy.Destination.Scheme != "https" {
|
|
return nil
|
|
}
|
|
|
|
sni := policy.Destination.Hostname()
|
|
if policy.TLSServerName != "" {
|
|
sni = policy.TLSServerName
|
|
}
|
|
tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{
|
|
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
|
|
TlsParams: &envoy_extensions_transport_sockets_tls_v3.TlsParameters{
|
|
EcdhCurves: []string{
|
|
"X25519",
|
|
"P-256",
|
|
"P-384",
|
|
"P-521",
|
|
},
|
|
},
|
|
AlpnProtocols: []string{"http/1.1"},
|
|
ValidationContextType: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
|
|
ValidationContext: buildPolicyValidationContext(policy),
|
|
},
|
|
},
|
|
Sni: sni,
|
|
}
|
|
if policy.ClientCertificate != nil {
|
|
tlsContext.CommonTlsContext.TlsCertificates = append(tlsContext.CommonTlsContext.TlsCertificates,
|
|
envoyTLSCertificateFromGoTLSCertificate(policy.ClientCertificate))
|
|
}
|
|
|
|
tlsConfig, _ := ptypes.MarshalAny(tlsContext)
|
|
return &envoy_config_core_v3.TransportSocket{
|
|
Name: "tls",
|
|
ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{
|
|
TypedConfig: tlsConfig,
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildPolicyValidationContext(policy *config.Policy) *envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext {
|
|
sni := policy.Destination.Hostname()
|
|
if policy.TLSServerName != "" {
|
|
sni = policy.TLSServerName
|
|
}
|
|
validationContext := &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
|
|
MatchSubjectAltNames: []*envoy_type_matcher_v3.StringMatcher{{
|
|
MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
|
|
Exact: sni,
|
|
},
|
|
}},
|
|
}
|
|
if policy.TLSCustomCAFile != "" {
|
|
validationContext.TrustedCa = inlineFilename(policy.TLSCustomCAFile)
|
|
} else if policy.TLSCustomCA != "" {
|
|
bs, err := base64.StdEncoding.DecodeString(policy.TLSCustomCA)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("invalid custom CA certificate")
|
|
}
|
|
validationContext.TrustedCa = inlineBytesAsFilename("custom-ca.pem", bs)
|
|
} else {
|
|
rootCA, err := getRootCertificateAuthority()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("unable to enable certificate verification because no root CAs were found")
|
|
} else {
|
|
validationContext.TrustedCa = inlineFilename(rootCA)
|
|
}
|
|
}
|
|
|
|
if policy.TLSSkipVerify {
|
|
validationContext.TrustChainVerification = envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext_ACCEPT_UNTRUSTED
|
|
}
|
|
|
|
return validationContext
|
|
}
|
|
|
|
func buildCluster(
|
|
name string,
|
|
endpoint *url.URL,
|
|
transportSocket *envoy_config_core_v3.TransportSocket,
|
|
forceHTTP2 bool,
|
|
dnsLookupFamily envoy_config_cluster_v3.Cluster_DnsLookupFamily,
|
|
) *envoy_config_cluster_v3.Cluster {
|
|
defaultPort := 80
|
|
if transportSocket != nil && transportSocket.Name == "tls" {
|
|
defaultPort = 443
|
|
}
|
|
|
|
if endpoint.Hostname() == "localhost" {
|
|
u := new(url.URL)
|
|
*u = *endpoint
|
|
u.Host = strings.Replace(endpoint.Host, "localhost", "127.0.0.1", -1)
|
|
endpoint = u
|
|
}
|
|
|
|
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,
|
|
TransportSocket: transportSocket,
|
|
DnsLookupFamily: dnsLookupFamily,
|
|
}
|
|
|
|
if forceHTTP2 {
|
|
cluster.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{
|
|
AllowConnect: true,
|
|
}
|
|
}
|
|
|
|
// for IPs we use a static discovery type, otherwise we use DNS
|
|
if net.ParseIP(urlutil.StripPort(endpoint.Host)) != nil {
|
|
cluster.ClusterDiscoveryType = &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_STATIC}
|
|
} else {
|
|
cluster.ClusterDiscoveryType = &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_STRICT_DNS}
|
|
}
|
|
|
|
return cluster
|
|
}
|