Kubernetes: Secretも安全にGitOpsできる「SealedSecret」が便利

GitOpsは、Kubernetes環境におけるインフラストラクチャとアプリケーションの管理に変革をもたらしました。このアプローチでは、Gitリポジトリを「真実の単一の源（single source of truth）」として使用し、環境の望ましい状態を宣言的に定義します。しかし、GitOpsの実践において、機密情報の管理は常に大きな課題でした。

Kubernetesの標準的なSecretリソースは、Base64エンコーディングを使用しているだけで、実質的には平文と同様です。GitリポジトリにSecretをそのまま保存することは、セキュリティ上大きなリスクとなります。この問題があり、GitOpsの採用を躊躇したり、部分的にGitOpsじゃない運用が残ったりしていました。

こうした課題を解決するのがSealedSecretです。SealedSecretは、Bitnami社が開発したオープンソースのツールで、Kubernetes Secret管理におけるセキュリティとGitOpsの原則との間の溝を埋めるソリューションです。SealedSecretは、暗号化された機密情報をGitリポジトリに入れることで、GitOpsを可能にします。

SealedSecretには次のような利点があります:

1. セキュリティの向上：機密情報を暗号化された状態で保存できます。
2. GitOpsとの親和性：暗号化されたSecretをバージョン管理し、GitOpsワークフローに統合できます。
3. 自動化の促進：クラスター内での自動復号化により、CICD/パイプラインの簡素化が可能です。
4. アクセス制御の改善：暗号化と復号化の権限を分離できます。

このガイドでは、SealedSecretの基本的な概念から実践的な使用方法、さらには高度な管理テクニックまでを、GitOpsの文脈で説明していきます。SealedSecretを理解し活用することで、セキュアなGitOpsの実践が可能になり、Kubernetes環境での機密情報管理の課題を克服できるでしょう。

これからの章では、SealedSecretの仕組み、インストール方法、使用方法、そして実際のユースケースについて詳しく見ていきます。

# 2. SealedSecretの仕組み

SealedSecretは、Kubernetesクラスター内で機密情報を安全に管理するための巧妙な仕組みを持っています。その核心は公開鍵暗号方式にあります。以下に、SealedSecretの動作原理を段階的に説明します。

## 2.1 暗号化と復号化のプロセス

1. 鍵ペアの生成:
    - SealedSecret controllerがクラスター内にデプロイされると、公開鍵と秘密鍵のペアを生成します。
    - 公開鍵はクラスター外で使用可能ですが、秘密鍵はcontroller内部に安全に保管されます。
2. 暗号化 (クラスター外):
    - ユーザーは`kubeseal`というクライアントツールを使用します。
    - `kubeseal`は通常のKubernetes Secretを入力として受け取ります。
    - クラスターから公開鍵を取得し、この鍵を使ってSecretデータを暗号化します。
    - 暗号化されたデータを含む新しいSealedSecretリソースを生成します。
3. 復号化 (クラスター内):
    - 暗号化されたSealedSecretがクラスターにデプロイされると、SealedSecret controllerがこれを検出します。
    - controllerは、保持している秘密鍵を使ってSealedSecretを復号化します。
    - 復号化されたデータから、通常のKubernetes Secretを再作成します。

```mermaid
sequenceDiagram
    participant User
    participant GitRepo as Gitリポジトリ
    participant K8s as Kubernetesクラスター
    participant Controller as SealedSecret Controller
    participant Secret as Kubernetes Secret

    User->>GitRepo: SealedSecretをプッシュ
    GitRepo->>K8s: SealedSecretをデプロイ
    K8s->>Controller: SealedSecretを検知
    Controller->>Controller: 秘密鍵で復号化
    Controller->>K8s: 通常のSecretを作成
    K8s->>Secret: Secretを保存
    Note over Secret: アプリケーションが<br/>Secretを使用可能
```

## 2.2 クラスター内での動作原理

1. 監視と検出:
    - SealedSecret controllerは常にクラスター内のSealedSecretリソースを監視しています。
    - 新しいSealedSecretが作成されたり、既存のものが更新されたりすると、即座に検出します。
2. 名前空間とリソース名の取り扱い:
    - SealedSecretには、どの名前空間にSecretを作成するか、どの名前で作成するかの情報が含まれています。
    - これにより、特定の名前空間やリソース名に限定してSecretを作成することが可能です。
3. アクセス制御:
    - SealedSecretの復号化は、controller内部でのみ行われます。
    - 通常のKubernetes RBACシステムを通じて、生成されたSecretへのアクセスを制御できます。
4. 更新と同期:
    - SealedSecretが更新されると、controllerは自動的に対応するSecretも更新します。
    - これにより、GitOpsワークフローでの継続的な同期が可能になります。
5. 削除の取り扱い:
    - SealedSecretが削除されても、対応するSecretは自動的には削除されません。
    - これは、誤ってSealedSecretを削除した場合のセーフガードとなります。

このような仕組みにより、SealedSecretは機密情報をGitリポジトリで管理しながらも、クラスター内でのみ復号化を行うという、セキュアな運用を実現しています。

<blockquote>

❓ **SealedSecret Controllerが使用する秘密鍵はコントローラー内部にあるとのことですが、実際はSecretとして永続化されていますか？**

SealedSecret Controllerが使用する秘密鍵は実際にはKubernetes Secret として永続化されています。この点について詳しく説明します：

- 秘密鍵の保存場所:
    - SealedSecret Controllerの秘密鍵は、Kubernetes Secret として保存されます。
    - このSecretは通常、SealedSecret Controllerと同じ名前空間（デフォルトは `kube-system`）に作成されます。
- Secretの名前:
    - 秘密鍵を含むSecretの名前は通常 `sealed-secrets-key` です。
- 鍵の生成と管理:
    - コントローラーが初めて起動する際に、新しい鍵ペア（公開鍵と秘密鍵）を生成します。
    - 既存の秘密鍵がある場合、コントローラーはそれを読み込んで使用します。
- 鍵のローテーション:
    - SealedSecret Controllerは、デフォルトで30日ごとに新しい鍵ペアを生成します。
    - 古い鍵も保持され、以前に暗号化されたSealedSecretの復号化に使用できます。
- バックアップと復元:
    - `sealed-secrets-key` Secretをバックアップすることで、秘密鍵をバックアップできます。
    - クラスターの移行や災害復旧の際に、このバックアップを使用して秘密鍵を復元できます。
- セキュリティ上の考慮事項:
    - `sealed-secrets-key` Secretへのアクセスは厳重に制限する必要があります。
    - このSecretへアクセスできる人は、すべてのSealedSecretを復号化できる能力を持つことになります。
- カスタム設定:
    - 環境変数やコマンドライン引数を通じて、鍵の保存場所や名前をカスタマイズすることも可能です。
- 複数クラスターでの運用:
    - 複数のクラスターで同じSealedSecretを使用したい場合、同じ `sealed-secrets-key` Secretを各クラスターにデプロイすることで実現できます。

</blockquote>

# 3. SealedSecretのインストール

SealedSecretを使用するには、主に2つのコンポーネントをインストールする必要があります：

1. クラスター側: SealedSecret controller
2. クライアント側: kubesealコマンドラインツール

## 3.1 kubesealのインストール

最新バージョン: v0.27.1 (2024年7月18日リリース)

### nix（NixOS or nix package manager）

```bash
nix-env -iA nixpkgs.kubeseal
```

### Linux/macOS（Homebrewを使用）

```bash
brew install kubeseal
```

### Linux（バイナリをダウンロード）

```bash
wget <https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.1/kubeseal-0.27.1-linux-amd64.tar.gz>
tar -xvzf kubeseal-0.27.1-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
```

### Windows（Chocolateyを使用）

```powershell
choco install kubeseal
```

## 3.2 SealedSecret controllerのインストール

最新Helmチャートバージョン: 2.16.1 (アプリケーションバージョン: 0.27.1)

### 方法1: Helmを使用する場合

```bash
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm repo update
helm install sealed-secrets sealed-secrets/sealed-secrets --version 2.16.1
```

### 方法2: Helmfileを使用する場合

1. 以下の内容で`helmfile.yaml`を作成します：

```yaml
repositories:
  - name: sealed-secrets
    url: <https://bitnami-labs.github.io/sealed-secrets>

releases:
  - name: sealed-secrets
    namespace: kube-system
    chart: sealed-secrets/sealed-secrets
    version: 2.16.1
    values:
      - fullnameOverride: sealed-secrets-controller
```

1. 以下のコマンドを実行してデプロイします：

```bash
helmfile sync
```

### 方法3: マニフェストを直接適用する場合

```bash
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.1/controller.yaml
```

## 3.3 インストールの確認

1. controllerのインストール確認:

```bash
kubectl get pods -n kube-system | grep sealed-secrets-controller
```

1. kubesealのインストール確認:

```bash
kubeseal --version
```

## 3.4 注意事項

- クラスターのセキュリティポリシーに応じて、適切な名前空間にcontrollerをインストールしてください。
- kubesealのバージョン（0.27.1）とcontrollerのバージョン（0.27.1）が一致していることを確認してください。

以上の手順で、SealedSecretの最新バージョンのセットアップが完了します。次のセクションでは、実際の使用方法について説明します。

# 4. SealedSecretの使用方法

SealedSecretを使用する基本的な流れは以下の通りです：

1. 通常のKubernetes Secretを作成
2. kubesealを使用してSecretをSealedSecretに変換
3. SealedSecretをクラスターにデプロイ

以下、各ステップを詳しく見ていきます。

## 4.1 通常のKubernetes Secretの作成

まず、通常のKubernetes Secretを作成します。例として、データベースの認証情報を含むSecretを作成してみましょう。

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
stringData:
  username: admin
  password: t0p-Secret
```

このYAMLを`mysecret.yaml`として保存します。

## 4.2 SecretをSealedSecretに変換

次に、kubesealを使用してこのSecretをSealedSecretに変換します。

```bash
kubeseal --format yaml < mysecret.yaml > mysealedsecret.yaml
```

このコマンドは、`mysecret.yaml`を入力として受け取り、暗号化されたSealedSecretを`mysealedsecret.yaml`として出力します。

## 4.3 SealedSecretのデプロイ

生成されたSealedSecretをクラスターにデプロイします。

```bash
kubectl apply -f mysealedsecret.yaml
```

これにより、暗号化されたSealedSecretがクラスターにデプロイされます。SealedSecret controllerが自動的にこれを検出し、復号化して通常のSecretを作成します。

## 4.4 Secretの確認

デプロイされたSecretを確認します。

```bash
kubectl get secrets
```

mysecretという名前のSecretが表示されるはずです。

:::note info

SealedSecretをデモするためコードも作ったので、よかったら使ってみてください。

https://github.com/suinplayground/kubernetes-playground?tab=readme-ov-file#sealedsecret

:::

## 4.5 注意点と高度な使用方法

1. 名前空間の指定:
特定の名前空間にSealedSecretをデプロイする場合、kubesealコマンドに`-namespace`オプションを追加します。
    
    ```bash
    kubeseal --format yaml --namespace mynamespace < mysecret.yaml > mysealedsecret.yaml
    ```
    
2. クラスター外での暗号化:
クラスターの公開鍵を使用してローカルで暗号化する場合：
    
    ```bash
    kubeseal --fetch-cert > publickey.pem
    kubeseal --cert publickey.pem --format yaml < mysecret.yaml > mysealedsecret.yaml
    ```
    
3. 既存のSecretの更新:
既存のSealedSecretを更新する場合、新しいSecretを作成し、同じ名前でSealedSecretに変換して適用します。
4. テンプレート化:
Helm等のテンプレートエンジンと組み合わせる場合、`-raw`オプションを使用して値のみを暗号化することができます。

## 4.6 トラブルシューティング

- SealedSecretが正しく復号化されない場合、controllerのログを確認してください。
    
    ```bash
    kubectl logs -n kube-system -l app.kubernetes.io/name=sealed-secrets
    ```
    
- クラスターの公開鍵が更新された場合、既存のSealedSecretを再暗号化する必要があります。

以上が、SealedSecretの基本的な使用方法です。次のセクションでは、より具体的な実践例を見ていきます。

# 5. 実践例：データベース接続情報の管理

この実践例では、PostgreSQLデータベースの接続情報をSealedSecretを使って管理する方法を説明します。

## 5.1 プレーンなSecretの作成

まず、通常のKubernetes Secretを作成します。

```bash
cat << EOF > db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  DB_HOST: mydb.example.com
  DB_PORT: "5432"
  DB_NAME: myapp
  DB_USER: myuser
  DB_PASSWORD: mysecretpassword
EOF
```

## 5.2 SealedSecretへの変換

次に、kubesealを使用してこのSecretをSealedSecretに変換します。

```bash
kubeseal --format yaml < db-secret.yaml > sealed-db-secret.yaml
```

生成されたsealed-db-secret.yamlファイルの内容は以下のようになります：

```yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
spec:
  encryptedData:
    DB_HOST: AgBy8hH...（長い暗号化文字列）
    DB_PORT: AgBy8hH...
    DB_NAME: AgBy8hH...
    DB_USER: AgBy8hH...
    DB_PASSWORD: AgBy8hH...
```

このファイルは安全にGitリポジトリにコミットできます。

## 5.3 GitOpsワークフローへの統合

1. sealed-db-secret.yamlをGitリポジトリにコミットし、プッシュします。

```bash
git add sealed-db-secret.yaml
git commit -m "Add sealed database credentials"
git push
```

1. CI/CDパイプラインまたはGitOpsツール（例：ArgoCD、Flux）の設定:

ArgoCD使用の場合のApplication CRの例:

```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/myapp.git
    targetRevision: HEAD
    path: k8s
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp
```

1. アプリケーションのDeploymentで、SealedSecretから生成されるSecretを参照:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        envFrom:
        - secretRef:
            name: db-credentials
```

この設定により、SealedSecret Controllerが自動的にSealedSecretを復号化し、通常のSecretを生成します。アプリケーションはこのSecretから環境変数として接続情報を取得できます。

## 5.4 更新とローテーション

接続情報を更新する場合:

1. 新しい接続情報でdb-secret.yamlを更新
2. kubesealコマンドで再暗号化
3. 新しいsealed-db-secret.yamlをコミットしてプッシュ

GitOpsツールが変更を検知し、クラスター内のリソースを自動的に更新します。

このワークフローにより、データベース接続情報を安全に管理しながら、GitOpsの原則に従ったデプロイメントが可能になります。

# 6. SealedSecretの管理

## 6.1 シークレットのローテーション

SealedSecretを使用する際は、常にシークレットをローテーションする必要があります。ただし、シークレットが別のシークレット（シーリングキー）で暗号化されているため、これら2つの層がどのように関連しているかを理解し、適切な決定を下すことが重要です。

重要なポイント：

- シーリング用の秘密鍵が漏洩した場合、実際のシークレット値をローテーションする前に、「緊急のキー更新」のセクションの手順に従う必要があります。
- SealedSecretのキー更新と再暗号化機能は、実際のシークレット値の定期的なローテーションの代替にはなりません。

## 6.2 シーリングキーの更新

- シーリングキーは30日ごとに自動的に更新されます。
- 新しいシーリングキーが作成され、コントローラーが使用できるアクティブなシーリングキーのセットに追加されます。
- 最も新しく作成されたシーリングキーが、`kubeseal`を使用して新しいシークレットをシールする際に使用されます。

更新期間の調整：

```
kubectl edit deployment/sealed-secrets-controller --namespace=kube-system
```

- `-key-renew-period=<value>` フラグを追加して更新期間を調整できます（例：`720h30m`）。

## 6.3 緊急のキー更新

シーリングキーが漏洩した疑いがある場合、新しいローテーションされたシークレットをシールし始める前に、できるだけ早くキーを更新する必要があります。

緊急キー更新の方法：

- `-key-cutoff-time` フラグまたは `SEALED_SECRETS_KEY_CUTOFF_TIME` 環境変数を使用します。
- 日付形式はRFC1123を使用します（`date -R` コマンドで生成可能）。

## 6.4 手動キー管理（上級者向け）

- シーリングキーは通常のKubernetesシークレットとして、SealedSecretコントローラーと同じ名前空間に存在します。
- 手動でシーリングキーを管理（作成、移動、削除）することが可能です。
- シーリングキーを複数のクラスター間で共有することで、同じSealedSecretを複数のクラスターに適用できます。

注意：手動で作成、削除、またはラベル変更されたシーリングキーは、コントローラーを再起動するまで自動的に認識されません。

## 6.5 再暗号化（上級者向け）

古いシーリングキーを削除する前に、SealedSecretsを最新の秘密鍵で再暗号化する必要があります。

再暗号化の方法：

```bash
kubeseal --re-encrypt <my_sealed_secret.json >tmp.json \\
  && mv tmp.json my_sealed_secret.json
```

この操作により、最新のキーで新たに暗号化されたシールドシークレットファイルが生成されます。シークレットがクライアントに漏れることはありません。

注意点：

- 古いキーは自動的にガベージコレクションされません。
- 再暗号化は定期的に行うことをお勧めしますが、これは実際のシークレットの定期的なローテーションの代替にはなりません。

## 一緒に働きませんか？

僕の会社(株式会社クラフトマンソフトウェア)では、Kubernetesエンジニアを募集しています！興味がある方はぜひ↓も御覧ください😌

https://craftsman-software.com/recruit/