envoyconfig: fallback to global custom ca when no policy ca is defined (#2235)

* envoyconfig: fallback to global custom ca when no policy ca is defined

* update upgrading

* combine custom ca with root cas
This commit is contained in:
Caleb Doxsey 2021-05-28 09:36:15 -06:00 committed by GitHub
parent 88902003f7
commit 9b61d04dd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 53 deletions

View file

@ -118,7 +118,7 @@ func (b *Builder) buildPolicyCluster(ctx context.Context, options *config.Option
cluster.AltStatName = getClusterStatsName(policy) cluster.AltStatName = getClusterStatsName(policy)
name := getClusterID(policy) name := getClusterID(policy)
endpoints, err := b.buildPolicyEndpoints(ctx, policy) endpoints, err := b.buildPolicyEndpoints(ctx, options, policy)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -138,10 +138,10 @@ func (b *Builder) buildPolicyCluster(ctx context.Context, options *config.Option
return cluster, nil return cluster, nil
} }
func (b *Builder) buildPolicyEndpoints(ctx context.Context, policy *config.Policy) ([]Endpoint, error) { func (b *Builder) buildPolicyEndpoints(ctx context.Context, options *config.Options, policy *config.Policy) ([]Endpoint, error) {
var endpoints []Endpoint var endpoints []Endpoint
for _, dst := range policy.To { for _, dst := range policy.To {
ts, err := b.buildPolicyTransportSocket(ctx, policy, dst.URL) ts, err := b.buildPolicyTransportSocket(ctx, options, policy, dst.URL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -165,21 +165,11 @@ func (b *Builder) buildInternalTransportSocket(ctx context.Context, options *con
}, },
}}, }},
} }
if options.CAFile != "" { bs, err := getCombinedCertificateAuthority(options.CA, options.CAFile)
validationContext.TrustedCa = b.filemgr.FileDataSource(options.CAFile) if err != nil {
} else if options.CA != "" { log.Error(ctx).Err(err).Msg("unable to enable certificate verification because no root CAs were found")
bs, err := base64.StdEncoding.DecodeString(options.CA)
if err != nil {
log.Error(ctx).Err(err).Msg("invalid custom CA certificate")
}
validationContext.TrustedCa = b.filemgr.BytesDataSource("custom-ca.pem", bs)
} else { } else {
rootCA, err := getRootCertificateAuthority() validationContext.TrustedCa = b.filemgr.BytesDataSource("ca.pem", bs)
if err != nil {
log.Error(ctx).Err(err).Msg("unable to enable certificate verification because no root CAs were found")
} else {
validationContext.TrustedCa = b.filemgr.FileDataSource(rootCA)
}
} }
tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{ tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{ CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
@ -199,12 +189,17 @@ func (b *Builder) buildInternalTransportSocket(ctx context.Context, options *con
}, nil }, nil
} }
func (b *Builder) buildPolicyTransportSocket(ctx context.Context, policy *config.Policy, dst url.URL) (*envoy_config_core_v3.TransportSocket, error) { func (b *Builder) buildPolicyTransportSocket(
ctx context.Context,
options *config.Options,
policy *config.Policy,
dst url.URL,
) (*envoy_config_core_v3.TransportSocket, error) {
if dst.Scheme != "https" { if dst.Scheme != "https" {
return nil, nil return nil, nil
} }
vc, err := b.buildPolicyValidationContext(ctx, policy, dst) vc, err := b.buildPolicyValidationContext(ctx, options, policy, dst)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -262,7 +257,9 @@ func (b *Builder) buildPolicyTransportSocket(ctx context.Context, policy *config
func (b *Builder) buildPolicyValidationContext( func (b *Builder) buildPolicyValidationContext(
ctx context.Context, ctx context.Context,
policy *config.Policy, dst url.URL, options *config.Options,
policy *config.Policy,
dst url.URL,
) (*envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext, error) { ) (*envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext, error) {
sni := dst.Hostname() sni := dst.Hostname()
if policy.TLSServerName != "" { if policy.TLSServerName != "" {
@ -284,11 +281,11 @@ func (b *Builder) buildPolicyValidationContext(
} }
validationContext.TrustedCa = b.filemgr.BytesDataSource("custom-ca.pem", bs) validationContext.TrustedCa = b.filemgr.BytesDataSource("custom-ca.pem", bs)
} else { } else {
rootCA, err := getRootCertificateAuthority() bs, err := getCombinedCertificateAuthority(options.CA, options.CAFile)
if err != nil { if err != nil {
log.Error(ctx).Err(err).Msg("unable to enable certificate verification because no root CAs were found") log.Error(ctx).Err(err).Msg("unable to enable certificate verification because no root CAs were found")
} else { } else {
validationContext.TrustedCa = b.filemgr.FileDataSource(rootCA) validationContext.TrustedCa = b.filemgr.BytesDataSource("ca.pem", bs)
} }
} }

View file

@ -24,18 +24,25 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
customCA := filepath.Join(cacheDir, "pomerium", "envoy", "files", "custom-ca-32484c314b584447463735303142374c31414145374650305a525539554938594d524855353757313942494d473847535231.pem") customCA := filepath.Join(cacheDir, "pomerium", "envoy", "files", "custom-ca-32484c314b584447463735303142374c31414145374650305a525539554938594d524855353757313942494d473847535231.pem")
b := New("local-grpc", "local-http", filemgr.NewManager(), nil) b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
rootCAPath, _ := getRootCertificateAuthority() rootCABytes, _ := getCombinedCertificateAuthority("", "")
rootCA := b.filemgr.FileDataSource(rootCAPath).GetFilename() rootCA := b.filemgr.BytesDataSource("ca.pem", rootCABytes).GetFilename()
o1 := config.NewDefaultOptions()
o2 := config.NewDefaultOptions()
o2.CA = base64.StdEncoding.EncodeToString([]byte{0, 0, 0, 0})
combinedCABytes, _ := getCombinedCertificateAuthority(o2.CA, "")
combinedCA := b.filemgr.BytesDataSource("ca.pem", combinedCABytes).GetFilename()
t.Run("insecure", func(t *testing.T) { t.Run("insecure", func(t *testing.T) {
ts, err := b.buildPolicyTransportSocket(ctx, &config.Policy{ ts, err := b.buildPolicyTransportSocket(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "http://example.com"), To: mustParseWeightedURLs(t, "http://example.com"),
}, *mustParseURL(t, "http://example.com")) }, *mustParseURL(t, "http://example.com"))
require.NoError(t, err) require.NoError(t, err)
assert.Nil(t, ts) assert.Nil(t, ts)
}) })
t.Run("host as sni", func(t *testing.T) { t.Run("host as sni", func(t *testing.T) {
ts, err := b.buildPolicyTransportSocket(ctx, &config.Policy{ ts, err := b.buildPolicyTransportSocket(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"), To: mustParseWeightedURLs(t, "https://example.com"),
}, *mustParseURL(t, "https://example.com")) }, *mustParseURL(t, "https://example.com"))
require.NoError(t, err) require.NoError(t, err)
@ -85,7 +92,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
`, ts) `, ts)
}) })
t.Run("tls_server_name as sni", func(t *testing.T) { t.Run("tls_server_name as sni", func(t *testing.T) {
ts, err := b.buildPolicyTransportSocket(ctx, &config.Policy{ ts, err := b.buildPolicyTransportSocket(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"), To: mustParseWeightedURLs(t, "https://example.com"),
TLSServerName: "use-this-name.example.com", TLSServerName: "use-this-name.example.com",
}, *mustParseURL(t, "https://example.com")) }, *mustParseURL(t, "https://example.com"))
@ -136,7 +143,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
`, ts) `, ts)
}) })
t.Run("tls_skip_verify", func(t *testing.T) { t.Run("tls_skip_verify", func(t *testing.T) {
ts, err := b.buildPolicyTransportSocket(ctx, &config.Policy{ ts, err := b.buildPolicyTransportSocket(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"), To: mustParseWeightedURLs(t, "https://example.com"),
TLSSkipVerify: true, TLSSkipVerify: true,
}, *mustParseURL(t, "https://example.com")) }, *mustParseURL(t, "https://example.com"))
@ -188,7 +195,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
`, ts) `, ts)
}) })
t.Run("custom ca", func(t *testing.T) { t.Run("custom ca", func(t *testing.T) {
ts, err := b.buildPolicyTransportSocket(ctx, &config.Policy{ ts, err := b.buildPolicyTransportSocket(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"), To: mustParseWeightedURLs(t, "https://example.com"),
TLSCustomCA: base64.StdEncoding.EncodeToString([]byte{0, 0, 0, 0}), TLSCustomCA: base64.StdEncoding.EncodeToString([]byte{0, 0, 0, 0}),
}, *mustParseURL(t, "https://example.com")) }, *mustParseURL(t, "https://example.com"))
@ -202,21 +209,21 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
"alpnProtocols": ["h2", "http/1.1"], "alpnProtocols": ["h2", "http/1.1"],
"tlsParams": { "tlsParams": {
"cipherSuites": [ "cipherSuites": [
"ECDHE-ECDSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-CHACHA20-POLY1305", "ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305", "ECDHE-RSA-CHACHA20-POLY1305",
"ECDHE-ECDSA-AES128-SHA", "ECDHE-ECDSA-AES128-SHA",
"ECDHE-RSA-AES128-SHA", "ECDHE-RSA-AES128-SHA",
"AES128-GCM-SHA256", "AES128-GCM-SHA256",
"AES128-SHA", "AES128-SHA",
"ECDHE-ECDSA-AES256-SHA", "ECDHE-ECDSA-AES256-SHA",
"ECDHE-RSA-AES256-SHA", "ECDHE-RSA-AES256-SHA",
"AES256-GCM-SHA384", "AES256-GCM-SHA384",
"AES256-SHA" "AES256-SHA"
], ],
"ecdhCurves": [ "ecdhCurves": [
"X25519", "X25519",
"P-256", "P-256",
@ -238,9 +245,59 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
} }
`, ts) `, ts)
}) })
t.Run("options custom ca", func(t *testing.T) {
ts, err := b.buildPolicyTransportSocket(ctx, o2, &config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"),
}, *mustParseURL(t, "https://example.com"))
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `
{
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"commonTlsContext": {
"alpnProtocols": ["h2", "http/1.1"],
"tlsParams": {
"cipherSuites": [
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305",
"ECDHE-ECDSA-AES128-SHA",
"ECDHE-RSA-AES128-SHA",
"AES128-GCM-SHA256",
"AES128-SHA",
"ECDHE-ECDSA-AES256-SHA",
"ECDHE-RSA-AES256-SHA",
"AES256-GCM-SHA384",
"AES256-SHA"
],
"ecdhCurves": [
"X25519",
"P-256",
"P-384",
"P-521"
]
},
"validationContext": {
"matchSubjectAltNames": [{
"exact": "example.com"
}],
"trustedCa": {
"filename": "`+combinedCA+`"
}
}
},
"sni": "example.com"
}
}
`, ts)
})
t.Run("client certificate", func(t *testing.T) { t.Run("client certificate", func(t *testing.T) {
clientCert, _ := cryptutil.CertificateFromBase64(aExampleComCert, aExampleComKey) clientCert, _ := cryptutil.CertificateFromBase64(aExampleComCert, aExampleComKey)
ts, err := b.buildPolicyTransportSocket(ctx, &config.Policy{ ts, err := b.buildPolicyTransportSocket(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "https://example.com"), To: mustParseWeightedURLs(t, "https://example.com"),
ClientCertificate: clientCert, ClientCertificate: clientCert,
}, *mustParseURL(t, "https://example.com")) }, *mustParseURL(t, "https://example.com"))
@ -303,10 +360,11 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
func Test_buildCluster(t *testing.T) { func Test_buildCluster(t *testing.T) {
ctx := context.Background() ctx := context.Background()
b := New("local-grpc", "local-http", filemgr.NewManager(), nil) b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
rootCAPath, _ := getRootCertificateAuthority() rootCABytes, _ := getCombinedCertificateAuthority("", "")
rootCA := b.filemgr.FileDataSource(rootCAPath).GetFilename() rootCA := b.filemgr.BytesDataSource("ca.pem", rootCABytes).GetFilename()
o1 := config.NewDefaultOptions()
t.Run("insecure", func(t *testing.T) { t.Run("insecure", func(t *testing.T) {
endpoints, err := b.buildPolicyEndpoints(ctx, &config.Policy{ endpoints, err := b.buildPolicyEndpoints(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "http://example.com", "http://1.2.3.4"), To: mustParseWeightedURLs(t, "http://example.com", "http://1.2.3.4"),
}) })
require.NoError(t, err) require.NoError(t, err)
@ -365,7 +423,7 @@ func Test_buildCluster(t *testing.T) {
`, cluster) `, cluster)
}) })
t.Run("secure", func(t *testing.T) { t.Run("secure", func(t *testing.T) {
endpoints, err := b.buildPolicyEndpoints(ctx, &config.Policy{ endpoints, err := b.buildPolicyEndpoints(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, To: mustParseWeightedURLs(t,
"https://example.com", "https://example.com",
"https://example.com", "https://example.com",
@ -529,7 +587,7 @@ func Test_buildCluster(t *testing.T) {
`, cluster) `, cluster)
}) })
t.Run("ip addresses", func(t *testing.T) { t.Run("ip addresses", func(t *testing.T) {
endpoints, err := b.buildPolicyEndpoints(ctx, &config.Policy{ endpoints, err := b.buildPolicyEndpoints(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "http://127.0.0.1", "http://127.0.0.2"), To: mustParseWeightedURLs(t, "http://127.0.0.1", "http://127.0.0.2"),
}) })
require.NoError(t, err) require.NoError(t, err)
@ -586,7 +644,7 @@ func Test_buildCluster(t *testing.T) {
`, cluster) `, cluster)
}) })
t.Run("weights", func(t *testing.T) { t.Run("weights", func(t *testing.T) {
endpoints, err := b.buildPolicyEndpoints(ctx, &config.Policy{ endpoints, err := b.buildPolicyEndpoints(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "http://127.0.0.1:8080,1", "http://127.0.0.2,2"), To: mustParseWeightedURLs(t, "http://127.0.0.1:8080,1", "http://127.0.0.2,2"),
}) })
require.NoError(t, err) require.NoError(t, err)
@ -645,7 +703,7 @@ func Test_buildCluster(t *testing.T) {
`, cluster) `, cluster)
}) })
t.Run("localhost", func(t *testing.T) { t.Run("localhost", func(t *testing.T) {
endpoints, err := b.buildPolicyEndpoints(ctx, &config.Policy{ endpoints, err := b.buildPolicyEndpoints(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "http://localhost"), To: mustParseWeightedURLs(t, "http://localhost"),
}) })
require.NoError(t, err) require.NoError(t, err)
@ -692,7 +750,7 @@ func Test_buildCluster(t *testing.T) {
`, cluster) `, cluster)
}) })
t.Run("outlier", func(t *testing.T) { t.Run("outlier", func(t *testing.T) {
endpoints, err := b.buildPolicyEndpoints(ctx, &config.Policy{ endpoints, err := b.buildPolicyEndpoints(ctx, o1, &config.Policy{
To: mustParseWeightedURLs(t, "http://example.com"), To: mustParseWeightedURLs(t, "http://example.com"),
}) })
require.NoError(t, err) require.NoError(t, err)

View file

@ -6,6 +6,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
@ -203,6 +204,38 @@ func getRootCertificateAuthority() (string, error) {
return rootCABundle.value, nil return rootCABundle.value, nil
} }
func getCombinedCertificateAuthority(customCA, customCAFile string) ([]byte, error) {
rootFile, err := getRootCertificateAuthority()
if err != nil {
return nil, err
}
combined, err := os.ReadFile(rootFile)
if err != nil {
return nil, fmt.Errorf("error reading root certificates: %w", err)
}
if customCA != "" {
bs, err := base64.StdEncoding.DecodeString(customCA)
if err != nil {
return nil, err
}
combined = append(combined, '\n')
combined = append(combined, bs...)
}
if customCAFile != "" {
bs, err := os.ReadFile(customCAFile)
if err != nil {
return nil, err
}
combined = append(combined, '\n')
combined = append(combined, bs...)
}
return combined, nil
}
func marshalAny(msg proto.Message) *anypb.Any { func marshalAny(msg proto.Message) *anypb.Any {
any := new(anypb.Any) any := new(anypb.Any)
_ = anypb.MarshalFrom(any, msg, proto.MarshalOptions{ _ = anypb.MarshalFrom(any, msg, proto.MarshalOptions{