【Kubernetesシークレット】設定とマネージメントについて解説


はじめに

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

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

この記事では、Kubernetesでアプリケーションシークレットを効果的に管理する方法についてさらに詳しく説明していきます。

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

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

Kubernetesシークレット入門

Kubectlを使用したシークレットの作成とボリュームマウントとしてのシークレットのマウント

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

リテラル値またはファイルからシークレットを作成できます。 この場合、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

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

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

$ 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

ここで、このポッド内で実行すると、/ 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:/# 

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

シークレットをポッド環境変数として利用可能にする

シークレットをenvvarとして使用できるようにするために、シークレットオブジェクトマニフェストを作成して適用します。 データをプレーンテキストとしてシークレットファイルに入れる前に、まずデータをエンコードします。

$ 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

シークレットをenvvarとして挿入するには、シークレットが保存されているキーを使用してシークレットにアクセスし、ポッドのenvvarにマップする必要があります。 次の例では、別のデモポッド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を実行し、APP_AUTH_TOKEN env varの値を確認すると、シークレットのデコードされた値が表示されます。

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

参考文献

最近、Kubernetesは、保存されているシークレットを暗号化するための新機能を発表しました。 是非、そちらを読むことを強くお勧めします。 この機能はまったく新しいものですが、それでも試してみること価値があると思います。

Kubernetesシークレットの高度な管理

これまで、Kubernetesシークレットがどのように機能し、ポッドでそれらを消費する方法について説明しました。 ただし、複数のKubernetesクラスター(開発/ステージング/本番)を実行している場合は、一元化されたシークレットストアと、ポッド環境に必要なシークレットを設定するための安全なメカニズムが必要です。

ここからは、これに対する2つのソリューションを検討します。

  1. Kubernetesベースの認証でVaultを使用する
  2. DockerfilesにChamberを統合して、AWSパラメーターストアからシークレットを入力する(AWS上のKubernetesクラスターの場合)

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

Vaultベースの認証のワークフローは、次のようにまとめることができます。

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

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

Vaultとは

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

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で効果的に認証できるようにして、アプリケーションがシークレットを取得できるようにすることです。

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 host — Vaultが接続できるアドレス

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

Cluster authority data — 接続を検証するための証明

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

User token — トークンレビューアの役割を持つvault-authサービスアカウント。 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で認証し、シークレットを取得できます。 しかし、これはこの戦いの半分に過ぎません。 私たちの目標は、アプリケーションがそれらを消費できるように、環境変数にVaultシークレットを設定することです。

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

  1. vaultenvをDockerコンテナと統合します。
  2. エントリポイントスクリプトの先頭でvaultenvを使用して、環境にデータを入力します。‍

AWSパラメータストアにシークレットを保存する

AWSパラメータストアとは?

AWSパラメータストアとは、シークレットを保存するためにAWSが提供する非常にスケーラブルで安全なサービスです。 AWSCLIを使用してシークレットを読み書きできます。 EC2インスタンスまたはECSタスクからアクセスする場合は、適切なIAMロールを設定する必要があります。

チャンバーとは?

Chamberは、AWSパラメータストアからシークレットを読み書きするのに役立つコマンドラインユーティリティです。 実行環境にシークレットを入力したり、さまざまな形式でエクスポートしたりするために、他の多くのコマンドをサポートしています。

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

  1. AWS_DEFAULT_REGION
  2. AWS_SECRET_KEY_ID
  3. AWS_SECRET_ACCESS_KEY

チャンバーをローカルにインストールする

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

brew update 
brew install chamber

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

以下を使用して、AWSパラメータストアにシークレットを書き込みます。

chamber write <service> <key> <value>

以下を使用して、AWSパラメータストアからシークレットを読み取ります。

chamber read <service> <key>

チャンバーとKubernetesアプリの統合

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

  • アプリのDockerfile
  • スクリプトのエントリポイント
  • デプロイメントマニフェストファイル
  • 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)"

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

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

ChamberはAWS_SECRET_KEY_IDAWS_SECRET_ACCESS_KEYにもアクセスする必要がありますが、ポッドマニフェストファイルでこれらを設定することはお勧めしません。 AWSパラメータストアで認証するには、IAMロールベースのパーミッションメソッドを使用することを強くお勧めします。 これを行うには、Kubernetesワーカーノードに割り当てられたIAMロールでssm:GetParametersアクションが有効になっていることを確認してください。

これらすべてを設定すると、ポッド内のコンテナがSERVICE環境変数の下のシークレットを読み取ります。 次に、AWS Parameter Storeで認証して、コンテナーの環境にそのサービスに必要なすべてのシークレットを設定してください。

まとめ

Kubernetesシークレットは、正しく管理されていれば、デプロイプロセスを非常に簡素化できます。それらをアプリケーションの実行環境に挿入するか、カスタムビルドのロジックを使用してその場で読み取るかを選択できます。

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

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

Kubernetesのセットアップを監視する必要がある場合は、MetricFireデモを予約して直接お問い合わせください。 MetricFireの多くのお客様のKubernetesクラスターの監視をお助けしています。