javaspringspring-bootazureazure-keyvault

Configuring properties injection from Azure Keyvault using spring boot and spring-cloud-azure-starter-keyvault-secrets


I would like to replace spring.datasource.password with a password from azure keyvault.

I have a project based on spring boot:

    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.1</version>

I am using dependencies :

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.azure</groupId>
            <artifactId>azure-identity</artifactId>
            <version>1.13.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.azure.spring</groupId>
            <artifactId>spring-cloud-azure-starter-keyvault</artifactId>
        </dependency> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency> 
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency> 
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
       <dependency>
            <groupId>com.azure.spring</groupId>
            <artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId> 
        </dependency> 

I have created the config:

@Configuration
public class KeyVaultConfig {

    @Bean
    SecretClient secretClient(
            @Value("${spring.cloud.azure.keyvault.secret.endpoint}") String keyVaultUri,
            @Value("${azure.client.id}") String clientId,
            @Value("${azure.client.secret}") String clientSecret,
            @Value("${azure.tenant.id}") String tenantId) {
        ClientSecretCredential credential = new ClientSecretCredentialBuilder()
                .clientId(clientId)
                .clientSecret(clientSecret)
                .tenantId(tenantId)
                .build();

        return new SecretClientBuilder()
                .vaultUrl(keyVaultUri)
                .credential(credential)
                .buildClient();
    } 
}

This config works fine when I do not want to inject properties. For instance this code:

    @GetMapping("/status")
    public String status() {
        log.info("generating status page");
        return "read from keyvault: " + keyVaultService.getSecret("psur-db-url");
    }

works just fine. So this proves that the credentials are set correctly.

But when I try to use this setup with combination of properties:

spring.cloud.azure.compatibility-verifier.enabled=false
spring.cloud.azure.keyvault.secret.property-source-enabled=true
 
spring.datasource.url={my-secret-db-url}
spring.datasource.username=user
spring.datasource.password={my-secret-db-password}

I have tried different configurations in properties as well as configuring the ClientSecretCredential in different way. But I never managed the properties to be injected. Currently I am getting exception

Caused by: java.lang.IllegalArgumentException: URL must start with 'jdbc'

which suggest that the url was never replaced. I am suspecting that the SecretClient is not created with my config, because when I put a breakpoint there it is never reached. But this is just a guess. Am I missing something in my config? Or maybe the configuration is wrong?


Solution

  • The solution which currently works for me is:

    1. Pom.xml
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-identity</artifactId>
                <version>1.13.0</version>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>com.azure.spring</groupId>
                <artifactId>spring-cloud-azure-starter-keyvault</artifactId>
            </dependency> 
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency> 
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
            </dependency> 
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
    
    

    I have removed the dependency to spring-cloud-azure-starter-keyvault-secrets.

    1. SecretClient config.

    I have entirely removed the KeyVaultConfig class. I am using the auto configred SecretClient (according to https://learn.microsoft.com/en-us/azure/developer/java/sdk/identity-azure-hosted-auth#default-azure-credential). I have setup environment variables in my Intellij according to (https://learn.microsoft.com/en-us/azure/developer/java/sdk/identity-azure-hosted-auth#environment-variables)

    AZURE_CLIENT_ID ID of a Microsoft Entra application.
    AZURE_TENANT_ID ID of the application's Microsoft Entra tenant.
    AZURE_CLIENT_SECRET One of the application's client secrets.
    

    enter image description here

    1. Properties file:
    spring.cloud.azure.keyvault.secret.property-sources[0].endpoint=https://dev-my-kv.vault.azure.net/
      
    spring.datasource.url=${my-secret-db-url}
    spring.datasource.username=user
    spring.datasource.password=${my-secret-db-password}
    
    

    Although this works I would prefere not to need to setup the environment variables so I am still looking for a way to create my own SecurityClient, inject into the context and reuse while connecting to keyvault.

    Solution nr 2:

    I am using same pom.xml as in Solution nr 1, as well I do not define my own SecretClient. I am just setting the client, secret and tenant in properties file:

    spring.cloud.azure.credential.client-id=###
    spring.cloud.azure.credential.client-secret=###
    spring.cloud.azure.profile.tenant-id=###
    
    spring.cloud.azure.keyvault.secret.property-sources[0].endpoint=https://dev-my-kv.vault.azure.net/
    ...
    

    they will be used to create ClientSecretCredential which will be injected into SecretClient.