Kubernetes

Keeper Secrets ManagerのKubernetesへの統合によるシークレットの動的な取得

ほとんどの事例で、Kubernetes External Secrets Operator連携を推奨します。本ページでは、外部シークレットオペレーターを使用しない連携方法について解説します。

機能

  • Kubernetes内にKeeperボルトからシークレットを取り込みます

  • すべてのポッドでKeeperボルトのシークレットにリアルタイムにアクセスできます

  • Keeperボルトからローカルファイルシステムにファイル添付を安全にコピーします

Keeper Secrets Manager機能の完全なリストについては、概要をご参照ください。

前提条件

このページでは、Secrets ManagerとKubernetesとの連携について説明します。 この連携を利用するための必要条件は以下のとおりです。

概説

Keeper Secrets ManagerをK8sクラスタに統合して、すべてのポッドでKeeperシークレットにリアルタイムにアクセスできます。

設定

Secrets Managerの設定を作成

Commanderを使用して、Kubernetes用のSecrets Managerデバイスの設定を作成します。この設定はIPが固定されておらず、事前に初期化されていることにご注意ください。

次のコマンドを使用して、Commanderで設定を作成します。

secrets-manager client add --app <APP NAME> --unlock-ip --config-init k8s

例:

My Vault> sm client add --app MyAdd --unlock-ip --config-init k8s

Successfully generated Client Device
====================================

Initialized Config:

apiVersion: v1
data:
  config: ewog2N...ICIxMCIKfQ==
metadata:
  name: ksm-config
  namespace: default
type:Opaque

IP Lock:Disabled
Token Expires On:2021-10-13 12:45:45
App Access Expires on:Never

上記の例では、8行目から14行目までをコピーし、secret.yamlという名前のファイルに挿入します。 次に、Kubectlがインストールされたマシンを使用していて、クラスタにアクセスできる場合は、KSM SDKの設定をKubernetesシークレットに追加します。

$ kubectl apply -f secret.yaml

Secrets Mangerの設定の作成について詳しくは、設定のドキュメントをご参照ください。

代替方法:ワンタイムアクセストークンとKSM CLI

または、Commander(もしくはVault UI)でワンタイムアクセストークンを生成し、Keeper Secrets Manager CLIでワンタイムアクセストークンを使用した、以下に示すような設定(XX:XXXは置き換えます)を作成することもできます。

$ ksm init k8s XX:XXX

apiVersion: v1
data:
  config: ewog2N[...]ICIxMCIKfQ==
kind:Secret
metadata:
  name: ksm-config
  namespace: default
type:Opaque

Kubectlがインストールされたマシンを使用していて、クラスタにアクセスできる場合は、--applyパラメータを設定して、KSM SDKの設定をKubernetesシークレットに自動的に追加できます。

トークンの引き換えの出力をファイルにパイプし、Kubectlで適用できます。 以下に例を示します。

$ ksm init k8s XX:XXX > secret.yaml
$ kubectl apply -f secret.yaml
secret/ksm-config created

KSM設定の使用

KSM設定は、シークレットを使用してK8sコンテナにプルできます。

apiVersion: v1
kind:Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: my_container:XXXXX
    env:
      - name:KSM_CONFIG
        valueFrom:
          secretKeyRef:
            name: ksm-config
            key: config
  restartPolicy:Never

K8sクラスタで実行されているKeeper Developer SDKは、実行時に環境変数KSM_CONFIGを使用してデバイス設定を取得し、Keeperボルトと通信します。

例1 - SDKを使用したカスタムアプリ

ウェブアプリケーションを使用してデータベースシークレットを表示するデプロイメントとサービスを生成する簡単な例を以下に示します。この例では、ウェブアプリケーション用のKeeper Python Developer SDKを使用します。このSDKでKubernetesシークレットから設定を取得し、KeeperボルトからPostgreSQLデータベース記録の情報を取得します。

Keeperボルトから、以下の情報を使用して「データベース」記録タイプが作成されます。

では、ウェブアプリケーションを作成しましょう。ウェブページは、任意の開発者用SDKを使用して作成できます。この例では、Python SDKを使用します。単一のエンドポイントを指定する単純なFlaskアプリケーションは、ボルトの記録シークレットを含むHTMLを表示します。シークレットは、Keeper表記法構文を使用して取得されます。

from flask import Flask
from keeper_secrets_manager_core import SecretsManager
import os

app = Flask(__name__)

@app.route("/")
def hello_world():
    sm = SecretsManager()
    return """
<h1>Database</h1>
<ul>
    <li>Type: {}</li>
    <li>Host: {}</li>
    <li>Port: {}</li>
    <li>Login: {}</li>
    <li>Password: {}</li>
</ul>
""".format(
       sm.get_notation(os.environ.get("DB_TYPE")),
       sm.get_notation(os.environ.get("DB_HOST")),
       sm.get_notation(os.environ.get("DB_PORT")),
       sm.get_notation(os.environ.get("DB_LOGIN")),
       sm.get_notation(os.environ.get("DB_PASS")))

次のパートでは、Dockerfileを作成します。以下のDockerfileのベースは、Docker HubのPython Debianイメージです。

Python SDKは、暗号化モジュールを使用します。このモジュールはRust言語をインストールする必要があります。RustがプレインストールされているDocker Hubイメージもあります。

FROM python:3.10.0-slim-bullseye
RUN apt-get update \
    && apt-get install -y gcc make libffi-dev curl libssl-dev \
    && apt-get clean
RUN pip3 install --upgrade pip wheel

# 暗号化のビルドにRustが必要
RUN curl https://sh.rustup.rs -sSf > /tmp/rust.sh \
    && chmod a+x /tmp/rust.sh \
    && /tmp/rust.sh -y
ENV PATH $PATH:/root/.cargo/bin

RUN pip3 install \
    flask \
    keeper-secrets-manager-core

RUN groupadd -g 5000 demo
RUN useradd -g demo demo

# アプリケーションをイメージにコピー
COPY demo.py /demo.py

USER demo

ENV FLASK_APP demo

EXPOSE 5000
CMD ["flask", "run"]

次に、ksm_demoという名前のDockerイメージをビルドします。

$ docker build -t ksm_demo .

Kubernetesクラスタにアクセスできると仮定すると、設定はワンタイムアクセストークンから生成されて、自動的に適用されます。

$ ksm init k8s --apply XX:XXXXXXXXXXX
secret/ksm-config created
Created secret for KSM config.

kubectl get secretと入力すると、シークレットのエントリが表示されます。

$ kubectl get secret ksm-config
NAME         TYPE     DATA   AGE
ksm-config   Opaque   1      55s

これで、ksm_demo Dockerイメージのデプロイメントとサービスを作成できます。この例では、ファイルにksm_demo.yamlという名前を付けます。

必要なシークレットとSDKの設定は、コンテナの一覧のenvセクションで定義します。このセクションでは、KSM_CONFIG環境変数を定義して、ksm-config Kubernetesシークレット、特にシークレットのconfigキーの値を取得します。

他の環境変数は単なる名前/値のリストです。この値はKeeper表記法で、ウェブアプリケーションによってSDKの表記法取得メソッドに送信されます。

ksm_demo.yamlファイルの2番目の記録は、サービスの定義です。これは、Kubernetesクラスタで動作するサービスであれば何にでも変更できます。この例では、外部IPアドレスを使用します。この例では、KubernetesノードのIPアドレスのいずれかを使用するのが安全です。たとえば、10.0.1.18を使用します。

---
apiVersion: apps/v1
kind:Deployment
metadata:
  name: ksm-demo-deployment
  labels:
    app: ksm-demo
spec:
  replicas:1
  selector:
    matchLabels:
      app: ksm-demo
  template:
    metadata:
      labels:
        app: ksm-demo
    spec:
      nodeSelector:
        kubernetes.io/hostname: work
      dnsPolicy:"None"
      dnsConfig:
        nameservers:
          - 10.0.1.207
          - 1.1.1.1
      containers:
        - name: ksm-demo
          image: ksm_demo:latest
          imagePullPolicy:IfNotPresent
          ports:
            - containerPort:5000
              protocol:TCP
          env:
            - name:KSM_CONFIG
              valueFrom:
                secretKeyRef:
                  name: ksm-config
                  key: config
            - name:DB_TYPE
              value: "keeper://IUCvqyWcx7sG-BGIK1R9-g/field/Type"
            - name:DB_HOST
              value: "keeper://IUCvqyWcx7sG-BGIK1R9-g/field/host[hostName]"
            - name:DB_PORT
              value: "keeper://IUCvqyWcx7sG-BGIK1R9-g/field/host[port]"
            - name:DB_LOGIN
              value: "keeper://IUCvqyWcx7sG-BGIK1R9-g/field/login"
            - name:DB_PASS
              value: "keeper://IUCvqyWcx7sG-BGIK1R9-g/field/password"
---   
apiVersion: v1
kind:Service
metadata:
  name: ksm-demo-service
spec:
  ports:
    - name: http
      port:5000
      targetPort:5000
      protocol:TCP
  selector:
    app: ksm-demo
  externalIPs:
    - 10.0.1.18

これで、デプロイメントとサービスを適用する準備ができました。

$ kubectl apply -f ksm_demo.yaml
deployment/ksm-demo-deployment created
service/ksm-demo-service created

デプロイメントの準備ができるまで待ちます。コマンドラインまたはKubernetesダッシュボードを使用して監視します。

$ kubectl get deployment ksm-demo-deployment
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
ksm-demo-deployment   1/1     1            1           46m

$ kubectl get svc ksm-demo-service
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
ksm-demo-service   ClusterIP   10.107.91.89   10.0.1.18     5000/TCP   56m

最後に、ウェブブラウザを使用して外部IPアドレスのポート5000にアクセスすると、Keeperボルトのデータベース記録のシークレットが表示されます。

例2 - NGINX SSL証明書

この例では、Keeperボルトから取得したデフォルトのNGINX DockerイメージとSSL証明書を含むポッドが作成されます。

SSL証明書、秘密鍵、証明書のパスワードを格納するログイン記録がボルトに作成されます。

ワンタイムトークンが生成され、KubernetesのConfigMapに追加されます。

$ ksm init k8s XX:XXX > secret.yaml
$ kubectl apply -f secret.yaml
secret/ksm-config created

例のウェブサイトは、単なるインデックスHTMLページです。このHTMLをConfigMapに保存して、ドキュメントのルートディレクトリにマウントできます。

---
apiVersion: v1
kind:ConfigMap
metadata:
  name: nginx-html-config
  namespace: default
  labels:
    app: nginx
data:
  index.html: |
    <html>
      <head>
         <title>Nginx Test Page</title>
      </head>
      <body>
        <h1>Hello From Keeper Secrets Manager!</h1>
      </body>
    </html>

default.confは、この例によって上書きされます。証明書、鍵、パスワードは、/etc/keysディレクトリに格納されます。非対話型起動の場合、NGINXは証明書のパスワードをファイルに格納し、サーバー設定でssl_password_fileを指定する必要があります。

---
apiVersion: v1
kind:ConfigMap
metadata:
  name: nginx-config
  namespace: default
  labels:
    app: nginx
data:
  default.conf: |
    server {
      listen 80 default;
      server_name localhost;
      location / {
        root /var/www/nginx-default;
        index index.html index.htm;
      }
    }
    server {
      listen 443 ssl;
      server_name localhost;
      ssl_certificate /etc/keys/example.com.crt;
      ssl_certificate_key /etc/keys/example.com.key;
      ssl_password_file /etc/keys/global.pass;
      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
      ssl_ciphers HIGH:!aNULL:!MD5;
      location / {
        root /var/www/nginx-default;
        index index.html index.htm;
      }
    }

この例のデプロイメントは以下のようになります。

---
apiVersion: apps/v1
kind:Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas:1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      initContainers:
        - name: ksm
          image: keeper/keeper-secrets-manager-writer:latest
          env:
            - name:KSM_CONFIG
              valueFrom:
                secretKeyRef:
                  name: ksm-config
                  key: config
            - name:SECRETS
              value: |
                5x0v0VFwYj2VvuhamrJhzQ/field/password > file:/etc/keys/global.pass
                5x0v0VFwYj2VvuhamrJhzQ/file/example.com.crt > file:/etc/keys/example.com.crt
                5x0v0VFwYj2VvuhamrJhzQ/file/example.com.key > file:/etc/keys/example.com.key
          volumeMounts:
            - mountPath: "/etc/keys"
              name: keys-volume
      containers:
        - name: nginx
          image: nginx:1.21.4-alpine
          imagePullPolicy:IfNotPresent
          ports:
            - containerPort:80
              protocol:TCP
            - containerPort:443
              protocol:TCP
          volumeMounts:
            - mountPath: "/etc/keys"
              name: keys-volume
            - mountPath: "/etc/nginx/conf.d/default.conf"
              name: nginx-config-file
              subPath: default.conf
            - mountPath: "/var/www/nginx-default/index.html"
              name: nginx-html-file
              subPath: index.html
      volumes:
        - name: keys-volume
          emptyDir: {}
        - name: nginx-config-file
          configMap:
            name: nginx-config
        - name: nginx-html-file
          configMap:
            name: nginx-html-config
      imagePullSecrets:
        - name: my-docker-hub-secrets

初期化コンテナには、dockerイメージkeeper/keeper-secrets-manager-writerが使用されます。コンテナはシークレットを取得し、NGINXで使用できるようにディスクに書き込みます。シークレットはポッドのemptyDirボリュームに書き込まれ、/etc/keysにマウントされます。このディレクトリは、ポッドが削除されると削除されます。

Keeper Secrets Manager Writerのドキュメントはこちらで入手できます。

また、メインコンテナは、ポッドのemptyDirボリュームを/etc/keysにマウントします。さらに、default.confを/etc/nginx/conf.dにマウントし、index.htmlファイルをサーバーのドキュメントルートにマウントします。

最後は、以下に示すように、NGINXにアクセスするサービスを作成します。

---
apiVersion: v1
kind:Service
metadata:
  name: nginx-service
spec:
  ports:
    - name: http
      port:80
      targetPort: 80
      protocol:TCP
    - name: https
      port:443
      targetPort:443
      protocol:TCP
  selector:
    app: nginx
  externalIPs:
    - XXX.XXX.XXX.XX

サービスは、httpsで外部IP(https://XXX.XXX.XXX.XXX)にアクセスしてテストできます。アドレスバーの鍵が証明書が有効であることを示していることにご注意ください。

外部シークレット

外部シークレットは、外部APIからのシークレットを同期してKubernetesに注入するKubernetesオペレータです。KeeperボルトからKubernetesにシークレットを同期するために外部シークレットをセットアップする方法の詳細については、こちらをご参照ください。

次の手順

この時点で、Secrets Manager SDKのいずれかを使用して、Keeper Secrets ManagerをK8sデプロイメントに統合できるようになりました。

最終更新