Spring Cloud Services - Config Server

June 22, 2018

Spring Cloud Config is a great tool to externalize configuration in a microservice architecture. It has server and client side support. In this blog post we are going to look into how to set up the server part locally and on Cloud Foundry and use it with a client application.

Config Server running locally

The config server can be embedded in a Spring Boot application using the @EnableConfigServer annotation, but here we look into how to run it using the Spring Boot CLI with Spring Cloud CLI extension installed.
The spring cloud --list lists the available services which can be started

spring cloud --list
configserver dataflow eureka h2 hystrixdashboard kafka zipkin

By default running spring cloud configserver it will run in the “native” profile and serving configuration from the local directory ./launcher. We can customise the config server with a configserver.yml file inside the ~/.spring-cloud folder

spring:
  profiles:
    active: git

  cloud:
    config:
      server:
        git:
          uri: file://${user.home}/projects/cloudfoundry/config-repo

Inside the config-repo we create an example foo-service.yml (foo-service will be our client application) file with the content

foo:
  message: Hallo
  secret: '{cipher}6050a8af7826c89dcd30c9046f484a5a169d27beb40a67db72440474c5cc6d77'

The foo.secret value was encrypted using

spring encrypt Gruetzi --key s3cr3t

We set the ENCRYPT_KEY environment variable before starting the config server

export ENCRYPT_KEY=s3cr3t

And we start up the config server using

spring cloud configserver

We can easily verify that the config server knows about the external configuration of the foo-service

http :8888/foo-service/default
{
    "label": null,
    "name": "foo-service",
    "profiles": [
        "default"
    ],
    "propertySources": [
        {
            "name": "file:///Users/zoal/projects/cloudfoundry/config-repo/foo-service.yml",
            "source": {
                "foo.message": "Hallo",
                "foo.secret": "6050a8af7826c89dcd30c9046f484a5a169d27beb40a67db72440474c5cc6d77"
            }
        }
    ],
    "state": null,
    "version": null
}

After starting the foo-service client application we can see in the logs:

[           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:8888

The foo-service is using config-first bootstrap and with the spring.cloud.config.uri locates the config server which is set to http://localhost:8888 by default.

With a simple controller we print out the foo.message and foo.secret properties which we can verify with

http :8080 -a user:password
Hallo/Gruetzi

Config Server on Pivotal Cloud Foundry (PCF)

So far so good. Let’s setup this little example on a Pivotal Web Services (an instance of PCF hosted by Pivotal) where we will use the Config Server from Spring Cloud Service.

First we need to install the CloudFoundry CLI and create a Pivotal Web Services account.

After we pushed the config-rep to GitHub we can configure a config-server service instance with this shared git repository. We will use the p-config-server service with the trial plan, which we found from that is available when as a response to cf marketplace We create a service instance named config-server with the following command

cf cs p-config-server trial config-server -c config-server-setup.json

where the config-server-setup.json contains the shared git repository:

{
  "git": {
    "uri": "https://github.com/altfatterz/config-repo.git"
  }
}

We wait until the service instance is created, the status can be easily checked using cf service config-server.

Next we need to set the encrypt key for the config-server. By default the config-server will decrypt the secrets and return the decrypted secrets to the client applications like in our case the to the foo-service. It is important that we protect the config server and have https connection between the config server and its client applications. It is also possible that the client applications decrypt the secrets locally. In this case we need to set the spring.cloud.config.server.encrypt.enabled=false in the bootstrap.yml and set the encrypt key for the client applications.

In Pivotal Web Services from the Config Server Dashboard we can get the config-server service instance configuration and by adding the "encrypt":{"key":"s3cr3t"} we can update the service instance:

cf update-service config-server -c '{"count":1,"git":{"uri":"https://github.com/altfatterz/config-repo.git"}, "encrypt":{"key":"s3cr3t"} }'

Then we have to add the following dependency to the foo-service:

<dependency>
    <groupId>io.pivotal.spring.cloud</groupId>
    <artifactId>spring-cloud-services-starter-config-client</artifactId>
</dependency>

edit the bootstrap.yml configuration file

spring:
  cloud:
    config:
      uri: ${vcap.services.config-server.credentials.uri:http://localhost:8888}

and add the mainfest.yml file

applications:
- name: foo-service
  host: foo-service
  memory: 756M
  instances: 1
  path: target/foo-service-0.0.1-SNAPSHOT.jar
  services:
  - config-server

and deploy the service using cf push from the foo-service directory.

After we verify that the app is up and running via cf service foo-service we can test our service using

http foo-service.cfapps.io -a user:password

Hallo/Gruetzi

Let’s check how does this work. When the foo-service was bound the to config-server service instance the connection details were set in the VCAP_SERVICES environment variable.

cf env foo-service

{
 "VCAP_SERVICES": {
  "p-config-server": [
   {
    "binding_name": null,
    "credentials": {
     "access_token_uri": "https://p-spring-cloud-services.uaa.run.pivotal.io/oauth/token",
     "client_id": "p-config-server-9c1a682a-089d-49be-97e0-1ad1f83366a1",
     "client_secret": "a2MRVZLjUyCy",
     "uri": "https://config-7e4b48ae-efd6-4773-b377-c8d1edf5ced6.cfapps.io"
    },
    "instance_name": "config-server",
    "label": "p-config-server",
    "name": "config-server",
    "plan": "trial",
    "provider": null,
    "syslog_drain_url": null,
    "tags": [
     "configuration",
     "spring-cloud"
    ],
    "volume_mounts": []
   }
  ]
 }
}

We can see that the communication between the config-server service instance and its client applications is via OAuth 2.0 protocol. The client must obtain first an OAuth 2.0 token in order to make requests directly against the config-server. The included spring-cloud-services-starter-config-client dependency handles these OAuth 2.0 requests behind the scene for us.

curl https://p-config-server-9c1a682a-089d-49be-97e0-1ad1f83366a1:[email protected]/oauth/token -d grant_type=client_credentials

We get an access token like this. Note that according to the response the token is only valid for 29 seconds.

{
    "access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIzN2MxZjczNTMwMDc0OTQ2ODFjZTIxNzFiZWIxZDllZiIsInN1YiI6InAtY29uZmlnLXNlcnZlci05YzFhNjgyYS0wODlkLTQ5YmUtOTdlMC0xYWQxZjgzMzY2YTEiLCJhdXRob3JpdGllcyI6WyJwLWNvbmZpZy1zZXJ2ZXIuN2U0YjQ4YWUtZWZkNi00NzczLWIzNzctYzhkMWVkZjVjZWQ2LnJlYWQiXSwic2NvcGUiOlsicC1jb25maWctc2VydmVyLjdlNGI0OGFlLWVmZDYtNDc3My1iMzc3LWM4ZDFlZGY1Y2VkNi5yZWFkIl0sImNsaWVudF9pZCI6InAtY29uZmlnLXNlcnZlci05YzFhNjgyYS0wODlkLTQ5YmUtOTdlMC0xYWQxZjgzMzY2YTEiLCJjaWQiOiJwLWNvbmZpZy1zZXJ2ZXItOWMxYTY4MmEtMDg5ZC00OWJlLTk3ZTAtMWFkMWY4MzM2NmExIiwiYXpwIjoicC1jb25maWctc2VydmVyLTljMWE2ODJhLTA4OWQtNDliZS05N2UwLTFhZDFmODMzNjZhMSIsImdyYW50X3R5cGUiOiJjbGllbnRfY3JlZGVudGlhbHMiLCJyZXZfc2lnIjoiOWZlZjNmZDYiLCJpYXQiOjE1MzIxODkwMTQsImV4cCI6MTUzMjE4OTA0NCwiaXNzIjoiaHR0cHM6Ly9wLXNwcmluZy1jbG91ZC1zZXJ2aWNlcy51YWEucnVuLnBpdm90YWwuaW8vb2F1dGgvdG9rZW4iLCJ6aWQiOiIwNDlmYjNmOC1mOTc2LTQ1YTQtOWVhMy0wMDRkNDU2ZDRlMzgiLCJhdWQiOlsicC1jb25maWctc2VydmVyLTljMWE2ODJhLTA4OWQtNDliZS05N2UwLTFhZDFmODMzNjZhMSIsInAtY29uZmlnLXNlcnZlci43ZTRiNDhhZS1lZmQ2LTQ3NzMtYjM3Ny1jOGQxZWRmNWNlZDYiXX0.NDKKceOUv_WigMox38xoE7VuDRi6uz3zddFuu7F8Wz7UtLWgZ3wgLyzeKfT2f4gljgEOqT5XU3CJRazVHpbCdgTkhb4M37_sgFnHOZPYFgjN3GneSfT3ZLNhKyf36OTxbPA6sIgBYfFc1i6NDyG5y8CeLdDxEBkm5GMDtlvP-rptDhokeSlnJ6J1M6pJQZd7wYDqxZPfpvLesPE--yjUmRDO4Np8dF5DSOhSGM1El5HmCFJhy6lze1fizaLsGtbnqPIbZLlceENoullvHT-dt0w_Wxf9ErcUp_DIi3BMo5fUSCVyNLfTG965aye16_9v2IL9A08CnThxcCWZvk6Z1A",
    "token_type":"bearer",
    "expires_in":29,
    "scope":"p-config-server.7e4b48ae-efd6-4773-b377-c8d1edf5ced6.read",
    "jti":"37c1f7353007494681ce2171beb1d9ef"
}

And with this token the client application can send direct requests to the config-server:

http https://config-7e4b48ae-efd6-4773-b377-c8d1edf5ced6.cfapps.io/foo-service/default  'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIyMTE3NGUwMjNiNzU0ZTg3YWYxNTczYmUzNTI3YjJmYSIsInN1YiI6InAtY29uZmlnLXNlcnZlci05YzFhNjgyYS0wODlkLTQ5YmUtOTdlMC0xYWQxZjgzMzY2YTEiLCJhdXRob3JpdGllcyI6WyJwLWNvbmZpZy1zZXJ2ZXIuN2U0YjQ4YWUtZWZkNi00NzczLWIzNzctYzhkMWVkZjVjZWQ2LnJlYWQiXSwic2NvcGUiOlsicC1jb25maWctc2VydmVyLjdlNGI0OGFlLWVmZDYtNDc3My1iMzc3LWM4ZDFlZGY1Y2VkNi5yZWFkIl0sImNsaWVudF9pZCI6InAtY29uZmlnLXNlcnZlci05YzFhNjgyYS0wODlkLTQ5YmUtOTdlMC0xYWQxZjgzMzY2YTEiLCJjaWQiOiJwLWNvbmZpZy1zZXJ2ZXItOWMxYTY4MmEtMDg5ZC00OWJlLTk3ZTAtMWFkMWY4MzM2NmExIiwiYXpwIjoicC1jb25maWctc2VydmVyLTljMWE2ODJhLTA4OWQtNDliZS05N2UwLTFhZDFmODMzNjZhMSIsImdyYW50X3R5cGUiOiJjbGllbnRfY3JlZGVudGlhbHMiLCJyZXZfc2lnIjoiOWZlZjNmZDYiLCJpYXQiOjE1MzIxODk4MTgsImV4cCI6MTUzMjE4OTg0OCwiaXNzIjoiaHR0cHM6Ly9wLXNwcmluZy1jbG91ZC1zZXJ2aWNlcy51YWEucnVuLnBpdm90YWwuaW8vb2F1dGgvdG9rZW4iLCJ6aWQiOiIwNDlmYjNmOC1mOTc2LTQ1YTQtOWVhMy0wMDRkNDU2ZDRlMzgiLCJhdWQiOlsicC1jb25maWctc2VydmVyLTljMWE2ODJhLTA4OWQtNDliZS05N2UwLTFhZDFmODMzNjZhMSIsInAtY29uZmlnLXNlcnZlci43ZTRiNDhhZS1lZmQ2LTQ3NzMtYjM3Ny1jOGQxZWRmNWNlZDYiXX0.RE0MBHDPolEeiz9Y9R0kb1_rzdyCr1c7AQA3zz4qiPJDW0DsnOFa-_GS-z7eyrz4QCanvFmd9m9WpRsh_Y4bnATPiYclqdXy65-Ziu3FKZka8Tv8WQ7dNOM520d2Dn6PcDapz8hJ7zoPJ9RxS2daZVgNkhzgFQOLxzk6t5coecV8ccN4-lbF3yK_E0aHe2QsIyZSeiQoBWsKC1r_8wZSgVbCFcQ7WkcSS9MIQ9SOxyLpIKsxjDj2JASw7MNID5iBJ8J_-0FfNtu710j7ZhHOh_4Xy9r399ie34NvdQKHTjqoctBjEKUD6CqaUwuYQWIXNj__ox44ex3RXAEq0d92Jg'
{
    "label": null,
    "name": "foo-service",
    "profiles": [
        "default"
    ],
    "propertySources": [
        {
            "name": "https://github.com/altfatterz/config-repo.git/foo-service.yml",
            "source": {
                "foo.message": "Hallo",
                "foo.secret": "Gruetzi"
            }
        }
    ],
    "state": null,
    "version": "3635a8a138bcac086c66e55013ae1595aded0045"
}