From 9b61d04dd8592cda11b27addfb38a383bec72df5 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 28 May 2021 09:36:15 -0600 Subject: [PATCH] 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 --- config/envoyconfig/clusters.go | 41 +++++----- config/envoyconfig/clusters_test.go | 120 +++++++++++++++++++++------- config/envoyconfig/envoyconfig.go | 33 ++++++++ 3 files changed, 141 insertions(+), 53 deletions(-) diff --git a/config/envoyconfig/clusters.go b/config/envoyconfig/clusters.go index b48b93bc3..e43c45b95 100644 --- a/config/envoyconfig/clusters.go +++ b/config/envoyconfig/clusters.go @@ -118,7 +118,7 @@ func (b *Builder) buildPolicyCluster(ctx context.Context, options *config.Option cluster.AltStatName = getClusterStatsName(policy) name := getClusterID(policy) - endpoints, err := b.buildPolicyEndpoints(ctx, policy) + endpoints, err := b.buildPolicyEndpoints(ctx, options, policy) if err != nil { return nil, err } @@ -138,10 +138,10 @@ func (b *Builder) buildPolicyCluster(ctx context.Context, options *config.Option 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 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 { return nil, err } @@ -165,21 +165,11 @@ func (b *Builder) buildInternalTransportSocket(ctx context.Context, options *con }, }}, } - if options.CAFile != "" { - validationContext.TrustedCa = b.filemgr.FileDataSource(options.CAFile) - } else if options.CA != "" { - 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) + bs, err := getCombinedCertificateAuthority(options.CA, options.CAFile) + if err != nil { + log.Error(ctx).Err(err).Msg("unable to enable certificate verification because no root CAs were found") } else { - rootCA, err := getRootCertificateAuthority() - 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) - } + validationContext.TrustedCa = b.filemgr.BytesDataSource("ca.pem", bs) } tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{ CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{ @@ -199,12 +189,17 @@ func (b *Builder) buildInternalTransportSocket(ctx context.Context, options *con }, 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" { return nil, nil } - vc, err := b.buildPolicyValidationContext(ctx, policy, dst) + vc, err := b.buildPolicyValidationContext(ctx, options, policy, dst) if err != nil { return nil, err } @@ -262,7 +257,9 @@ func (b *Builder) buildPolicyTransportSocket(ctx context.Context, policy *config func (b *Builder) buildPolicyValidationContext( 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) { sni := dst.Hostname() if policy.TLSServerName != "" { @@ -284,11 +281,11 @@ func (b *Builder) buildPolicyValidationContext( } validationContext.TrustedCa = b.filemgr.BytesDataSource("custom-ca.pem", bs) } else { - rootCA, err := getRootCertificateAuthority() + bs, err := getCombinedCertificateAuthority(options.CA, options.CAFile) 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) + validationContext.TrustedCa = b.filemgr.BytesDataSource("ca.pem", bs) } } diff --git a/config/envoyconfig/clusters_test.go b/config/envoyconfig/clusters_test.go index ac37aa715..50e99b56b 100644 --- a/config/envoyconfig/clusters_test.go +++ b/config/envoyconfig/clusters_test.go @@ -24,18 +24,25 @@ func Test_buildPolicyTransportSocket(t *testing.T) { customCA := filepath.Join(cacheDir, "pomerium", "envoy", "files", "custom-ca-32484c314b584447463735303142374c31414145374650305a525539554938594d524855353757313942494d473847535231.pem") b := New("local-grpc", "local-http", filemgr.NewManager(), nil) - rootCAPath, _ := getRootCertificateAuthority() - rootCA := b.filemgr.FileDataSource(rootCAPath).GetFilename() + rootCABytes, _ := getCombinedCertificateAuthority("", "") + 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) { - ts, err := b.buildPolicyTransportSocket(ctx, &config.Policy{ + ts, err := b.buildPolicyTransportSocket(ctx, o1, &config.Policy{ To: mustParseWeightedURLs(t, "http://example.com"), }, *mustParseURL(t, "http://example.com")) require.NoError(t, err) assert.Nil(t, ts) }) 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"), }, *mustParseURL(t, "https://example.com")) require.NoError(t, err) @@ -85,7 +92,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) { `, ts) }) 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"), TLSServerName: "use-this-name.example.com", }, *mustParseURL(t, "https://example.com")) @@ -136,7 +143,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) { `, ts) }) 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"), TLSSkipVerify: true, }, *mustParseURL(t, "https://example.com")) @@ -188,7 +195,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) { `, ts) }) 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"), TLSCustomCA: base64.StdEncoding.EncodeToString([]byte{0, 0, 0, 0}), }, *mustParseURL(t, "https://example.com")) @@ -202,21 +209,21 @@ func Test_buildPolicyTransportSocket(t *testing.T) { "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" - ], + "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", @@ -238,9 +245,59 @@ func Test_buildPolicyTransportSocket(t *testing.T) { } `, 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) { 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"), ClientCertificate: clientCert, }, *mustParseURL(t, "https://example.com")) @@ -303,10 +360,11 @@ func Test_buildPolicyTransportSocket(t *testing.T) { func Test_buildCluster(t *testing.T) { ctx := context.Background() b := New("local-grpc", "local-http", filemgr.NewManager(), nil) - rootCAPath, _ := getRootCertificateAuthority() - rootCA := b.filemgr.FileDataSource(rootCAPath).GetFilename() + rootCABytes, _ := getCombinedCertificateAuthority("", "") + rootCA := b.filemgr.BytesDataSource("ca.pem", rootCABytes).GetFilename() + o1 := config.NewDefaultOptions() 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"), }) require.NoError(t, err) @@ -365,7 +423,7 @@ func Test_buildCluster(t *testing.T) { `, cluster) }) 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, "https://example.com", "https://example.com", @@ -529,7 +587,7 @@ func Test_buildCluster(t *testing.T) { `, cluster) }) 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"), }) require.NoError(t, err) @@ -586,7 +644,7 @@ func Test_buildCluster(t *testing.T) { `, cluster) }) 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"), }) require.NoError(t, err) @@ -645,7 +703,7 @@ func Test_buildCluster(t *testing.T) { `, cluster) }) 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"), }) require.NoError(t, err) @@ -692,7 +750,7 @@ func Test_buildCluster(t *testing.T) { `, cluster) }) 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"), }) require.NoError(t, err) diff --git a/config/envoyconfig/envoyconfig.go b/config/envoyconfig/envoyconfig.go index be33d3ad2..df5fb14bf 100644 --- a/config/envoyconfig/envoyconfig.go +++ b/config/envoyconfig/envoyconfig.go @@ -6,6 +6,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "encoding/base64" "encoding/pem" "errors" "fmt" @@ -203,6 +204,38 @@ func getRootCertificateAuthority() (string, error) { 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 { any := new(anypb.Any) _ = anypb.MarshalFrom(any, msg, proto.MarshalOptions{