koukiblog

たぶんweb系の話題

GitOps環境にExternal Secret Operatorを導入した

FluxなどGitOps環境でSecretリソースを安全に扱う方法を考え、External Secret Operatorを導入しました。

External Secret Operator ( https://external-secrets.io/latest/ ) は、GCPのSecret Managerなど外部のSecret管理ツールからデータを読み取って、kubernetesのsecretを生成してくれます。 external secretリソースで、SecretManagerのkeyを指定すると、SecretManagerに保存してあるデータでsecretを作成します。

具体的には、こういうマニフェストを作ると、exampleという名前のsecretが、SecretManagerに保存済みの"sm-key" の値で作成されます。

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: es-example
spec:
  secretStoreRef:
    kind: ClusterSecretStore
    name: gcp-cluster-secret-store
  target:
    name: example
    creationPolicy: Owner
  dataFrom:
    - extract:
        key: sm-key

mozilla/sops のようにマニフェストを暗号化/複合化するアプローチの場合、GitOpsのツールが複合化できるように設定せなばならず、それが面倒ですが、External Secret Operatorの場合、External Secret ControllerがSecretの生成まで行ってくれるため、GitOpsのツールは暗号化を気にすることなく、Secretを利用できます。 最終的にはkuberentes標準のSecretリソースになるため、扱いやすいです。

インストール

External Secret Operatorのインストールは、Helmを利用するのが一般的なようです。今回は Fluxを導入済みのため、FluxでExternal Secret Operatorをインストールしました。

examplesにFluxを利用したインストール例が載っているため、これを参考にしてインストールしました。 https://external-secrets.io/v0.9.8/examples/gitops-using-fluxcd/

まず、レポジトリの定義を行います。GitRepostitoryのURLがhttpでかつmainブランチになっていなのですが、https にしたうえで、現時点で最新のv0.9.8 を指定します。

apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
  name: external-secrets
  namespace: flux-system
spec:
  interval: 10m
  url: https://charts.external-secrets.io
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: external-secrets
  namespace: flux-system
spec:
  interval: 10m
  ref:
    tag: v0.9.8
  url: https://github.com/external-secrets/external-secrets

次にCRDを定義します。CRDは差分が検出できないのか、毎回リソースの更新 ( CustomResourceDefinition/clustersecretstores.external-secrets.io configured というログがでる)が発生してしまいます。頻繁に更新が行われても特に問題は出なそうでしたが、気になったので、作成後にintervalを十分に長くすることで対応しました。

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: external-secrets-crds
  namespace: flux-system
spec:
  interval: 10m
  path: ./deploy/crds
  prune: true
  sourceRef:
    kind: GitRepository
    name: external-secrets

次に、Helmを利用してoperatorをインストールします。ここでも現時点で最新の0.9.8を指定しました。namespaceは別途手動で作成していたのでcreateNamespaceはfalseにします。

apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: external-secrets
  namespace: flux-system
spec:
  # Override Release name to avoid the pattern Namespace-Release
  # Ref: https://fluxcd.io/docs/components/helm/api/#helm.toolkit.fluxcd.io/v2beta1.HelmRelease
  releaseName: external-secrets
  targetNamespace: external-secrets
  interval: 10m
  chart:
    spec:
      chart: external-secrets
      version: 0.9.8
      sourceRef:
        kind: HelmRepository
        name: external-secrets
        namespace: flux-system
  values:
    installCRDs: false

  # Ref: https://fluxcd.io/docs/components/helm/api/#helm.toolkit.fluxcd.io/v2beta1.Install
  install:
    createNamespace: false

最後にClusterSecretStoreを定義します。このマニフェストはExternal Secrets OperatorのCRDが追加済みでないとエラーになるので、 dependsOnで依存関係を明示しておきます。こうすることで、Fluxが意図した順番に処理することができます。

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: external-secrets-crs
  namespace: flux-system
spec:
  dependsOn:
    - name: external-secrets-crds
  interval: 10m
  path: ./infrastructure/external-secrets/crs
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system

ClusterSecretStoreを定義するマニフェストは、例にしたがって /infrastructure/external-secrets/crs 配下に配置します。今回はGCPのSecretManagerを利用し、WorkloadIdentityで認証を行うので、サービスアカウントにアノテーションを追加します。WorkloadIdentityの設定については省略します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-secrets
  namespace: external-secrets
  annotations:
    iam.gke.io/gcp-service-account: {serviceaccount-email}@{gcp-projectid}.iam.gserviceaccount.com
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: gcp-cluster-secret-store
spec:
  provider:
    gcpsm:
      projectID: {projectid}
      auth:
        workloadIdentity:
          clusterLocation: {cluster location}
          clusterName: {cluster name}
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets

ここまで設定すると、ExternalSecretリソースを利用してSecretManagerに登録済みの値でSecretが作成できるようになります。マニフェストにはSecretManagerのkeyしか記載されないので、Gitレポジトリにcommitすることができ、GitOpsフローでSecretが管理できるようになりました。

GKEのSecret管理について調べると、Seald Secret、Berglas など出てきますが、どちらも現在は古いようなので、External Secret Operatorがよいんじゃないかと思います。 特にBerglasはSecretManagerリリース後は利用は推奨されていないようでした。

今までは、secretのマニフェスト自体を暗号化して、デプロイ直前に復号してapplyする方法が使われていて、これはGitOpsと相性悪くてどうしたものかと思っていたのですが、External Secret Operatiorで良い感じに解決できました