Reactive Springboot with Spring Cloud Vault

In the previous post, we saw how we can create reactive Microservices using Spring-boot and Kotlin. I want to write this as a series of articles to address various cross-cutting concerns when we encounter during the implementation of Microservices architecture. In this post, we will see about securing our Microservices using Spring Cloud Security and storing the credentials of the service and MongoDB in the Hashicorp Vault and then retrieve them using Spring Cloud Vault.

In addition to providing a secure means of storing the credential and tokens in the vault, it gives us the advantage of dynamically serving them for your Microservices. We will be using the Hashicorp vault for our demo and use the Azure Vault in the next series. To begin with download the vaultproject from here according to you operating system.

Create a vault config like below and the additional properties of the vault can be checked here. We are using the in-memory vault so the tokens will be persisted anywhere and disable_mlock prevents the memory being swapped to the disk. It is OK to use it for development/testing. Since I am using a MacOS for development mlock is not supported by the system.

backend "inmem" {
}

listener "tcp" {
  address = "0.0.0.0:8200"
  tls_disable = 1
}

disable_mlock = true

Start the vault server using the below command.

vault server -config vault.conf

Open another terminal tab and then give the following commands.

// Setting the vault address to a non http address

export VAULT_ADDR=http://127.0.0.1:8200

// Vault will be initialized with 5 keys and it requires at least 2 of them to unseal it.
// Only after unsealing the vault, it will be open for writing and reading.
// The keys and root token displayed cannot be retrieved, keep them safe.
vault init -key-shares=5 -key-threshold=2

For our example, we are going the use the root token supplied during the init or we can create an entirely new token and use it. vault token-create command can create you a new token.

Now we have export the vault token so that it doesn’t have to passed as an argument everytime.

export VAULT_TOKEN=622eab55-368c-599a-0bce-324c5f59df03

If you run vault token-lookup you should see everything that is stored in the vault. Now we have unseal the vault using any of the two keys generated during the init.

vault unseal row8CH0rtP1GbZjU8/QQ+pZsL2Cs5wXtBY1WaaNpPQKH
vault unseal wQNZFJyfErD4bdnpwrE4sjOuTCGrIV1bxr1ZlgEwI5g+

Now I am going to write a password which will be used to secure our Microservice which we developed in the previous post.

vault write secret/reactive-sec-demo X-Service-Pass=S0meP@ssw0rd

Add the below two packages one for the vault and another for the security in the build.gradle file. We need add the dependency bom and snapshot repo as well. So refer the Github repo of the demo project for the complete list of dependencies.

+       compile('org.springframework.boot:spring-boot-starter-security-reactive')
+       compile('org.springframework.cloud:spring-cloud-starter-vault-config')

Once the dependencies are added, create a security configuration file.

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Value("\${X-Service-Pass}")
    val pass:String?=null;

    @Bean
    @PostConstruct
    fun users () = MapUserDetailsRepository(User.
            withUsername("bytesville")
           .password(pass)
            .roles("ADMIN", "USER")
            .build());
}

EnableWebFluxSecurity annotation registers a WebFluxConfigurer which gives a chance to add custom configuration to you flux application and WebFilterChainFilter which adds security behavior to all the incoming requests.

MapUserDetailsRepository is an in-memory implementation of UserDetailsRepository which we will be using it. If you want to bring your own implementation you have to use a reactive datastore such as a reactive MongoDB or Redis or Couchbase, etc.

We have created a user with username “bytesville” and the password will be retrieved from the spring cloud vault during the run time.

Spring Cloud Vault is injected once you include the dependency in your build.gradle file and you have to add the below configuration in the bootstrap.yml

spring:
    application:
        name: reactive-sec-demo
    cloud:
        vault:
            host: localhost
            port: 8200
            scheme: http
            authentication: TOKEN
            token: 622eab55-368c-599a-0bce-324c5f59df03

Now curl the application and test.

$ curl -v -u bytesville:S0meP@ssw0rd localhost:8080/books | json_pp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'bytesville'
> GET /books HTTP/1.1
> Host: localhost:8080
> Authorization: Basic Ynl0ZXN2aWxsZTpTMG1lUEBzc3cwcmQ=
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
<
{ [295 bytes data]
100   288    0   288    0     0   5374      0 --:--:-- --:--:-- --:--:--  5433
* Connection #0 to host localhost left intact
[
   {
      "id" : "59f865cb1939c823e3f478dd",
      "title" : "Cloud Native Java",
      "author" : "Josh Long and Kenny"
   },
   {
      "author" : "Martin Fowler",
      "title" : "Patterns of Enterprise Architecture",
      "id" : "59f865cb1939c823e3f478df"
   },
   {
      "id" : "59f865cb1939c823e3f478de",
      "author" : "Sam Newman",
      "title" : "Building Microservices"
   }
]

Next we need to add the authentication mechanism to the web client. We have to use the ExchangeFilterFunctions.basicAuthentication to provide the user credentials.

    @Value("\${X-Service-Pass}")
    val password:String?=null;

    @Bean
    @PostConstruct
    fun webClient(): WebClient = WebClient
            .create("http://localhost:8080/books")
            .mutate()
            .filter(ExchangeFilterFunctions.basicAuthentication("bytesville", password))
            .build()

Login in to your mongo DB instance and create a db user

db.createUser({ user: 'mongoadmin', pwd: 'some-initial-password', roles:["root"]] });

And then connect the MongoDB instance via the Hashicorp’s vault and I am using the ssl option set as false for development.

vault write mongodb/config/connection uri="mongodb://mongoadmin:some-initial-password@localhost:27017/admin?ssl=false"

Now we have to create a user which our application will be using to supply the credentials in runtime.

vault write mongodb/roles/readwrite db=mytestdb roles='[{"role": "readWrite", "db": "mytestdb"}]'

I was actually stuck at this for a quite a time and played around with the credentials string. “mytestdb” is the name of my database and the spring boot uses “test” by default. If you want to override it do so by specifying it in your application.yml file

spring.data.mongodb.database=test # Database name.

One last thing, don’t forget to include the spring cloud vault config database dependency it will automatically look for database connection credentials from the vault when you enable it.

spring:
    cloud:
        vault:
            mongodb:
              enabled: true
              role: readwrite
              backend: mongodb

As always the source code is available on the Github.

Leave a Reply

Your email address will not be published. Required fields are marked *