Sal
Sal

Reputation: 27

Plain Spring: Load secrets from Azure Key Vault

I am currently trying to replace hardcoded secrets on my Spring (non SpringBoot) application using the Spring Cloud Azure library as documented in here.

Currently I have managed to retrieve the secrets after the application startup:

@Configuration
public class KeyVaultConfig {
   @Value("${spring.cloud.azure.keyvault.secret.endpoint}")
   private String keyVaultUri;
   private SecretClient secretClient;

   public KeyVaultConfig() {}

   @PostConstruct
   private void setKeyVaultProvider() {
       this.secretClient = new SecretClientBuilder()
               .vaultUrl(keyVaultUri)
               .credential(new DefaultAzureCredentialBuilder().build())
               .buildClient();
   }

   public SecretClient getSecretClient() {
       return secretClient;
   }
}

And then I succesfully retrieve the secret in another class as:

    @PostConstruct
    public void getTest() {
        System.out.println("lul");
        SecretClient secretClient = keyVaultConfig.getSecretClient();
        secretClient.getSecret("secret");
    }

Now, the problem is that I need to use the secret as soon as the context is created because my app-context.xml is defined as this:

    <bean id="dataSourceMysql" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="${db.driverClassName}"/>
    <property name="jdbcUrl" value="${db.url}"/>
    <property name="user" value="${db.username}" />
    <property name="password" value="${db.password}" />
    <property name="acquireIncrement" value="${c3p0.acquireIncrement}" />
    <property name="minPoolSize" value="${c3p0.minPoolSize}" />
    <property name="maxPoolSize" value="${c3p0.maxPoolSize}" />
    <property name="maxIdleTime" value="${c3p0.maxIdleTime}" />
    <property name="preferredTestQuery" value="SELECT 1234567890" />
    <property name="testConnectionOnCheckout" value="${c3p0.testConnectionOnCheckout}" />
    <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}" />

</bean>

Any advice or suggestion? Thanks!

Upvotes: 0

Views: 603

Answers (1)

Suresh Chikkam
Suresh Chikkam

Reputation: 3473

To use the secret retrieved from Azure Key Vault as the password for your MySQL DataSource in the Spring application.

Firstly, Add Spring Cloud Azure Dependencies.

pom.xml:

<dependency>
    <groupId>com.azure.spring</groupId>
    <artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId>
    <version>5.9.1</version>
</dependency>

If you use any certificates the add Azure-identity dependency also.

  • Create a configuration class to allow Spring to read secrets from Azure Key Vault before that setup the secret in azure key-vault.

enter image description here

KeyVaultConfig:

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KeyVaultConfig {

    @Value("${spring.cloud.azure.keyvault.uri}")
    private String keyVaultUri;

    @Bean
    public SecretClient secretClient() {
        return new SecretClientBuilder()
                .vaultUrl(keyVaultUri)
                .credential(new DefaultAzureCredentialBuilder().build())
                .buildClient();
    }
}
  • Retrieve the secret from Azure Key Vault and set it as a property in the Spring application context.

SecretInitializer:

import com.azure.security.keyvault.secrets.SecretClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class SecretInitializer {

    @Autowired
    private SecretClient secretClient;

    @Autowired
    private ConfigurableApplicationContext context;

    @PostConstruct
    public void init() {
        String secretValue = secretClient.getSecret("your-secret-name").getValue();
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource("secrets", Collections.singletonMap("db.password", secretValue)));
    }
}

Its better to configure in application.properties, configure your MySQL DataSource, including the password placeholder.

properties:

db.driverClassName=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/your_database
db.username=username
db.password=${db.password}
c3p0.acquireIncrement=3
c3p0.minPoolSize=5
c3p0.maxPoolSize=20
c3p0.maxIdleTime=1800
c3p0.testConnectionOnCheckout=true
c3p0.idleConnectionTestPeriod=1800
  • Set up MySQL DataSource bean using the properties from the configuration file.

DataSourceConfig:

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Value("${db.driverClassName}")
    private String driverClassName;

    @Value("${db.url}")
    private String jdbcUrl;

    @Value("${db.username}")
    private String username;

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driverClassName);
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUser(username);
        dataSource.setPassword("${db.password}");
        // Set other properties such as maxPoolSize, minPoolSize, etc.
        return dataSource;
    }
}

Secret retrieved: enter image description here

Upvotes: 0

Related Questions