avatar

Bhuwan Upadhyay

Talks all about software engineering

Published on

Secure application secrets by using spring vault

Authors

Introduction

Security and working with secrets is a concern of every developer working with databases, user credentials or API keys. Vault steps in by providing a secure storage combined with access control, revocation, key rolling and auditing. In short: Vault is a service for securely accessing and storing secrets.

In this post, I’ll walk you through to store secrets into vault for your application.

Image

You’ll learn how to setup Spring Cloud Vault to store and read database credentials for your stand-alone application from vault.

Start PostgreSQL and Vault locally

You can download docker-compose.yml file and just run the following to get PostgreSQL and Vault started locally in docker containers.

docker-compose up -d

Creating the VAULT PostgreSQL credentials

Let's access vault container in your terminal.

docker exec -it example-vault /bin/sh
export VAULT_TOKEN="00000000-0000-0000-0000-000000000000"
export VAULT_ADDR="http://127.0.0.1:8200"

Once you have access to vault and postgres access, do the following to enable postgres and create a role that the application will use to connect to PostgreSQL.

  • Enable secrets for postgresql
vault secrets enable postgresql
  • Write postgresql connection url
vault write postgresql/config/connection \
    connection_url="postgresql://user:password@example-vault-db:5432/postgres?sslmode=disable"
  • Apply lease setting: vault create new username and password for database
vault write postgresql/config/lease lease=1h lease_max=24h
  • Add role for user in database
vault write postgresql/roles/readonly \
    sql="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
    GRANT ALL ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
  • To generate new set of credentials run (Vault is now configured to create and manage credentials for Postgres!)
vault read postgresql/creds/readonly

Output

Key                Value
---                -----
lease_id           postgresql/creds/readonly/ihWk1Q9cC3uHgjFBYOBJZFTv
lease_duration     1h
lease_renewable    true
password           4878ef62-ed3d-098e-22c7-71af6891eaa8
username           token-954d49c2-9269-451d-9ce9-97e5aa887222

For more info visit vault documentation here: PostgreSQL Secret Engines

Create a Spring Boot Project

curl https://start.spring.io/starter.tgz -d dependencies=postgresql,cloud-starter-vault-config,data-jpa \
  -d groupId=io.github.bhuwanupadhyay \
  -d artifactId=example-vault \
  -d packageName=io.github.bhuwanupadhyay.tutorial \
  -d baseDir=example-vault \
  -d bootVersion=2.2.2.RELEASE | tar -xzvf -
cd example-vault

Using VAULT in Spring Boot Project with Spring Cloud

Needed maven dependencies under you maven project pom.xml

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-vault-config-databases</artifactId>
</dependency>
<dependency>
 <groupId>org.postgresql</groupId>
 <artifactId>postgresql</artifactId>
 <scope>runtime</scope>
</dependency>

The spring cloud use bootstrap.yml or bootstrap.properties file. So, to connect with vault you need to define following values in bootstrap.yml file.

spring:
  application:
    name: example-vault
  cloud:
    vault:
      uri: http://localhost:8200
      token: '00000000-0000-0000-0000-000000000000'
      database:
        enabled: true
        role: readonly
        backend: postgresql
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
  jpa:
    hibernate:
      ddl-auto: create-drop

Database Access: PostgreSQL (read/write)

Let's implement customer entity and its spring data repository as follows:

Entity

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;

    public Customer() {
    }

    public Customer(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Customer customer = (Customer) o;
        return Objects.equals(id, customer.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

Entity Data Repository

public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

Test Scenario

@Spring BootTest
class DemoApplicationTests {

    @Autowired
    private CustomerRepository repository;

    @BeforeEach
    void setUp() {
        repository.deleteAll();
    }

    @Test
    void canAddCustomer() {
        String name = "Vault - " + UUID.randomUUID().toString();
        repository.save(new Customer(name));
        assertEquals(name, repository.findAll().get(0).getName());
    }
}

You can find example on github: Source Code