Skip to content

Add flag guarding SPIFFE Bundle provider #8343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 4, 2025
4 changes: 4 additions & 0 deletions credentials/tls/certprovider/pemfile/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 36 additions & 4 deletions credentials/tls/certprovider/pemfile/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions internal/envconfig/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
4 changes: 4 additions & 0 deletions internal/xds/bootstrap/tlscreds/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
42 changes: 41 additions & 1 deletion internal/xds/bootstrap/tlscreds/bundle_ext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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(`{
Expand Down Expand Up @@ -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
Expand Down