Skip to content

redhat-developer-demos/spring-boot-secrets-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Configuring Spring Boot Application on Kubernetes with Secrets

A demo Spring Boot application that demonstrates on using Kubernetes Secrets to configure Spring Boot application.

Setup

  • You might need an access to Kubernetes Cluster to play with this application. The easiest way to get local Kuberentes cluster up and running is using minikube

  • Install Spring Boot CLI

  • Untar/Unzip Spring Boot CLI 1.5.7.RELEASE and add it your path

Note
The rest of the document assumes you have minikube up and running

Build and Deploy

Create project

For the demo purpose we will be creating a simple Spring Boot project,

spring init --artifactId=spring-boot-secrets-demo \
   --name="Kubernetes:: Spring Boot:: Demos :: Secrets Demo"  \
   --groupId="com.redhat.developers" \
   --package-name="com.redhat.developers" \
   --dependencies=web,actuator,security,lombok \
   --extract spring-boot-secrets-demo (1)

cd spring-boot-secrets-demo

./mvnw io.fabric8:fabric8-maven-plugin:3.5.30:setup (2)
  1. Creates a Spring Boot project with web and actuator dependencies

  2. Adds the fabric8-maven-plugin to the project that will help with deploying the application on Kubernetes

The complete demo sources is available here

The directory spring-boot-secrets-demo will be referred to as $PROJECT_HOME throughout this document.

Configure application security

The spring-boot-secrets-demo application uses spring-security which by default enables security on the entire spring boot application. The default user and password is displayed something like Using default security password: 981d5f9f-c8ea-413f-8f3b-71daaa20d53c when the spring boot application boots up.

To demonstrate how Kubernetes secrets can be used to override the default user/password, update the application.properties to be like:

security.user.name=${SECRETS_DEMO_USER:demo} (1)
security.user.password=${SECRETS_DEMO_USER_PASSWD:demo} (2)
  1. The spring security user

  2. The spring security password

Secrets as Environment variables

Create kubernetes secrets called spring-security (this just a name I am using but it could be anything of your choice) and add two properties like spring.user.name and spring.user.password to it.

Create secret

kubectl create secret generic spring-security \
 --from-literal=spring.user.name=demo \
 --from-literal=spring.user.password=password (1)
kubectl get secret spring-security -o yaml (2)
  1. creates a kubernetes secret called spring-security with to properties called spring.user.name and spring.user.password

  2. Displays the secret spring-security content

Example output of kubectl get secret spring-security -o yaml

apiVersion: v1
data:
  spring.user.name: ZGVtbw== (1)
  spring.user.password: cGFzc3dvcmQ= (1)
kind: Secret
metadata:
  creationTimestamp: 2017-09-19T15:24:29Z
  name: spring-security
  namespace: default
  resourceVersion: "71363"
  selfLink: /api/v1/namespaces/default/secrets/spring-security
  uid: a0e0254e-9d4e-11e7-9b8d-080027da6995
type: Opaque
  1. Displays the value of the secret as base64 encoded string

Kubernetes Manifests

As the application is configured to use fabric8-maven-plugin, we can create Kubernetes deployment and service as fragments in $PROJECT_HOME/src/main/fabric8. The fabric8-maven-plugin takes care of building the complete Kubernetes manifests by merging the contents of the fragment(s) from $PROJECT_HOME/src/main/fabric8 during deploy.

Deployment

Create a file called deployment.yaml in $PROJECT_HOME/src/main/fabric8 with the following contents,

spec:
  template:
    spec:
      containers:
        - env:
          - name: SECRETS_DEMO_USER (1)
            valueFrom:
              secretKeyRef: (2)
                name: spring-security (3)
                key: spring.user.name (4)
          - name: SECRETS_DEMO_USER_PASSWD (1)
            valueFrom:
              secretKeyRef: (2)
                name: spring-security (3)
                key: spring.user.password (4)
  1. Define Environment variable called SECRETS_DEMO_USER and SECRETS_DEMO_USER_PASSWD

  2. Defines that the value will be a Secret reference

  3. Name of the Secret from where to look for the key/value pair, this should be same name you have used in Create secret

  4. The property key inside the Secret, whose value needs to be assigned to environment variable SECRETS_DEMO_USER and SECRETS_DEMO_USER_PASSWD respectively

Service

To enable easy access of the application, we make the type of Kubernetes service as NodePort, create a file called svc.yaml in $PROJECT_HOME/src/main/fabric8 with the following contents,

apiVersion: v1
kind: Service
spec:
  type: NodePort (1)
  1. expose the service using NodePort

Deploy

To deploy the application execute the command ./mvnw clean fabric8:deploy. The application deployment status can be checked using the command kubectl get pods -w

Access the Application

To access and test the application execute the following command,

curl -i $(minikube service spring-boot-secrets-demo --url)/; echo ""; (1)
curl -u demo:password $(minikube service spring-boot-secrets-demo --url)/mygithuborgs; echo ""; (2)
  1. Should fail with unauthorized (HTTP 401) error

  2. Should still display 404 - as there is no resource at / but your request is now authorized

The above command should display a message like Hello jerry! Welcome to Configuring Spring Boot on Kubernetes!

Note
minikube service spring-boot-secrets-demo --url is used to get the service url and port via which we can access the application

Secrets mounted as Files

The secrets could also be mounted as files inside the containers. To demostrate lets build a simple REST controller called GitHubController, that will query the GitHub API for your organizations list. Querying for organizations list is a authorized call hence you need to create your Personal Access Token

Store the your github user id in a file called github.user and the Personal Access Token in a file called github.token.

Create secret from files

The secrets can also be created from file, we will use github.user and github.token to create a new secret called spring-github-demo

kubectl create secret generic spring-github-demo \
  --from-file ./github.user \ (1)
  --from-file ./github.token (2)

kubectl get secret spring-github-demo -oyaml (3)
  1. Adds file github.user to secret

  2. Adds file github.token to secret

  3. Display the created secret

Example output from kubectl get secret spring-github-demo -oyaml

apiVersion: v1
data:
  github.token: NzIyMzUxMjMwMDlmYjYyMzNkMTAzZTlmMjVmMGIzOTQyNWRiOTc5Zgo= (1)
  github.user: a2FtZXNoc2FtcGF0aAo= (2)
kind: Secret
metadata:
  creationTimestamp: 2017-09-19T16:27:10Z
  name: spring-github-demo
  namespace: default
  resourceVersion: "75673"
  selfLink: /api/v1/namespaces/default/secrets/spring-github-demo
  uid: 632e05fd-9d57-11e7-9b8d-080027da6995
type: Opaque
  1. File ./github.token stored as base64 encoded string

  2. File ./github.user stored as base64 encoded string

Update application.properties

security.user.name=${SECRETS_DEMO_USER:demo}
security.user.password=${SECRETS_DEMO_USER_PASSWD:demo}
demo.secretsPath=/deployments (1)
  1. The secret files mount path

Update deployment

The deployment.yaml needs to be updated to mount the user and token as files, update the deployment.yaml in $PROJECT_HOME/src/main/fabric8 with the following contents,

spec:
  template:
    spec:
      containers:
        - env:
          - name: SECRETS_DEMO_USER
            valueFrom:
              secretKeyRef:
                name: spring-security
                key: spring.user.name
          - name: SECRETS_DEMO_USER_PASSWD
            valueFrom:
              secretKeyRef:
                name: spring-security
                key: spring.user.password
          volumeMounts:
          - name: github-user (1)
            mountPath: "/deployments/github" (2)
            readOnly: true
      volumes:
      - name: github-user
        secret:
          secretName: spring-github-demo (3)
          items:
          - key: github.user (4)
            path: user (5)
          - key: github.token (6)
            path: token (7)
  1. Logical name of the volume to refer later in the deployment

  2. The path or location within container where to mount the files

  3. The secret name from which to load the secrets

  4. The key github.user from secret spring-github-demo

  5. The path under /deployments/github, in this case /deployments/github/user

  6. The key github.token from secret spring-github-demo

  7. The path under /deployments/github, in this case /deployments/github/token

Update KubernetesSpringBootDemosSecretsDemoApplication

package com.redhat.developers;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class KubernetesSpringBootDemosSecretsDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(KubernetesSpringBootDemosSecretsDemoApplication.class, args);
	}

	@Configuration
    public class DemoConfiguration {

        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { (1)
            return restTemplateBuilder.build();
        }
    }
}
  1. Adding RestTemplate that will be used to query GitHub REST API

Create a REST API to query GitHub

Create a Controller called GitHubController that will query the GitHub API using your github user id and token.

package com.redhat.developers;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplate;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

/**
 * A simple demo rest controller that calls GitHub API to perform some simple operations which will require authorizations
 * The idea of this controller is to demonstrate how to use the Kubernetes Secrets mounted as file
 * and use the token and username to call GitHub
 *
 * @author kameshsampath
 */
@Slf4j
@RestController
public class GitHubController {

    @Value("${demo.secretsPath}")
    private String secretsPath; //(1)

    private final RestTemplate restTemplate;

    public GitHubController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    /**
     * Get the list of organizations that the user belongs to in GitHub
     */
    @GetMapping("/mygithuborgs")
    public ResponseEntity<String> listMyOrganizations() {
        try {

            final URI githubUserSecretsURI = ResourceUtils.getURL(secretsPath + "/github/user").toURI(); // (2)
            final URI githubUserTokenSecretsURI = ResourceUtils.getURL(secretsPath + "/github/token").toURI(); //(3)

            final byte[] encodedGithubUser = Files.readAllBytes(Paths.get(githubUserSecretsURI));
            final byte[] encodedGithubToken = Files.readAllBytes(Paths.get(githubUserTokenSecretsURI));

            String githubUser = sanitize(encodedGithubUser);

            String githubUserToken = sanitize(encodedGithubToken);

            String authHeader = String.format("%s:%s", githubUser, githubUserToken);

            log.info("Listing Organizations of user :{}", githubUser);

            String basicAutheader = Base64.getEncoder().encodeToString(authHeader.getBytes());

            log.info("Auth Header : {}", basicAutheader);

            ResponseEntity<String> response =
                restTemplate.exchange(buildHttpEntity("user/orgs", basicAutheader), String.class);

            return ResponseEntity
                .status(response.getStatusCode().value())
                .body(response.getBody());

        } catch (URISyntaxException e) {
            log.error("Error querying github", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());

        } catch (IOException e) {
            log.error("Error querying github", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }
    }

    /**
     * A method to build {@link RequestEntity} by adding needed basic authentication headers
     *
     * @param path           - the github api path to call with out leading &quot;/&quot;
     * @param basicAutheader - the Basic Authorization Base64 string representation header value
     * @return {@link RequestEntity}
     */
    private RequestEntity<Void> buildHttpEntity(String path, String basicAutheader) {
        URI githubApiUri = new UriTemplate("https://api.github.com/{path}").expand(path);
        log.info("Calling API:{}", githubApiUri.toASCIIString());
        RequestEntity<Void> requestEntity =
            RequestEntity.get(githubApiUri)
                .header("Authorization", String.format(" Basic %s", basicAutheader))
                .accept(MediaType.parseMediaType("application/vnd.github.v3+json"))
                .build();

        return requestEntity;
    }

    /**
     * remove all new lines from the String
     *
     * @param strBytes - the string bytes where newline to be removed
     * @return sanitized string without newlines
     */
    private String sanitize(byte[] strBytes) {
        return new String(strBytes)
            .replace("\r", "")
            .replace("\n", "");
    }
}
  1. The secrets path where to look for the file

  2. The github user secret path mounted inside the container

  3. The github user token path mounted inside the container

The application can be Deployed like before.

Access the Application

To access and test the application execute the following command,

curl -i $(minikube service spring-boot-secrets-demo --url)/mygithuborgs; echo ""; (1)
curl -u demo:password $(minikube service spring-boot-secrets-demo --url)/mygithuborgs; echo ""; (2)
  1. Should fail with unauthorized (HTTP 401) error

  2. Should display your github organizations

--END--

About

Demo app to supplement blog Configuring Spring Boot App on Kubernetes Part II

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published