Родитель
646a458325
Коммит
4543480ed2
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -15,7 +15,7 @@ If you are using Maven, add the following dependency.
|
|||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-keyvault-secrets-spring-boot-starter</artifactId>
|
||||
<version>0.2.3</version>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -34,16 +34,6 @@
|
|||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-spring-boot</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-keyvault</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-client-runtime</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-client-authentication</artifactId>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<project.rootdir>${project.basedir}/../..</project.rootdir>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<fastjson.version>1.2.61</fastjson.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -38,6 +39,12 @@
|
|||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-keyvault-secrets-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- util dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
|
|
|
@ -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<PropertySource<?>> 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);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,18 @@ public class AppRunner implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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.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");
|
||||
}
|
||||
|
||||
app.start();
|
||||
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<String> 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");
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<cobertura.version>2.1.1</cobertura.version>
|
||||
<secure.channel.version>0.1.53</secure.channel.version>
|
||||
<expect4j.version>1.6</expect4j.version>
|
||||
<commons.net.version>3.3</commons.net.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -92,9 +93,8 @@
|
|||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.3</version>
|
||||
<version>${commons.net.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
@ -168,8 +168,12 @@
|
|||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<keyvault.app.jar.path>${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.jar</keyvault.app.jar.path>
|
||||
<keyvault.app.zip.path>${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.zip</keyvault.app.zip.path>
|
||||
<keyvault.app.jar.path>
|
||||
${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.jar
|
||||
</keyvault.app.jar.path>
|
||||
<keyvault.app.zip.path>
|
||||
${project.rootdir}/azure-spring-boot-tests/azure-spring-boot-test-application/target/app.zip
|
||||
</keyvault.app.zip.path>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
<executions>
|
||||
|
|
|
@ -151,16 +151,17 @@
|
|||
<artifactId>azure-storage-blob</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- declare version in current pom temporarily, move to azure-dependency-bom later -->
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-keyvault</artifactId>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-client-runtime</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-security-keyvault-secrets</artifactId>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
<!-- declare version in current pom temporarily, move to azure-dependency-bom later -->
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-identity</artifactId>
|
||||
<version>1.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String> 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<SecretItem> secrets = this.keyVaultClient.listSecrets(this.vaultUri);
|
||||
secrets.loadAll();
|
||||
|
||||
secrets.forEach(s -> {
|
||||
final String secretName = s.id().replace(vaultUri + "/secrets/", "");
|
||||
final PagedIterable<SecretProperties> 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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<String, KeyCertReader> 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<AuthenticationResult> 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<SecretItem> mockResult = new PagedList<SecretItem>() {
|
||||
@Override
|
||||
public Page<SecretItem> 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<SecretProperties> {
|
||||
private String name;
|
||||
|
||||
class MockPage implements Page<SecretItem> {
|
||||
public MockPage(PagedFlux<SecretProperties> 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<SecretProperties> pagedFlux) {
|
||||
super(pagedFlux);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SecretItem> items() {
|
||||
mockSecretItem.withId("testPropertyName1");
|
||||
return Collections.singletonList(mockSecretItem);
|
||||
public void forEach(Consumer<? super SecretProperties> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче