Spring Boot & Infisical | Managing your sensitive configuration
Hello everyone! In this article, I’ll show you how to employ Infisical in your Spring Boot application.
Hello everyone! In this article, I’ll show you how to employ Infisical in your Spring Boot application.
Why Infisical?
Most of my projects utilize HashiCorp Vault to store sensitive data such as database usernames and passwords.
But last year, HashiCorp changed Terraform’s license to non-open source. This could also happen to Vault, or not.
The second reason why I chose Infisical for this article is because IBM acquired Terraform this year.
Introduction
Like the HashiCorp Vault, Infisical is a secret management platform. Let’s start by starting up the Infisical instance locally with the docker-compose file provided by the documentation. First, we need to download it with the following command.
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
The second step is to get the .env file. We can do it by command provided by the documentation.
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
Now we can start an instance of Infisical.
docker-compose -f docker-compose.yml up
After that, you should see this screen at http://localhost:8080
Then, after successfully signing up, you will see a window offering you the ability to download an emergency kit.
Now, we can create a new project.
Let’s create a project with the name medium-secret-service.
After that, the project window appears.
Then let’s create two secrets — DATABASE_USERNAME and DATABASE_PASSWORD.
Now it’s time to give access to these secrets to our service. Go to the tab Machine Identities. Create a new one and save clientId; after that, create a clientSecret for identity. Now, you can add identity to the project.
Coding Time
The next step is to configure Maven to access the Infisical SDK.
In order to access it, you have to modify your ~/.m2/settings.xml
<profiles>
<profile>
<repository>
<id>infisical</id>
<url>https://maven.pkg.github.com/infisical/sdk</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<servers>
<server>
<id>infisical</id>
<username>YOUR_GITHUB_NAME_HERE</username>
<password>YOUR_GITHUB_PAT_HERERE</password>
</server>
</servers>
After that, we can add a dependency to our pom.xml
<dependency>
<groupId>com.infisical</groupId>
<artifactId>sdk</artifactId>
<version>2.2.3-SNAPSHOT</version>
</dependency>
Next, we will implement two services to obtain secrets from properties or the Infisical. Next, we will implement two services to obtain secrets from properties or the Infisical. Before creating the services, we should define our model for secret data.
package io.vrnsky.mediumsecretservice.config.secret;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Secret {
private String username;
private String password;
}
Then, let’s declare the interface for our services.
package io.vrnsky.mediumsecretservice.config.secret;
public interface SecretService {
Secret accessSecret();
}
Then, let’s declare the interface for our services. The first implementation will be a straightforward approach to get secret configuration from application.properties or application.yml
package io.vrnsky.mediumsecretservice.config.secret;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
@Service
@Profile("!infisical")
public class PropertySecretService implements SecretService {
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Override
public Secret accessSecret() {
return new Secret(username, password);
}
}
The second implementation will work with the Infisical through its SDK.
package io.vrnsky.mediumsecretservice.config.secret;
import com.infisical.sdk.InfisicalClient;
import com.infisical.sdk.schema.AuthenticationOptions;
import com.infisical.sdk.schema.ClientSettings;
import com.infisical.sdk.schema.GetSecretOptions;
import com.infisical.sdk.schema.GetSecretResponseSecret;
import com.infisical.sdk.schema.UniversalAuthMethod;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
@Service
@Profile("infisical")
public class InfisicalSecretService implements SecretService {
@Value("${infisical.clientId}")
private String clientId;
@Value("${infisical.clientSecret}")
private String clientSecret;
@Value("${infisical.projectId}")
private String projectId;
@Value("${infisical.siteUrl}")
private String siteUrl;
@Override
public Secret accessSecret() {
ClientSettings settings = new ClientSettings();
settings.setSiteURL(siteUrl);
AuthenticationOptions authOptions = new AuthenticationOptions();
UniversalAuthMethod authMethod = new UniversalAuthMethod();
authMethod.setClientID(clientId);
authMethod.setClientSecret(clientSecret);
authOptions.setUniversalAuth(authMethod);
settings.setAuth(authOptions);
InfisicalClient infisicalClient = new InfisicalClient(settings);
GetSecretOptions options = new GetSecretOptions();
options.setSecretName("DATABASE_USERNAME");
options.setEnvironment("dev");
options.setProjectID(projectId);
Secret secret = new Secret();
GetSecretResponseSecret databaseUserNameSecret = infisicalClient.getSecret(options);
secret.setUsername(databaseUserNameSecret.getSecretValue());
options.setSecretName("DATABASE_PASSWORD");
GetSecretResponseSecret databasePassword = infisicalClient.getSecret(options);
secret.setPassword(databasePassword.getSecretValue());
infisicalClient.close();
return secret;
}
}
Okay, the next thing that we should do is configure our Spring Boot service to use data from the Secret Service instance.
package io.vrnsky.mediumsecretservice.config;
import io.vrnsky.mediumsecretservice.config.secret.Secret;
import io.vrnsky.mediumsecretservice.config.secret.SecretService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Data
@Configuration
public class ServiceConfiguration {
@Value("${spring.datasource.url}")
private String url;
@Bean
@Primary
public DataSource dataSource(SecretService secretService) {
Secret secret = secretService.accessSecret();
return DataSourceBuilder.create()
.url(url)
.username(secret.getUsername())
.password(secret.getPassword())
.build();
}
}
After that, we can check if our service works. First, let’s run the service with this configuration and check that all works fine if the fiscal profile is not enabled.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/medium_service
username: postgres
password: 55555
Let’s now try to run with only the URL in configuration and secret data from the Infisical.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/medium_service
infisical:
siteUrl: http://localhost:80
projectId: 04608ded-201a-471c-9795-248f8c8b1887
clientId: 1ab6f04d-de7f-46cd-9d55-ebf16e5f5c00
clientSecret: 75be0b336f4ee245221ef6d49a9a1fe2ef959fbe309c275879a18d9ab1ce3d43
The service should run as usual.
Conclusion
I think it is best to pay attention to how you store your sensitive configuration, as it is a security measure that prevents restricted access to your data.
I hope you enjoyed this article; feel free to comment about your experience with the Infisical.