mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-05 21:36:02 +02:00
envoy: implement policy TLS options (#724)
* envoy: implement policy TLS options * fix tests * log which CAs are being used
This commit is contained in:
parent
e24e026ffc
commit
e854cfe83b
10 changed files with 258 additions and 161 deletions
|
@ -2,12 +2,16 @@ package config
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
@ -21,8 +25,8 @@ type Policy struct {
|
|||
AllowedGroups []string `mapstructure:"allowed_groups" yaml:"allowed_groups,omitempty" json:"allowed_groups,omitempty"`
|
||||
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"`
|
||||
|
||||
Source *StringURL `yaml:",omitempty" json:"source,omitempty"`
|
||||
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty"`
|
||||
Source *StringURL `yaml:",omitempty" json:"source,omitempty" hash:"ignore"`
|
||||
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty" hash:"ignore"`
|
||||
|
||||
// Additional route matching options
|
||||
Prefix string `mapstructure:"prefix" yaml:"prefix,omitempty" json:"prefix,omitempty"`
|
||||
|
@ -62,7 +66,6 @@ type Policy struct {
|
|||
// route when verifying server certificates.
|
||||
TLSCustomCA string `mapstructure:"tls_custom_ca" yaml:"tls_custom_ca,omitempty"`
|
||||
TLSCustomCAFile string `mapstructure:"tls_custom_ca_file" yaml:"tls_custom_ca_file,omitempty"`
|
||||
RootCAs *x509.CertPool `yaml:",omitempty"`
|
||||
|
||||
// Contains the x.509 client certificate to present to the downstream
|
||||
// host.
|
||||
|
@ -70,7 +73,7 @@ type Policy struct {
|
|||
TLSClientKey string `mapstructure:"tls_client_key" yaml:"tls_client_key,omitempty"`
|
||||
TLSClientCertFile string `mapstructure:"tls_client_cert_file" yaml:"tls_client_cert_file,omitempty"`
|
||||
TLSClientKeyFile string `mapstructure:"tls_client_key_file" yaml:"tls_client_key_file,omitempty"`
|
||||
ClientCertificate *tls.Certificate `yaml:",omitempty"`
|
||||
ClientCertificate *tls.Certificate `yaml:",omitempty" hash:"ignore"`
|
||||
|
||||
// SetRequestHeaders adds a collection of headers to the downstream request
|
||||
// in the form of key value pairs. Note bene, this will overwrite the
|
||||
|
@ -127,19 +130,28 @@ func (p *Policy) Validate() error {
|
|||
}
|
||||
|
||||
if p.TLSCustomCA != "" {
|
||||
p.RootCAs, err = cryptutil.CertPoolFromBase64(p.TLSCustomCA)
|
||||
_, err := base64.StdEncoding.DecodeString(p.TLSCustomCA)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: couldn't decode custom ca %w", err)
|
||||
return fmt.Errorf("config: couldn't decode custom ca: %w", err)
|
||||
}
|
||||
} else if p.TLSCustomCAFile != "" {
|
||||
p.RootCAs, err = cryptutil.CertPoolFromFile(p.TLSCustomCAFile)
|
||||
_, err := os.Stat(p.TLSCustomCAFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: couldn't load custom ca file %w", err)
|
||||
return fmt.Errorf("config: couldn't load client ca file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checksum returns the xxhash hash for the policy.
|
||||
func (p *Policy) Checksum() uint64 {
|
||||
cs, _ := hashstructure.Hash(p, &hashstructure.HashOptions{
|
||||
Hasher: xxhash.New(),
|
||||
})
|
||||
return cs
|
||||
}
|
||||
|
||||
func (p *Policy) String() string {
|
||||
if p.Source == nil || p.Destination == nil {
|
||||
return fmt.Sprintf("%s → %s", p.From, p.To)
|
||||
|
|
|
@ -34,15 +34,20 @@ func main() {
|
|||
err = http.ListenAndServe(bindAddr, http.HandlerFunc(handle))
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to listen and serve: %v", err)
|
||||
fmt.Fprintf(os.Stderr, "failed to listen and serve: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
|
||||
conn, err := (&websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}).Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error upgrading websocket connection: %v\n", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ func bootstrapCerts(ctx context.Context) (*TLSCertsBundle, error) {
|
|||
name string
|
||||
}{
|
||||
{&bundle.Trusted, filepath.Join(wd, "trusted"), true, "*.localhost.pomerium.io"},
|
||||
{&bundle.WronglyNamed, filepath.Join(wd, "wrongly-named"), true, "*.localhost.notpomerium.io"},
|
||||
{&bundle.WronglyNamed, filepath.Join(wd, "trusted"), true, "*.localhost.notpomerium.io"},
|
||||
{&bundle.Untrusted, filepath.Join(wd, "untrusted"), false, "*.localhost.pomerium.io"},
|
||||
}
|
||||
|
||||
|
|
|
@ -185,8 +185,6 @@ func TestWebsocket(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTLSSkipVerify(t *testing.T) {
|
||||
t.SkipNow()
|
||||
|
||||
ctx := mainCtx
|
||||
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
|
||||
defer clearTimeout()
|
||||
|
@ -221,13 +219,11 @@ func TestTLSSkipVerify(t *testing.T) {
|
|||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
|
||||
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTLSServerName(t *testing.T) {
|
||||
t.SkipNow()
|
||||
|
||||
ctx := mainCtx
|
||||
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
|
||||
defer clearTimeout()
|
||||
|
@ -262,13 +258,11 @@ func TestTLSServerName(t *testing.T) {
|
|||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
|
||||
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTLSCustomCA(t *testing.T) {
|
||||
t.SkipNow()
|
||||
|
||||
ctx := mainCtx
|
||||
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
|
||||
defer clearTimeout()
|
||||
|
@ -303,13 +297,11 @@ func TestTLSCustomCA(t *testing.T) {
|
|||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
|
||||
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTLSClientCert(t *testing.T) {
|
||||
t.SkipNow()
|
||||
|
||||
ctx := mainCtx
|
||||
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
|
||||
defer clearTimeout()
|
||||
|
@ -343,7 +335,7 @@ func TestTLSClientCert(t *testing.T) {
|
|||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
|
||||
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
package controlplane
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
|
||||
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) {
|
||||
|
@ -117,3 +127,109 @@ func inlineBytes(bs []byte) *envoy_config_core_v3.DataSource {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func inlineBytesAsFilename(name string, bs []byte) *envoy_config_core_v3.DataSource {
|
||||
ext := filepath.Ext(name)
|
||||
name = fmt.Sprintf("%s-%x%s", 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
|
||||
}
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
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: "grpc",
|
||||
Scheme: "http",
|
||||
Host: srv.GRPCListener.Addr().String(),
|
||||
}
|
||||
httpURL := &url.URL{
|
||||
|
@ -25,46 +28,116 @@ func (srv *Server) buildClusters(options *config.Options) []*envoy_config_cluste
|
|||
Host: srv.HTTPListener.Addr().String(),
|
||||
}
|
||||
authzURL := &url.URL{
|
||||
Scheme: strings.Replace(options.AuthorizeURL.Scheme, "http", "grpc", -1),
|
||||
Scheme: options.AuthorizeURL.Scheme,
|
||||
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),
|
||||
srv.buildInternalCluster("pomerium-control-plane-grpc", grpcURL, true),
|
||||
srv.buildInternalCluster("pomerium-control-plane-http", httpURL, false),
|
||||
srv.buildInternalCluster("pomerium-authz", authzURL, true),
|
||||
}
|
||||
|
||||
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,
|
||||
}))
|
||||
clusters = append(clusters, srv.buildPolicyCluster(&policy))
|
||||
}
|
||||
}
|
||||
|
||||
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) buildInternalCluster(name string, endpoint *url.URL, forceHTTP2 bool) *envoy_config_cluster_v3.Cluster {
|
||||
var transportSocket *envoy_config_core_v3.TransportSocket
|
||||
if endpoint.Scheme == "https" {
|
||||
transportSocket = &envoy_config_core_v3.TransportSocket{
|
||||
Name: "tls",
|
||||
}
|
||||
}
|
||||
return srv.buildCluster(name, endpoint, transportSocket, forceHTTP2)
|
||||
}
|
||||
|
||||
func (srv *Server) buildCluster(name string, endpoint *url.URL) *envoy_config_cluster_v3.Cluster {
|
||||
func (srv *Server) buildPolicyCluster(policy *config.Policy) *envoy_config_cluster_v3.Cluster {
|
||||
name := getPolicyName(policy)
|
||||
return srv.buildCluster(name, policy.Destination, srv.buildPolicyTransportSocket(policy), false)
|
||||
}
|
||||
|
||||
func (srv *Server) 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{
|
||||
AlpnProtocols: []string{"http/1.1"},
|
||||
ValidationContextType: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
|
||||
ValidationContext: srv.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 (srv *Server) 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 (srv *Server) buildCluster(
|
||||
name string,
|
||||
endpoint *url.URL,
|
||||
transportSocket *envoy_config_core_v3.TransportSocket,
|
||||
forceHTTP2 bool,
|
||||
) *envoy_config_cluster_v3.Cluster {
|
||||
defaultPort := 80
|
||||
if endpoint.Scheme == "https" || endpoint.Scheme == "grpcs" {
|
||||
if transportSocket != nil && transportSocket.Name == "tls" {
|
||||
defaultPort = 443
|
||||
}
|
||||
|
||||
|
@ -84,15 +157,12 @@ func (srv *Server) buildCluster(name string, endpoint *url.URL) *envoy_config_cl
|
|||
}},
|
||||
},
|
||||
RespectDnsTtl: true,
|
||||
TransportSocket: transportSocket,
|
||||
}
|
||||
|
||||
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 forceHTTP2 {
|
||||
cluster.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{
|
||||
AllowConnect: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package controlplane
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
|
@ -309,33 +306,7 @@ func (srv *Server) buildDownstreamTLSContext(options *config.Options, domain str
|
|||
return nil
|
||||
}
|
||||
|
||||
envoyCert := &envoy_extensions_transport_sockets_tls_v3.TlsCertificate{}
|
||||
var chain bytes.Buffer
|
||||
for _, cbs := range cert.Certificate {
|
||||
_ = pem.Encode(&chain, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cbs,
|
||||
})
|
||||
}
|
||||
envoyCert.CertificateChain = inlineBytes(chain.Bytes())
|
||||
if cert.OCSPStaple != nil {
|
||||
envoyCert.OcspStaple = inlineBytes(cert.OCSPStaple)
|
||||
}
|
||||
if bs, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey); err == nil {
|
||||
envoyCert.PrivateKey = inlineBytes(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))
|
||||
}
|
||||
|
||||
envoyCert := envoyTLSCertificateFromGoTLSCertificate(cert)
|
||||
return &envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext{
|
||||
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
|
||||
TlsCertificates: []*envoy_extensions_transport_sockets_tls_v3.TlsCertificate{envoyCert},
|
||||
|
|
|
@ -121,7 +121,7 @@ func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) []*
|
|||
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"}
|
||||
}
|
||||
|
||||
clusterName, _, _ := srv.getClusterDetails(policy.Destination)
|
||||
clusterName := getPolicyName(&policy)
|
||||
|
||||
var requestHeadersToAdd []*envoy_config_core_v3.HeaderValueOption
|
||||
for k, v := range policy.SetRequestHeaders {
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
@ -38,33 +37,6 @@ func CertificateFromFile(certFile, keyFile string) (*tls.Certificate, error) {
|
|||
return &cert, err
|
||||
}
|
||||
|
||||
// CertPoolFromBase64 takes a base64 encoded string and returns a new
|
||||
// X509 certificate pool.
|
||||
func CertPoolFromBase64(encPemCerts string) (*x509.CertPool, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(encPemCerts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't decode pem %v: %w", b, err)
|
||||
}
|
||||
return bytesToCertPool(b)
|
||||
}
|
||||
|
||||
// CertPoolFromFile reads a file and returns an X509 certificate pool.
|
||||
func CertPoolFromFile(pemFile string) (*x509.CertPool, error) {
|
||||
b, err := ioutil.ReadFile(pemFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytesToCertPool(b)
|
||||
}
|
||||
|
||||
func bytesToCertPool(b []byte) (*x509.CertPool, error) {
|
||||
certPool := x509.NewCertPool()
|
||||
if ok := certPool.AppendCertsFromPEM(b); !ok {
|
||||
return nil, fmt.Errorf("could append certs from PEM")
|
||||
}
|
||||
return certPool, nil
|
||||
}
|
||||
|
||||
// DecodePublicKey decodes a PEM-encoded ECDSA public key.
|
||||
func DecodePublicKey(encodedKey []byte) (*ecdsa.PublicKey, error) {
|
||||
block, _ := pem.Decode(encodedKey)
|
||||
|
|
|
@ -64,47 +64,6 @@ func TestCertifcateFromBase64(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCertPoolFromBase64(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
encPemCerts string
|
||||
wantErr bool
|
||||
}{
|
||||
{"good", "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lKQUszMmhoR0JIcmFtTUEwR0NTcUdTSWIzRFFFQkN3VUFNR0l4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIREExVFlXNGdSbkpoYm1OcApjMk52TVE4d0RRWURWUVFLREFaQ1lXUlRVMHd4RlRBVEJnTlZCQU1NRENvdVltRmtjM05zTG1OdmJUQWVGdzB4Ck9UQTJNVEl4TlRNeE5UbGFGdzB5TVRBMk1URXhOVE14TlRsYU1HSXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWUQKVlFRSURBcERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhEQTFUWVc0Z1JuSmhibU5wYzJOdk1ROHdEUVlEVlFRSwpEQVpDWVdSVFUwd3hGVEFUQmdOVkJBTU1EQ291WW1Ga2MzTnNMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1JRTdQaU03Z1RDczloUTFYQll6Sk1ZNjF5b2FFbXdJclg1bFo2eEt5eDIKUG16QVMyQk1UT3F5dE1BUGdMYXcrWExKaGdMNVhFRmRFeXQvY2NSTHZPbVVMbEEzcG1jY1lZejJRVUxGUnRNVwpoeWVmZE9zS25SRlNKaUZ6YklSTWVWWGswV3ZvQmoxSUZWS3RzeWpicXY5dS8yQ1ZTbmRyT2ZFazBURzIzVTNBCnhQeFR1VzFDcmJWOC9xNzFGZEl6U09jaWNjZkNGSHBzS09vM1N0L3FiTFZ5dEg1YW9oYmNhYkZYUk5zS0VxdmUKd3c5SGRGeEJJdUdhK1J1VDVxMGlCaWt1c2JwSkhBd25ucVA3aS9kQWNnQ3NrZ2paakZlRVU0RUZ5K2IrYTFTWQpRQ2VGeHhDN2MzRHZhUmhCQjBWVmZQbGtQejBzdzZsODY1TWFUSWJSeW9VQ0F3RUFBYU15TURBd0NRWURWUjBUCkJBSXdBREFqQmdOVkhSRUVIREFhZ2d3cUxtSmhaSE56YkM1amIyMkNDbUpoWkhOemJDNWpiMjB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFJaTV1OXc4bWdUNnBwQ2M3eHNHK0E5ZkkzVzR6K3FTS2FwaHI1bHM3MEdCS2JpWQpZTEpVWVpoUGZXcGgxcXRra1UwTEhGUG04M1ZhNTJlSUhyalhUMFZlNEt0TzFuMElBZkl0RmFXNjJDSmdoR1luCmp6dzByeXpnQzRQeUZwTk1uTnRCcm9QdS9iUGdXaU1nTE9OcEVaaGlneDRROHdmMVkvVTlzK3pDQ3hvSmxhS1IKTVhidVE4N1g3bS85VlJueHhvNk56NVpmN09USFRwTk9JNlZqYTBCeGJtSUFVNnlyaXc5VXJnaWJYZk9qM2o2bgpNVExCdWdVVklCMGJCYWFzSnNBTUsrdzRMQU52YXBlWjBET1NuT1I0S0syNEowT3lvRjVmSG1wNTllTTE3SW9GClFxQmh6cG1RVWd1bmVjRVc4QlRxck5wRzc5UjF1K1YrNHd3Y2tQYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", false},
|
||||
{"bad base64", "!", true},
|
||||
{"bad certificate", "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lKQUszMmhoR0JIcmFtTUEwR0NTcUdTSWIzRFFFQkN3VUFNR0l4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIREExVFlXNGdSbkpoYm1OcApjMk52TVE4d0RRWURWUVFLREFaQ1lXUlRVMHd4RlRBVEJnTlZCQU1NRENvdVltRmtjM05zTG1OdmJUQWVGdzB4Ck9UQTJNVEl4TlRNeE5UbGFGdzB5TVRBMk1URXhOVE14TlRsYU1HSXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWUQKVlFRSURBcERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhEQTFUWVc0Z1JuSmhibU5wYzJOdk1ROHdEUVlEVlFRSwpEQVpDWVdSVFUwd3hGVEFUQmdOVkJBTU1EQ291WW1Ga2MzTnNMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1JRTdQaU03Z1RDczloUTFYQll6Sk1ZNjF5b2FFbXdJclg1bFo2eEt5eDIKUG16QVMyQk1UT3F5dE1BUGdMYXcrWExKaGdMNVhFRmRFeXQvY2NSTHZPbVVMbEEzcG1jY1lZejJRVUxGUnRNVwpoeWVmZE9zS25SRlNKaUZ6YklSTWVWWGswV3ZvQmoxSUZWS3RzeWpicXY5dS8yQ1ZTbmRyT2ZFazBURzIzVTNBCnhQeFR1VzFDcmJWOC9xNzFGZEl6U09jaWNjZkNGSHBzS09vM1N0L3FiTFZ5dEg1YW9oYmNhYkZYUk5zS0VxdmUKd3c5SGRGeEJJdUdhK1J1VDVxMGlCaWt1c2JwSkhBd25ucVA3aS9kQWNnQ3NrZ2paakZlRVU0RUZ5K2IrYTFTWQpRQ2VGeHhDN2MzRHZhUmhCQjBWVmZQbGtQejBzdzZsODY1TWFUSWJSeW9VQ0F3RUFBYU15TURBd0NRWURWUjBUCkJBSXdBREFqQmdOVkhSRUVIREFhZ2d3cUxtSmhaSE56YkM1amIyMkNDbUpoWkhOemJDNWpiMjB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFJaTV1OXc4bWdUNnBwQ2M3eHNHK0E5ZkkzVzR6K3FTS2FwaHI1bHM3MEdCS2JpWQpZTEpVWVpoUGZXcGgxcXRra1UwTEhGUG04M1ZhNTJlSUhyalhUMFZlNEt0TzFuMElBZkl0RmFXNjJDSmdoR1luCmp6dzByeXpnQzRQeUZwTk1uTnRCcm9QdS9iUGdXaU1nTE9OcEVaaGlneDRROHdmMVkvVTlzK3pDQ3hvSmxhS1IKTVhidVE4N1g3bS85VlJueHhvNk56NVpmN09USFRwTk9JNlZqYTBCeGJtSUFVNnlyaXc5VXJnaWJYZk9qM2o2bgpNVExCdWdVVklCMGJCYWFzSnNBTUsrdzRMQU52YXBlWjBET1NuT1I0S0syNEowT3lvRjVmSG1wNTllTTE3SW9GClFxQmh6cG1RVWd1bmVjRVD4QlRxck5wRzc5UjF1K1YrNHd3Y2tQYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := CertPoolFromBase64(tt.encPemCerts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CertPoolFromBase64() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertPoolFromFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pemFile string
|
||||
wantErr bool
|
||||
}{
|
||||
{"good", "./testdata/ca.pem", false},
|
||||
{"bad doesn't exist", "./testdata/no-exist.pem", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := CertPoolFromFile(tt.pemFile)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CertPoolFromFile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificateFromFile(t *testing.T) {
|
||||
cert, err := CertificateFromFile("testdata/example-cert.pem", "testdata/example-key.pem")
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Reference in a new issue