Skip to content

Commit

Permalink
Add support for Azure KeyVault (#347)
Browse files Browse the repository at this point in the history
* Add keyvault feature

Co-authored-by: Nemanja Mikic <nemanja.mikic@oracle.com>
  • Loading branch information
graemerocher and n0tl3ss committed Jun 6, 2022
1 parent 1a9e169 commit a6924d9
Show file tree
Hide file tree
Showing 19 changed files with 598 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.azure.identity.VisualStudioCodeCredential;
import com.azure.identity.VisualStudioCodeCredentialBuilder;
import io.micronaut.azure.condition.ClientCertificateCredentialsCondition;
import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.util.StringUtils;
Expand All @@ -45,6 +46,7 @@
* @since 3.1
*/
@Factory
@BootstrapContextCompatible
public class AzureCredentialFactory {

/**
Expand All @@ -55,6 +57,7 @@ public class AzureCredentialFactory {
*/
@Requires(condition = ClientCertificateCredentialsCondition.class)
@Singleton
@BootstrapContextCompatible
public ClientCertificateCredentialBuilder clientCertificateCredentialBuilder(AzureCredentialsConfiguration.ClientCertificateCredentialConfiguration configuration) {
final ClientCertificateCredentialBuilder builder = new ClientCertificateCredentialBuilder();
configuration.getPfxCertificatePath().ifPresent(s -> {
Expand All @@ -76,6 +79,7 @@ public ClientCertificateCredentialBuilder clientCertificateCredentialBuilder(Azu
*/
@Requires(beans = ClientCertificateCredentialBuilder.class)
@Singleton
@BootstrapContextCompatible
public ClientCertificateCredential clientCertificateCredential(ClientCertificateCredentialBuilder builder) {
return builder.build();
}
Expand All @@ -88,6 +92,7 @@ public ClientCertificateCredential clientCertificateCredential(ClientCertificate
*/
@Requires(property = AzureCredentialsConfiguration.ClientSecretCredentialConfiguration.CLIENT_SECRET)
@Singleton
@BootstrapContextCompatible
public ClientSecretCredentialBuilder clientSecretCredentialBuilder(AzureCredentialsConfiguration.ClientSecretCredentialConfiguration configuration) {
final ClientSecretCredentialBuilder builder = new ClientSecretCredentialBuilder();
builder.clientSecret(configuration.getSecret());
Expand All @@ -105,6 +110,7 @@ public ClientSecretCredentialBuilder clientSecretCredentialBuilder(AzureCredenti
*/
@Requires(beans = ClientSecretCredentialBuilder.class)
@Singleton
@BootstrapContextCompatible
public ClientSecretCredential clientSecretCredential(ClientSecretCredentialBuilder builder) {
return builder.build();
}
Expand All @@ -118,6 +124,7 @@ public ClientSecretCredential clientSecretCredential(ClientSecretCredentialBuild
@Requires(property = AzureCredentialsConfiguration.UsernamePasswordCredentialConfiguration.USERNAME)
@Requires(property = AzureCredentialsConfiguration.UsernamePasswordCredentialConfiguration.PASSWORD)
@Singleton
@BootstrapContextCompatible
public UsernamePasswordCredentialBuilder usernamePasswordCredentialBuilder(AzureCredentialsConfiguration.UsernamePasswordCredentialConfiguration configuration) {
final UsernamePasswordCredentialBuilder builder = new UsernamePasswordCredentialBuilder();
builder.username(configuration.getUsername());
Expand All @@ -138,6 +145,7 @@ public UsernamePasswordCredentialBuilder usernamePasswordCredentialBuilder(Azure

@Requires(beans = UsernamePasswordCredentialBuilder.class)
@Singleton
@BootstrapContextCompatible
public UsernamePasswordCredential usernamePasswordCredential(UsernamePasswordCredentialBuilder builder) {
return builder.build();
}
Expand All @@ -150,6 +158,7 @@ public UsernamePasswordCredential usernamePasswordCredential(UsernamePasswordCre
*/
@Requires(property = AzureCredentialsConfiguration.ManagedIdentityCredentialConfiguration.ENABLED, notEquals = StringUtils.FALSE, defaultValue = StringUtils.FALSE)
@Singleton
@BootstrapContextCompatible
public ManagedIdentityCredentialBuilder managedIdentityCredentialBuilder(AzureCredentialsConfiguration.ManagedIdentityCredentialConfiguration configuration) {
final ManagedIdentityCredentialBuilder builder = new ManagedIdentityCredentialBuilder();
configuration.getClientId().ifPresent(builder::clientId);
Expand All @@ -168,6 +177,7 @@ public ManagedIdentityCredentialBuilder managedIdentityCredentialBuilder(AzureCr
*/
@Requires(beans = ManagedIdentityCredentialBuilder.class)
@Singleton
@BootstrapContextCompatible
public ManagedIdentityCredential managedIdentityCredential(ManagedIdentityCredentialBuilder builder) {
return builder.build();
}
Expand All @@ -180,6 +190,7 @@ public ManagedIdentityCredential managedIdentityCredential(ManagedIdentityCreden
*/
@Requires(property = AzureCredentialsConfiguration.AzureCliCredentialConfiguration.ENABLED, notEquals = StringUtils.FALSE, defaultValue = StringUtils.FALSE)
@Singleton
@BootstrapContextCompatible
public AzureCliCredentialBuilder azureCliCredentialBuilder(AzureCredentialsConfiguration.AzureCliCredentialConfiguration configuration) {
return new AzureCliCredentialBuilder();
}
Expand All @@ -195,6 +206,7 @@ public AzureCliCredentialBuilder azureCliCredentialBuilder(AzureCredentialsConfi
*/
@Requires(beans = AzureCliCredentialBuilder.class)
@Singleton
@BootstrapContextCompatible
public AzureCliCredential azureCliCredential(AzureCliCredentialBuilder builder) {
return builder.build();
}
Expand All @@ -207,6 +219,7 @@ public AzureCliCredential azureCliCredential(AzureCliCredentialBuilder builder)
*/
@Requires(property = AzureCredentialsConfiguration.IntelliJCredentialConfiguration.ENABLED, notEquals = StringUtils.FALSE, defaultValue = StringUtils.FALSE)
@Singleton
@BootstrapContextCompatible
public IntelliJCredentialBuilder intelliJCredentialBuilder(AzureCredentialsConfiguration.IntelliJCredentialConfiguration configuration) {
final IntelliJCredentialBuilder builder = new IntelliJCredentialBuilder();
configuration.getKeePassDatabasePath().ifPresent(builder::keePassDatabasePath);
Expand All @@ -225,6 +238,7 @@ public IntelliJCredentialBuilder intelliJCredentialBuilder(AzureCredentialsConfi
*/
@Requires(beans = IntelliJCredentialBuilder.class)
@Singleton
@BootstrapContextCompatible
public IntelliJCredential intelliJCredential(IntelliJCredentialBuilder builder) {
return builder.build();
}
Expand All @@ -237,6 +251,7 @@ public IntelliJCredential intelliJCredential(IntelliJCredentialBuilder builder)
*/
@Requires(property = AzureCredentialsConfiguration.VisualStudioCodeCredentialConfiguration.ENABLED, notEquals = StringUtils.FALSE, defaultValue = StringUtils.FALSE)
@Singleton
@BootstrapContextCompatible
public VisualStudioCodeCredentialBuilder visualStudioCodeCredentialBuilder(AzureCredentialsConfiguration.VisualStudioCodeCredentialConfiguration configuration) {
final VisualStudioCodeCredentialBuilder builder = new VisualStudioCodeCredentialBuilder();
configuration.getTenantId().ifPresent(builder::tenantId);
Expand All @@ -255,6 +270,7 @@ public VisualStudioCodeCredentialBuilder visualStudioCodeCredentialBuilder(Azure
*/
@Requires(beans = VisualStudioCodeCredentialBuilder.class)
@Singleton
@BootstrapContextCompatible
public VisualStudioCodeCredential visualStudioCodeCredential(VisualStudioCodeCredentialBuilder builder) {
return builder.build();
}
Expand All @@ -265,6 +281,7 @@ public VisualStudioCodeCredential visualStudioCodeCredential(VisualStudioCodeCre
* @return builder
*/
@Singleton
@BootstrapContextCompatible
public DefaultAzureCredentialBuilder defaultAzureCredentialBuilder() {
return new DefaultAzureCredentialBuilder();
}
Expand All @@ -281,6 +298,7 @@ public DefaultAzureCredentialBuilder defaultAzureCredentialBuilder() {
*/
@Requires(missingBeans = TokenCredential.class)
@Singleton
@BootstrapContextCompatible
public DefaultAzureCredential defaultAzureCredential(DefaultAzureCredentialBuilder builder) {
return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.micronaut.azure.credentials;

import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.NonNull;
Expand All @@ -30,6 +31,7 @@
* @since 3.1
*/
@ConfigurationProperties(AzureCredentialsConfiguration.PREFIX)
@BootstrapContextCompatible
public interface AzureCredentialsConfiguration {
String PREFIX = Environment.AZURE + ".credential";

Expand All @@ -38,6 +40,7 @@ public interface AzureCredentialsConfiguration {
* The client certificate credential configuration.
*/
@ConfigurationProperties(ClientCertificateCredentialConfiguration.NAME)
@BootstrapContextCompatible
interface ClientCertificateCredentialConfiguration {
String NAME = "client-certificate";

Expand Down Expand Up @@ -84,6 +87,7 @@ interface ClientCertificateCredentialConfiguration {
* The client secret credential configuration.
*/
@ConfigurationProperties(ClientSecretCredentialConfiguration.NAME)
@BootstrapContextCompatible
interface ClientSecretCredentialConfiguration {
String NAME = "client-secret";
String CLIENT_SECRET = AzureCredentialsConfiguration.PREFIX + "." + NAME + "." + "secret";
Expand Down Expand Up @@ -117,6 +121,7 @@ interface ClientSecretCredentialConfiguration {
* The username password credential configuration.
*/
@ConfigurationProperties(UsernamePasswordCredentialConfiguration.NAME)
@BootstrapContextCompatible
interface UsernamePasswordCredentialConfiguration {
String NAME = "username-password";
String USERNAME = AzureCredentialsConfiguration.PREFIX + "." + NAME + "." + "username";
Expand Down Expand Up @@ -158,6 +163,7 @@ interface UsernamePasswordCredentialConfiguration {
* The managed identity credential configuration.
*/
@ConfigurationProperties(ManagedIdentityCredentialConfiguration.NAME)
@BootstrapContextCompatible
interface ManagedIdentityCredentialConfiguration extends Toggleable {
String NAME = "managed-identity";
String ENABLED = AzureCredentialsConfiguration.PREFIX + "." + NAME + "." + "enabled";
Expand All @@ -174,6 +180,7 @@ interface ManagedIdentityCredentialConfiguration extends Toggleable {
* The Azure CLI credential configuration.
*/
@ConfigurationProperties(AzureCliCredentialConfiguration.NAME)
@BootstrapContextCompatible
interface AzureCliCredentialConfiguration extends Toggleable {
String NAME = "cli";
String ENABLED = AzureCredentialsConfiguration.PREFIX + "." + NAME + "." + "enabled";
Expand All @@ -183,6 +190,7 @@ interface AzureCliCredentialConfiguration extends Toggleable {
* The IntelliJ credential configuration.
*/
@ConfigurationProperties(IntelliJCredentialConfiguration.NAME)
@BootstrapContextCompatible
interface IntelliJCredentialConfiguration extends Toggleable {
String NAME = "intellij";
String ENABLED = AzureCredentialsConfiguration.PREFIX + "." + NAME + "." + "enabled";
Expand All @@ -209,6 +217,7 @@ interface IntelliJCredentialConfiguration extends Toggleable {
* The Visual studion code credential configuration.
*/
@ConfigurationProperties(VisualStudioCodeCredentialConfiguration.NAME)
@BootstrapContextCompatible
interface VisualStudioCodeCredentialConfiguration extends Toggleable {
String NAME = "visual-studio-code";
String ENABLED = AzureCredentialsConfiguration.PREFIX + "." + NAME + "." + "enabled";
Expand Down
30 changes: 30 additions & 0 deletions azure-secret-manager/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
plugins {
id 'io.micronaut.build.internal.azure-module'
}

micronautBuild {
// new module, so no binary check
binaryCompatibility {
enabled.set(false)
}
}

dependencies {
annotationProcessor(libs.micronaut.inject.java)
implementation platform(project(":azure-bom"))

api(libs.micronaut.inject)
api(libs.micronaut.http.server)
api project(":azure-sdk")
api(libs.azure.identity)
api(libs.azure.security.keyvault.secrets)

implementation(libs.micronaut.discovery.client)
implementation(libs.micronaut.reactor)

testAnnotationProcessor(libs.micronaut.inject.java)
testCompileOnly(libs.micronaut.inject.groovy)
testImplementation(libs.micronaut.runtime)
testImplementation(libs.micronaut.http.client)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.azure.secretmanager;

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import io.micronaut.azure.secretmanager.client.SecretKeyVaultClient;
import io.micronaut.azure.secretmanager.configuration.AzureKeyVaultConfigurationProperties;
import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.context.env.PropertySource;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.StringUtils;
import io.micronaut.discovery.config.ConfigurationClient;
import io.micronaut.scheduling.TaskExecutors;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;

/**
* Distributed configuration client implementation that fetches application secret values from Azure keyvalut.
* @author Nemanja Mikic
*/
@Singleton
@Requires(beans = SecretClient.class)
@BootstrapContextCompatible
public class AzureVaultConfigurationClient implements ConfigurationClient {

private static final Logger LOG = LoggerFactory.getLogger(AzureVaultConfigurationClient.class);

private final AzureKeyVaultConfigurationProperties azureKeyVaultConfigurationProperties;
private final ExecutorService executorService;
private final SecretKeyVaultClient secretClient;
private final String vaultUrl;

/**
* Default Constructor.
*
* @param azureKeyVaultConfigurationProperties Azure Secret Vault Client Configuration
* @param executorService Executor Service
* @param secretClient The secrets client
*/
public AzureVaultConfigurationClient(
AzureKeyVaultConfigurationProperties azureKeyVaultConfigurationProperties,
@Named(TaskExecutors.IO) @Nullable ExecutorService executorService,
SecretKeyVaultClient secretClient) {
this.azureKeyVaultConfigurationProperties = azureKeyVaultConfigurationProperties;
this.executorService = executorService;
this.secretClient = secretClient;
this.vaultUrl = azureKeyVaultConfigurationProperties.getVaultURL();
}

@Override
public Publisher<PropertySource> getPropertySources(Environment environment) {

if (StringUtils.isEmpty(vaultUrl)) {
return Flux.empty();
}

List<Flux<PropertySource>> propertySources = new ArrayList<>();
Scheduler scheduler = executorService != null ? Schedulers.fromExecutor(executorService) : null;

Map<String, Object> secrets = new HashMap<>();

int retrieved = 0;

if (LOG.isDebugEnabled()) {
LOG.debug("Retrieving secrets from Azure Secret Vault with URL: {}", azureKeyVaultConfigurationProperties.getVaultURL());
}

for (KeyVaultSecret keyVaultSecret : secretClient.listSecrets()) {

retrieved += 1;
secrets.put(
keyVaultSecret.getName(),
keyVaultSecret.getValue()
);
secrets.put(
keyVaultSecret.getName().replace('-', '.'),
keyVaultSecret.getValue()
);
secrets.put(
keyVaultSecret.getName().replace('-', '_'),
keyVaultSecret.getValue()
);
if (LOG.isTraceEnabled()) {
LOG.trace("Retrieved secret: {}", keyVaultSecret.getName());
}

}
if (LOG.isDebugEnabled()) {
LOG.debug("{} secrets were retrieved from Azure Secret Vault with URL: {}", retrieved, azureKeyVaultConfigurationProperties.getVaultURL());
}

Flux<PropertySource> propertySourceFlowable = Flux.just(
PropertySource.of(secrets)
);

if (scheduler != null) {
propertySourceFlowable = propertySourceFlowable.subscribeOn(scheduler);
}
propertySources.add(propertySourceFlowable);
return Flux.merge(propertySources);
}

@Override
public String getDescription() {
return "Retrieves secrets from Azure key vaults";
}
}
Loading

0 comments on commit a6924d9

Please sign in to comment.