diff --git a/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/README.md b/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/README.md index a2a1acb5..23bdd0a2 100644 --- a/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/README.md +++ b/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/README.md @@ -70,6 +70,7 @@ Open `application.properties` file and add below properties to specify your Azur azure.keyvault.uri=put-your-azure-keyvault-uri-here azure.keyvault.client-id=put-your-azure-client-id-here azure.keyvault.client-key=put-your-azure-client-key-here +azure.keyvault.tenant-id=put-your-azure-tenant-id-here # Uncomment following property if you want to specify the secrets to load from Key Vault # azure.keyvault.secret.keys=yourSecretPropertyName1,yourSecretPropertyName2 diff --git a/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/src/main/resources/application.properties b/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/src/main/resources/application.properties index 05c88a63..550e4e8e 100644 --- a/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/src/main/resources/application.properties +++ b/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample/src/main/resources/application.properties @@ -1,3 +1,4 @@ azure.keyvault.client-id=put-your-azure-client-id-here azure.keyvault.client-key=put-your-azure-client-key-here azure.keyvault.uri=put-your-azure-keyvault-uri-here +azure.keyvault.tenant-id= diff --git a/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/README.md b/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/README.md index 4b0ff647..de0f6905 100644 --- a/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/README.md +++ b/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/README.md @@ -15,7 +15,7 @@ If you are using Maven, add the following dependency. com.microsoft.azure azure-keyvault-secrets-spring-boot-starter - 0.2.3 + 2.2.1 ``` @@ -26,6 +26,7 @@ azure.keyvault.enabled=true azure.keyvault.uri=put-your-azure-keyvault-uri-here azure.keyvault.client-id=put-your-azure-client-id-here azure.keyvault.client-key=put-your-azure-client-key-here +azure.keyvault.tenant-id=put-your-azure-tenant-id-here azure.keyvault.token-acquire-timeout-seconds=60 azure.keyvault.refresh-interval=1800000 azure.keyvault.secret.keys=key1,key2,key3 diff --git a/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/pom.xml b/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/pom.xml index 09a4168e..08d0921c 100644 --- a/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/pom.xml +++ b/azure-spring-boot-starters/azure-keyvault-secrets-spring-boot-starter/pom.xml @@ -34,16 +34,6 @@ com.microsoft.azure azure-spring-boot - - com.microsoft.azure - azure-keyvault - - - com.microsoft.azure - azure-client-runtime - - - com.microsoft.azure azure-client-authentication diff --git a/azure-spring-boot-tests/azure-spring-boot-test-application/pom.xml b/azure-spring-boot-tests/azure-spring-boot-test-application/pom.xml index c0f73aad..b391121f 100644 --- a/azure-spring-boot-tests/azure-spring-boot-test-application/pom.xml +++ b/azure-spring-boot-tests/azure-spring-boot-test-application/pom.xml @@ -20,6 +20,7 @@ ${project.basedir}/../.. UTF-8 UTF-8 + 1.2.61 @@ -38,6 +39,12 @@ com.microsoft.azure azure-keyvault-secrets-spring-boot-starter + + + com.alibaba + fastjson + ${fastjson.version} + diff --git a/azure-spring-boot-tests/azure-spring-boot-test-application/src/main/java/com/microsoft/azure/test/Application.java b/azure-spring-boot-tests/azure-spring-boot-test-application/src/main/java/com/microsoft/azure/test/Application.java index 4d65547a..96305453 100644 --- a/azure-spring-boot-tests/azure-spring-boot-test-application/src/main/java/com/microsoft/azure/test/Application.java +++ b/azure-spring-boot-tests/azure-spring-boot-test-application/src/main/java/com/microsoft/azure/test/Application.java @@ -4,23 +4,41 @@ * license information. */ package com.microsoft.azure.test; + +import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@Slf4j @SpringBootApplication @RestController public class Application implements CommandLineRunner { + @Autowired + private ConfigurableEnvironment environment; + + @Value("${azure.cosmosdb.key:local}") private String cosmosDBkey; private static ObjectMapper mapper = new ObjectMapper(); + public static void main(String[] args) { SpringApplication.run(Application.class, args); } @@ -40,6 +58,36 @@ public class Application implements CommandLineRunner { return cosmosDBkey; } + @GetMapping("env/{key}") + public String env(@PathVariable String key) { + final String property = environment.getProperty(key); + return property; + } + + @GetMapping("list") + public String list() { + final List list = new ArrayList(); + final MutablePropertySources propertySources = this.environment.getPropertySources(); + final Iterator> iterator = propertySources.iterator(); + while (iterator.hasNext()) { + final PropertySource next = iterator.next(); + list.add(next.getName()); + } + return JSON.toJSONString(list); + } + + @GetMapping("getSpecificProperty/{ps}/{key}") + public String getSpecificProperty(@PathVariable String ps, @PathVariable String key) { + final MutablePropertySources propertySources = this.environment.getPropertySources(); + final PropertySource propertySource = propertySources.get(ps); + if (propertySource != null) { + final Object property = propertySource.getProperty(key); + return property == null ? null : property.toString(); + } else { + return null; + } + } + public void run(String... varl) throws Exception { System.out.println("property your-property-name value is: " + cosmosDBkey); } diff --git a/azure-spring-boot-tests/azure-spring-boot-test-core/src/main/java/com/microsoft/azure/test/AppRunner.java b/azure-spring-boot-tests/azure-spring-boot-test-core/src/main/java/com/microsoft/azure/test/AppRunner.java index ee81eb69..fa746e55 100644 --- a/azure-spring-boot-tests/azure-spring-boot-test-core/src/main/java/com/microsoft/azure/test/AppRunner.java +++ b/azure-spring-boot-tests/azure-spring-boot-test-core/src/main/java/com/microsoft/azure/test/AppRunner.java @@ -42,6 +42,18 @@ public class AppRunner implements AutoCloseable { app = builder.build().run(); } } + + public ConfigurableApplicationContext start(String dummy) { + if (app == null) { + final SpringApplicationBuilder builder = new SpringApplicationBuilder(appClass); + builder.properties("spring.jmx.enabled=false"); + builder.properties(String.format("server.port=%d", availableTcpPort())); + builder.properties(props()); + + app = builder.build().run(); + } + return app; + } private int availableTcpPort() { return SocketUtils.findAvailableTcpPort(); diff --git a/azure-spring-boot-tests/azure-spring-boot-test-keyvault/src/test/java/com/microsoft/azure/test/keyvault/KeyVaultIT.java b/azure-spring-boot-tests/azure-spring-boot-test-keyvault/src/test/java/com/microsoft/azure/test/keyvault/KeyVaultIT.java index d7fa0290..a259948e 100755 --- a/azure-spring-boot-tests/azure-spring-boot-test-keyvault/src/test/java/com/microsoft/azure/test/keyvault/KeyVaultIT.java +++ b/azure-spring-boot-tests/azure-spring-boot-test-keyvault/src/test/java/com/microsoft/azure/test/keyvault/KeyVaultIT.java @@ -22,6 +22,10 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; @@ -85,8 +89,16 @@ public class KeyVaultIT { app.property("azure.keyvault.uri", vault.vaultUri()); app.property("azure.keyvault.client-id", access.clientId()); app.property("azure.keyvault.client-key", access.clientSecret()); - - app.start(); + app.property("azure.keyvault.tenant-id", access.tenant()); + + final ConfigurableApplicationContext dummy = app.start("dummy"); + final ConfigurableEnvironment environment = dummy.getEnvironment(); + final MutablePropertySources propertySources = environment.getPropertySources(); + for (final PropertySource propertySource : propertySources) { + System.out.println("name = " + propertySource.getName() + "\nsource = " + propertySource + .getSource().getClass() + "\n"); + } + assertEquals(KEY_VAULT_VALUE, app.getProperty("key")); app.close(); log.info("--------------------->test over"); @@ -100,6 +112,7 @@ public class KeyVaultIT { app.property("azure.keyvault.uri", vault.vaultUri()); app.property("azure.keyvault.client-id", access.clientId()); app.property("azure.keyvault.client-key", access.clientSecret()); + app.property("azure.keyvault.tenant-id", access.tenant()); app.property("azure.keyvault.secret.keys", "key"); app.start(); @@ -133,6 +146,7 @@ public class KeyVaultIT { appService.zipDeploy(zipFile); log.info(String.format("Successfully deployed the artifact to https://%s", appService.defaultHostName())); + break; } catch (Exception e) { log.debug( String.format("Exception occurred when deploying the zip package: %s, " + @@ -141,7 +155,9 @@ public class KeyVaultIT { } // Restart App Service + log.info("restarting app service..."); appService.restart(); + log.info("restarting app service finished..."); final String resourceUrl = "https://" + appService.name() + ".azurewebsites.net" + "/get"; // warm up @@ -179,7 +195,13 @@ public class KeyVaultIT { // run java application final List commands = new ArrayList<>(); commands.add(String.format("cd /home/%s", VM_USER_NAME)); - commands.add(String.format("nohup java -jar -Dazure.keyvault.uri=%s %s &", vault.vaultUri(), + commands.add( + String. + format("nohup java -jar -Xdebug " + + "-Xrunjdwp:server=y,transport=dt_socket,address=4000,suspend=n " + + "-Dazure.keyvault.uri=%s %s &" + + " >/log.txt 2>&1" + , vault.vaultUri(), TEST_KEY_VAULT_JAR_FILE_NAME)); vmTool.runCommandOnVM(vm, commands); @@ -191,6 +213,7 @@ public class KeyVaultIT { assertEquals(response.getStatusCode(), HttpStatus.OK); assertEquals(response.getBody(), KEY_VAULT_VALUE); + log.info("key vault value is: {}", response.getBody()); log.info("--------------------->test virtual machine with MSI over"); } diff --git a/azure-spring-boot-tests/pom.xml b/azure-spring-boot-tests/pom.xml index 67186362..8886ecb3 100644 --- a/azure-spring-boot-tests/pom.xml +++ b/azure-spring-boot-tests/pom.xml @@ -36,6 +36,7 @@ 2.1.1 0.1.53 1.6 + 3.3 @@ -92,9 +93,8 @@ commons-net commons-net - 3.3 + ${commons.net.version} - @@ -168,8 +168,12 @@ maven-failsafe-plugin - ${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.jar - ${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.zip + + ${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.jar + + + ${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.zip + diff --git a/azure-spring-boot/pom.xml b/azure-spring-boot/pom.xml index 720e6f03..6d906f8f 100644 --- a/azure-spring-boot/pom.xml +++ b/azure-spring-boot/pom.xml @@ -151,16 +151,17 @@ azure-storage-blob true + - com.microsoft.azure - azure-keyvault - true - - - com.microsoft.azure - azure-client-runtime - - + com.azure + azure-security-keyvault-secrets + 4.1.0 + + + + com.azure + azure-identity + 1.0.2 com.microsoft.azure diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultCredential.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultCredential.java deleted file mode 100644 index 5d8dbb5f..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultCredential.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring; - -import com.microsoft.aad.adal4j.AuthenticationResult; -import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; -import com.microsoft.azure.utils.AADAuthUtil; -import org.apache.commons.lang3.StringUtils; - -import java.util.concurrent.atomic.AtomicLong; - - -public class AzureKeyVaultCredential extends KeyVaultCredentials { - private static final long DEFAULT_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS = 60L; - private String clientId; - private String clientKey; - private long timeoutInSeconds; - - private AADAuthUtil aadAuthUtil; - private String token = ""; - private AtomicLong lastAcquireTokenTime = new AtomicLong(); - private AtomicLong expireIn = new AtomicLong(); - private static final long EXPIRE_BUFFER_TIME = 10 * 1000; //buffer time 10s - - public AzureKeyVaultCredential(String clientId, String clientKey, long timeoutInSeconds, AADAuthUtil aadAuthUtil) { - this.clientId = clientId; - this.clientKey = clientKey; - this.timeoutInSeconds = timeoutInSeconds; - this.aadAuthUtil = aadAuthUtil; - } - - public AzureKeyVaultCredential(String clientId, String clientKey, long timeoutInSeconds) { - this(clientId, clientKey, timeoutInSeconds, new AADAuthUtil()); - } - - public AzureKeyVaultCredential(String clientId, String clientKey) { - this(clientId, clientKey, DEFAULT_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS); - } - - @Override - public String doAuthenticate(String authorization, String resource, String scope) { - if (StringUtils.isEmpty(token) || needRefresh()) { - refreshToken(authorization, resource); - } - return token; - } - - private synchronized void refreshToken(String authorization, String resource) { - if (!needRefresh()) { //double check - return; - } - - try { - final AuthenticationResult result = aadAuthUtil.getToken(authorization, - resource, - clientId, - clientKey, - timeoutInSeconds); - token = result.getAccessToken(); - expireIn.set(result.getExpiresAfter()); - lastAcquireTokenTime.set(System.currentTimeMillis()); - } catch (Exception ex) { - throw new IllegalStateException("Failed to do authentication.", ex); - } - } - - private boolean needRefresh() { - return ((System.currentTimeMillis() - lastAcquireTokenTime.get() + EXPIRE_BUFFER_TIME) / 1000) >= - expireIn.get(); - } -} diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultMSICredential.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultMSICredential.java deleted file mode 100644 index 907466e9..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultMSICredential.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring; - -import com.microsoft.azure.AzureEnvironment; -import com.microsoft.azure.credentials.AzureTokenCredentials; -import com.microsoft.azure.credentials.MSICredentials; -import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; - -import java.io.IOException; - -public class AzureKeyVaultMSICredential extends KeyVaultCredentials { - private final AzureTokenCredentials tokenCredentials; - - public AzureKeyVaultMSICredential(AzureTokenCredentials tokenCredentials) { - this.tokenCredentials = tokenCredentials; - } - - public AzureKeyVaultMSICredential(AzureEnvironment environment) { - this.tokenCredentials = new MSICredentials(environment); - } - - public AzureKeyVaultMSICredential(AzureEnvironment environment, String clientId) { - this.tokenCredentials = new MSICredentials(environment).withClientId(clientId); - } - - @Override - public String doAuthenticate(String authorization, String resource, String scope) { - try { - return tokenCredentials.getToken(resource); - } catch (IOException e) { - throw new IllegalStateException("Failed to do authentication.", e); - } - } -} diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/Constants.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/Constants.java index 6890caad..ee86f3c2 100644 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/Constants.java +++ b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/Constants.java @@ -12,6 +12,7 @@ public class Constants { 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"; diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultCertificateCredential.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultCertificateCredential.java deleted file mode 100644 index 39afa081..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultCertificateCredential.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring; - -import com.microsoft.aad.adal4j.AsymmetricKeyCredential; -import com.microsoft.aad.adal4j.AuthenticationContext; -import com.microsoft.aad.adal4j.AuthenticationResult; -import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; -import com.microsoft.azure.keyvault.spring.certificate.KeyCert; -import com.microsoft.azure.keyvault.spring.certificate.KeyCertReader; -import com.microsoft.azure.keyvault.spring.certificate.KeyCertReaderFactory; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.Resource; -import org.springframework.util.Assert; - -import java.net.MalformedURLException; -import java.security.PrivateKey; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -@Slf4j -public class KeyVaultCertificateCredential extends KeyVaultCredentials { - private static final long DEFAULT_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS = 60L; - private final String clientId; - private final Resource certResource; - private final String certPassword; - private final long timeoutInSeconds; - - public KeyVaultCertificateCredential(String clientId, Resource certResource, String certPassword, - long timeoutInSeconds) { - Assert.isTrue(certResource.exists(), String.format("Certificate file %s should exist.", - certResource.getFilename())); - - this.clientId = clientId; - this.certResource = certResource; - this.certPassword = certPassword; - this.timeoutInSeconds = timeoutInSeconds <= 0 ? DEFAULT_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS : timeoutInSeconds; - } - - public KeyVaultCertificateCredential(String clientId, Resource certResource, String certPassword) { - this(clientId, certResource, certPassword, DEFAULT_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS); - } - - @Override - public String doAuthenticate(String authorization, String resource, String scope) { - final String certFileName = certResource.getFilename(); - final KeyCertReader certReader = KeyCertReaderFactory.getReader(certFileName); - - final KeyCert keyCert = certReader.read(certResource, certPassword); - - try { - final AuthenticationContext context = new AuthenticationContext(authorization, false, - Executors.newSingleThreadExecutor()); - - final AsymmetricKeyCredential asymmetricKeyCredential = AsymmetricKeyCredential.create(clientId, - keyCert.getKey(), keyCert.getCertificate()); - - final AuthenticationResult authResult = context.acquireToken(resource, asymmetricKeyCredential, null) - .get(timeoutInSeconds, TimeUnit.SECONDS); - - return authResult.getAccessToken(); - } catch (MalformedURLException | InterruptedException | ExecutionException | TimeoutException e) { - final String errMsg = String.format("Failed to authenticate with Key Vault using certificate %s", - certFileName); - log.error(errMsg, e); - throw new IllegalStateException(errMsg, e); - } - } -} diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessor.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessor.java index 6b455aa7..92630863 100644 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessor.java +++ b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessor.java @@ -6,6 +6,7 @@ package com.microsoft.azure.keyvault.spring; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.boot.env.EnvironmentPostProcessor; @@ -13,13 +14,13 @@ import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.ClassUtils; +@Slf4j public class KeyVaultEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { public static final int DEFAULT_ORDER = ConfigFileApplicationListener.DEFAULT_ORDER + 1; private int order = DEFAULT_ORDER; @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - if (isKeyVaultEnabled(environment)) { final KeyVaultEnvironmentPostProcessorHelper helper = new KeyVaultEnvironmentPostProcessorHelper(environment); @@ -37,7 +38,7 @@ public class KeyVaultEnvironmentPostProcessor implements EnvironmentPostProcesso } private boolean isKeyVaultClientAvailable() { - return ClassUtils.isPresent("com.microsoft.azure.keyvault.KeyVaultClient", + return ClassUtils.isPresent("com.azure.security.keyvault.secrets.SecretClient", KeyVaultEnvironmentPostProcessor.class.getClassLoader()); } diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorHelper.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorHelper.java index 49518f16..939074a9 100644 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorHelper.java +++ b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorHelper.java @@ -6,24 +6,21 @@ package com.microsoft.azure.keyvault.spring; -import com.microsoft.azure.AzureEnvironment; -import com.microsoft.azure.AzureResponseBuilder; -import com.microsoft.azure.credentials.AppServiceMSICredentials; -import com.microsoft.azure.keyvault.KeyVaultClient; -import com.microsoft.azure.serializer.AzureJacksonAdapter; -import com.microsoft.azure.spring.support.UserAgent; +import com.azure.core.credential.TokenCredential; +import com.azure.identity.ClientCertificateCredentialBuilder; +import com.azure.identity.ClientSecretCredential; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.identity.ManagedIdentityCredentialBuilder; +import com.azure.security.keyvault.secrets.SecretClient; +import com.azure.security.keyvault.secrets.SecretClientBuilder; import com.microsoft.azure.telemetry.TelemetrySender; -import com.microsoft.rest.RestClient; -import com.microsoft.rest.credentials.ServiceClientCredentials; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.Map; @@ -32,14 +29,13 @@ import java.util.Optional; import static com.microsoft.azure.telemetry.TelemetryData.SERVICE_NAME; import static com.microsoft.azure.telemetry.TelemetryData.getClassPackageSimpleName; +@Slf4j class KeyVaultEnvironmentPostProcessorHelper { - private static final Logger LOG = LoggerFactory.getLogger(KeyVaultEnvironmentPostProcessorHelper.class); private final ConfigurableEnvironment environment; public KeyVaultEnvironmentPostProcessorHelper(final ConfigurableEnvironment environment) { this.environment = environment; - // As @PostConstructor not available when post processor, call it explicitly. sendTelemetry(); } @@ -50,21 +46,15 @@ class KeyVaultEnvironmentPostProcessorHelper { this.environment.getProperty(Constants.AZURE_KEYVAULT_REFRESH_INTERVAL)) .map(Long::valueOf).orElse(Constants.DEFAULT_REFRESH_INTERVAL_MS); final String secretKeys = this.environment.getProperty(Constants.AZURE_KEYVAULT_SECRET_KEYS); - final ServiceClientCredentials credentials = getCredentials(); - - final RestClient restClient = new RestClient.Builder().withBaseUrl(vaultUri) - .withCredentials(credentials) - .withSerializerAdapter(new AzureJacksonAdapter()) - .withResponseBuilderFactory(new AzureResponseBuilder.Factory()) - .withUserAgent(UserAgent.getUserAgent(Constants.AZURE_KEYVAULT_USER_AGENT, - allowTelemetry(this.environment))) - .build(); - - final KeyVaultClient kvClient = new KeyVaultClient(restClient); + final TokenCredential tokenCredential = getCredentials(); + final SecretClient secretClient = new SecretClientBuilder() + .vaultUrl(vaultUri) + .credential(tokenCredential) + .buildClient(); try { final MutablePropertySources sources = this.environment.getPropertySources(); - final KeyVaultOperation kvOperation = new KeyVaultOperation(kvClient, + final KeyVaultOperation kvOperation = new KeyVaultOperation(secretClient, vaultUri, refreshInterval, secretKeys); @@ -81,62 +71,58 @@ class KeyVaultEnvironmentPostProcessorHelper { } } - ServiceClientCredentials getCredentials() { - if (this.environment.containsProperty("MSI_ENDPOINT") - && this.environment.containsProperty("MSI_SECRET")) { - LOG.debug("Will use MSI credentials for app services"); - final String msiEndpoint = getProperty(this.environment, "MSI_ENDPOINT"); - final String msiSecret = getProperty(this.environment, "MSI_SECRET"); - final AppServiceMSICredentials msiCredentials = new AppServiceMSICredentials(AzureEnvironment.AZURE, - msiEndpoint, msiSecret); - - if (this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_ID)) { - LOG.debug("Will use MSI credentials for app services with user assigned identity"); - final String clientId = getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_ID); - msiCredentials.withClientId(clientId); - } - - return msiCredentials; - } - - final long timeAcquiringTimeoutInSeconds = this.environment.getProperty( - Constants.AZURE_TOKEN_ACQUIRE_TIMEOUT_IN_SECONDS, Long.class, Constants.TOKEN_ACQUIRE_TIMEOUT_SECS); + public TokenCredential getCredentials() { + //use service principle to authenticate if (this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_ID) - && this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_KEY)) { - LOG.debug("Will use custom credentials"); + && this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_KEY) + && this.environment.containsProperty(Constants.AZURE_KEYVAULT_TENANT_ID)) { + log.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); - return new AzureKeyVaultCredential(clientId, clientKey, timeAcquiringTimeoutInSeconds); + final String tenantId = getProperty(this.environment, Constants.AZURE_KEYVAULT_TENANT_ID); + final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder() + .clientId(clientId) + .clientSecret(clientKey) + .tenantId(tenantId) + .build(); + return clientSecretCredential; } - - if (this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_ID) && - this.environment.containsProperty(Constants.AZURE_KEYVAULT_CERTIFICATE_PATH)) { - final String clientId = getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_ID); + //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)) { // 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); - LOG.info("Read certificate from {}...", certPath); - final Resource certResource = new DefaultResourceLoader().getResource(certPath); - - return new KeyVaultCertificateCredential(clientId, certResource, certPwd, timeAcquiringTimeoutInSeconds); + 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)) + .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)) + .pfxCertificate(certPath, certPwd) + .build(); + } } + //use MSI to authenticate if (this.environment.containsProperty(Constants.AZURE_KEYVAULT_CLIENT_ID)) { - LOG.debug("Will use MSI credentials for VMs with specified clientId"); + log.debug("Will use MSI credentials with specified clientId"); final String clientId = getProperty(this.environment, Constants.AZURE_KEYVAULT_CLIENT_ID); - return new AzureKeyVaultMSICredential(AzureEnvironment.AZURE, clientId); + return new ManagedIdentityCredentialBuilder().clientId(clientId).build(); } - - LOG.debug("Will use MSI credentials for VM"); - return new AzureKeyVaultMSICredential(AzureEnvironment.AZURE); + log.debug("Will use MSI credentials"); + return new ManagedIdentityCredentialBuilder().build(); } private String getProperty(final ConfigurableEnvironment env, final String propertyName) { Assert.notNull(env, "env must not be null!"); Assert.notNull(propertyName, "propertyName must not be null!"); - final String property = env.getProperty(propertyName); - if (property == null || property.isEmpty()) { throw new IllegalArgumentException("property " + propertyName + " must not be null"); } @@ -145,7 +131,6 @@ class KeyVaultEnvironmentPostProcessorHelper { 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); } diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java index 2a028e25..fa09f0b4 100644 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java +++ b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/KeyVaultOperation.java @@ -6,10 +6,10 @@ package com.microsoft.azure.keyvault.spring; -import com.microsoft.azure.PagedList; -import com.microsoft.azure.keyvault.KeyVaultClient; -import com.microsoft.azure.keyvault.models.SecretBundle; -import com.microsoft.azure.keyvault.models.SecretItem; +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 lombok.extern.slf4j.Slf4j; import org.springframework.lang.NonNull; import org.springframework.util.Assert; @@ -27,7 +27,7 @@ public class KeyVaultOperation { private final String[] secretKeys; private final Object refreshLock = new Object(); - private final KeyVaultClient keyVaultClient; + private final SecretClient keyVaultClient; private final String vaultUri; private ArrayList propertyNames = new ArrayList<>(); @@ -36,7 +36,7 @@ public class KeyVaultOperation { private final AtomicLong lastUpdateTime = new AtomicLong(); private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); - public KeyVaultOperation(final KeyVaultClient keyVaultClient, + public KeyVaultOperation(final SecretClient keyVaultClient, String vaultUri, final long refreshInterval, final String secretKeysConfig) { @@ -111,8 +111,8 @@ public class KeyVaultOperation { refreshPropertyNames(); } if (this.propertyNames.contains(secretName)) { - final SecretBundle secretBundle = this.keyVaultClient.getSecret(this.vaultUri, secretName); - return secretBundle == null ? null : secretBundle.value(); + final KeyVaultSecret secret = this.keyVaultClient.getSecret(secretName); + return secret == null ? null : secret.getValue(); } else { return null; } @@ -135,11 +135,9 @@ public class KeyVaultOperation { if (this.secretKeys == null || secretKeys.length == 0) { this.propertyNames.clear(); - final PagedList secrets = this.keyVaultClient.listSecrets(this.vaultUri); - secrets.loadAll(); - - secrets.forEach(s -> { - final String secretName = s.id().replace(vaultUri + "/secrets/", ""); + final PagedIterable secretProperties = keyVaultClient.listPropertiesOfSecrets(); + secretProperties.forEach(s -> { + final String secretName = s.getName().replace(vaultUri + "/secrets/", ""); addSecretIfNotExist(secretName); }); @@ -157,12 +155,9 @@ public class KeyVaultOperation { 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); diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCert.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCert.java deleted file mode 100644 index d478189b..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCert.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring.certificate; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.security.PrivateKey; -import java.security.cert.X509Certificate; - -@Getter -@AllArgsConstructor -public class KeyCert { - private final X509Certificate certificate; - private final PrivateKey key; -} diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCertReader.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCertReader.java deleted file mode 100644 index fcd723a3..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCertReader.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring.certificate; - -import org.springframework.core.io.Resource; - -public interface KeyCertReader { - KeyCert read(Resource resource, String password); -} diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCertReaderFactory.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCertReaderFactory.java deleted file mode 100644 index 0b361616..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/KeyCertReaderFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring.certificate; - -import org.apache.commons.io.FilenameUtils; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * Factory to create different certificate reader based on extension. - */ -public class KeyCertReaderFactory { - private static final ConcurrentMap readerMap = new ConcurrentHashMap<>(); - private static final String NOT_SUPPORTED_CERT = "Certificate type %s not supported."; - private static final String PFX_EXTENSION = "pfx"; - - public static KeyCertReader getReader(String certFile) { - final String extension = FilenameUtils.getExtension(certFile); - - switch (extension) { - case PFX_EXTENSION: - return readerMap.computeIfAbsent(PFX_EXTENSION, k -> new PfxCertReader()); - default: - throw new IllegalStateException(String.format(NOT_SUPPORTED_CERT, extension)); - } - } -} diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/PfxCertReader.java b/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/PfxCertReader.java deleted file mode 100644 index fe4e351d..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/keyvault/spring/certificate/PfxCertReader.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring.certificate; - -import org.springframework.core.io.Resource; - -import java.io.*; -import java.security.*; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Enumeration; - -public class PfxCertReader implements KeyCertReader { - private String CERT_NOT_FOUND = "Cert file %s not found."; - private String CERT_READ_FAILURE = "Failed to read cert file %s."; - private KeyStore store; - - @Override - public KeyCert read(Resource resource, String password) { - try (InputStream inputStream = resource.getInputStream()) { - getOrInitPKCS12Store(); - - final char[] passwordArray = password == null ? new char[]{} : password.toCharArray(); - store.load(inputStream, passwordArray); - - final Enumeration aliases = store.aliases(); - while (aliases.hasMoreElements()) { - final String alias = aliases.nextElement(); - - if (store.getCertificate(alias).getType().equals("X.509") && store.isKeyEntry(alias)) { - final X509Certificate certificate = (X509Certificate) store.getCertificate(alias); - final PrivateKey key = (PrivateKey) store.getKey(alias, passwordArray); - - return new KeyCert(certificate, key); - } - } - - throw new IllegalStateException(String.format(CERT_READ_FAILURE, resource.getFilename())); - } catch (FileNotFoundException e) { - throw new IllegalStateException(String.format(CERT_NOT_FOUND, resource.getFilename()), e); - } catch (IOException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException | - UnrecoverableKeyException | CertificateException e) { - throw new IllegalStateException(String.format(CERT_READ_FAILURE, resource.getFilename()), e); - } - } - - private void getOrInitPKCS12Store() throws KeyStoreException, NoSuchProviderException { - if (this.store == null) { - this.store = KeyStore.getInstance("pkcs12", "SunJSSE"); - } - } -} diff --git a/azure-spring-boot/src/main/java/com/microsoft/azure/utils/AADAuthUtil.java b/azure-spring-boot/src/main/java/com/microsoft/azure/utils/AADAuthUtil.java deleted file mode 100644 index 9bb206c5..00000000 --- a/azure-spring-boot/src/main/java/com/microsoft/azure/utils/AADAuthUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.utils; - -import com.microsoft.aad.adal4j.AuthenticationContext; -import com.microsoft.aad.adal4j.AuthenticationResult; -import com.microsoft.aad.adal4j.ClientCredential; - -import java.net.MalformedURLException; -import java.util.concurrent.*; - -public class AADAuthUtil { - public AuthenticationResult getToken(String authorization, - String resource, - String clientId, - String clientKey, - long tokenAcquireTimeout) { - AuthenticationContext context = null; - AuthenticationResult result = null; - final ExecutorService executorService = Executors.newSingleThreadExecutor(); - try { - context = new AuthenticationContext(authorization, false, executorService); - final ClientCredential credential = new ClientCredential(clientId, clientKey); - - final Future future = context.acquireToken(resource, credential, null); - result = future.get(tokenAcquireTimeout, TimeUnit.SECONDS); - } catch (MalformedURLException | TimeoutException | InterruptedException | ExecutionException ex) { - throw new IllegalStateException("Failed to do authentication.", ex); - } finally { - executorService.shutdown(); - } - return result; - } -} diff --git a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultCredentialUnitTest.java b/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultCredentialUnitTest.java deleted file mode 100644 index 77796221..00000000 --- a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/AzureKeyVaultCredentialUnitTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring; - -import com.microsoft.aad.adal4j.AuthenticationResult; -import com.microsoft.azure.utils.AADAuthUtil; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class AzureKeyVaultCredentialUnitTest { - - @Test(expected = RuntimeException.class) - public void testDoAuthenticationRejctIfInvalidCredential() { - final AzureKeyVaultCredential keyVaultCredential = new AzureKeyVaultCredential("fakeClientId", - "fakeClientKey", - Constants.TOKEN_ACQUIRE_TIMEOUT_SECS); - keyVaultCredential.doAuthenticate("https://fakeauthorizationurl.com", "keyvault", "scope"); - } - - @Test - public void testDoAuthenticationPass() throws Exception { - final MockAADAuthUtil mockAADAuthUtil = new MockAADAuthUtil("token1", 11L); - final AzureKeyVaultCredential keyVaultCredential = new AzureKeyVaultCredential("fakeClientId", - "fakeClientKey", - Constants.TOKEN_ACQUIRE_TIMEOUT_SECS, - mockAADAuthUtil); - final String token = keyVaultCredential.doAuthenticate("https://fakeauthorizationurl.com", "keyvault", "scope"); - //assert token from cache - mockAADAuthUtil.updateToken("token2"); - assertThat(keyVaultCredential.doAuthenticate("https://fakeauthorizationurl.com", - "keyvault", - "scope")).isEqualTo(token); - //assert token refresh - Thread.sleep(1000L); - assertThat(keyVaultCredential.doAuthenticate("https://fakeauthorizationurl.com", - "keyvault", - "scope")).isNotEqualTo(token); - } - - class MockAADAuthUtil extends AADAuthUtil { - private AuthenticationResult result; - - public void updateToken(String token) { - result = new AuthenticationResult("mockType", - token, - "mockRefreshToken", - result.getExpiresAfter(), - "mockIdToken", - null, - true); - } - - public MockAADAuthUtil(String token, long expiresIn) { - this.result = new AuthenticationResult("mockType", - token, - "mockRefreshToken", - expiresIn, - "mockIdToken", - null, - true); - } - - @Override - public AuthenticationResult getToken(String authorization, - String resource, - String clientId, - String clientKey, - long tokenAcquireTimeout) { - return result; - } - } -} diff --git a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyCertReaderFactoryTest.java b/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyCertReaderFactoryTest.java deleted file mode 100644 index c6832940..00000000 --- a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyCertReaderFactoryTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring; - -import com.microsoft.azure.keyvault.spring.certificate.KeyCertReader; -import com.microsoft.azure.keyvault.spring.certificate.KeyCertReaderFactory; -import com.microsoft.azure.keyvault.spring.certificate.PfxCertReader; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class KeyCertReaderFactoryTest { - private static final String FAKE_PFX_CERT = "fake-pfx-cert.pfx"; - - @Test - public void testPfxCertificateReader() { - final KeyCertReader certReader = KeyCertReaderFactory.getReader(FAKE_PFX_CERT); - assertThat(certReader).isInstanceOf(PfxCertReader.class); - } -} diff --git a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyCertReaderTest.java b/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyCertReaderTest.java deleted file mode 100644 index 7b61ee2c..00000000 --- a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyCertReaderTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.keyvault.spring; - -import com.microsoft.azure.keyvault.spring.certificate.KeyCert; -import com.microsoft.azure.keyvault.spring.certificate.PfxCertReader; -import org.junit.Test; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; - -import static org.assertj.core.api.Assertions.assertThat; - -public class KeyCertReaderTest { - private static final String TEST_PFX_FILE = "testkeyvault.pfx"; - private static final String TEST_SUBJECT = "CN=testkeyvault"; - private static final String TEST_PFX_PASSWORD = "123456"; - - private static final String TEST_NO_PASSWORD_PFX_FILE = "nopwdcert.pfx"; - private static final String TEST_NO_PASSWORD_SUBJECT = "CN=nopwdcert"; - private static final String TEST_NO_PASSWORD = null; - - @Test - public void testPfxCertReaderCanRead() { - validatePfxCertRead(TEST_PFX_FILE, TEST_PFX_PASSWORD, TEST_SUBJECT); - } - - @Test - public void testPfxCertNoPasswordReaderCanRead() { - validatePfxCertRead(TEST_NO_PASSWORD_PFX_FILE, TEST_NO_PASSWORD, TEST_NO_PASSWORD_SUBJECT); - } - - private void validatePfxCertRead(String file, String password, String expectedSubject) { - final Resource resource = new DefaultResourceLoader().getResource(file); - final PfxCertReader reader = new PfxCertReader(); - final KeyCert pfxCert = reader.read(resource, password); - - assertThat(pfxCert).isNotNull(); - assertThat(pfxCert.getCertificate()).isNotNull(); - assertThat(pfxCert.getCertificate().getSubjectX500Principal().getName()).isEqualTo(expectedSubject); - assertThat(pfxCert.getKey()).isNotNull(); - } -} diff --git a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java b/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java index 80096047..58acf35b 100644 --- a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java +++ b/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultEnvironmentPostProcessorTest.java @@ -6,9 +6,10 @@ package com.microsoft.azure.keyvault.spring; -import com.microsoft.azure.credentials.AppServiceMSICredentials; -import com.microsoft.azure.credentials.MSICredentials; -import com.microsoft.rest.credentials.ServiceClientCredentials; +import com.azure.core.credential.TokenCredential; +import com.azure.identity.ClientCertificateCredential; +import com.azure.identity.ClientSecretCredential; +import com.azure.identity.ManagedIdentityCredential; import org.hamcrest.core.IsInstanceOf; import org.junit.Before; import org.junit.Test; @@ -25,8 +26,8 @@ import org.springframework.mock.env.MockEnvironment; import java.util.HashMap; import java.util.Map; -import static com.microsoft.azure.keyvault.spring.Constants.AZURE_KEYVAULT_CLIENT_ID; import static com.microsoft.azure.keyvault.spring.Constants.AZURE_KEYVAULT_CERTIFICATE_PATH; +import static com.microsoft.azure.keyvault.spring.Constants.AZURE_KEYVAULT_CLIENT_ID; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -43,6 +44,33 @@ public class KeyVaultEnvironmentPostProcessorTest { propertySources = environment.getPropertySources(); } + @Test + public void testGetCredentialsWhenUsingClientAndKey() { + testProperties.put("azure.keyvault.client-id", "aaaa-bbbb-cccc-dddd"); + testProperties.put("azure.keyvault.client-key", "mySecret"); + testProperties.put("azure.keyvault.tenant-id", "myid"); + propertySources.addLast(new MapPropertySource("Test_Properties", testProperties)); + + keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment); + + final TokenCredential credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); + + assertThat(credentials, IsInstanceOf.instanceOf(ClientSecretCredential.class)); + } + + @Test + public void testGetCredentialsWhenPFXCertConfigured() { + testProperties.put(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"); + + propertySources.addLast(new MapPropertySource("Test_Properties", testProperties)); + keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment); + + final TokenCredential credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); + assertThat(credentials, IsInstanceOf.instanceOf(ClientCertificateCredential.class)); + } + @Test public void testGetCredentialsWhenMSIEnabledInAppService() { testProperties.put("MSI_ENDPOINT", "fakeendpoint"); @@ -51,23 +79,12 @@ public class KeyVaultEnvironmentPostProcessorTest { keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment); - final ServiceClientCredentials credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); + final TokenCredential credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); - assertThat(credentials, IsInstanceOf.instanceOf(AppServiceMSICredentials.class)); + assertThat(credentials, IsInstanceOf.instanceOf(ManagedIdentityCredential.class)); } - @Test - public void testGetCredentialsWhenUsingClientAndKey() { - testProperties.put("azure.keyvault.client-id", "aaaa-bbbb-cccc-dddd"); - testProperties.put("azure.keyvault.client-key", "mySecret"); - propertySources.addLast(new MapPropertySource("Test_Properties", testProperties)); - keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment); - - final ServiceClientCredentials credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); - - assertThat(credentials, IsInstanceOf.instanceOf(AzureKeyVaultCredential.class)); - } @Test public void testGetCredentialsWhenMSIEnabledInVMWithClientId() { @@ -76,30 +93,18 @@ public class KeyVaultEnvironmentPostProcessorTest { keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment); - final ServiceClientCredentials credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); + final TokenCredential credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); - assertThat(credentials, IsInstanceOf.instanceOf(AzureKeyVaultMSICredential.class)); + assertThat(credentials, IsInstanceOf.instanceOf(ManagedIdentityCredential.class)); } @Test public void testGetCredentialsWhenMSIEnabledInVMWithoutClientId() { keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment); - final ServiceClientCredentials credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); + final TokenCredential credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); - assertThat(credentials, IsInstanceOf.instanceOf(AzureKeyVaultMSICredential.class)); - } - - @Test - public void testGetCredentialsWhenPFXCertConfigured() { - testProperties.put(AZURE_KEYVAULT_CLIENT_ID, "aaaa-bbbb-cccc-dddd"); - testProperties.put(AZURE_KEYVAULT_CERTIFICATE_PATH, "fake-pfx-cert.pfx"); - - propertySources.addLast(new MapPropertySource("Test_Properties", testProperties)); - keyVaultEnvironmentPostProcessorHelper = new KeyVaultEnvironmentPostProcessorHelper(environment); - - final ServiceClientCredentials credentials = keyVaultEnvironmentPostProcessorHelper.getCredentials(); - assertThat(credentials, IsInstanceOf.instanceOf(KeyVaultCertificateCredential.class)); + assertThat(credentials, IsInstanceOf.instanceOf(ManagedIdentityCredential.class)); } @Test diff --git a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java b/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java index fa6e23a6..e81e3911 100644 --- a/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java +++ b/azure-spring-boot/src/test/java/com/microsoft/azure/keyvault/spring/KeyVaultOperationUnitTest.java @@ -5,20 +5,19 @@ */ package com.microsoft.azure.keyvault.spring; -import com.microsoft.azure.Page; -import com.microsoft.azure.PagedList; -import com.microsoft.azure.keyvault.KeyVaultClient; -import com.microsoft.azure.keyvault.models.SecretBundle; -import com.microsoft.azure.keyvault.models.SecretItem; -import com.microsoft.rest.RestException; +import com.azure.core.http.rest.PagedFlux; +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.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; @@ -32,7 +31,7 @@ public class KeyVaultOperationUnitTest { private static final String secretKey1 = "key1"; - private static final String fakeVaultUri = "https://fake.vault.com"; + private static final String fakeVaultUri = "https:fake.vault.com"; private static final String TEST_SPRING_RELAXED_BINDING_NAME_0 = "acme.my-project.person.first-name"; @@ -52,27 +51,15 @@ public class KeyVaultOperationUnitTest { ); @Mock - private KeyVaultClient keyVaultClient; + private SecretClient keyVaultClient; private KeyVaultOperation keyVaultOperation; public void setupSecretBundle(String id, String value, String secretKeysConfig) { - final PagedList mockResult = new PagedList() { - @Override - public Page nextPage(String s) throws RestException { - return new MockPage(); - } - }; - - final SecretItem secretItem = new SecretItem(); - secretItem.withId(id); - mockResult.add(secretItem); - - final SecretBundle secretBundle = new SecretBundle(); - - secretBundle.withId(id).withValue(value); - - when(keyVaultClient.listSecrets(anyString())).thenReturn(mockResult); - when(keyVaultClient.getSecret(anyString(), anyString())).thenReturn(secretBundle); + //provision for list + when(keyVaultClient.listPropertiesOfSecrets()).thenReturn(new MockPage(new PagedFlux<>(() -> null), id)); + //provison for get + final KeyVaultSecret secretBundle = new KeyVaultSecret(id, value); + when(keyVaultClient.getSecret(anyString())).thenReturn(secretBundle); keyVaultOperation = new KeyVaultOperation(keyVaultClient, fakeVaultUri, Constants.TOKEN_ACQUIRE_TIMEOUT_SECS, @@ -135,20 +122,39 @@ public class KeyVaultOperationUnitTest { ); } + class MockPage extends PagedIterable { + private String name; - class MockPage implements Page { + public MockPage(PagedFlux pagedFlux, String name) { + super(pagedFlux); + this.name = name; + } - final SecretItem mockSecretItem = new SecretItem(); - - @Override - public String nextPageLink() { - return null; + /** + * Creates instance given {@link PagedFlux}. + * + * @param pagedFlux to use as iterable + */ + public MockPage(PagedFlux pagedFlux) { + super(pagedFlux); } @Override - public List items() { - mockSecretItem.withId("testPropertyName1"); - return Collections.singletonList(mockSecretItem); + public void forEach(Consumer action) { + action.accept(new MockSecretProperties(name)); + } + } + + class MockSecretProperties extends SecretProperties { + private String name; + + public MockSecretProperties(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; } } }