Skip to content

xds: file_watcher cert provider: support identity-only and roots-only configs#12800

Open
paulmurhy123 wants to merge 1 commit intogrpc:masterfrom
paulmurhy123:xds-file-watcher-optional-identity-or-roots
Open

xds: file_watcher cert provider: support identity-only and roots-only configs#12800
paulmurhy123 wants to merge 1 commit intogrpc:masterfrom
paulmurhy123:xds-file-watcher-optional-identity-or-roots

Conversation

@paulmurhy123
Copy link
Copy Markdown
Contributor

Summary

The file_watcher CertificateProviderProvider currently requires all of certificate_file, private_key_file, and ca_certificate_file (or spiffe_trust_bundle_map_file) in every config. This forces one-way TLS deployments — clients that only verify the server, or servers that don't validate client certs — to supply unused identity or trust files purely to satisfy the validator.

This change relaxes the validator to accept three valid topologies:

  • Identity-only (certificate_file + private_key_file)
  • Roots-only (ca_certificate_file or spiffe_trust_bundle_map_file)
  • Both

certificate_file and private_key_file must travel together; at least one of {identity pair, ca file, spiffe map file} must be present.

Why no other layer needs to change

CertProviderSslContextProvider already handles all three TLS topologies via its isMtls / isRegularTlsAndClientSide / isRegularTlsAndServerSide helpers, and only asks the underlying CertificateProvider for whichever halves the xDS-side CommonTlsContext references. The file_watcher validator was the lone bottleneck preventing per-instance plugin configs from being identity-only or roots-only. Two corresponding tweaks at the runtime-provider level:

  1. FileWatcherCertificateProvider's constructor drops checkNotNull on certFile/keyFile, adds the same pairing/at-least-one invariants, and checkAndReloadCertificates gates the identity-reload branch on certFile != null so roots-only providers don't trip on missing identity files.
  2. When notifyCertUpdates=true is requested for a roots-only config, both layers throw UnsupportedOperationException (not IllegalArgumentException) so CertificateProviderStore.createOrGetProvider's probe-then-fall-back catch block can engage. Without this, legitimate roots-only callers (notifyCertUpdates=false) would fail on the store's unconditional first probe.

Prior art

The grpc-go pemfile provider — the Go counterpart of this Java file_watcher plugin — already implements exactly this contract. From credentials/tls/certprovider/pemfile/watcher.go lines 80-86:

func (o Options) validate() error {
    if o.CertFile == "" && o.KeyFile == "" && o.RootFile == "" && o.SPIFFEBundleMapFile == "" {
        return fmt.Errorf("pemfile: at least one credential file needs to be specified")
    }
    if keySpecified, certSpecified := o.KeyFile != "", o.CertFile != ""; keySpecified != certSpecified {
        return fmt.Errorf("pemfile: private key file and identity cert file should be both specified or not specified")
    }

This PR aligns grpc-java's file_watcher with the grpc-go pemfile semantics: at least one of {identity pair, root, spiffe} required, identity cert + key paired.

Test plan

  • Validator tests in FileWatcherCertificateProviderProviderTest: identity-only, ca-roots-only, spiffe-roots-only succeed; cert-without-key, key-without-cert, and empty config raise NullPointerException with new messages; notifyCertUpdates=true with roots-only raises UnsupportedOperationException; regression test verifies the CertificateProviderStore probe-then-fall-back contract.
  • Runtime tests in FileWatcherCertificateProviderTest: identity-only / ca-roots-only / spiffe-roots-only providers reload only their configured half; constructor rejects unpaired identity, both-halves-missing, and notifyCertUpdates=true without identity.
  • Verified RED → GREEN for each new test (failing for the expected reason before the fix, passing after).
  • Full :grpc-xds:test passes (3956 tests, 0 failures).
  • :grpc-xds:checkstyleMain and :grpc-xds:checkstyleTest clean.

… configs

The file_watcher CertificateProviderProvider previously required all of
certificate_file, private_key_file, and ca_certificate_file (or
spiffe_trust_bundle_map_file) in every config. One-way TLS deployments
(clients that only verify the server, or servers that don't validate client
certs) had to supply unused identity or trust files just to satisfy the
validator.

CertProviderSslContextProvider already routes identity-only and roots-only
modes correctly via its three TLS topology helpers (isMtls,
isRegularTlsAndClientSide, isRegularTlsAndServerSide); the validator was the
only thing forcing every plugin instance to carry both halves. Allow either
half to be omitted, require certificate_file/private_key_file as a pair,
require at least one of {identity pair, ca file, spiffe map file}, and gate
the identity-reload branch in FileWatcherCertificateProvider on certFile
being non-null.

Throw UnsupportedOperationException (not IllegalArgumentException) when a
roots-only config is asked to deliver identity updates, so
CertificateProviderStore.createOrGetProvider's catch block can fall back
from its initial notifyCertUpdates=true probe to the caller's actual value.
Without this, legitimate roots-only callers (notifyCertUpdates=false) would
fail on the store's unconditional first probe.

This matches the contract the grpc-go pemfile provider already implements
(credentials/tls/certprovider/pemfile/watcher.go validate()): at least one
file required, identity cert + key paired.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant