Bug report
Description
When SslMeterBinder is constructed, it only registers update handlers for the SSL bundles that already exist at construction time (those returned by sslBundles.getBundleNames()).
If an SslBundle is registered after the SslMeterBinder has been created, the register handler runs once and onBundleChange(bundleName) is invoked, but no update handler is ever attached for that bundle. Subsequent calls to SslBundleRegistry.updateBundle(bundleName, ...) do not trigger onBundleChange again, so the ssl.chain.expiry gauges never reflect refreshed certificates for dynamically registered bundles.
This looks like a bug in the metrics binding rather than an intentional limitation, as ssl.chain.expiry is documented as tracking the expiry of SSL bundle certificate chains.
Affects Version(s)
How to reproduce
A minimal way to reproduce is:
- Create a
DefaultSslBundleRegistry.
- Construct an
SslMeterBinder with:
- a mocked
SslInfo that returns no bundles from getBundles()
- the
DefaultSslBundleRegistry
- a fixed
Clock
- Bind the
SslMeterBinder to a MeterRegistry.
- After the binder has been constructed and bound, register a new bundle:
sslBundleRegistry.registerBundle("dynamic", bundle);
This will call onBundleChange("dynamic") once.
5. Update the same bundle:
sslBundleRegistry.updateBundle("dynamic", bundle);
- Observe that
SslInfo.getBundle("dynamic") is not called again and the ssl.chain.expiry gauges do not reflect the updated certificate chain.
This behavior can be reproduced with a unit test similar to:
// SslMeterBinderTests.shouldWatchUpdatesForBundlesRegisteredAfterConstruction
DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry();
SslInfo sslInfo = mock(SslInfo.class);
when(sslInfo.getBundles()).thenReturn(List.of());
// configure mocks for bundle "dynamic" and its certificate chain...
SslMeterBinder binder = new SslMeterBinder(sslInfo, sslBundleRegistry, CLOCK);
SimpleMeterRegistry meterRegistry = new SimpleMeterRegistry();
binder.bindTo(meterRegistry);
SslBundle bundle = mock(SslBundle.class);
sslBundleRegistry.registerBundle("dynamic", bundle);
sslBundleRegistry.updateBundle("dynamic", bundle);
// Fails with the current implementation: only 1 invocation
verify(sslInfo, atLeast(2)).getBundle("dynamic");
With the current implementation, the verification above fails with Mockito's "TooFewActualInvocations", showing that bundle updates do not trigger another onBundleChange.
Expected behavior
For dynamically registered bundles, I would expect ssl.chain.expiry metrics to be kept in sync with the underlying SSL bundle, just like for bundles that already exist when SslMeterBinder is constructed.
In practice, this means that when a new bundle is registered, an update handler should be attached for that bundle so that subsequent SslBundleRegistry.updateBundle(...) calls cause the corresponding ssl.chain.expiry gauges to be refreshed.
Actual behavior
Workaround
There is no straightforward workaround at the application level, since the registration and update callbacks are handled inside SslMeterBinder and SslBundles/SslBundleRegistry.
Bug report
Description
When
SslMeterBinderis constructed, it only registers update handlers for the SSL bundles that already exist at construction time (those returned bysslBundles.getBundleNames()).If an
SslBundleis registered after theSslMeterBinderhas been created, the register handler runs once andonBundleChange(bundleName)is invoked, but no update handler is ever attached for that bundle. Subsequent calls toSslBundleRegistry.updateBundle(bundleName, ...)do not triggeronBundleChangeagain, so thessl.chain.expirygauges never reflect refreshed certificates for dynamically registered bundles.This looks like a bug in the metrics binding rather than an intentional limitation, as
ssl.chain.expiryis documented as tracking the expiry of SSL bundle certificate chains.Affects Version(s)
How to reproduce
A minimal way to reproduce is:
DefaultSslBundleRegistry.SslMeterBinderwith:SslInfothat returns no bundles fromgetBundles()DefaultSslBundleRegistryClockSslMeterBinderto aMeterRegistry.This will call
onBundleChange("dynamic")once.5. Update the same bundle:
SslInfo.getBundle("dynamic")is not called again and thessl.chain.expirygauges do not reflect the updated certificate chain.This behavior can be reproduced with a unit test similar to:
With the current implementation, the verification above fails with Mockito's "TooFewActualInvocations", showing that bundle updates do not trigger another
onBundleChange.Expected behavior
For dynamically registered bundles, I would expect
ssl.chain.expirymetrics to be kept in sync with the underlying SSL bundle, just like for bundles that already exist whenSslMeterBinderis constructed.In practice, this means that when a new bundle is registered, an update handler should be attached for that bundle so that subsequent
SslBundleRegistry.updateBundle(...)calls cause the correspondingssl.chain.expirygauges to be refreshed.Actual behavior
For bundles present at binder construction time:
ssl.chain.expiryis updated when the bundle is updated.For bundles registered after binder construction:
SslBundleRegistry.updateBundle(...)does not triggeronBundleChangeagain.ssl.chain.expiryremains at the value computed at registration time and does not reflect refreshed certificates.Workaround
There is no straightforward workaround at the application level, since the registration and update callbacks are handled inside
SslMeterBinderandSslBundles/SslBundleRegistry.