Search code examples
azurespring-bootazure-keyvaultazure-managed-identity

Azure - Accessing Key Vault using User Managed Identity in Java Spring Boot : Error Details: ManagedIdentityCredential authentication unavailable?


I have a Java Spring boot application that just reads the secret from Azure Key Vault, below are steps used

  • Created an Application Registration

enter image description here

  • Copied the App Registration details

enter image description here

  • Generated Secret

enter image description here

  • Granted access on Azure Key Vault

enter image description here

and below is my Java Spring Boot application

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.contoso</groupId>
    <artifactId>keyvault</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>keyvault</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
        <azure.version>2.3.5</azure.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-keyvault-secrets-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.microsoft.azure</groupId>
                <artifactId>azure-spring-boot-bom</artifactId>
                <version>${azure.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

azure.keyvault.client-id=7a111923-xxxxxxxx-xxxxxx-31be31d233dd
azure.keyvault.client-key=gt_~k02yF_xxxxxx_vn3r1.GW
azure.keyvault.enabled=true
azure.keyvault.tenant-id=9cef136a-xxxx-xxxx-b7d3-d9d8a5a84182
azure.keyvault.uri=https://contosokvxxx.vault.azure.net/

KeyvaultApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;

@SpringBootApplication
@RestController
public class KeyvaultApplication {

   public static void main(String[] args) {
     SpringApplication.run(KeyvaultApplication.class, args);
   }

  @GetMapping("get")
  public String get() {
    return connectionString;
  }

  @Value("${connectionString}")
  private String connectionString;

  public void run(String... varl) throws Exception {
    System.out.println(String.format("\nConnection String stored in Azure Key Vault:\n%s\n",connectionString));
  }  

}

It works and I could read the Secret from Azure Key Vault

enter image description here

so decided to remove the secrets from the application.properties and commented as below

#azure.keyvault.client-id=7a111923-1xxxxxxxx-31be31d233dd
#azure.keyvault.client-key=gt_~k02yF_xxxxxxxx-Hr6vn3r1.GW
azure.keyvault.enabled=true
#azure.keyvault.tenant-id=9cef136axxxxxxx-3-d9d8a5a84182
azure.keyvault.uri=https://contosokvxxx.vault.azure.net/

and added an Environment variable as shown below using setx

setx AzureServicesAuthConnectionString "RunAs=App;AppId=bb01c08b-xxxxxxxx-106;TenantId=9cef1-xxxxxxx-d9d8a5a84182;AppKey=xxxxxxxx-4Dpg-E3zrj~"

The project compiles without any isssues

>mvn clean compile package

but failed running

>mvn spring-boot:run

Error:

11:40:27.334 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to configure KeyVault property source
        at com.microsoft.azure.keyvault.spring.KeyVaultEnvironmentPostProcessorHelper.addKeyVaultPropertySource(KeyVaultEnvironmentPostProcessorHelper.java:110)
        at com.microsoft.azure.keyvault.spring.KeyVaultEnvironmentPostProcessor.postProcessEnvironment(KeyVaultEnvironmentPostProcessor.java:47)

Caused by: java.lang.RuntimeException: Max retries 3 times exceeded. Error Details: ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: no further information.
        at com.azure.core.http.policy.RetryPolicy.lambda$attemptAsync$1(RetryPolicy.java:116)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:88)

Solution

  • Error Details: ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, Network is unreachable: no further information.

    The error means you could not connect to the Azure Instance Metadata Service endpoint, it is a REST Endpoint that is available at a well-known non-routable IP address (169.254.169.254), MSI use it to get the token, it can be accessed only from within the Azure service e.g. web app, VM, etc. Simply put, you could not use MSI(managed identity) in local.

    To use MSI get secret from the azure keyvault, follow this to deploy your application to azure web app, enable the system-assigned identity or user-assigned identity, then remove the azure.keyvault.client-key from application.properties, change the azure.keyvault.client-id with the MSI's client id, add it to the access policy of the keyvault, details follow this.

    azure.keyvault.client-id=56rqs994-0o66-43o3-9roo-8e3534d0cb23
    azure.keyvault.enabled=true
    azure.keyvault.tenant-id=72s988os-86s1-41ns-91ab-2q7pq011qo47
    azure.keyvault.uri=https://contosokv.vault.azure.net/   
    

    Reference - Tutorial: Reading a secret from Azure Key Vault in a Spring Boot application