diff --git a/credentials/tls/certprovider/pemfile/builder.go b/credentials/tls/certprovider/pemfile/builder.go index 58c7c434877f..a7f2f79106b9 100644 --- a/credentials/tls/certprovider/pemfile/builder.go +++ b/credentials/tls/certprovider/pemfile/builder.go @@ -24,6 +24,7 @@ import ( "time" "google.golang.org/grpc/credentials/tls/certprovider" + "google.golang.org/grpc/internal/envconfig" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/durationpb" ) @@ -72,6 +73,9 @@ func pluginConfigFromJSON(jd json.RawMessage) (Options, error) { if err := json.Unmarshal(jd, cfg); err != nil { return Options{}, fmt.Errorf("pemfile: json.Unmarshal(%s) failed: %v", string(jd), err) } + if !envconfig.XDSSPIFFEEnabled { + cfg.SPIFFETrustBundleMapFile = "" + } opts := Options{ CertFile: cfg.CertificateFile, diff --git a/credentials/tls/certprovider/pemfile/builder_test.go b/credentials/tls/certprovider/pemfile/builder_test.go index 1a556d113f28..0f2ca1d12be6 100644 --- a/credentials/tls/certprovider/pemfile/builder_test.go +++ b/credentials/tls/certprovider/pemfile/builder_test.go @@ -21,14 +21,18 @@ package pemfile import ( "encoding/json" "testing" + + "google.golang.org/grpc/internal/envconfig" + "google.golang.org/grpc/internal/testutils" ) func TestParseConfig(t *testing.T) { tests := []struct { - desc string - input any - wantOutput string - wantErr bool + desc string + input any + wantOutput string + wantErr bool + enabledSpiffe bool }{ { desc: "non JSON input", @@ -107,10 +111,38 @@ func TestParseConfig(t *testing.T) { }`), wantOutput: "file_watcher:/a/b/cert.pem:/a/b/key.pem:/a/b/ca.pem::3m20s", }, + { + desc: "good config with spiffe disabled", + input: json.RawMessage(` + { + "certificate_file": "/a/b/cert.pem", + "private_key_file": "/a/b/key.pem", + "ca_certificate_file": "/a/b/ca.pem", + "spiffe_trust_bundle_map_file": "/a/b/spiffe_bundle.json", + "refresh_interval": "200s" + }`), + wantOutput: "file_watcher:/a/b/cert.pem:/a/b/key.pem:/a/b/ca.pem::3m20s", + }, + { + desc: "good config with spiffe enabled", + input: json.RawMessage(` + { + "certificate_file": "/a/b/cert.pem", + "private_key_file": "/a/b/key.pem", + "ca_certificate_file": "/a/b/ca.pem", + "spiffe_trust_bundle_map_file": "/a/b/spiffe_bundle.json", + "refresh_interval": "200s" + }`), + wantOutput: "file_watcher:/a/b/cert.pem:/a/b/key.pem:/a/b/ca.pem:/a/b/spiffe_bundle.json:3m20s", + enabledSpiffe: true, + }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { + if test.enabledSpiffe { + testutils.SetEnvConfig(t, &envconfig.XDSSPIFFEEnabled, true) + } builder := &pluginBuilder{} bc, err := builder.ParseConfig(test.input) diff --git a/internal/envconfig/xds.go b/internal/envconfig/xds.go index 2eb97f832b1c..e87551552ad7 100644 --- a/internal/envconfig/xds.go +++ b/internal/envconfig/xds.go @@ -63,4 +63,9 @@ var ( // For more details, see: // https://github.com/grpc/proposal/blob/master/A82-xds-system-root-certs.md. XDSSystemRootCertsEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_SYSTEM_ROOT_CERTS", false) + + // XDSSPIFFEEnabled controls if SPIFFE Bundle Maps can be used as roots of + // trust. For more details, see: + // https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md + XDSSPIFFEEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_MTLS_SPIFFE", false) ) diff --git a/internal/xds/bootstrap/tlscreds/bundle.go b/internal/xds/bootstrap/tlscreds/bundle.go index 7b9631adeeb4..dda233137e9b 100644 --- a/internal/xds/bootstrap/tlscreds/bundle.go +++ b/internal/xds/bootstrap/tlscreds/bundle.go @@ -36,6 +36,7 @@ import ( "google.golang.org/grpc/credentials/tls/certprovider" "google.golang.org/grpc/credentials/tls/certprovider/pemfile" "google.golang.org/grpc/internal/credentials/spiffe" + "google.golang.org/grpc/internal/envconfig" ) // bundle is an implementation of credentials.Bundle which implements mTLS @@ -64,6 +65,9 @@ func NewBundle(jd json.RawMessage) (credentials.Bundle, func(), error) { } } // Else the config field is absent. Treat it as an empty config. + if !envconfig.XDSSPIFFEEnabled { + cfg.SPIFFETrustBundleMapFile = "" + } if cfg.CACertificateFile == "" && cfg.CertificateFile == "" && cfg.PrivateKeyFile == "" && cfg.SPIFFETrustBundleMapFile == "" { // We cannot use (and do not need) a file_watcher provider in this case, // and can simply directly use the TLS transport credentials. diff --git a/internal/xds/bootstrap/tlscreds/bundle_ext_test.go b/internal/xds/bootstrap/tlscreds/bundle_ext_test.go index 4c03fe94cf30..155376ed51d7 100644 --- a/internal/xds/bootstrap/tlscreds/bundle_ext_test.go +++ b/internal/xds/bootstrap/tlscreds/bundle_ext_test.go @@ -30,6 +30,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/grpctest" "google.golang.org/grpc/internal/stubserver" "google.golang.org/grpc/internal/testutils" @@ -237,6 +238,7 @@ func (s) TestCaReloading(t *testing.T) { // is performed and checked for failure, ensuring that gRPC is correctly using // the changed-on-disk bundle map. func (s) Test_SPIFFE_Reloading(t *testing.T) { + testutils.SetEnvConfig(t, &envconfig.XDSSPIFFEEnabled, true) clientSPIFFEBundle, err := os.ReadFile(testdata.Path("spiffe_end2end/client_spiffebundle.json")) if err != nil { t.Fatalf("Failed to read test SPIFFE bundle: %v", err) @@ -357,6 +359,7 @@ func (s) TestMTLS(t *testing.T) { // chain that is compatible with the client's configured SPIFFE bundle map. An // MTLS connection is attempted between the two and checked for success. func (s) Test_MTLS_SPIFFE(t *testing.T) { + testutils.SetEnvConfig(t, &envconfig.XDSSPIFFEEnabled, true) tests := []struct { name string serverOption grpc.ServerOption @@ -372,7 +375,7 @@ func (s) Test_MTLS_SPIFFE(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - s := stubserver.StartTestService(t, nil, grpc.Creds(testutils.CreateServerTLSCredentialsCompatibleWithSPIFFE(t, tls.RequireAndVerifyClientCert))) + s := stubserver.StartTestService(t, nil, tc.serverOption) defer s.Stop() cfg := fmt.Sprintf(`{ @@ -403,7 +406,44 @@ func (s) Test_MTLS_SPIFFE(t *testing.T) { } } +// Test_MTLS_SPIFFE_FlagDisabled configures a client and server. The server has +// a certificate chain that is compatible with the client's configured SPIFFE +// bundle map. However, the XDS flag that enabled SPIFFE usage is disabled. An +// MTLS connection is attempted between the two and checked for failure. +func (s) Test_MTLS_SPIFFE_FlagDisabled(t *testing.T) { + testutils.SetEnvConfig(t, &envconfig.XDSSPIFFEEnabled, false) + serverOption := grpc.Creds(testutils.CreateServerTLSCredentialsCompatibleWithSPIFFE(t, tls.RequireAndVerifyClientCert)) + s := stubserver.StartTestService(t, nil, serverOption) + defer s.Stop() + + cfg := fmt.Sprintf(`{ +"certificate_file": "%s", +"private_key_file": "%s", +"spiffe_trust_bundle_map_file": "%s" +}`, + testdata.Path("spiffe_end2end/client_spiffe.pem"), + testdata.Path("spiffe_end2end/client.key"), + testdata.Path("spiffe_end2end/client_spiffebundle.json")) + tlsBundle, stop, err := tlscreds.NewBundle([]byte(cfg)) + if err != nil { + t.Fatalf("Failed to create TLS bundle: %v", err) + } + defer stop() + conn, err := grpc.NewClient(s.Address, grpc.WithCredentialsBundle(tlsBundle), grpc.WithAuthority("x.test.example.com")) + if err != nil { + t.Fatalf("Error dialing: %v", err) + } + defer conn.Close() + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + if _, err = client.EmptyCall(ctx, &testpb.Empty{}); err == nil { + t.Errorf("EmptyCall(): got success want failure") + } +} + func (s) Test_MTLS_SPIFFE_Failure(t *testing.T) { + testutils.SetEnvConfig(t, &envconfig.XDSSPIFFEEnabled, true) tests := []struct { name string certFile string