skip to Main Content

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?

2

Answers


  1. Chosen as BEST ANSWER

    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.


  2. Another tip when dealing with similiar topic is to set the logging:

        com.azure.identity: DEBUG
    

    and look in logs for text containing: "Azure Identity =>"
    For instance:

    2024-08-16T13:22:25.227+02:00  INFO 4828 --- [my-service] [nio-8082-exec-1] c.azure.identity.ChainedTokenCredential  : Azure Identity => Attempted credential EnvironmentCredential returns a token
    

    In that way you can understand if your config is working and what is happening under the hood.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search