Search code examples
spring-bootazure-keyvault

Azure Key Vault access from local application


I am having my local spring boot application which is trying to read the keyvault secret by using the below code .

@Component
public class KeyVaultHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultHelper.class);
    
    public SecretClient getSecretClient() {
        return new SecretClientBuilder()
                .vaultUrl("https://mba-key-vault-int.vault.azure.net/")
                .credential(new ClientSecretCredentialBuilder()
                        .tenantId("de8e2ba9-95c1-4fbb-b558-6bf8bb1d8081")
                        .clientId("b74bcf37-n044-4242-9187-d85b486379c1")
                        .clientSecret("mU48Q~LpWJL7EZDpTzjiNDzqVywcmseUKaR62b5P")
                        .build())
                .buildClient();
    }


    public String saveSecretToFile() {
        String secretValue = getSecretClient().getSecret("secretName").getValue();
        System.out.println("secret name", secretValue);
    }
}

While calling saveSecretToFile() from other class, I am getting the exception as com.azure.security.keyvault.secrets.implementation.models.KeyVaultErrorException: Status code 401, "{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing a Bearer or PoP token."}}"

Note: I set the service principal as per the link https://azuresdkdocs.blob.core.windows.net/$web/java/azure-security-keyvault-secrets/4.2.3/index.html

I'm getting the access token by using

 public String getAccessToken() { 
      MultiValueMap<String,String> formData = prepareFormData();
      ResponseEntity<JsonNode> response = restTemplate.postForEntity(
      String.format("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId),
      httpEntity(formData),JsonNode.class); 
      JsonNode jsonNode=response.getBody(); 
      return jsonNode.get("access_token").asText(); 
      }
      
        MultiValueMap<String, String> prepareFormData() {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("grant_type", "client_credentials");
        map.add("client_id", "b74bcf37-n044-4242-9187-d85b486379c1");
        map.add("client_secret", "mU48Q~LpWJL7EZDpTzjiNDzqVywcmseUKaR62b5P");
        map.add("scope", "https://vault.azure.net/.default");
        return map;
    }

    HttpEntity<MultiValueMap<String, String>> httpEntity(MultiValueMap<String, String> map) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        httpHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, httpHeaders);
        return request;
    }

But , I am not sure where to use the access token as there is no REST call in the key vault access code.


Solution

  • The access token can be given as follows

    public SecretClient getSecretClient() {
            return new SecretClientBuilder()
                    .vaultUrl("https://mba-key-vault-int.vault.azure.net/")
                    .addPolicy(new CustomHeaderPolicy("AUTHORIZATION","Bearer "+getAccessToken()))
                    .credential(new ClientSecretCredentialBuilder()
                            .tenantId("de8e2ba9-95c1-4fbb-b558-6bf8bb1d8081")
                            .clientId("b74bcf37-n044-4242-9187-d85b486379c1")
                            .clientSecret("mU48Q~LpWJL7EZDpTzjiNDzqVywcmseUKaR62b5P")
                            .build())
                    .buildClient();
        }
    

    And the custom header policy file can be created like below.

    import com.azure.core.http.HttpPipelineCallContext;
    import com.azure.core.http.HttpPipelineNextPolicy;
    import com.azure.core.http.HttpResponse;
    import com.azure.core.http.policy.HttpPipelinePolicy;
    import reactor.core.publisher.Mono;
    
    public class CustomHeaderPolicy implements HttpPipelinePolicy {
        private final String headerName;
        private final String headerValue;
    
        public CustomHeaderPolicy(String headerName, String headerValue) {
            this.headerName = headerName;
            this.headerValue = headerValue;
        }
    
        @Override
        public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
            // Add the custom header to the request
            context.getHttpRequest().setHeader(headerName, headerValue);
            return next.process();
        }
    }
    

    After providing access token , I am able to connect to Azure key vault from my local Spring boot application