diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index ce4478d829bd..4d0c51fa5a55 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -25,6 +25,7 @@
/sdk/servicebus/ @yvgopal @nemakam @hemanttanwar @conniey
/sdk/storage/ @amishra-dev @rickle-msft @jaschrep-msft @gapra-msft @alzimmermsft @sima-zhu
/sdk/textanalytics/ @samvaity @mssfang @sima-zhu
+/sdk/spring/ @saragluna @yiliuTo @chenrujun @jialindai
# end to end tests
/sdk/e2e/ @jianghaolu @g2vinay
diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt
index 50b9edde2ab3..6146514f7bd8 100644
--- a/eng/versioning/external_dependencies.txt
+++ b/eng/versioning/external_dependencies.txt
@@ -81,7 +81,10 @@ pl.pragmatists:JUnitParams;1.1.1
## Test dependency versions
cglib:cglib-nodep;3.2.7
+com.alibaba:fastjson;1.2.61
+com.github.cverges.expect4j:expect4j;1.6
com.github.tomakehurst:wiremock-standalone;2.24.1
+com.jcraft:jsch;0.1.53
com.microsoft.azure:adal4j;1.6.5
com.microsoft.azure:azure;1.24.1
com.microsoft.azure:azure-mgmt-graph-rbac;1.3.0
@@ -98,8 +101,14 @@ io.opentelemetry:opentelemetry-api;0.2.4
io.opentelemetry:opentelemetry-sdk;0.2.4
io.projectreactor:reactor-test;3.3.5.RELEASE
junit:junit;4.13-beta-3
+org.apache.maven:maven-compat;3.6.2
+org.apache.maven:maven-embedder;3.6.2
+org.apache.maven.wagon:wagon-http;3.3.4
+org.apache.maven.wagon:wagon-provider-api;3.3.4
org.assertj:assertj-core;3.11.1
org.bouncycastle:bcprov-jdk15on;1.60
+org.eclipse.aether:aether-connector-basic;1.1.0
+org.eclipse.aether:aether-transport-wagon;1.1.0
org.eclipse.jetty:jetty-http;9.4.11.v20180605
org.eclipse.jetty:jetty-server;9.4.11.v20180605
org.hamcrest:hamcrest-all;1.3
@@ -125,6 +134,8 @@ org.junit.platform:junit-platform-testkit;1.6.2
org.junit.vintage:junit-vintage-engine;5.6.2
org.openjdk.jmh:jmh-core;1.22
org.openjdk.jmh:jmh-generator-annprocess;1.22
+org.springframework.boot:spring-boot;2.2.0.RELEASE
+org.springframework:spring-context;5.2.0.RELEASE
org.spockframework:spock-core;1.3-groovy-2.5
org.testng:testng;6.14.3
uk.org.lidalia:slf4j-test;1.2.0
@@ -238,3 +249,4 @@ storage_com.microsoft.azure:azure-storage;8.4.0
# sdk\spring\azure-spring-boot\pom.xml
spring_io.micrometer:micrometer-core;1.3.0
spring_io.micrometer:micrometer-registry-azure-monitor;1.3.0
+spring_com.microsoft.azure:azure;1.34.0
diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt
index efdf57ee6fbd..a6237c228702 100644
--- a/eng/versioning/version_client.txt
+++ b/eng/versioning/version_client.txt
@@ -54,6 +54,9 @@ com.microsoft.azure:azure-data-gremlin-spring-boot-starter;2.2.4;2.2.5-beta.1
com.microsoft.azure:azure-keyvault-secrets-spring-boot-starter;2.2.4;2.2.5-beta.1
com.microsoft.azure:azure-servicebus-jms-spring-boot-starter;2.2.4;2.2.5-beta.1
com.microsoft.azure:azure-spring-boot-metrics-starter;2.2.4;2.2.5-beta.1
+com.microsoft.azure:azure-spring-boot-tests;2.2.4;2.2.5-beta.1
+com.microsoft.azure:azure-spring-boot-test-core;2.2.4;2.2.5-beta.1
+
# Unreleased dependencies: Copy the entry from above, prepend "unreleased_" and remove the current
# version. Unreleased dependencies are only valid for dependency versions.
diff --git a/sdk/spring/azure-spring-boot-starter-keyvault-secrets/README.md b/sdk/spring/azure-spring-boot-starter-keyvault-secrets/README.md
index 0017dd2b9f28..5d836ae8f86f 100644
--- a/sdk/spring/azure-spring-boot-starter-keyvault-secrets/README.md
+++ b/sdk/spring/azure-spring-boot-starter-keyvault-secrets/README.md
@@ -107,6 +107,62 @@ azure.keyvault.allow.telemetry=false
When telemetry is enabled, an HTTP request will be sent to URL `https://dc.services.visualstudio.com/v2/track`. So please make sure it's not blocked by your firewall.
Find more information about Azure Service Privacy Statement, please check [Microsoft Online Services Privacy Statement](https://www.microsoft.com/privacystatement/OnlineServices/Default.aspx).
+## Multiple Key Vault support
+
+If you want to use multiple key vaults you need to define names for each of the
+key vaults you want to use and in which order the key vaults should be consulted.
+If a property exists in multiple key vaults the order determine which value you
+will get back.
+
+The example below shows a setup for 2 key vaults, named `keyvault1` and
+`keyvault2`. The order specifies that `keyvault1` will be consulted first.
+
+```
+azure.keyvault.order=keyvault1,keyvault2
+azure.keyvault.keyvault1.uri=put-a-azure-keyvault-uri-here
+azure.keyvault.keyvault1.client-id=put-a-azure-client-id-here
+azure.keyvault.keyvault1.client-key=put-a-azure-client-key-here
+azure.keyvault.keyvault1.tenant-id=put-a-azure-tenant-id-here
+azure.keyvault.keyvault2.uri=put-a-azure-keyvault-uri-here
+azure.keyvault.keyvault2.client-id=put-a-azure-client-id-here
+azure.keyvault.keyvault2.client-key=put-a-azure-client-key-here
+azure.keyvault.keyvault2.tenant-id=put-a-azure-tenant-id-here
+```
+
+Note if you decide to use multiple key vault support and you already have an
+existing configuration, please make sure you migrate that configuration to the
+multiple key vault variant. Mixing multiple key vaults with an existing single
+key vault configuration is a non supported scenario.
+
+## Case sensitive key mode
+
+The new case sensitive mode allows you to use case sensitive key vault names. Note
+that the key vault secret key still needs to honor the naming limitation as
+described in [About keys, secrets, and certificates](https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates).
+
+To enable case sensitive mode use:
+
+```
+azure.keyvault.case-sensitive-keys=true
+```
+
+## Placeholders in properties
+
+If your Spring property is using a name that does not honor the key vault secret
+key limitation use the following technique as described by
+[Externalized Configuration](https://docs.spring.io/autorepo/docs/spring-boot/2.2.7.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config-placeholders-in-properties)
+in the Spring Boot documentation.
+
+An example of using a placeholder:
+
+```
+my.not.compliant.property=${myCompliantKeyVaultSecret}
+```
+
+The application will take care of getting the value that is backed by the
+`myCompliantKeyVaultSecret` key name and assign its value to the non compliant
+`my.not.compliant.property`.
+
## Troubleshooting
## Next steps
## Contributing
diff --git a/sdk/spring/azure-spring-boot-tests/README.md b/sdk/spring/azure-spring-boot-tests/README.md
new file mode 100644
index 000000000000..928b7a2efe58
--- /dev/null
+++ b/sdk/spring/azure-spring-boot-tests/README.md
@@ -0,0 +1,18 @@
+#Azure Spring Boot Integration tests client library for Java
+
+## Key concepts
+The structure of integration tests are organized as:
+
+- azure-spring-boot-tests (top folder for integration tests)
+ - azure-spring-boot-test-core (common code shared by integration tests)
+ - azure-spring-boot-test-keyvault (integration tests for key vault starter)
+ - azure-spring-boot-test-aad (integration tests for aad starter)
+ - azure-spring-boot-test-cosmosdb (integration tests for cosmos starter)
+ - azure-spring-boot-test-application (an application used by other integration tests)
+
+## Getting started
+## Key concepts
+## Examples
+## Troubleshooting
+## Next steps
+## Contributing
diff --git a/sdk/spring/azure-spring-boot-tests/azure-spring-boot-test-aad/pom.xml b/sdk/spring/azure-spring-boot-tests/azure-spring-boot-test-aad/pom.xml
new file mode 100644
index 000000000000..fac57c9a0259
--- /dev/null
+++ b/sdk/spring/azure-spring-boot-tests/azure-spring-boot-test-aad/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ azure-spring-boot-tests
+ com.microsoft.azure
+ 2.2.5-beta.1
+ ../pom.xml
+
+ 4.0.0
+
+ azure-spring-boot-test-aad
+
+
+
+ com.microsoft.azure
+ azure-spring-boot-test-core
+ 2.2.5-beta.1
+
+
+ com.microsoft.azure
+ azure-active-directory-spring-boot-starter
+ 2.2.5-beta.1
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.2.0.RELEASE
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.0.0-M3
+
+
+
+
+ com.microsoft.azure:*
+ org.springframework.boot:spring-boot-starter-web:[2.2.0.RELEASE]
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 2.22.0
+
+ ${skipSpringITs}
+
+
+
+
+
diff --git a/sdk/spring/azure-spring-boot-tests/azure-spring-boot-test-aad/src/test/java/com/microsoft/azure/test/keyvault/approle/AADAppRoleStatelessAuthenticationFilterIT.java b/sdk/spring/azure-spring-boot-tests/azure-spring-boot-test-aad/src/test/java/com/microsoft/azure/test/keyvault/approle/AADAppRoleStatelessAuthenticationFilterIT.java
new file mode 100644
index 000000000000..792f62b587d6
--- /dev/null
+++ b/sdk/spring/azure-spring-boot-tests/azure-spring-boot-test-aad/src/test/java/com/microsoft/azure/test/keyvault/approle/AADAppRoleStatelessAuthenticationFilterIT.java
@@ -0,0 +1,127 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.test.keyvault.approle;
+
+import com.microsoft.azure.spring.autoconfigure.aad.AADAppRoleStatelessAuthenticationFilter;
+import com.microsoft.azure.test.utils.AppRunner;
+import com.microsoft.azure.test.oauth.OAuthResponse;
+import com.microsoft.azure.test.oauth.OAuthUtils;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+
+import static com.microsoft.azure.test.oauth.OAuthUtils.AAD_CLIENT_ID;
+import static com.microsoft.azure.test.oauth.OAuthUtils.AAD_CLIENT_SECRET;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class AADAppRoleStatelessAuthenticationFilterIT {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AADAppRoleStatelessAuthenticationFilterIT.class);
+ private final RestTemplate restTemplate = new RestTemplate();
+
+ @Test
+ @Ignore
+ public void testAADAppRoleStatelessAuthenticationFilter() {
+ final OAuthResponse authResponse = OAuthUtils.executeOAuth2ROPCFlow(System.getenv(AAD_CLIENT_ID),
+ System.getenv(AAD_CLIENT_SECRET));
+ assertNotNull(authResponse);
+
+ try (AppRunner app = new AppRunner(DumbApp.class)) {
+
+ app.property("azure.activedirectory.client-id", System.getenv(AAD_CLIENT_ID));
+ app.property("azure.activedirectory.session-stateless", "true");
+
+ app.start();
+
+ final ResponseEntity response = restTemplate.exchange(app.root() + "public",
+ HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class, new HashMap<>());
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertEquals("public endpoint response", response.getBody());
+
+ try {
+ restTemplate.exchange(app.root() + "authorized",
+ HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class, new HashMap<>());
+ } catch (Exception e) {
+ assertEquals(HttpClientErrorException.Forbidden.class, e.getClass());
+ }
+
+ final HttpHeaders headers = new HttpHeaders();
+ headers.set("Authorization", String.format("Bearer %s", authResponse.getIdToken()));
+ final HttpEntity
+ * If the (normalizedName+) AZURE_KEYVAULT_ENABLED is set to false the user
+ * wants to disable the key vault, if it is set to true the key vault will
+ * be enabled.
+ *
+ *
+ * If the KeyVaultClient implementation is not available then key vault
+ * support will not be enabled.
+ *
+ *
+ * @param environment the environment.
+ * @param normalizedName the normalized name used to differentiate between
+ * multiple key vaults.
+ * @return true if the key vault is enabled, false otherwise.
+ */
+ private boolean isKeyVaultEnabled(ConfigurableEnvironment environment, String normalizedName) {
+ if (environment.getProperty(AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_VAULT_URI) == null) {
return false;
}
- return environment.getProperty(Constants.AZURE_KEYVAULT_ENABLED, Boolean.class, true)
- && isKeyVaultClientAvailable();
+ return environment.getProperty(AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_ENABLED,
+ Boolean.class, true) && isKeyVaultClientAvailable();
+ }
+
+ /**
+ * Determine whether or not multiple key vaults are enabled.
+ *
+ *
+ * Look for the AZURE_KEYVAULT_ORDER property to determine if multiple key
+ * vault support should be enabled.
+ *
+ * The normalizedName is used to target a specific key vault (note if the
+ * name is the empty string it works as before with only one key vault
+ * present). The normalized name is the name of the specific key vault plus
+ * a trailing "." at the end.
+ *
+ *
+ * @param normalizedName the normalized name.
+ */
+ public void addKeyVaultPropertySource(String normalizedName) {
+ final String vaultUri = getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_VAULT_URI);
final Long refreshInterval = Optional.ofNullable(
- this.environment.getProperty(Constants.AZURE_KEYVAULT_REFRESH_INTERVAL))
- .map(Long::valueOf).orElse(Constants.DEFAULT_REFRESH_INTERVAL_MS);
+ this.environment.getProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_REFRESH_INTERVAL))
+ .map(Long::valueOf).orElse(DEFAULT_REFRESH_INTERVAL_MS);
final Binder binder = Binder.get(this.environment);
- final List secretKeys = binder.bind(Constants.AZURE_KEYVAULT_SECRET_KEYS, Bindable.listOf(String.class))
+ final List secretKeys = binder.bind(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_SECRET_KEYS, Bindable.listOf(String.class))
.orElse(Collections.emptyList());
- final TokenCredential tokenCredential = getCredentials();
+ final TokenCredential tokenCredential = getCredentials(normalizedName);
final SecretClient secretClient = new SecretClientBuilder()
.vaultUrl(vaultUri)
.credential(tokenCredential)
@@ -66,16 +92,28 @@ public void addKeyVaultPropertySource() {
.buildClient();
try {
final MutablePropertySources sources = this.environment.getPropertySources();
+ final boolean caseSensitive = Boolean.parseBoolean(
+ this.environment.getProperty(Constants.AZURE_KEYVAULT_CASE_SENSITIVE_KEYS, "false"));
final KeyVaultOperation kvOperation = new KeyVaultOperation(secretClient,
vaultUri,
refreshInterval,
- secretKeys);
+ secretKeys,
+ caseSensitive);
- if (sources.contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
- sources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
+ if (normalizedName.equals("")) {
+ if (sources.contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
+ sources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new KeyVaultPropertySource(kvOperation));
+ } else {
+ sources.addFirst(new KeyVaultPropertySource(kvOperation));
+ }
} else {
- sources.addFirst(new KeyVaultPropertySource(kvOperation));
+ if (sources.contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
+ sources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
+ new KeyVaultPropertySource(normalizedName, kvOperation));
+ } else {
+ sources.addFirst(new KeyVaultPropertySource(normalizedName, kvOperation));
+ }
}
} catch (final Exception ex) {
@@ -83,15 +121,36 @@ public void addKeyVaultPropertySource() {
}
}
+ /**
+ * Get the token credentials.
+ *
+ * @return the token credentials.
+ */
public TokenCredential getCredentials() {
+ return getCredentials("");
+ }
+
+ /**
+ * Get the token credentials.
+ *
+ * @param normalizedName the normalized name of the key vault.
+ * @return the token credentials.
+ */
+ public TokenCredential getCredentials(String normalizedName) {
//use service principle to authenticate
- if (this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_ID)
- && this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_KEY)
- && this.environment.containsProperty(Constants.AZURE_KEYVAULT_TENANT_ID)) {
+ if (this.environment.containsProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_ID)
+ && this.environment.containsProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_KEY)
+ && this.environment.containsProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_TENANT_ID)) {
LOGGER.debug("Will use custom credentials");
- final String clientId = getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_ID);
- final String clientKey = getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_KEY);
- final String tenantId = getProperty(this.environment, Constants.AZURE_KEYVAULT_TENANT_ID);
+ final String clientId = getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_ID);
+ final String clientKey = getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_KEY);
+ final String tenantId = getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_TENANT_ID);
return new ClientSecretCredentialBuilder()
.clientId(clientId)
.clientSecret(clientKey)
@@ -99,31 +158,42 @@ public TokenCredential getCredentials() {
.build();
}
//use certificate to authenticate
- if (this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_ID)
- && this.environment.containsProperty(Constants.AZURE_KEYVAULT_CERTIFICATE_PATH)
- && this.environment.containsProperty(Constants.AZURE_KEYVAULT_TENANT_ID)) {
+ if (this.environment.containsProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_ID)
+ && this.environment.containsProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CERTIFICATE_PATH)
+ && this.environment.containsProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_TENANT_ID)) {
// Password can be empty
- final String certPwd = this.environment.getProperty(Constants.AZURE_KEYVAULT_CERTIFICATE_PASSWORD);
- final String certPath = getProperty(this.environment, Constants.AZURE_KEYVAULT_CERTIFICATE_PATH);
+ final String certPwd = this.environment.getProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CERTIFICATE_PASSWORD);
+ final String certPath = getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CERTIFICATE_PATH);
if (StringUtils.isEmpty(certPwd)) {
return new ClientCertificateCredentialBuilder()
- .tenantId(getProperty(this.environment, Constants.AZURE_KEYVAULT_TENANT_ID))
- .clientId(getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_ID))
+ .tenantId(getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_TENANT_ID))
+ .clientId(getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_ID))
.pemCertificate(certPath)
.build();
} else {
return new ClientCertificateCredentialBuilder()
- .tenantId(getProperty(this.environment, Constants.AZURE_KEYVAULT_TENANT_ID))
- .clientId(getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_ID))
+ .tenantId(getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_TENANT_ID))
+ .clientId(getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_ID))
.pfxCertificate(certPath, certPwd)
.build();
}
}
//use MSI to authenticate
- if (this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_ID)) {
+ if (this.environment.containsProperty(
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_ID)) {
LOGGER.debug("Will use MSI credentials with specified clientId");
- final String clientId = getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_ID);
+ final String clientId = getProperty(this.environment,
+ AZURE_KEYVAULT_PREFIX + normalizedName + AZURE_KEYVAULT_CLIENT_ID);
return new ManagedIdentityCredentialBuilder().clientId(clientId).build();
}
LOGGER.debug("Will use MSI credentials");
@@ -142,7 +212,7 @@ private String getProperty(final ConfigurableEnvironment env, final String prope
private boolean allowTelemetry(final ConfigurableEnvironment env) {
Assert.notNull(env, "env must not be null!");
- return env.getProperty(Constants.AZURE_KEYVAULT_ALLOW_TELEMETRY, Boolean.class, true);
+ return env.getProperty(AZURE_KEYVAULT_PREFIX + AZURE_KEYVAULT_ALLOW_TELEMETRY, Boolean.class, true);
}
private void sendTelemetry() {
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java b/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java
index f97e1595ee33..5bdd133a7fbe 100644
--- a/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java
@@ -3,72 +3,78 @@
package com.microsoft.azure.keyvault.spring;
-import com.azure.core.http.rest.PagedIterable;
import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
-import com.azure.security.keyvault.secrets.models.SecretProperties;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Locale;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Encapsulate key vault secret client in this class to provide a delegate of key vault operations.
*/
public class KeyVaultOperation {
- private final long cacheRefreshIntervalInMs;
- private final List secretKeys;
- private final Object refreshLock = new Object();
- private final SecretClient secretClient;
- private final String vaultUri;
-
- private ArrayList propertyNames = new ArrayList<>();
- private String[] propertyNamesArr;
-
- private final AtomicLong lastUpdateTime = new AtomicLong();
- private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+ /**
+ * Stores the case sensitive flag.
+ */
+ private final boolean caseSensitive;
- public KeyVaultOperation(final SecretClient secretClient,
- String vaultUri,
- final long refreshInterval,
- final List secretKeys) {
- this.cacheRefreshIntervalInMs = refreshInterval;
- this.secretKeys = secretKeys;
- this.secretClient = secretClient;
+ private final SecretClient keyVaultClient;
+ private final String vaultUri;
+ private volatile List secretNames;
+ private final boolean secretNamesAlreadyConfigured;
+ private final long secretNamesRefreshIntervalInMs;
+ private volatile long secretNamesLastUpdateTime;
+
+ public KeyVaultOperation(
+ final SecretClient keyVaultClient,
+ String vaultUri,
+ final long secretKeysRefreshIntervalInMs,
+ final List secretNames,
+ boolean caseSensitive
+ ) {
+ this.caseSensitive = caseSensitive;
+ this.keyVaultClient = keyVaultClient;
// TODO(pan): need to validate why last '/' need to be truncated.
this.vaultUri = StringUtils.trimTrailingCharacter(vaultUri.trim(), '/');
- fillSecretsList();
- }
-
- public String[] list() {
- try {
- this.rwLock.readLock().lock();
- return propertyNamesArr;
- } finally {
- this.rwLock.readLock().unlock();
- }
+ this.secretNames = Optional.ofNullable(secretNames)
+ .map(Collection::stream)
+ .orElseGet(Stream::empty)
+ .map(this::toKeyVaultSecretName)
+ .distinct()
+ .collect(Collectors.toList());
+ this.secretNamesAlreadyConfigured = !this.secretNames.isEmpty();
+ this.secretNamesRefreshIntervalInMs = secretKeysRefreshIntervalInMs;
+ this.secretNamesLastUpdateTime = 0;
}
- private String getKeyVaultSecretName(@NonNull String property) {
- if (property.matches("[a-z0-9A-Z-]+")) {
- return property.toLowerCase(Locale.US);
- } else if (property.matches("[A-Z0-9_]+")) {
- return property.toLowerCase(Locale.US).replaceAll("_", "-");
+ public String[] getPropertyNames() {
+ refreshSecretKeysIfNeeded();
+ if (!caseSensitive) {
+ return Optional.ofNullable(secretNames)
+ .map(Collection::stream)
+ .orElseGet(Stream::empty)
+ .flatMap(p -> Stream.of(p, p.replaceAll("-", ".")))
+ .distinct()
+ .toArray(String[]::new);
} else {
- return property.toLowerCase(Locale.US)
- .replaceAll("-", "") // my-project -> myproject
- .replaceAll("_", "") // my_project -> myproject
- .replaceAll("\\.", "-"); // acme.myproject -> acme-myproject
+ return Optional.ofNullable(secretNames)
+ .map(Collection::stream)
+ .orElseGet(Stream::empty)
+ .distinct()
+ .toArray(String[]::new);
}
}
+
/**
* For convention we need to support all relaxed binding format from spring, these may include:
*
@@ -85,67 +91,68 @@ private String getKeyVaultSecretName(@NonNull String property) {
* @param property of secret instance.
* @return the value of secret with given name or null.
*/
- public String get(final String property) {
- Assert.hasText(property, "property should contain text.");
- final String secretName = getKeyVaultSecretName(property);
-
- //if user don't set specific secret keys, then refresh token
- if (this.secretKeys == null || secretKeys.size() == 0) {
- // refresh periodically
- refreshPropertyNames();
- }
- if (this.propertyNames.contains(secretName)) {
- final KeyVaultSecret secret = this.secretClient.getSecret(secretName);
- return secret == null ? null : secret.getValue();
+ private String toKeyVaultSecretName(@NonNull String property) {
+ if (!caseSensitive) {
+ if (property.matches("[a-z0-9A-Z-]+")) {
+ return property.toLowerCase(Locale.US);
+ } else if (property.matches("[A-Z0-9_]+")) {
+ return property.toLowerCase(Locale.US).replaceAll("_", "-");
+ } else {
+ return property.toLowerCase(Locale.US)
+ .replaceAll("-", "") // my-project -> myproject
+ .replaceAll("_", "") // my_project -> myproject
+ .replaceAll("\\.", "-"); // acme.myproject -> acme-myproject
+ }
} else {
- return null;
+ return property;
}
}
- private void refreshPropertyNames() {
- if (System.currentTimeMillis() - this.lastUpdateTime.get() > this.cacheRefreshIntervalInMs) {
- synchronized (this.refreshLock) {
- if (System.currentTimeMillis() - this.lastUpdateTime.get() > this.cacheRefreshIntervalInMs) {
- this.lastUpdateTime.set(System.currentTimeMillis());
- fillSecretsList();
- }
- }
- }
+ public String get(final String property) {
+ Assert.hasText(property, "property should contain text.");
+ refreshSecretKeysIfNeeded();
+ return Optional.of(property)
+ .map(this::toKeyVaultSecretName)
+ .filter(secretNames::contains)
+ .map(this::getValueFromKeyVault)
+ .orElse(null);
}
- private void fillSecretsList() {
- try {
- this.rwLock.writeLock().lock();
- if (this.secretKeys == null || this.secretKeys.size() == 0) {
- this.propertyNames.clear();
+ private synchronized void refreshSecretKeysIfNeeded() {
+ if (needRefreshSecretKeys()) {
+ refreshKeyVaultSecretNames();
+ }
+ }
- final PagedIterable secretProperties = this.secretClient.listPropertiesOfSecrets();
- secretProperties.forEach(s -> {
- final String secretName = s.getName().replace(this.vaultUri + "/secrets/", "");
- addSecretIfNotExist(secretName);
- });
+ private boolean needRefreshSecretKeys() {
+ return !secretNamesAlreadyConfigured
+ && System.currentTimeMillis() - this.secretNamesLastUpdateTime > this.secretNamesRefreshIntervalInMs;
+ }
- this.lastUpdateTime.set(System.currentTimeMillis());
- } else {
- for (final String secretKey : this.secretKeys) {
- addSecretIfNotExist(secretKey);
- }
- }
- this.propertyNamesArr = this.propertyNames.toArray(new String[0]);
- } finally {
- this.rwLock.writeLock().unlock();
- }
+ private void refreshKeyVaultSecretNames() {
+ secretNames = Optional.of(keyVaultClient)
+ .map(SecretClient::listPropertiesOfSecrets)
+ .map(secretProperties -> {
+ final List secretNameList = new ArrayList<>();
+ secretProperties.forEach(s -> {
+ final String secretName = s.getName().replace(vaultUri + "/secrets/", "");
+ secretNameList.add(secretName);
+ });
+ return secretNameList;
+ })
+ .map(Collection::stream)
+ .orElseGet(Stream::empty)
+ .map(this::toKeyVaultSecretName)
+ .distinct()
+ .collect(Collectors.toList());
+ this.secretNamesLastUpdateTime = System.currentTimeMillis();
}
- private void addSecretIfNotExist(final String secretName) {
- final String secretNameLowerCase = secretName.toLowerCase(Locale.US);
- if (!propertyNames.contains(secretNameLowerCase)) {
- propertyNames.add(secretNameLowerCase);
- }
- final String secretNameSeparatedByDot = secretNameLowerCase.replaceAll("-", ".");
- if (!propertyNames.contains(secretNameSeparatedByDot)) {
- propertyNames.add(secretNameSeparatedByDot);
- }
+ private String getValueFromKeyVault(String name) {
+ return Optional.ofNullable(name)
+ .map(keyVaultClient::getSecret)
+ .map(KeyVaultSecret::getValue)
+ .orElse(null);
}
}
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySource.java b/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySource.java
index dceb197c7b0c..6f2f9955a94a 100644
--- a/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySource.java
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySource.java
@@ -3,24 +3,30 @@
package com.microsoft.azure.keyvault.spring;
-import com.microsoft.azure.utils.Constants;
import org.springframework.core.env.EnumerablePropertySource;
+import static com.microsoft.azure.utils.Constants.AZURE_KEYVAULT_PROPERTYSOURCE_NAME;
+import org.springframework.core.env.PropertySource;
/**
* A key vault implementation of {@link EnumerablePropertySource} to enumerate all property pairs in Key Vault.
*/
-public class KeyVaultPropertySource extends EnumerablePropertySource {
+public class KeyVaultPropertySource extends PropertySource {
private final KeyVaultOperation operations;
+ public KeyVaultPropertySource(String keyVaultName, KeyVaultOperation operation) {
+ super(keyVaultName, operation);
+ this.operations = operation;
+ }
+
public KeyVaultPropertySource(KeyVaultOperation operation) {
- super(Constants.AZURE_KEYVAULT_PROPERTYSOURCE_NAME, operation);
+ super(AZURE_KEYVAULT_PROPERTYSOURCE_NAME, operation);
this.operations = operation;
}
public String[] getPropertyNames() {
- return this.operations.list();
+ return this.operations.getPropertyNames();
}
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/utils/Constants.java b/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/utils/Constants.java
index 2c2426ce955a..c3050b3a5af2 100644
--- a/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/utils/Constants.java
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/microsoft/azure/utils/Constants.java
@@ -4,19 +4,24 @@
package com.microsoft.azure.utils;
public class Constants {
+ /**
+ * The constant used to define the prefix of all Azure Key Vault properties.
+ */
+ public static final String AZURE_KEYVAULT_PREFIX = "azure.keyvault.";
+
public static final String AZURE_KEYVAULT_USER_AGENT = "spring-boot-starter/" + PropertyLoader.getProjectVersion();
- public static final String AZURE_KEYVAULT_CLIENT_ID = "azure.keyvault.client-id";
- public static final String AZURE_KEYVAULT_CLIENT_KEY = "azure.keyvault.client-key";
- public static final String AZURE_KEYVAULT_TENANT_ID = "azure.keyvault.tenant-id";
- public static final String AZURE_KEYVAULT_CERTIFICATE_PATH = "azure.keyvault.certificate.path";
- public static final String AZURE_KEYVAULT_CERTIFICATE_PASSWORD = "azure.keyvault.certificate.password";
- public static final String AZURE_KEYVAULT_ENABLED = "azure.keyvault.enabled";
- public static final String AZURE_KEYVAULT_VAULT_URI = "azure.keyvault.uri";
- public static final String AZURE_KEYVAULT_REFRESH_INTERVAL = "azure.keyvault.refresh-interval";
- public static final String AZURE_KEYVAULT_SECRET_KEYS = "azure.keyvault.secret.keys";
+ public static final String AZURE_KEYVAULT_CLIENT_ID = "client-id";
+ public static final String AZURE_KEYVAULT_CLIENT_KEY = "client-key";
+ public static final String AZURE_KEYVAULT_TENANT_ID = "tenant-id";
+ public static final String AZURE_KEYVAULT_CERTIFICATE_PATH = "certificate.path";
+ public static final String AZURE_KEYVAULT_CERTIFICATE_PASSWORD = "certificate.password";
+ public static final String AZURE_KEYVAULT_ENABLED = "enabled";
+ public static final String AZURE_KEYVAULT_VAULT_URI = "uri";
+ public static final String AZURE_KEYVAULT_REFRESH_INTERVAL = "refresh-interval";
+ public static final String AZURE_KEYVAULT_SECRET_KEYS = "secret.keys";
public static final String AZURE_KEYVAULT_PROPERTYSOURCE_NAME = "azurekv";
- public static final String AZURE_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS = "azure.keyvault.token-acquire-timeout-seconds";
- public static final String AZURE_KEYVAULT_ALLOW_TELEMETRY = "azure.keyvault.allow.telemetry";
+ public static final String AZURE_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS = "token-acquire-timeout-seconds";
+ public static final String AZURE_KEYVAULT_ALLOW_TELEMETRY = "allow.telemetry";
public static final long DEFAULT_REFRESH_INTERVAL_MS = 1800000L;
public static final long TOKEN_ACQUIRE_TIMEOUT_SECS = 60L;
@@ -32,4 +37,14 @@ public class Constants {
public static final String SPRINGBOOT_KEY_VAULT_APPLICATION_ID =
String.join("-", AZURE, SPRING, KEY_VAULT) + "/" + SPRINGBOOT_VERSION;
+ /**
+ * The constant used to define the order of the key vaults you are
+ * delivering (comma delimited, e.g 'myvault, myvault2').
+ */
+ public static final String AZURE_KEYVAULT_ORDER = "order";
+
+ /**
+ * Defines the constant for the property that enables/disables case sensitive keys.
+ */
+ public static final String AZURE_KEYVAULT_CASE_SENSITIVE_KEYS = "azure.keyvault.case-sensitive-keys";
}
diff --git a/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories b/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories
index 776f90864ad4..e517f6f32156 100644
--- a/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories
+++ b/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories
@@ -5,7 +5,4 @@ com.microsoft.azure.spring.autoconfigure.cosmosdb.CosmosDbReactiveRepositoriesAu
com.microsoft.azure.spring.autoconfigure.gremlin.GremlinAutoConfiguration,\
com.microsoft.azure.spring.autoconfigure.gremlin.GremlinRepositoriesAutoConfiguration,\
com.microsoft.azure.spring.autoconfigure.aad.AADAuthenticationFilterAutoConfiguration,\
-com.microsoft.azure.spring.autoconfigure.aad.AADOAuth2AutoConfiguration,\
-com.microsoft.azure.spring.autoconfigure.btoc.AADB2CAutoConfiguration,\
-com.microsoft.azure.spring.autoconfigure.metrics.AzureMonitorMetricsExportAutoConfiguration,\
-com.microsoft.azure.spring.autoconfigure.jms.ServiceBusJMSAutoConfiguration
+com.microsoft.azure.spring.autoconfigure.aad.AADOAuth2AutoConfiguration
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/CaseSensitiveKeyVaultTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/CaseSensitiveKeyVaultTest.java
new file mode 100644
index 000000000000..f55b24306216
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/CaseSensitiveKeyVaultTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.keyvault.spring;
+
+import com.azure.security.keyvault.secrets.SecretClient;
+import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
+import static com.microsoft.azure.utils.Constants.TOKEN_ACQUIRE_TIMEOUT_SECS;
+import java.util.Arrays;
+import java.util.List;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import static org.mockito.Mockito.when;
+import org.mockito.junit.MockitoJUnitRunner;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class CaseSensitiveKeyVaultTest {
+ @Mock
+ private SecretClient keyVaultClient;
+
+ @Test
+ public void testGet() {
+ final List keys = Arrays.asList("key1", "Key2");
+
+ final KeyVaultOperation keyVaultOperation = new KeyVaultOperation(
+ keyVaultClient,
+ "https:fake.vault.com",
+ TOKEN_ACQUIRE_TIMEOUT_SECS,
+ keys,
+ true);
+
+ final KeyVaultSecret key1 = new KeyVaultSecret("key1", "value1");
+ when(keyVaultClient.getSecret("key1")).thenReturn(key1);
+ final KeyVaultSecret key2 = new KeyVaultSecret("Key2", "Value2");
+ when(keyVaultClient.getSecret("Key2")).thenReturn(key2);
+
+ assertEquals("value1", keyVaultOperation.get("key1"));
+ assertEquals("Value2", keyVaultOperation.get("Key2"));
+ }
+}
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java
index bca8b8dc7620..6663972fa573 100644
--- a/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java
@@ -25,6 +25,7 @@
import static com.microsoft.azure.utils.Constants.AZURE_KEYVAULT_CERTIFICATE_PATH;
import static com.microsoft.azure.utils.Constants.AZURE_KEYVAULT_CLIENT_ID;
+import static com.microsoft.azure.utils.Constants.AZURE_KEYVAULT_PREFIX;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -58,9 +59,9 @@ public void testGetCredentialsWhenUsingClientAndKey() {
@Test
public void testGetCredentialsWhenPFXCertConfigured() {
- testProperties.put(AZURE_KEYVAULT_CLIENT_ID, "aaaa-bbbb-cccc-dddd");
+ testProperties.put(AZURE_KEYVAULT_PREFIX + AZURE_KEYVAULT_CLIENT_ID, "aaaa-bbbb-cccc-dddd");
testProperties.put("azure.keyvault.tenant-id", "myid");
- testProperties.put(AZURE_KEYVAULT_CERTIFICATE_PATH, "fake-pfx-cert.pfx");
+ testProperties.put(AZURE_KEYVAULT_PREFIX + AZURE_KEYVAULT_CERTIFICATE_PATH, "fake-pfx-cert.pfx");
propertySources.addLast(new MapPropertySource("Test_Properties", testProperties));
keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment);
@@ -124,6 +125,29 @@ public void postProcessorOrderConfigurable() {
context.getBean(KeyVaultEnvironmentPostProcessor.class).getOrder());
});
}
+
+ /**
+ * Test the multiple key vault support.
+ */
+ @Test
+ public void testMultipleKeyVaults() {
+ testProperties.put("azure.keyvault.order", "myvault, myvault2");
+ testProperties.put("azure.keyvault.myvault.client-id", "aaaa-bbbb-cccc-dddd");
+ testProperties.put("azure.keyvault.myvault.client-key", "mySecret");
+ testProperties.put("azure.keyvault.myvault.tenant-id", "myid");
+ testProperties.put("azure.keyvault.myvault2.client-id", "aaaa-bbbb-cccc-dddd");
+ testProperties.put("azure.keyvault.myvault2.client-key", "mySecret");
+ testProperties.put("azure.keyvault.myvault2.tenant-id", "myid");
+ propertySources.addLast(new MapPropertySource("Test_Properties", testProperties));
+
+ keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment);
+
+ final TokenCredential credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials("myvault.");
+ assertThat(credentials, IsInstanceOf.instanceOf(ClientSecretCredential.class));
+
+ final TokenCredential credentials2 = keyVaultEnvironmentPostProcessorHelper.getCredentials("myvault2.");
+ assertThat(credentials2, IsInstanceOf.instanceOf(ClientSecretCredential.class));
+ }
}
@Configuration
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java
index 31b652fcc8b0..51094f3020e4 100644
--- a/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java
@@ -63,7 +63,8 @@ public void setupSecretBundle(String id, String value, List secretKeysCo
keyVaultOperation = new KeyVaultOperation(keyVaultClient,
FAKE_VAULT_URI,
Constants.TOKEN_ACQUIRE_TIMEOUT_SECS,
- secretKeysConfig);
+ secretKeysConfig,
+ false);
}
@Test
@@ -90,13 +91,13 @@ public void testGetAndHitWhenSecretsProvided() {
public void testList() {
//test list with no specific secret keys
setupSecretBundle(TEST_PROPERTY_NAME_1, TEST_PROPERTY_NAME_1, null);
- final String[] result = keyVaultOperation.list();
+ final String[] result = keyVaultOperation.getPropertyNames();
assertThat(result.length).isEqualTo(1);
assertThat(result[0]).isEqualToIgnoringCase(TEST_PROPERTY_NAME_1);
//test list with specific secret key configs
setupSecretBundle(TEST_PROPERTY_NAME_1, TEST_PROPERTY_NAME_1, SECRET_KEYS_CONFIG);
- final String[] specificResult = keyVaultOperation.list();
+ final String[] specificResult = keyVaultOperation.getPropertyNames();
assertThat(specificResult.length).isEqualTo(3);
assertThat(specificResult[0]).isEqualTo(SECRET_KEYS_CONFIG.get(0));
}
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySourceUnitTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySourceUnitTest.java
index 7aa681435c72..4b546a57f198 100644
--- a/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySourceUnitTest.java
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultPropertySourceUnitTest.java
@@ -26,7 +26,7 @@ public void setup() {
final String[] propertyNameList = new String[]{TEST_PROPERTY_NAME_1};
when(keyVaultOperation.get(anyString())).thenReturn(TEST_PROPERTY_NAME_1);
- when(keyVaultOperation.list()).thenReturn(propertyNameList);
+ when(keyVaultOperation.getPropertyNames()).thenReturn(propertyNameList);
keyVaultPropertySource = new KeyVaultPropertySource(keyVaultOperation);
}
diff --git a/sdk/spring/pom.xml b/sdk/spring/pom.xml
index f0bee2850414..b9e7c1e1216c 100644
--- a/sdk/spring/pom.xml
+++ b/sdk/spring/pom.xml
@@ -19,6 +19,7 @@
azure-spring-boot-starter-metricsazure-spring-boot-starter-servicebus-jmsazure-spring-boot-samples
+ azure-spring-boot-tests
diff --git a/sdk/spring/tests.yml b/sdk/spring/tests.yml
index 9da99dbd701d..0a51f922ad7b 100644
--- a/sdk/spring/tests.yml
+++ b/sdk/spring/tests.yml
@@ -1,9 +1,32 @@
trigger: none
jobs:
- - template: ../../eng/pipelines/templates/jobs/archetype-sdk-tests.yml
- parameters:
- ServiceDirectory: spring
- EnvVars:
- AZURE_TEST_MODE: LIVE
- Artifacts: []
+ - template: ../../eng/pipelines/templates/jobs/archetype-sdk-tests.yml
+ parameters:
+ TimeoutInMinutes: 240
+ ServiceDirectory: spring
+ TestStepMavenInputs:
+ options: '-Dmaven.wagon.http.pool=false $(DefaultOptions) -Dmaven.javadoc.skip=true -Drevapi.skip=true -DskipSpringITs=false -pl $(ProjectList)'
+ goals: "verify"
+
+ Artifacts:
+ - name: azure-spring-boot-tests
+ groupId: com.microsoft.azure
+ safeName: azurespringboot-tests
+ - name: azure-spring-boot-test-core
+ groupId: com.microsoft.azure
+ safeName: azurespringboottestcore
+ - name: azure-spring-boot-test-cosmosdb
+ groupId: com.microsoft.azure
+ safeName: azurespringboottestcosmosdb
+ - name: azure-spring-boot-test-aad
+ groupId: com.microsoft.azure
+ safeName: azurespringboottestaad
+ - name: azure-spring-boot-test-keyvault
+ groupId: com.microsoft.azure
+ safeName: azurespringboottestkeyvault
+ - name: azure-spring-boot-test-application
+ groupId: com.microsoft.azure
+ safeName: azurespringboottestapplication
+ EnvVars:
+ AZURE_TEST_MODE: LIVE