Tutorial: Secure Spring Boot apps using Azure Key Vault certificates
This tutorial shows you how to secure your Spring Boot (including Azure Spring Cloud) apps with TLS/SSL certificates using Azure Key Vault and managed identities for Azure resources.
Production-grade Spring Boot applications, whether in the cloud or on-premises, require end-to-end encryption for network traffic using standard TLS protocols. Most TLS/SSL certificates you come across are discoverable from a public root certificate authority (CA). Sometimes, however, this discovery isn't possible. When certificates aren't discoverable, the app must have some way to load such certificates, present them to inbound network connections, and accept them from outbound network connections.
Spring Boot apps typically enable TLS by installing the certificates. The certificates are installed into the local key store of the JVM that's running the Spring Boot app. With Spring on Azure, certificates are not installed locally. Instead, Spring integration for Microsoft Azure provides a secure and frictionless way to enable TLS with help from Azure Key Vault and managed identity for Azure resources.
In this tutorial, you learn how to:
- Create a GNU/Linux VM with system-assigned managed identity
- Create an Azure Key Vault
- Create a self-signed TLS/SSL certificate
- Store the self-signed TLS/SSL certificate in the Azure Key Vault
- Run a Spring Boot application where the TLS/SSL certificate for inbound connections comes from Azure Key Vault
- Run a Spring Boot application where the TLS/SSL certificate for outbound connections comes from Azure Key Vault
Prerequisites
- If you don't have an Azure subscription, create a free account before you begin.
- The
curlcommand. Most UNIX-like operating systems have this command pre-installed. OS-specific clients are available at the official curl website.
- The
jqcommand. Most UNIX-like operating systems have this command pre-installed. OS-specific clients are available at the official jq website.
- Azure CLI, version 2.14.1 or later
A supported Java Development Kit (JDK), version 8. For more information, see Java support on Azure and Azure Stack.
Apache Maven, version 3.0.
Create a GNU/Linux VM with system-assigned managed identity
Use the following steps to create an Azure VM with a system-assigned managed identity and prepare it to run the Spring Boot application. For an overview of managed identities for Azure resources, see What are managed identities for Azure resources?.
Open a Bash shell.
Sign out and delete some authentication files to remove any lingering credentials.
az logout rm ~/.azure/accessTokens.json rm ~/.azure/azureProfile.jsonSign in to your Azure CLI.
az loginSet the subscription ID. Be sure to replace the placeholder with the appropriate value.
az account set -s <your subscription ID>Create an Azure resource group. Take note of the resource group name for later use.
az group create \ --name <your resource group name> \ --location <your resource group region>Set default resource group.
az configure --defaults group=<your resource group name>Create the VM instance with system-assigned managed identity enabled, using the image
UbuntuLTSprovided byUbuntuServer.az vm create \ --name <your VM name> \ --debug \ --generate-ssh-keys \ --assign-identity \ --image UbuntuLTS \ --admin-username azureuserIn the JSON output, note down the value of the
publicIpAddressandsystemAssignedIdentityproperties. You'll use these values later in the tutorial.Note
The name
UbuntuLTSis an Uniform Resource Name (URN) alias, which is a shortened version created for popular images like UbuntuLTS. Run the following command to display a cached list of popular images in table format:az vm image list --output tableInstall the Microsoft OpenJDK. For complete information about OpenJDK, see Microsoft Build of OpenJDK.
ssh azureuser@<your VM public IP address>Add the repository. Replace the version placeholder in following commands and execute:
wget https://packages.microsoft.com/config/ubuntu/{version}/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.debInstall the Microsoft Build of OpenJDK by running the following commands:
sudo apt install apt-transport-https sudo apt update sudo apt install msopenjdk-11Note
Another, faster way to get set up the certified Azul Zulu for Azure – Enterprise Edition VM image, which can avoid the installation of Azure SDK. For complete information about Azul Zulu for Azure, see Download Java for Azure. Run the following command to obtain the URN name.
az vm image list \ --offer Zulu \ --location <your region> \ --all | grep urnThis command may take a while to complete. When the command completes, it produces output similar to the following lines. Select the value for JDK 11 on Ubuntu.
"urn": "azul:azul-zulu11-ubuntu-2004:zulu-jdk11-ubtu2004:20.11.0", ... "urn": "azul:azul-zulu8-ubuntu-2004:zulu-jdk8-ubtu2004:20.11.0", "urn": "azul:azul-zulu8-windows-2019:azul-zulu8-windows2019:20.11.0",Use the following command to accept the terms for the image to allow the VM to be created.
az vm image terms accept --urn azul:azul-zulu11-ubuntu-2004:zulu-jdk11-ubtu2004:20.11.0
Create and configure an Azure Key Vault
Use the following steps to create an Azure Key Vault, and to grant permission for the VM's system-assigned managed identity to access the Key Vault for certificates.
Create an Azure Key Vault within the resource group.
az keyvault create \ --name <your Key Vault name> \ --location <your resource group region> export KEY_VAULT_URI=$(az keyvault show --name <your Key Vault name> | jq -r '.properties.vaultUri')Take note of the
KEY_VAULT_URIvalue. You'll use it later.Grant the VM permission to use the Key Vault for certificates.
az keyvault set-policy \ --name <your Key Vault name> \ --object-id <your system-assigned identity> \ --secret-permissions get list \ --certificate-permissions get list import
Create and store a self-signed TLS/SSL certificate
The steps in this tutorial apply to any TLS/SSL certificate (including self-signed) stored directly in Azure Key Vault. Self-signed certificates aren't suitable for use in production, but are useful for dev and test applications. This tutorial uses a self-signed certificate. To create the certificate, use the following command.
az keyvault certificate create \
--vault-name <your Key Vault name> \
--name mycert \
--policy "$(az keyvault certificate get-default-policy)"
Run a Spring Boot application with secure inbound connections
In this section, you'll create a Spring Boot starter application where the TLS/SSL certificate for inbound connections comes from Azure Key Vault.
To create the application, use the following steps:
- Browse to https://start.spring.io/.
- Select the choices as shown in the picture following this list.
- Project: Maven Project
- Language: Java
- Spring Boot: 2.5.2
- Group: com.contoso (You can put any valid Java package name here.)
- Artifact: ssltest (You can put any valid Java class name here.)
- Packaging: Jar
- Java: 11
- Select Add Dependencies....
- In the text field, type Spring Web and press Ctrl+Enter.
- In the text field type Azure Support and press Enter. Your screen should look like the following.
- At the bottom of the page, select Generate.
- When prompted, download the project to a path on your local computer. This tutorial uses an ssltest directory in the current user's home directory. The values above will give you an ssltest.zip file in that directory.
Enable the Spring Boot app to load the TLS/SSL certificate
To enable the app to load the certificate, use the following steps:
Unzip the ssltest.zip file.
Remove the test directory and its subdirectories. This tutorial ignores the test, so you can safely delete the directory.
Rename application.properties in src/main/resources to application.yml.
The file layout will look like the following.
├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── main ├── java │ └── com │ └── contoso │ └── ssltest │ └── SsltestApplication.java └── resources ├── application.yml ├── static └── templatesModify the POM to add a dependency on
azure-spring-boot-starter-keyvault-certificates. Add the following code to the<dependencies>section of the pom.xml file.<dependency> <groupId>com.azure.spring</groupId> <artifactId>azure-spring-boot-starter-keyvault-certificates</artifactId> </dependency>Edit the src/main/resources/application.yml file so that it has the following contents.
server: ssl: key-alias: <the name of the certificate in Azure Key Vault to use> key-store-type: AzureKeyVault trust-store-type: AzureKeyVault port: 8443 azure: keyvault: uri: <the URI of the Azure Key Vault to use>These values enable the Spring Boot app to perform the load action for the TLS/SSL certificate, as mentioned at the beginning of the tutorial. The following table describes the property values.
Property Name Explanation server.port The local TCP port on which to listen for HTTPS connections. server.ssl.key-alias The value of the --nameargument you passed toaz keyvault certificate create.server.ssl.key-store-type Must be AzureKeyVault.server.ssl.trust-store-type Must be AzureKeyVault.azure.keyvault.uri The vaultUriproperty in the return JSON fromaz keyvault create. You saved this value in an environment variable.The only property specific to Key Vault is
azure.keyvault.uri. The app is running on a VM whose system-assigned managed identity has been granted access to the Key Vault. Therefore, the app has also been granted access.
These changes enable the Spring Boot app to load the TLS/SSL certificate. In the next section, you'll enable the app to perform the accept action for the TLS/SSL certificate, as mentioned at the beginning of the tutorial.
Create a Spring Boot REST controller
To create the REST controller, use the following steps:
Edit the src/main/java/com/contoso/ssltest/SsltestApplication.java file so that it has the following contents.
package com.contoso.ssltest; 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; @SpringBootApplication @RestController public class SsltestApplication { public static void main(String[] args) { SpringApplication.run(SsltestApplication.class, args); } @GetMapping(value = "/ssl-test") public String inbound(){ return "Inbound TLS is working!!"; } @GetMapping(value = "/exit") public void exit() { System.exit(0); } }Calling
System.exit(0)from within an unauthenticated REST GET call is only for demonstration purposes. Don't useSystem.exit(0)in a real application.This code illustrates the present action mentioned at the beginning of this tutorial. The following list highlights some details about this code:
- There's now a
@RestControllerannotation on theSsltestApplicationclass generated by Spring Initializr. - There's a method annotated with
@GetMapping, with avaluefor the HTTP call you'll make. - The
inboundmethod simply returns a greeting when a browser makes an HTTPS request to the/ssl-testpath. Theinboundmethod illustrates how the server presents the TLS/SSL certificate to the browser. - The
exitmethod will cause the JVM to exit when invoked. This method is a convenience to make the sample easy to run in the context of this tutorial.
- There's now a
Open a new Bash shell and navigate to the ssltest directory. Run the following command.
mvn clean packageMaven compiles the code and packages it up into an executable JAR file
Verify that the network security group created within
<your resource group name>allows inbound traffic on ports 22 and 8443 from your IP address. To learn about configuring network security group rules to allow inbound traffic, see the Work with security rules section of Create, change, or delete a network security group.Put the executable JAR file on the VM.
cd target sftp azureuser@<your VM public IP address> put *.jar
Run the app on the server
Now that you've built the Spring Boot app and uploaded it to the VM, use the following steps to run it on the VM and call the REST endpoint with curl.
Use SSH to connect to the VM, then run the executable jar.
set -o noglob ssh azureuser@<your VM public IP address> "java -jar *.jar"Open a new Bash shell and execute the following command to verify that the server presents the TLS/SSL certificate.
curl --insecure https://<your VM public IP address>:8443/ssl-testInvoke the
exitpath to kill the server and close the network sockets.curl --insecure https://<your VM public IP address>:8443/exit
Now that you've seen the load and present actions with a self-signed TLS/SSL certificate, you'll make some trivial changes to the app to see the accept action as well.
Run a Spring Boot application with secure outbound connections
In this section, you'll modify the code in the previous section so that the TLS/SSL certificate for outbound connections comes from Azure Key Vault. Therefore, the load, present, and accept actions are satisfied from the Azure Key Vault.
Modify the SsltestApplication to illustrate outbound TLS connections
Use the following steps to modify the application:
Add the dependency on Apache HTTP Client by adding the following code to the
<dependencies>section of the pom.xml file.<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency>Add a new rest endpoint called
ssl-test-outbound. This endpoint opens up a TLS socket to itself and verifies that the TLS connection accepts the TLS/SSL certificate.Replace the contents of SsltestApplication.java with the following code.
package com.contoso.ssltest; import java.security.KeyStore; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import com.azure.security.keyvault.jca.KeyVaultLoadStoreParameter; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; @SpringBootApplication @RestController public class SsltestApplication { public static void main(String[] args) { SpringApplication.run(SsltestApplication.class, args); } @GetMapping(value = "/ssl-test") public String inbound(){ return "Inbound TLS is working!!"; } @GetMapping(value = "/ssl-test-outbound") public String outbound() throws Exception { KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault"); KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( System.getProperty("azure.keyvault.uri")); azureKeyVaultKeyStore.load(parameter); SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial(azureKeyVaultKeyStore, null) .build(); HostnameVerifier allowAll = (String hostName, SSLSession session) -> true; SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, allowAll); CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(csf) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient); RestTemplate restTemplate = new RestTemplate(requestFactory); String sslTest = "https://localhost:8443/ssl-test"; ResponseEntity<String> response = restTemplate.getForEntity(sslTest, String.class); return "Outbound TLS " + (response.getStatusCode() == HttpStatus.OK ? "is" : "is not") + " Working!!"; } @GetMapping(value = "/exit") public void exit() { System.exit(0); } }Build the app.
cd ssltest mvn clean packageUpload the app again using the same
sftpcommand from earlier in this article.cd target sftp <your VM public IP address> put *.jarRun the app on the VM.
set -o noglob ssh azureuser@<your VM public IP address> "java -jar *.jar"After the server is running, verify that the server accepts the TLS/SSL certificate. In the same Bash shell where you issued the previous
curlcommand, run the following command.curl --insecure https://<your VM public IP address>:8443/ssl-test-outboundYou should see the message
Outbound TLS is working!!.Invoke the
exitpath to kill the server and close the network sockets.curl --insecure https://<your VM public IP address>:8443/exit
You've now observed a simple illustration of the load, present, and accept actions with a self-signed TLS/SSL certificate stored in Azure Key Vault.
Clean up resources
When you're finished with the Azure resources you created in this tutorial, you can delete them using the following command:
az group delete --name <your resource group name>
Next steps
Explore other things you can do with Spring and Azure.