diff --git a/router-tests/events/nats_events_test.go b/router-tests/events/nats_events_test.go index 8315f95f74..8a6a927086 100644 --- a/router-tests/events/nats_events_test.go +++ b/router-tests/events/nats_events_test.go @@ -238,10 +238,12 @@ func TestNatsEvents(t *testing.T) { core.WithSubscriptionHeartbeatInterval(heartbeatInterval), }, EnableNats: true, - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", + TLSConfig: config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, }, }, func(t *testing.T, xEnv *testenv.Environment) { subscribePayload := []byte(`{"query":"subscription { employeeUpdated(employeeID: 3) { id details { forename surname } } }"}`) @@ -287,7 +289,7 @@ func TestNatsEvents(t *testing.T) { testenv.Run(t, &testenv.Config{ RouterConfigJSONTemplate: testenv.ConfigWithEdfsNatsJSONTemplate, EnableNats: true, - TLSConfig: nil, // Force Http/1 + TLSConfig: config.TLSConfiguration{}, // empty to force HTTP/1 RouterOptions: []core.Option{ core.WithSubscriptionHeartbeatInterval(heartbeatInterval), }, diff --git a/router-tests/security/subgraph_mtls_test.go b/router-tests/security/subgraph_mtls_test.go index 40747a85cf..d82d6a7b52 100644 --- a/router-tests/security/subgraph_mtls_test.go +++ b/router-tests/security/subgraph_mtls_test.go @@ -23,6 +23,27 @@ import ( "github.com/wundergraph/cosmo/router/pkg/config" ) +var ( + clientTLSAllInsecureSkipVerify = config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + InsecureSkipCaVerification: true, + }, + }, + } + clientTLSEmployeesInsecureSkipVerifyWithTestdataCert = config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + InsecureSkipCaVerification: true, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, + }, + }, + } +) + func TestSubgraphMTLS(t *testing.T) { t.Parallel() @@ -42,11 +63,7 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - }, - }), + core.WithTLSConfig(clientTLSAllInsecureSkipVerify), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -66,9 +83,11 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: false, + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + InsecureSkipCaVerification: false, + }, }, }), }, @@ -99,13 +118,15 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - CaFile: certPath, - }, - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { - InsecureSkipCaVerification: true, + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + CaFile: certPath, + }, + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + InsecureSkipCaVerification: true, + }, }, }, }), @@ -136,11 +157,13 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + InsecureSkipCaVerification: true, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, }, }), }, @@ -162,11 +185,7 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - }, - }), + core.WithTLSConfig(clientTLSAllInsecureSkipVerify), }, }, func(t *testing.T, xEnv *testenv.Environment) { res, err := xEnv.MakeGraphQLRequest(testenv.GraphQLRequest{ @@ -187,11 +206,13 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert-2.pem", - KeyFile: "../testdata/tls/key-2.pem", + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + InsecureSkipCaVerification: true, + CertFile: "../testdata/tls/cert-2.pem", + KeyFile: "../testdata/tls/key-2.pem", + }, }, }), }, @@ -219,15 +240,7 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { - InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, - }, - }), + core.WithTLSConfig(clientTLSEmployeesInsecureSkipVerifyWithTestdataCert), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -248,17 +261,19 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert-2.pem", - KeyFile: "../testdata/tls/key-2.pem", - }, - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", + CertFile: "../testdata/tls/cert-2.pem", + KeyFile: "../testdata/tls/key-2.pem", + }, + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + InsecureSkipCaVerification: true, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, }, }, }), @@ -283,17 +298,19 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert-2.pem", - KeyFile: "../testdata/tls/key-2.pem", + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + InsecureSkipCaVerification: true, + CertFile: "../testdata/tls/cert-2.pem", + KeyFile: "../testdata/tls/key-2.pem", + }, }, }, }), @@ -321,16 +338,18 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ InsecureSkipCaVerification: true, - // NO CertFile/KeyFile — proves fields are NOT inherited from All + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + InsecureSkipCaVerification: true, + // NO CertFile/KeyFile — proves fields are NOT inherited from All + }, }, }, }), @@ -368,9 +387,11 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - CaFile: certPath, + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + CaFile: certPath, + }, }, }), }, @@ -402,10 +423,12 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { - CaFile: certPath, + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + CaFile: certPath, + }, }, }, }), @@ -436,13 +459,15 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - }, - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { - CaFile: certPath, + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + InsecureSkipCaVerification: true, + }, + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + CaFile: certPath, + }, }, }, }), @@ -485,11 +510,13 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - CaFile: certPath, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + All: config.TLSClientCertConfiguration{ + CaFile: certPath, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, }, }), }, @@ -527,12 +554,14 @@ func TestSubgraphMTLS(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { - CaFile: certPath, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", + core.WithTLSConfig(config.TLSConfiguration{ + Client: config.ClientTLSConfiguration{ + Subgraphs: map[string]config.TLSClientCertConfiguration{ + "employees": { + CaFile: certPath, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, }, }, }), @@ -570,11 +599,7 @@ func TestSubgraphMTLS(t *testing.T) { }, }, })), - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - All: config.TLSClientCertConfiguration{ - InsecureSkipCaVerification: true, - }, - }), + core.WithTLSConfig(clientTLSAllInsecureSkipVerify), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -601,15 +626,7 @@ func TestSubgraphMTLS(t *testing.T) { }, }, })), - core.WithSubgraphTLSConfiguration(config.ClientTLSConfiguration{ - Subgraphs: map[string]config.TLSClientCertConfiguration{ - "employees": { - InsecureSkipCaVerification: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, - }, - }), + core.WithTLSConfig(clientTLSEmployeesInsecureSkipVerifyWithTestdataCert), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -667,7 +684,7 @@ func TestSubgraphMTLSEnvVarConfig(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(cfg.TLS.Client), + core.WithTLSConfig(config.TLSConfiguration{Client: cfg.TLS.Client}), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -695,7 +712,7 @@ func TestSubgraphMTLSEnvVarConfig(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(cfg.TLS.Client), + core.WithTLSConfig(config.TLSConfiguration{Client: cfg.TLS.Client}), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -724,7 +741,7 @@ func TestSubgraphMTLSEnvVarConfig(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(cfg.TLS.Client), + core.WithTLSConfig(config.TLSConfiguration{Client: cfg.TLS.Client}), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -763,7 +780,7 @@ func TestSubgraphMTLSEnvVarConfig(t *testing.T) { }, }, RouterOptions: []core.Option{ - core.WithSubgraphTLSConfiguration(cfg.TLS.Client), + core.WithTLSConfig(config.TLSConfiguration{Client: cfg.TLS.Client}), }, }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ diff --git a/router-tests/security/tls_test.go b/router-tests/security/tls_test.go index 184240436b..a49c9a6172 100644 --- a/router-tests/security/tls_test.go +++ b/router-tests/security/tls_test.go @@ -15,11 +15,21 @@ import ( "github.com/stretchr/testify/require" "github.com/wundergraph/cosmo/router-tests/testenv" - "github.com/wundergraph/cosmo/router/core" + "github.com/wundergraph/cosmo/router/pkg/config" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) +func newTestdataCertsServerTLSConfig() config.TLSConfiguration { + return config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + }, + } +} + func TestTLS(t *testing.T) { t.Parallel() @@ -27,21 +37,17 @@ func TestTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, + TLSConfig: newTestdataCertsServerTLSConfig(), }, func(t *testing.T, xEnv *testenv.Environment) { require.Contains(t, xEnv.RouterURL, "https://") }) }) - t.Run("Ensure router URL is not https when nil TLSConfig is passed", func(t *testing.T) { + t.Run("Ensure router URL is not https when no TLSConfig is passed", func(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: nil, + TLSConfig: config.TLSConfiguration{}, // empty on purpose }, func(t *testing.T, xEnv *testenv.Environment) { require.Contains(t, xEnv.RouterURL, "http://") }) @@ -51,11 +57,7 @@ func TestTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, + TLSConfig: newTestdataCertsServerTLSConfig(), }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query { employees { id } }`, @@ -71,11 +73,7 @@ func TestTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, + TLSConfig: newTestdataCertsServerTLSConfig(), }, func(t *testing.T, xEnv *testenv.Environment) { res, err := xEnv.MakeRequest(http.MethodGet, "/", http.Header{ "Accept": []string{"text/html"}, @@ -95,11 +93,7 @@ func TestTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, + TLSConfig: newTestdataCertsServerTLSConfig(), }, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query { employees { id } }`, @@ -112,11 +106,7 @@ func TestTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, + TLSConfig: newTestdataCertsServerTLSConfig(), }, func(t *testing.T, xEnv *testenv.Environment) { req, err := http.NewRequestWithContext(xEnv.Context, http.MethodPost, xEnv.RouterURL, strings.NewReader(`query { employees { id } }`)) require.NoError(t, err) @@ -153,11 +143,7 @@ func TestTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, + TLSConfig: newTestdataCertsServerTLSConfig(), }, func(t *testing.T, xEnv *testenv.Environment) { req, err := http.NewRequestWithContext(xEnv.Context, http.MethodPost, xEnv.RouterURL, strings.NewReader(`query { employees { id } }`)) require.NoError(t, err) @@ -174,11 +160,7 @@ func TestTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - }, + TLSConfig: newTestdataCertsServerTLSConfig(), }, func(t *testing.T, xEnv *testenv.Environment) { req, err := http.NewRequestWithContext(xEnv.Context, http.MethodPost, xEnv.RouterURL, strings.NewReader(`query { employees { id } }`)) require.NoError(t, err) @@ -203,13 +185,15 @@ func TestMTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - ClientAuth: &core.TlsClientAuthConfig{ - Required: true, + TLSConfig: config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + ClientAuth: config.TLSClientAuthConfiguration{ + Required: true, + CertFile: "../testdata/tls/cert.pem", + }, }, }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -231,13 +215,15 @@ func TestMTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - ClientAuth: &core.TlsClientAuthConfig{ - Required: true, + TLSConfig: config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + ClientAuth: config.TLSClientAuthConfiguration{ + Required: true, + CertFile: "../testdata/tls/cert.pem", + }, }, }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -252,12 +238,14 @@ func TestMTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - ClientAuth: &core.TlsClientAuthConfig{ - Required: false, // Default + TLSConfig: config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, + CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + ClientAuth: config.TLSClientAuthConfiguration{ + Required: false, // Default + }, }, }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -272,13 +260,15 @@ func TestMTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - ClientAuth: &core.TlsClientAuthConfig{ - Required: false, + TLSConfig: config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + ClientAuth: config.TLSClientAuthConfiguration{ + Required: false, + CertFile: "../testdata/tls/cert.pem", + }, }, }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -337,13 +327,15 @@ func TestMTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - ClientAuth: &core.TlsClientAuthConfig{ - Required: true, + TLSConfig: config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + ClientAuth: config.TLSClientAuthConfiguration{ + Required: true, + CertFile: "../testdata/tls/cert.pem", + }, }, }, LogObservation: testenv.LogObservationConfig{ @@ -382,13 +374,15 @@ func TestMTLS(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{ - TLSConfig: &core.TlsConfig{ - Enabled: true, - CertFile: "../testdata/tls/cert.pem", - KeyFile: "../testdata/tls/key.pem", - ClientAuth: &core.TlsClientAuthConfig{ - Required: false, + TLSConfig: config.TLSConfiguration{ + Server: config.TLSServerConfiguration{ + Enabled: true, CertFile: "../testdata/tls/cert.pem", + KeyFile: "../testdata/tls/key.pem", + ClientAuth: config.TLSClientAuthConfiguration{ + Required: false, + CertFile: "../testdata/tls/cert.pem", + }, }, }, }, func(t *testing.T, xEnv *testenv.Environment) { diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index adf7180cc7..1c34137589 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "io" - "log" "math/rand" "mime/multipart" "net" @@ -329,7 +328,7 @@ type Config struct { KafkaSeeds []string DisableWebSockets bool DisableParentBasedSampler bool - TLSConfig *core.TlsConfig + TLSConfig config.TLSConfiguration TraceExporter trace.SpanExporter TracingSanitizeUTF8 *config.SanitizeUTF8Config IPAnonymization *core.IPAnonymizationConfig @@ -717,20 +716,15 @@ func CreateTestSupervisorEnv(t testing.TB, cfg *Config) (*Environment, error) { }, }) - if cfg.TLSConfig != nil && cfg.TLSConfig.Enabled { - - cert, err := tls.LoadX509KeyPair(cfg.TLSConfig.CertFile, cfg.TLSConfig.KeyFile) + if cfg.TLSConfig.Server.Enabled { + cert, err := tls.LoadX509KeyPair(cfg.TLSConfig.Server.CertFile, cfg.TLSConfig.Server.KeyFile) require.NoError(t, err) - caCert, err := os.ReadFile(cfg.TLSConfig.CertFile) - if err != nil { - log.Fatal(err) - } + caCert, err := os.ReadFile(cfg.TLSConfig.Server.CertFile) + require.NoError(t, err) caCertPool := x509.NewCertPool() - if ok := caCertPool.AppendCertsFromPEM(caCert); !ok { - t.Fatalf("could not append ca cert to pool") - } + require.True(t, caCertPool.AppendCertsFromPEM(caCert), "could not append ca cert to pool") // Retain the default transport settings httpClient := cleanhttp.DefaultPooledClient() @@ -1148,20 +1142,15 @@ func CreateTestEnv(t testing.TB, cfg *Config) (*Environment, error) { return nil, err } - if cfg.TLSConfig != nil && cfg.TLSConfig.Enabled { - - cert, err := tls.LoadX509KeyPair(cfg.TLSConfig.CertFile, cfg.TLSConfig.KeyFile) + if cfg.TLSConfig.Server.Enabled { + cert, err := tls.LoadX509KeyPair(cfg.TLSConfig.Server.CertFile, cfg.TLSConfig.Server.KeyFile) require.NoError(t, err) - caCert, err := os.ReadFile(cfg.TLSConfig.CertFile) - if err != nil { - log.Fatal(err) - } + caCert, err := os.ReadFile(cfg.TLSConfig.Server.CertFile) + require.NoError(t, err) caCertPool := x509.NewCertPool() - if ok := caCertPool.AppendCertsFromPEM(caCert); !ok { - t.Fatalf("could not append ca cert to pool") - } + require.True(t, caCertPool.AppendCertsFromPEM(caCert), "could not append ca cert to pool") // Retain the default transport settings httpClient := cleanhttp.DefaultPooledClient() diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 39bbcf0ee8..45c7179c9e 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -163,7 +163,7 @@ func newGraphServer(routerCtx context.Context, r *Router, response *routerconfig } // Build subgraph client TLS configs (mTLS for outbound subgraph connections) - defaultClientTLS, perSubgraphTLS, err := buildSubgraphTLSConfigs(r.logger, &r.subgraphTLSConfiguration) + defaultClientTLS, perSubgraphTLS, err := buildSubgraphTLSConfigs(r.logger, &r.tls.settings.Client) if err != nil { return nil, fmt.Errorf("could not build subgraph client TLS config: %w", err) } diff --git a/router/core/http_server.go b/router/core/http_server.go index 89139f3e3d..354b2f0fb8 100644 --- a/router/core/http_server.go +++ b/router/core/http_server.go @@ -46,7 +46,6 @@ var notReadyState = &serverState{ type server struct { mu sync.RWMutex httpServer *http.Server - tlsConfig *TlsConfig logger *zap.Logger state atomic.Pointer[serverState] healthcheck health.Checker @@ -57,7 +56,6 @@ type server struct { type httpServerOptions struct { addr string logger *zap.Logger - tlsConfig *TlsConfig tlsServerConfig *tls.Config healthcheck health.Checker baseURL string @@ -93,7 +91,6 @@ func newServer(opts *httpServerOptions) (*server, error) { n := &server{ httpServer: httpServer, - tlsConfig: opts.tlsConfig, logger: opts.logger, mu: sync.RWMutex{}, healthcheck: opts.healthcheck, @@ -153,7 +150,7 @@ func (s *server) SwapGraphServer(ctx context.Context, svr *graphServer) { // listenAndServe starts the server using the pre-bound listener and blocks until shutdown. // This method is called in a goroutine; the port was already bound in newServer(). func (s *server) listenAndServe() error { - if s.tlsConfig != nil && s.tlsConfig.Enabled { + if s.httpServer.TLSConfig != nil { // Use TLS with the pre-bound listener if err := s.httpServer.ServeTLS(s.listener, "", ""); err != nil && !errors.Is(err, http.ErrServerClosed) { return err diff --git a/router/core/router.go b/router/core/router.go index 503a25a77a..cb01b20988 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -143,17 +143,13 @@ type ( Method IPAnonymizationMethod } - TlsClientAuthConfig struct { - Required bool - CertFile string - } - TlsConfig struct { - Enabled bool - CertFile string - KeyFile string + settings config.TLSConfiguration - ClientAuth *TlsClientAuthConfig + // compiledServerConfig resembles a tls.Config created out of the "settings" field. + // It's used for the routers http server. + // It's created once during bootstrap and reused during server swap. + compiledServerConfig *tls.Config } RouterConfigPollerConfig struct { @@ -360,8 +356,12 @@ func NewRouter(opts ...Option) (*Router, error) { r.corsOptions.AllowHeaders = stringsx.RemoveDuplicates(append(r.corsOptions.AllowHeaders, defaultCorsHeaders...)) r.corsOptions.AllowMethods = stringsx.RemoveDuplicates(append(r.corsOptions.AllowMethods, defaultMethods...)) - if r.tlsConfig != nil && r.tlsConfig.Enabled { + if r.tls.settings.Server.Enabled { r.baseURL = fmt.Sprintf("https://%s", r.listenAddr) + r.tls.compiledServerConfig, err = r.serverTLSConfig() + if err != nil { + return nil, fmt.Errorf("failed to construct tls config: %w", err) + } } else { r.baseURL = fmt.Sprintf("http://%s", r.listenAddr) } @@ -372,53 +372,6 @@ func NewRouter(opts ...Option) (*Router, error) { } r.graphqlEndpointURL = graphqlEndpointURL - if r.tlsConfig != nil && r.tlsConfig.Enabled { - if r.tlsConfig.CertFile == "" { - return nil, errors.New("tls cert file not provided") - } - - if r.tlsConfig.KeyFile == "" { - return nil, errors.New("tls key file not provided") - } - - var caCertPool *x509.CertPool - clientAuthMode := tls.NoClientCert - - if r.tlsConfig.ClientAuth != nil && r.tlsConfig.ClientAuth.CertFile != "" { - caCert, err := os.ReadFile(r.tlsConfig.ClientAuth.CertFile) - if err != nil { - return nil, fmt.Errorf("failed to read cert file: %w", err) - } - - // Create a CA an empty cert pool and add the CA cert to it to serve as authority to validate client certs - caPool := x509.NewCertPool() - if ok := caPool.AppendCertsFromPEM(caCert); !ok { - return nil, errors.New("failed to append cert to pool") - } - caCertPool = caPool - - if r.tlsConfig.ClientAuth.Required { - clientAuthMode = tls.RequireAndVerifyClientCert - } else { - clientAuthMode = tls.VerifyClientCertIfGiven - } - - r.logger.Debug("Client auth enabled", zap.String("mode", clientAuthMode.String())) - } - - // Load the server cert and private key - cer, err := tls.LoadX509KeyPair(r.tlsConfig.CertFile, r.tlsConfig.KeyFile) - if err != nil { - return nil, fmt.Errorf("failed to load tls cert and key: %w", err) - } - - r.tlsServerConfig = &tls.Config{ - ClientCAs: caCertPool, - Certificates: []tls.Certificate{cer}, - ClientAuth: clientAuthMode, - } - } - if r.traceConfig.Enabled { if len(r.traceConfig.Propagators) > 0 { propagators, err := rtrace.BuildPropagators(r.traceConfig.Propagators...) @@ -604,6 +557,63 @@ func NewRouter(opts ...Option) (*Router, error) { return r, nil } +// serverTLSConfig creates a new tls.Config from r.tls.Server.Settings. +// It's meant to be used as the routers http server tls configuration. +// If TLS is not configured it returns nil. +// If settings are invalid or a config can't be created it returns an error. +func (r *Router) serverTLSConfig() (*tls.Config, error) { + serverTLS := r.tls.settings.Server + + if !serverTLS.Enabled { + return nil, nil + } + + if serverTLS.CertFile == "" { + return nil, errors.New("tls cert file not provided") + } + + if serverTLS.KeyFile == "" { + return nil, errors.New("tls key file not provided") + } + + var caCertPool *x509.CertPool + clientAuthMode := tls.NoClientCert + + if serverTLS.ClientAuth.CertFile != "" { + caCert, err := os.ReadFile(serverTLS.ClientAuth.CertFile) + if err != nil { + return nil, fmt.Errorf("failed to read cert file: %w", err) + } + + // Create a CA an empty cert pool and add the CA cert to it to serve as authority to validate client certs + caPool := x509.NewCertPool() + if ok := caPool.AppendCertsFromPEM(caCert); !ok { + return nil, errors.New("failed to append cert to pool") + } + caCertPool = caPool + + if serverTLS.ClientAuth.Required { + clientAuthMode = tls.RequireAndVerifyClientCert + } else { + clientAuthMode = tls.VerifyClientCertIfGiven + } + + r.logger.Debug("Client auth enabled", zap.String("mode", clientAuthMode.String())) + } + + // Load the server cert and private key + cer, err := tls.LoadX509KeyPair(serverTLS.CertFile, serverTLS.KeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load tls cert and key: %w", err) + } + + return &tls.Config{ + ClientCAs: caCertPool, + Certificates: []tls.Certificate{cer}, + ClientAuth: clientAuthMode, + }, nil +} + // newGraphServer creates a new server. func (r *Router) newServer(ctx context.Context, response *routerconfig.Response) error { server, err := newGraphServer(ctx, r, response, r.proxy) @@ -787,8 +797,7 @@ func (r *Router) NewServer(ctx context.Context) (Server, error) { r.httpServer, err = newServer(&httpServerOptions{ addr: r.listenAddr, logger: r.logger, - tlsConfig: r.tlsConfig, - tlsServerConfig: r.tlsServerConfig, + tlsServerConfig: r.tls.compiledServerConfig, healthcheck: r.healthcheck, baseURL: r.baseURL, maxHeaderBytes: int(r.routerTrafficConfig.MaxHeaderBytes.Uint64()), @@ -1453,8 +1462,7 @@ func (r *Router) Start(ctx context.Context) error { r.httpServer, err = newServer(&httpServerOptions{ addr: r.listenAddr, logger: r.logger, - tlsConfig: r.tlsConfig, - tlsServerConfig: r.tlsServerConfig, + tlsServerConfig: r.tls.compiledServerConfig, healthcheck: r.healthcheck, baseURL: r.baseURL, maxHeaderBytes: int(r.routerTrafficConfig.MaxHeaderBytes.Uint64()), @@ -2325,15 +2333,9 @@ func WithAccessLogs(cfg *AccessLogsConfig) Option { } } -func WithTLSConfig(cfg *TlsConfig) Option { - return func(r *Router) { - r.tlsConfig = cfg - } -} - -func WithSubgraphTLSConfiguration(cfg config.ClientTLSConfiguration) Option { +func WithTLSConfig(cfg config.TLSConfiguration) Option { return func(r *Router) { - r.subgraphTLSConfiguration = cfg + r.tls.settings = cfg } } diff --git a/router/core/router_config.go b/router/core/router_config.go index 6e69517ff9..5ebf60d4f3 100644 --- a/router/core/router_config.go +++ b/router/core/router_config.go @@ -1,7 +1,6 @@ package core import ( - "crypto/tls" "net/http" "time" @@ -120,9 +119,7 @@ type Config struct { accessLogsConfig *AccessLogsConfig // If connecting to localhost inside Docker fails, fallback to the docker internal address for the host localhostFallbackInsideDocker bool - tlsServerConfig *tls.Config - tlsConfig *TlsConfig - subgraphTLSConfiguration config.ClientTLSConfiguration + tls TlsConfig telemetryAttributes []config.CustomAttribute tracePropagators []propagation.TextMapPropagator compositePropagator propagation.TextMapPropagator @@ -260,8 +257,8 @@ func (c *Config) Usage() map[string]any { usage["development_mode"] = c.developmentMode usage["access_logs"] = c.accessLogsConfig != nil usage["localhost_fallback_inside_docker"] = c.localhostFallbackInsideDocker - usage["tls_server"] = c.tlsServerConfig != nil - usage["tls_client"] = c.tlsConfig != nil + usage["tls_server"] = c.tls.settings.Server.Enabled + usage["tls_client"] = c.tls.settings.Client.Enabled() usage["self_register"] = c.selfRegister != nil usage["registration_info"] = c.registrationInfo != nil diff --git a/router/core/supervisor_instance.go b/router/core/supervisor_instance.go index 01e36da879..a7c699cf5e 100644 --- a/router/core/supervisor_instance.go +++ b/router/core/supervisor_instance.go @@ -244,15 +244,7 @@ func optionsFromResources(logger *zap.Logger, config *config.Config, reloadPersi AllowHeaders: config.CORS.AllowHeaders, MaxAge: config.CORS.MaxAge, }), - WithTLSConfig(&TlsConfig{ - Enabled: config.TLS.Server.Enabled, - CertFile: config.TLS.Server.CertFile, - KeyFile: config.TLS.Server.KeyFile, - ClientAuth: &TlsClientAuthConfig{ - CertFile: config.TLS.Server.ClientAuth.CertFile, - Required: config.TLS.Server.ClientAuth.Required, - }, - }), + WithTLSConfig(config.TLS), WithDevelopmentMode(config.DevelopmentMode), WithTracing(TraceConfigFromTelemetry(&config.Telemetry)), WithMetrics(MetricConfigFromTelemetry(&config.Telemetry)), @@ -277,7 +269,6 @@ func optionsFromResources(logger *zap.Logger, config *config.Config, reloadPersi WithDemoMode(config.DemoMode), WithStreamsHandlerConfiguration(config.Events.Handlers), WithReloadPersistentState(reloadPersistentState), - WithSubgraphTLSConfiguration(config.TLS.Client), } return options diff --git a/router/core/tls.go b/router/core/tls.go index 752e0b214c..7f4d8a97e8 100644 --- a/router/core/tls.go +++ b/router/core/tls.go @@ -5,9 +5,10 @@ import ( "crypto/x509" "errors" "fmt" - "go.uber.org/zap" "os" + "go.uber.org/zap" + "github.com/wundergraph/cosmo/router/pkg/config" ) @@ -48,7 +49,7 @@ func buildSubgraphTLSConfigs(logger *zap.Logger, cfg *config.ClientTLSConfigurat hasAll := (cfg.All.CertFile != "" && cfg.All.KeyFile != "") || cfg.All.CaFile != "" || cfg.All.InsecureSkipCaVerification // If no global TLS config is provided and there are no subgraph specific TLS configs - if !hasAll && len(cfg.Subgraphs) == 0 { + if !cfg.Enabled() { return nil, nil, nil } diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index f9363eae3a..15dbb93f3c 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -891,6 +891,7 @@ type TLSServerConfiguration struct { CertFile string `yaml:"cert_file,omitempty" env:"TLS_SERVER_CERT_FILE"` KeyFile string `yaml:"key_file,omitempty" env:"TLS_SERVER_KEY_FILE"` + // ClientAuth configures the router to accept or require mTLS from clients. ClientAuth TLSClientAuthConfiguration `yaml:"client_auth,omitempty"` } @@ -908,6 +909,16 @@ type ClientTLSConfiguration struct { Subgraphs map[string]TLSClientCertConfiguration `yaml:"subgraphs,omitempty"` } +// Enabled returns true if anything in s has been configured.© +func (s ClientTLSConfiguration) Enabled() bool { + allConfigured := s.All.InsecureSkipCaVerification || + s.All.CaFile != "" || + s.All.KeyFile != "" || + s.All.CertFile != "" + + return allConfigured || len(s.Subgraphs) > 0 +} + type TLSConfiguration struct { Server TLSServerConfiguration `yaml:"server"` Client ClientTLSConfiguration `yaml:"client"`