Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6c8ec90
Add configured certificate filtering and lazy loading in KeyVault JCA
rujche Jul 10, 2026
86621f2
Add PR link to keyvault-jca changelog entry
rujche Jul 10, 2026
f4371a4
Address PR review comments for alias refresh and thread safety
rujche Jul 10, 2026
5507d46
Address additional PR review feedback on thread safety and refresh be…
rujche Jul 10, 2026
cc494aa
Make alias lookup use snapshots for thread-safe iteration
rujche Jul 10, 2026
b3cd9d5
Ensure refreshed aliases list remains mutable
rujche Jul 10, 2026
7875700
Restore key vault precedence over classpath in keystore lookups
rujche Jul 10, 2026
a343059
Refine lazy key vault loads and remove brittle reflection test
rujche Jul 10, 2026
a6fbf69
Fix import order and remove unused import
rujche Jul 10, 2026
9d74fac
Strengthen test isolation and list API assertions
rujche Jul 10, 2026
162a828
Retry certificate load when Key Vault returns null
rujche Jul 10, 2026
d588c4d
Suppress GoodLoggingCheck and fix synchronization consistency
rujche Jul 10, 2026
b4aae28
Retry key and chain loads after transient failures
rujche Jul 10, 2026
c553796
Use lazy warning logs and remove extra suppression
rujche Jul 10, 2026
c73eb95
Reset key vault certificate cache when client changes
rujche Jul 10, 2026
72887a0
Make key vault client update atomic and reuse cache reset
rujche Jul 10, 2026
ce99b3e
Ignore null configured certificate aliases
rujche Jul 10, 2026
7ec6baf
Use allCertificates precedence in KeyVaultKeyStore lookups
rujche Jul 10, 2026
f34921f
Add GoodLoggingCheck suppression for KeyVaultCertificates
rujche Jul 10, 2026
9506d26
Guard null certificate alias lookup and clarify refresh docs
rujche Jul 10, 2026
35885e3
Reduce lock contention in lazy certificate loading
rujche Jul 13, 2026
e67607c
Add certificates-filter-patterns property for alias regex filtering
rujche Jul 13, 2026
8cddbf0
Simplify certificates filter pattern README wording
rujche Jul 13, 2026
f1ce384
Align changelog filter pattern wording with README
rujche Jul 13, 2026
470b184
Rename certificate alias filter property
rujche Jul 13, 2026
524655e
Document default alias filter behavior
rujche Jul 13, 2026
7662126
Reorder default filter behavior sentence in README
rujche Jul 13, 2026
60907c2
Fix stale-client publish races in lazy loaders
rujche Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 2.12.0-beta.1 (Unreleased)

### Features Added
- Added lazy loading for Key Vault certificate details in the JCA keystore. Certificate details are now loaded by alias when requested, avoiding unnecessary reads for unconfigured certificates. ([#49774](https://github.com/Azure/azure-sdk-for-java/pull/49774))
- Added support for `azure.keyvault.jca.certificate-alias-filter-patterns` to filter Key Vault certificate aliases with include/exclude regex patterns. Include patterns are configured directly and exclude patterns are prefixed with `!`. If the property is not configured, alias filtering is disabled and all discovered Key Vault aliases remain eligible for lazy loading. ([#39487](https://github.com/Azure/azure-sdk-for-java/issues/39487))

### Breaking Changes

Expand Down
1 change: 1 addition & 0 deletions sdk/keyvault/azure-security-keyvault-jca/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ The JCA library supports configuring the following options:
* `azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate`: Indicates whether to refresh certificates when have untrusted certificate.
* `azure.keyvault.jca.certificates-refresh-interval`: The refresh interval time.
* `azure.keyvault.jca.certificates-refresh-interval-in-ms`: The refresh interval time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the KV contains many certificates, I want to support the following filtering logic:

  • Forward filtering: Users can select aliases from a list of names or regular expressions. Without configuration, all aliases are read by default.

  • Reverse filtering: Exclusion by name or regular expression. Without configuration, no exclusions are allowed by default.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. This behavior has now been implemented and documented in this PR:

  • Forward filtering: include aliases by name/regex via azure.keyvault.jca.certificate-alias-filter-patterns.
  • Reverse filtering: exclude aliases by name/regex using !-prefixed entries in the same property.
  • Default behavior when the property is not configured: no filtering is applied, so all discovered Key Vault aliases remain eligible for lazy loading.

I also updated README and CHANGELOG wording to make the default behavior explicit.

* `azure.keyvault.jca.certificate-alias-filter-patterns`: Comma-separated Key Vault alias regex filters. Use include regexes directly and exclude regexes with a `!` prefix. Regex patterns use full-alias matching (`Pattern.matcher(alias).matches()`). An alias is loaded only if it matches at least one include regex (or if no include regex is configured) and matches no exclude regex. Example: `^prod-.*,!^prod-deprecated$,!.*-old$`. If this property is not configured, all discovered Key Vault aliases are eligible for lazy loading.
* `azure.keyvault.disable-challenge-resource-verification`: Indicates whether to disable verification that the authentication challenge resource matches the Key Vault or Managed HSM domain.

You can configure these properties using:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<suppress files="com.azure.security.keyvault.jca.implementation.KeyVaultClient.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.ClasspathCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.JreCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.KeyVaultCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.SpecificPathCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
Comment thread
rujche marked this conversation as resolved.
<suppress files="com.azure.security.keyvault.jca.implementation.utils.AccessTokenUtil.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.utils.HttpUtil.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.logging.Level.FINE;
Expand All @@ -56,6 +58,9 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
*/
private static final Logger LOGGER = Logger.getLogger(KeyVaultKeyStore.class.getName());

static final String CERTIFICATE_ALIAS_FILTER_PATTERNS_PROPERTY
= "azure.keyvault.jca.certificate-alias-filter-patterns";

/**
* Stores the Jre key store certificates.
*/
Expand Down Expand Up @@ -146,8 +151,9 @@ public KeyVaultKeyStore() {
customCertificates = SpecificPathCertificates.getSpecificPathCertificates(customPath);
LOGGER.log(FINE, String.format("Loaded custom certificates: %s.", customCertificates.getAliases()));

keyVaultCertificates = new KeyVaultCertificates(refreshInterval, keyVaultUri, tenantId, clientId, clientSecret,
managedIdentity, accessToken, disableChallengeResourceVerification);
keyVaultCertificates
= new KeyVaultCertificates(refreshInterval, keyVaultUri, tenantId, clientId, clientSecret, managedIdentity,
accessToken, disableChallengeResourceVerification, getKeyVaultCertificateAliasFilterPatterns());
LOGGER.log(FINE, String.format("Loaded Key Vault certificates: %s.", keyVaultCertificates.getAliases()));

classpathCertificates = new ClasspathCertificates();
Expand All @@ -168,6 +174,15 @@ Long getRefreshInterval() {
.orElse(0L);
}

Set<String> getKeyVaultCertificateAliasFilterPatterns() {
return Optional.ofNullable(System.getProperty(CERTIFICATE_ALIAS_FILTER_PATTERNS_PROPERTY))
.map(value -> Stream.of(value.split(","))
.map(String::trim)
.filter(pattern -> !pattern.isEmpty())
.collect(Collectors.toSet()))
.orElse(Collections.emptySet());
}

/**
* get key vault key store by system property
*
Expand Down Expand Up @@ -254,16 +269,22 @@ public boolean engineEntryInstanceOf(String alias, Class<? extends KeyStore.Entr
*/
@Override
public Certificate engineGetCertificate(String alias) {
Certificate certificate = allCertificates.stream()
.map(AzureCertificates::getCertificates)
.filter(a -> a.containsKey(alias))
.findFirst()
.map(certificates -> certificates.get(alias))
.orElse(null);
Certificate certificate = null;
for (AzureCertificates certificatesSource : allCertificates) {
if (certificatesSource instanceof KeyVaultCertificates) {
certificate = ((KeyVaultCertificates) certificatesSource).getCertificate(alias);
} else {
certificate = certificatesSource.getCertificates().get(alias);
}

if (certificate != null) {
break;
}
}

if (refreshCertificatesWhenHaveUnTrustCertificate && certificate == null) {
keyVaultCertificates.refreshCertificates();
certificate = keyVaultCertificates.getCertificates().get(alias);
certificate = keyVaultCertificates.getCertificate(alias);
}

return certificate;
Expand Down Expand Up @@ -307,16 +328,22 @@ public String engineGetCertificateAlias(Certificate cert) {
*/
@Override
public Certificate[] engineGetCertificateChain(String alias) {
Certificate[] certificates = allCertificates.stream()
.map(AzureCertificates::getCertificateChains)
.filter(Objects::nonNull)
.filter(a -> a.containsKey(alias))
.findFirst()
.map(m -> m.get(alias))
.orElse(null);
Certificate[] certificates = null;
for (AzureCertificates certificatesSource : allCertificates) {
if (certificatesSource instanceof KeyVaultCertificates) {
certificates = ((KeyVaultCertificates) certificatesSource).getCertificateChain(alias);
} else {
certificates = certificatesSource.getCertificateChains().get(alias);
}

if (certificates != null) {
break;
}
}

if (refreshCertificatesWhenHaveUnTrustCertificate && certificates == null) {
keyVaultCertificates.refreshCertificates();
return keyVaultCertificates.getCertificateChains().get(alias);
return keyVaultCertificates.getCertificateChain(alias);
}
return certificates;
}
Expand Down Expand Up @@ -358,12 +385,20 @@ public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter
*/
@Override
public Key engineGetKey(String alias, char[] password) {
return allCertificates.stream()
.map(AzureCertificates::getCertificateKeys)
.filter(a -> a.containsKey(alias))
.findFirst()
.map(certificateKeys -> certificateKeys.get(alias))
.orElse(null);
Key key = null;
for (AzureCertificates certificatesSource : allCertificates) {
if (certificatesSource instanceof KeyVaultCertificates) {
key = ((KeyVaultCertificates) certificatesSource).getCertificateKey(alias);
} else {
key = certificatesSource.getCertificateKeys().get(alias);
}

if (key != null) {
break;
}
}

return key;
}

/**
Expand Down
Loading
Loading