”Kubernetes Secrets”を効果的＋効率的に管理する方法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/67e2e970-987e-f463-eaa3-7e657476d7d6.png)

#1.はじめに
現在、Kubernetesがコンテナオーケストレーションの標準となっています。多くの企業ががコンテナファーストの開発構造を徐々に採用しているため、既存のワークロードの大部分は、パブリッククラウドまたはプライベートデータセンターの仮想マシンで実行されるようになりました。それゆえ、多くの企業が以前の方法からKubernetesへの移行に直面し、また同時にその以降の難しさに直面しています。

Kubernetesへの移行は、監視、ロギング、CI / CD、そして最も重要なセキュリティを含むdevopsプロセス全体に影響を与え、セキュリティは、クラスターレベルとアプリケーションレベルの両方で処理されるべきです。

この投稿では、Kubernetesでアプリケーションシークレットを効果的に管理する方法について、説明していきたいと思います。

Kubernetesでは、API統合トークン、OAuthトークン、データベースパスワードなどの機密情報はシークレットオブジェクトによって管理されます。これらのSecretは、マウントされたボリュームとしてポッドにアクセスできるようになります。

環境ごとに異なるKubernetesクラスターを実行している場合（これを強くお勧めします）、環境固有のすべてのシークレットを1か所に保存することをお勧めします。次に、ポッドがデプロイされている環境をスマートに識別し、それに応じてシークレットをフェッチできるシークレット管理ツールがあることを確認します。これについては、この投稿の後半で詳しく説明します。

#2. Kubernetes Secretsを使い始める

###2.1 Kubectlを使用してSecretを作成し、Secretをボリュームマウントとしてマウントする

サードパーティのサービスで認証するためにアプリケーションが使用するトークンのSecretを作成してみましょう。

リテラル値またはファイルからSecretを作成できます。 この場合、秘密情報をaccess.txtというファイルに入れました。

```
$cat access.txt
APP_AUTH_TOKEN=WEj4VmNF755uc9vZdz98zvPXB6DkHp

$ kubectl create secret generic auth-token --from-file=./access.txt
secret "auth-token" created

$kubectl get secrets
NAME                  TYPE                                  DATA      AGE
auth-token            Opaque                                1         14s
default-token-k7vmv   kubernetes.io/service-account-token   3         17m

$ kubectl describe secret auth-token
Name:         auth-token
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
access.txt:  46 bytes
```

このSecretをマウントされたボリュームとしてポッドで使用してみましょう。

まず、デモポッドを作成し、マニフェストを適用します。

```
$ cat pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
spec:
  containers:
  - name: demo-pod
    image: ubuntu
    command: ["/bin/bash", "-ec", "while :; do echo '.'; sleep 5 ; done"]
    volumeMounts:
    - name: myvolume
      mountPath: "/tmp"
      readOnly: true
  volumes:
  - name: myvolume
    secret:
      secretName: auth-token

$ kubectl apply -f pod.yaml
pod "demo-pod" created

$ kubectl get pods
NAME       READY     STATUS    RESTARTS   AGE
demo-pod   1/1       Running   0          1m
```

これで、このポッド内で**exec**すると、**/ tmp**ディレクトリにマウントされたシークレットを見つけることができるはずです。

```
$ kubectl exec -it demo-pod /bin/bash
root@demo-pod:/# ls /tmp
access.txt
root@demo-pod:/# cat /tmp/access.txt
APP_AUTH_TOKEN=WEj4VmNF755uc9vZdz98zvPXB6DkHp
root@demo-pod:/# 
```

この方法を使用すると、機密データを含む構成ファイルを安全にマウントできま、その後、マウントされたディレクトリからアプリケーションで読み取ることができます。 ただし、機密データをアプリケーションのenv varとして利用できるようにしたい場合もあります。 次のセクションでそれをやってみましょう。

###2.2Secretsをポッド環境変数として利用可能にする
シークレットをenv varとして使用できるようにするために、シークレットオブジェクトマニフェストを作成して適用します。 最初にデータをエンコードしてから、シークレットファイルにプレーンテキストとして書き込みます。

```
$ echo WEj4VmNF755uc9vZdz98zvPXB6DkHp | base64
V0VqNFZtTkY3NTV1Yzl2WmR6OTh6dlBYQjZEa0hwCg==

$ cat auth_secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  auth_token: V0VqNFZtTkY3NTV1Yzl2WmR6OTh6dlBYQjZEa0hwCg==

$ kubectl apply -f auth_secret.yaml
secret "mysecret" created

$ kubectl describe secret mysecret
Name:         mysecret
Namespace:    default
Labels:       <none>
Annotations: 
Type:         Opaque

Data
====
auth_token:  31 bytes
```

Secretをenv変数として挿入するには、Secretが格納されているキーを使用してSecretにアクセスし、ポッドのenv変数にマップする必要があります。 次の例では、別のデモポッドdemo-pod-2でこれを行います。

```
$ cat pod-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod-2
spec:
  containers:
  - name: demo-pod-2
    image: ubuntu
    command: ["/bin/bash", "-ec", "while :; do echo '.'; sleep 5 ; done"]
    env:
      - name: APP_AUTH_TOKEN
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: auth_token

$ kubectl apply -f pod-2.yaml
pod "demo-pod-2" created
```

ここで、demo-pod-2に**exec**すると**APP_AUTH_TOKEN** env varの値を確認すると、Secretのデコードされた値が表示されます。

```
$ kubectl exec -it demo-pod-2 /bin/bash
root@demo-pod-2:/# echo $APP_AUTH_TOKEN
WEj4VmNF755uc9vZdz98zvPXB6DkHp
root@demo-pod-2:/# 
```

###2.3 さらに読む
最近、Kubernetesは保存されている[Secretを暗号化するための新機能](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)を発表しましたので、それを読むことを強くお勧めします。 この機能は非常に新しいものですが、それで試してみるのもいいかもしれません。

#3. Kubernetes Secretsの高度な管理

これまでに、Kubernetes Secretsがどのように機能するか、およびポッドでsecretを使用する方法について学びました。 ただし、複数のKubernetesクラスタ（開発/ステージング/本番）を実行している場合は、一元化されたsecretストアと、必要なsecretをポッド環境に取り込むための安全なメカニズムが必要です。

このための2つのソリューションを紹介します。

1. Kubernetesベースの認証でVaultを使用する

2. DockerfileにChamberを統合してAWSパラメーターストアからSecretを入力する（AWS上のKubernetesクラスターの場合）

3.1 Kubernetesベースの認証でVaultを使用する

Vaultベースの認証のワークフローは、次のように要約できます。

1. ポッドは特定の名前空間にデプロイされ、特定のサービスアカウントに関連付けられます。
2. 名前空間と関連するサービスアカウントは、VaultバックエンドのKubernetes認証役割に関連付けられています。
3. ポッドは、サービスアカウントのトークンを使用してVaultで認証し、**VAULT_TOKEN**を取得します。
4. **VAULT_TOKEN**および**VAULT_ADDRESS**を使用して、安全な方法でVaultからSecretsを取得できます。

さらに、これらのSecretsを環境変数としてポッドに注入するために、いくつかのオプションがあります。これについては後ほど説明します。

###3.2 Vaultとは
Vaultは、シークレットを効果的に保存および管理するための軽量ツールです。 完全に暗号化され、安全なKubernetes認証をサポートしています。 Vaultは通常、ストレージエンジンとしてConsulによってサポートされています。 したがって、ノードの障害に対して非常に信頼性が高く、回復力が高いです。


###3.3 Vaultの設定
Kubernetesアプリケーション用にVaultを設定するには、いくつかの方法があります。

1. 環境全体からアクセスできるKubernetesクラスターでVaultを設定する。

2. ホストされたバージョンのVaultを使用する。

KubernetesでVaultを設定する方法はこのブログの範囲外ですが、使用できるリソースを以下に示しておきます。

1. https://www.hashicorp.com/blog/announcing-the-vault-helm-chart
2. https://github.com/hashicorp/consul-helm 

Vaultを設定したら、Kubernetesクラスタに接続できます。 前述のように、目標は、ポッドがVaultで効果的に認証され、アプリケーションがSecretを取得できるようにすることです。

###3.4 Vaultを使用したKubernetesポッドの認証

まず、次の権限を持つKubernetesサービスアカウントを作成します。

```
$ cat vault_sa.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: role-tokenreview-binding
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: vault-auth
  namespace: default

$ kubectl apply -f vault_sa.yaml
clusterrolebinding.rbac.authorization.k8s.io "role-tokenreview-binding" created
```

次に、変数を取得して、VaultバックエンドでKubernetes認証を有効にします。

**Kubernetesホスト**: Vaultが接続できるアドレス。

```
k8s_host="$(kubectl config view --minify | grep server | cut -f 2- -d ":" | tr -d " ")"
```

**クラスター認証データ**: 接続を確認するための証明書。

```
k8s_cacert="$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 --decode)"
```

**ユーザートークン—ト**: クンレビューアの役割を持つボールト認証サービスアカウント。 Vaultは、このサービスアカウントを使用してクラスタと対話します。

```
secret_name="$(kubectl get serviceaccount vault-auth -o go-template='{{ (index .secrets 0).name }}')"

account_token="$(kubectl get secret ${secret_name} -o go-template='{{ .data.token }}' | base64 --decode)"
```

これらの値をすべて取得したら、VaultバックエンドでKubernetes認証を有効にすることができます。

```
vault auth enable kubernetes
vault write auth/kubernetes/config \
    token_reviewer_jwt=${account_token} \
    kubernetes_host=${k8s_host} \
    kubernetes_ca_cert=${k8s_cacert}
```

次に、新しく起動したポッドがVaultサーバーで認証できることを確認する必要があります。 このために、名前空間をVaultロールにバインドし、後でその名前空間のサービスアカウントJWTを認証に使用します。

```
vault write auth/kubernetes/role/demo bound_service_account_names=vault-auth bound_service_account_namespaces=default policies=demo-policy ttl=1h
```

ここでテストをしてみます！

デモポッドを作成し、Vaultでの認証を試みます。


```
kubectl run -it --rm --image=ubuntu --serviceaccount=vault-auth test -- /bin/bash
root$ apt-get update -y && apt-get install vim curl jq mysql-client -y

#Let's get the service account JWT token
root$ JWT="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"

#Now we can use this to get the vault token
root$ VAULT_TOKEN="$(curl --request POST --data '{"jwt": "'"$JWT"'", "role": "demo"}' -s -k https://${VAULT_ADDRESS}/v1/auth/kubernetes/login | jq -r '.auth.client_token')"
```

これで、**VAULT_TOKEN**を使用してVaultで認証し、Secretを取得できます。 ただし、これはまだプロセスの半分にすぎません。 私たちの目標は、アプリケーションが消費できるように、環境変数にVault Secretを設定することです。

アプリケーションに、**VAULT_TOKEN**および**VAULT_ADDRESS**環境変数を使用してVaultから直接読み取るロジックがある場合は、この部分を完全にスキップできます。 それ以外の場合は、以下を実行します。

1. vaultenvをDockerコンテナーと統合

2. エントリーポイントスクリプトの先頭で[vaultenv](https://github.com/channable/vaultenv)を使用して、環境を設定

#4. AWSパラメータストアにSecretsを保存する

###4.1 AWSパラメータストアとは？

これは、Secretsを保存するためにAWSが提供する、非常にスケーラブルで安全なサービスです。 AWS CLIを使用してSecretを読み書きできます。 EC2インスタンスまたはECSタスクからアクセスする場合は、適切なIAMロールを構成する必要があります。

###4.2 チャンバー(Chamber)とは？
[Chamber](https://github.com/segmentio/chamber)は、AWS Parameter Storeからシークレットを読み書きするのに役立つコマンドラインユーティリティです。 実行環境にSecretsを追加したり、さまざまな形式でエクスポートしたりするために、他の多くのコマンドをサポートしています。

これを始めるには、次の情報が必要です。

1. AWS_DEFAULT_REGION
2. AWS_SECRET_KEY_ID
3. AWS_SECRET_ACCESS_KEY

###4.3 Chamberのローカルインストール
Macを使用している場合、Chamberは次のコマンドを使用してローカルにインストールできます。

```
brew update
brew install chamber
```

ラップトップでAWS CLIが設定されていることを確認してください。

以下を使用して、SecretをAWS Parameter Storeに書き込みます。

```
chamber write <service> <key> <value>
```

以下を使用して、AWS Parameter StoreからSecretsを読み取ります。

```
chamber read <service> <key>
```

###4.4 ChamberとKubernetesアプリの統合
ChamberをKubernetesアプリと統合するには、以下にいくつかのマイナーな変更を加える必要があります。

1. アプリのDockerfile
2. スクリプトのエントリポイント
3. 配置マニフェストファイル

Dockerfileの上部に、次のコンテンツを追加します。

```
FROM golang:1.10.4 AS build

RUN CGO_ENABLED=0 GOOS=linux go get -v github.com/segmentio/chamber

FROM <existing base image>

COPY --from=build /go/bin/chamber /chamber
…
```

これにより、Chamberバイナリがビルドされてコンテナに統合されます。

エントリポイントスクリプトで、次の変更を行います。

メインのエントリポイントロジックが起動する前に、次のステートメントを追加して環境変数を設定します。

```
eval "$(chamber env $SERVICE)"
```

デプロイメントのマニフェストで次の環境変数を設定する必要があります。

1. **AWS_DEFAULT_REGION**：これは、アクセス認証情報のデフォルトのリージョンに対応します。
2. **SERVICE**：これは、Chamberを使用してパラメーターストアからSecretsを読み取りたいサービスに対応します。

Chamberは**AWS_SECRET_KEY_ID**および**AWS_SECRET_ACCESS_KEY**へのアクセスも必要ですが、ポッドマニフェストファイルでこれらを設定することはお勧めしません。 IAMロールベースのアクセス許可メソッドを使用してAWS Parameter Storeで認証することを強くお勧めします。 これを行うには、Kubernetesワーカーノードに割り当てられたIAMロールで**ssm：GetParameters**アクションが有効になっていることを確認します。

これらすべての設定が完了すると、ポッド内のコンテナが**SERVICE**環境変数のSecretsを読み取ります。 次に、AWS Parameter Storeを使用して認証し、そのサービスに必要なすべてのSecretsをコンテナーの環境に取り込みます。

###5. まとめ
Kubernetesシークレットは、正しく管理されていれば、デプロイメントプロセスを大幅に簡略化できます。 それらをアプリケーションの実行環境に挿入するか、カスタムビルドロジックを使用してオンザフライで読み取るかを選択できます。

AWSクラウドにKubernetesクラスターをデプロイした場合は、、最も簡単に開始でき、非常に安全であるChamberとAWS Parameter Storeの統合を使用することを強くお勧めします。

また、Kiamの助けを借りて、細かいIAMアクセスを使用してパラメーターストアへのアクセスを管理できます。 これにより、クラスター内の特定のポッドのみがAWSパラメーターストアからSecretsを取得して使用できるようになります。 しかし、それはまた別の議論となるので、またの機会としましょう。

それでは、またの記事で！