---
title: Spring BootアプリをKubernetesにデプロイする際にアプリ側でTLS Terminationをするメモ
tags: ["Spring Boot", "TLS", "Kubernetes", "PKS"]
categories: ["Dev", "CaaS", "Kubernetes"]
date: 2019-08-19T08:39:00Z
updated: 2019-09-29T14:08:56Z
---

KubernetesにSpring Bootアプリを置く場合はIngressを使用するなど、前段でTLS Terminationすることが多いと思うが、
アプリ側で実施したい場合のやり方を説明する。

`X-Forwaded-Proto`ヘッダーがLBが正しく設定してくれず、ログイン後のリダイレクトがうまく行かない（HTTPにリダイレクトされる)ことがあったのでメモ

> PKS + NSX-TでNSX-T Ingressを使うケースで発生。

Spring Boot 2.1.5で動作確認しているが、2.xであれば同じはず。

サンプルアプリとして[Spring Music](https://github.com/cloudfoundry-samples/spring-music)を使用する。
このアプリ自体はただのSpring Bootアプリであり、普通に8080番をlistenするHTTPアプリである。

`docker run`すると[http://localhost:8080](http://localhost:8080)でアクセスできる。
```bash
docker run --rm -p 8080:8080 trisberg/spring-music:cnb
```

![image](https://user-images.githubusercontent.com/106908/63246500-c60e7100-c29d-11e9-951f-074a7de57184.png)


ソースコードを変更せずに、このDokcerイメージをそのまま使ってTLS Terminationしたい。

次の`spring-music.yml`を作成する。ポイントは`initContainers`でTLS証明書(pem形式)をjks形式に変換し、マウントしたボリュームに配置しているところ。
あとは環境変数`SERVER_SSL_****`を設定し、アプリ側のTLS設定を行う。

> [この記事](https://blog.ik.am/entries/450)の応用版

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: spring-music
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-music
  namespace: spring-music
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-music
  template:
    metadata:
      labels:
        app: spring-music
    spec:
      initContainers:
      - image: openjdk:11-jdk-slim
        name: pem-to-keystore
        volumeMounts:
        - name: keystore-volume
          mountPath: /keystores
        - name: spring-music-tls
          mountPath: /spring-music-tls
        env:
        - name: SERVER_SSL_KEY_PASSWORD
          valueFrom:
            secretKeyRef:
              name: spring-music
              key: server.ssl.key-password
        command:          
        - sh
        - -c
        - |
          set -e
          openssl pkcs12 -export \
                  -name spring-music \
                  -in /spring-music-tls/tls.crt \
                  -inkey /spring-music-tls/tls.key \
                  -out /keystores/spring-music.p12 \
                  -password pass:foobar
          keytool -importkeystore \
                  -destkeystore /keystores/spring-music.jks \
                  -srckeystore /keystores/spring-music.p12 \
                  -deststoretype pkcs12 \
                  -srcstoretype pkcs12 \
                  -alias spring-music \
                  -deststorepass ${SERVER_SSL_KEY_PASSWORD} \
                  -destkeypass ${SERVER_SSL_KEY_PASSWORD} \
                  -srcstorepass foobar \
                  -srckeypass foobar \
                  -noprompt
      containers:
      - image: trisberg/spring-music:cnb
        name: spring-music
        ports:
        - containerPort: 8443
        volumeMounts:
        - name: keystore-volume
          mountPath: /keystores
          readOnly: true
        env:
        - name: SERVER_PORT
          value: "8443"
        - name: SERVER_SSL_ENABLED
          value: "true"
        - name: SERVER_SSL_PROTOCOL
          value: TLSv1.2
        - name: SERVER_SSL_KEY_STORE
          value: file:///keystores/spring-music.jks
        - name: SERVER_SSL_KEY_STORE_TYPE
          value: PKCS12
        - name: SERVER_SSL_KEY_ALIAS
          value: spring-music
        - name: SERVER_SSL_KEY_PASSWORD
          valueFrom:
            secretKeyRef:
              name: spring-music
              key: server.ssl.key-password
        - name: SERVER_SSL_KEY_STORE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: spring-music
              key: server.ssl.key-password
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8443
            scheme: HTTPS
          initialDelaySeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /actuator/info
            port: 8443
            scheme: HTTPS
          initialDelaySeconds: 20
          timeoutSeconds: 1
          periodSeconds: 10
          failureThreshold: 1
      volumes:
      - name: keystore-volume
        emptyDir: {}
      - name: spring-music-tls
        secret:
          secretName: spring-music-tls
---
kind: Service
apiVersion: v1
metadata:
  name: spring-music
  namespace: spring-music
spec:
  type: NodePort
  selector:
    app: spring-music
  ports:
  - protocol: TCP
    port: 8443
```

Keystoreのパスワードは次のようにSecretに保存する。

```bash
kubectl -n spring-music create secret generic spring-music \
  --dry-run -o yaml \
  --from-literal server.ssl.key-password=thisisasecret \
  > spring-music-secret.yml
```

次にTLS証明書を作成する。Let's Encryptなどを使用する場合はここはスキップして良い。

今回は`sslip.io`ドメインを使用し、この`*.sslip.io`に対する自己署名証明書を作成する。

`sslip.io`は`a-b-c-d.sslip.io`を`a.b.c.d`に解決してくれるDNSサービスである。

例えば、`10-195-99-214.sslip.io`は`10.195.99.214`を返す。

`*.sslip.io`に対して、`openssl`コマンドで自己署名証明書を作成する。[こちら](https://raw.githubusercontent.com/aws-quickstart/quickstart-pivotal-cloudfoundry/master/scripts/gen_ssl_certs.sh)からスクリプトをダウンロードし、次のコマンドを実行。

```
wget https://raw.githubusercontent.com/aws-quickstart/quickstart-pivotal-cloudfoundry/master/scripts/gen_ssl_certs.sh
chmod +x gen_ssl_certs.sh
./gen_ssl_certs.sh sslip.io
```

この証明書をSecretに保存する。

```bash
kubectl -n spring-music create secret tls spring-music-tls \
  --dry-run -o yaml \
  --cert=sslip.io.crt \
  --key=sslip.io.key \
  > spring-music-secret-tls.yml
```

これでマニフェストが揃ったので、`kubectl apply`を実行する。

```bash
kubectl apply -f spring-music.yml \
  -f spring-music-secret.yml \
  -f spring-music-secret-tls.yml
```

次のコマンドでログを確認。

```bash
kubectl -n spring-music logs \
  -f $(kubectl -n spring-music get pod -l app=spring-music -o jsonpath='{.items[0].metadata.name}')
```

次のメッセージが出ていればOK

```
2019-08-19 08:13:27.700  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8443 (https)
```


```
$ kubectl -n spring-music get all
NAME                                READY   STATUS    RESTARTS   AGE
pod/spring-music-6d6d57586b-w6mfc   1/1     Running   0          31s

NAME                   TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/spring-music   NodePort   10.100.200.241   <none>        8443:31308/TCP   30s

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/spring-music   1/1     1            1           31s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/spring-music-6d6d57586b   1         1         1       32s
```

この例では`type: NodePort`でサービスを公開している。ブラウザで`https://<Worker NodeのIPの.を-に置換>.sslip.io:<NodePort>`にアクセスして動作確認。

![image](https://user-images.githubusercontent.com/106908/63249916-6e740380-c2a5-11e9-8551-e49e300a21c0.png)

![image](https://user-images.githubusercontent.com/106908/63249944-7cc21f80-c2a5-11e9-879d-3709efd09856.png)
