TT Lab
はじめる
学ぶ 学習パス コース

CRDとオペレータ

オペレータの導入と権限境界の検証

TT Labで続きを見る

目標

Operatorのマニフェスト一式(ネームスペース、RBAC、コントローラーのDeployment、リーダー選出のLease)を正しい順序でインストールし、その権限が実際に意図した境界の中にあることを、自分で問い合わせて証明します。

なぜ重要なのか

Operatorは、クラスターで最も強い権限を持つワークロードになりうるものです。複数のネームスペースのリソースを作成・変更するため、そのサービスアカウントが窃取されると、攻撃者がその権限をそのまま手に入れます。そのため、2つのことが核心です。1つ目は、インストールの順序が依存関係であることです。コントローラーは起動した直後に自分の型をwatchするのでCRDが先に必要で、一覧の取得から始めるので権限が先に必要です。順序が間違っているとクラッシュループや403として現れますが、原因がコードではなくデプロイの順序だと気づくのに時間がかかります。2つ目は、権限はverbを絞る作業であることです。verbs: ["*"]は楽ですが、子の後片付けを所有者参照に任せれば、deleteさえ必要ないことが多いです。そして、RBACではwebservicesとwebservices/statusは完全に別のリソースです。この事実を知らないと、「権限を与えたのにstatusを書けない」という症状にはまり込みます。最後に、権限はドキュメントではなく検証の対象です。kubectl auth can-i --as=で、できることとできてはいけないことの両方を確認しておけば、その表がそのまま権限の境界の証拠になります。

ステップ

開始前の準備: ラボのPodはラボごとに新しく起動するため、前のラボのクラスターの状態は残っていません。ステップ2とステップ6がCRD webservices.apps.labhub.ioを要求し、ステップ7・ステップ8の権限の問い合わせがcrd-labネームスペースを対象にしているため、kubectl get crd webservices.apps.labhub.ioの結果が空なら、CRDを再び適用し、kubectl create ns crd-labも実行してください。CRDはv1alpha1とv1の2つのバージョンを持つ必要があり、ストレージバージョンはv1でなければなりません(ステップ6がその保存を検査します)。

  1. ネームスペースlabhub-operatorを作成し、ラベルapp.kubernetes.io/part-of=labhub-operatorとpod-security.kubernetes.io/enforce=restrictedを付けてください。
  2. /root/op/install/out/install-order.txtに、インストールの順序を1段階につき1行で書いてください。1行目はCustomResourceDefinition、2行目はServiceAccountとClusterRole/ClusterRoleBinding、3行目はDeploymentに言及する必要があり、前の行でDeploymentや컨트롤러(韓国語で「コントローラー」を意味する語です)という語を先に書かないでください(順序の判定は、最初に登場した行を基準にします)。CRD webservices.apps.labhub.ioもクラスターに存在している必要があります。
  3. labhub-operatorにServiceAccount webservice-controllerを作成し、ClusterRole webservice-controllerを次のルールで作成してください。
    • apiGroups: ["apps.labhub.io"]、resources: ["webservices"]、verbs: ["get","list","watch","update","patch"]
    • apiGroups: ["apps.labhub.io"]、resources: ["webservices/status","webservices/finalizers"]、verbs: ["get","update","patch"]
    • apiGroups: [""]、resources: ["events"]、verbs: ["create","patch"]
    • apiGroups: ["coordination.k8s.io"]、resources: ["leases"]、verbs: ["get","list","watch","create","update","patch"] verbにもリソースにも*を使ってはいけません。そして、ClusterRoleBinding webservice-controllerを作成し、subjectをkind: ServiceAccount、name: webservice-controller、namespace: labhub-operatorに指定してください。
  4. labhub-operatorにDeployment webservice-controllerを作成してください。spec.replicasは1または2、spec.template.spec.serviceAccountNameはwebservice-controller、コンテナイメージはghcr.io/labhub/webservice-controller:v0.1.0とし、argsに--leader-elect=trueを入れ、コンテナにlivenessProbeとreadinessProbeをそれぞれ置いて、resources.limits.memoryを設定してください。PodのsecurityContext.runAsNonRootはtrueである必要があります。
  5. labhub-operatorにLease webservice-controller.labhub.ioを作成してください。spec.holderIdentityはリーダーの身元を示す文字列、spec.leaseDurationSecondsは5以上、spec.renewTimeはマイクロ秒まであるRFC3339の時刻(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)です。そして、/root/op/install/out/lease-note.txtに、リーダー選出がないとなぜ困るのか(同じオブジェクトを2つが同時に調整する問題)を書いてください。
  6. Deploymentのコンテナイメージをghcr.io/labhub/webservice-controller:v0.2.0に上げて、ロールアウトが終わるまで待ってください。CRDはバージョンが2つ以上そのまま残っている必要があり、status.storedVersionsにv1がある必要があります。アップグレードの前後で何を確認したかを/root/op/install/out/upgrade-check.txtに書き、storedVersionsを確認した内容を必ず含めてください。
  7. /opt/lab/fixtures/operator/broken-operator.yamlを/root/op/install/fixed-operator.yamlにコピーして、間違っている3か所を直して適用してください。そのうえで、/root/op/install/out/diagnosis.txtに、何がなぜ間違っていたかを1行に1つずつ、3行以上書いてください。バインディングのサブジェクトの名前の問題と、statusサブリソースの権限の抜けは、必ず含める必要があります。修正後、kubectl auth can-i update webservices/status -n crd-lab --as=system:serviceaccount:labhub-operator:webservice-controllerとkubectl auth can-i list webservices --all-namespaces --as=...が、どちらもyesである必要があります。
  8. /root/op/install/out/permissions.jsonを作成してください。最上位のキーはchecksで、各要素はverb、resource、expected(yes/no)を持ち、必要ならnamespaceを持ちます。namespaceを書かない場合は、全ネームスペースを基準に確認されます。最低6件以上である必要があり、expectedがnoの項目が2件以上である必要があり、そのうち1つは必ずresourceがsecretsでなければなりません。たとえば、list webservices(yes)、watch webservices(yes)、crd-labでのupdate webservices/status(yes)、crd-labでのcreate events(yes)、crd-labでのupdate webservices/finalizers(yes)、crd-labでのget secrets(no)、crd-labでのdelete webservices(no)、create clusterrolebindings(no)の8件で十分です。すべての項目のexpectedが、実際の応答と同じである必要があります。

参考

Operator専用のネームスペースを作る

ネームスペースlabhub-operatorを作成し、ラベルapp.kubernetes.io/part-of=labhub-operatorとpod-security.kubernetes.io/enforce=restrictedを付けてください。

構成要素を一度に探すための所属ラベルと、Podに特権を許可しないポリシーのラベルが、それぞれ必要です。コントローラーは、特権がまったく必要ないワークロードです。

インストールの順序を整理する

/root/op/install/out/install-order.txtに、インストールの順序を1段階につき1行で書いてください。1行目はCustomResourceDefinition、2行目はServiceAccountとClusterRole/ClusterRoleBinding、3行目はDeploymentに言及する必要があり、前の行でDeploymentや컨트롤러(韓国語で「コントローラー」を意味する語です)という語を先に書かないでください(順序の判定は、最初に登場した行を基準にします)。CRD webservices.apps.labhub.ioもクラスターに存在している必要があります。

コントローラーは、起動した直後に自分の型をwatchして、一覧を取得します。その2つが先に準備できている必要があります。順序のドキュメントは1段階に1行ずつ書き、前の行に後の段階の名前を先に言及しないでください。

ワイルドカードのない最小権限を作る

labhub-operatorにServiceAccount webservice-controllerを作成し、ClusterRole webservice-controllerを次のルールで作成してください。

メインのリソースとサブリソースは、RBACでは別々のリソースです。statusとfinalizersを別々に書く必要があり、イベントはコアグループです。アスタリスクは、verbにもリソースにも使ってはいけません。

コントローラーのDeploymentを作成する

labhub-operatorにDeployment webservice-controllerを作成してください。spec.replicasは1または2、spec.template.spec.serviceAccountNameはwebservice-controller、コンテナイメージはghcr.io/labhub/webservice-controller:v0.1.0とし、argsに--leader-elect=trueを入れ、コンテナにlivenessProbeとreadinessProbeをそれぞれ置いて、resources.limits.memoryを設定してください。PodのsecurityContext.runAsNonRootはtrueである必要があります。

デフォルトのサービスアカウントで動かすと、せっかく作った権限が適用されません。レプリカを複数置けるようにする引数と、生きているか・受け付ける準備ができているかを確認する2つのプローブ、キャッシュが大きくなるときに備えたメモリの上限が必要です。

リーダー選出のLeaseを作成する

labhub-operatorにLease webservice-controller.labhub.ioを作成してください。spec.holderIdentityはリーダーの身元を示す文字列、spec.leaseDurationSecondsは5以上、spec.renewTimeはマイクロ秒まであるRFC3339の時刻(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)です。そして、/root/op/install/out/lease-note.txtに、リーダー選出がないとなぜ困るのか(同じオブジェクトを2つが同時に調整する問題)を書いてください。

Leaseには、今誰がリーダーなのか、どれだけの間有効なのか、最後に更新した時刻が入ります。更新時刻はマイクロ秒まである形式なので、桁数が合わないと拒否されます。

イメージを上げて、CRDのバージョンが保存されているか確認する

Deploymentのコンテナイメージをghcr.io/labhub/webservice-controller:v0.2.0に上げて、ロールアウトが終わるまで待ってください。CRDはバージョンが2つ以上そのまま残っている必要があり、status.storedVersionsにv1がある必要があります。アップグレードの前後で何を確認したかを/root/op/install/out/upgrade-check.txtに書き、storedVersionsを確認した内容を必ず含めてください。

コントローラーのイメージはローリングアップデートで上げてもかまいませんが、CRDの古いバージョンは、むやみに削除してはいけません。保存されたことのあるバージョンの一覧を、先に確認してください。ロールアウトが終わったかどうかも、確認する必要があります。

壊れたOperatorのマニフェストを直す

/opt/lab/fixtures/operator/broken-operator.yamlを/root/op/install/fixed-operator.yamlにコピーして、間違っている3か所を直して適用してください。そのうえで、/root/op/install/out/diagnosis.txtに、何がなぜ間違っていたかを1行に1つずつ、3行以上書いてください。バインディングのサブジェクトの名前の問題と、statusサブリソースの権限の抜けは、必ず含める必要があります。修正後、kubectl auth can-i update webservices/status -n crd-lab --as=system:serviceaccount:labhub-operator:webservice-controllerとkubectl auth can-i list webservices --all-namespaces --as=...が、どちらもyesである必要があります。

存在しないサブジェクトを指すバインディングは、エラーなしに作られます。そして、メインのリソースに対する権限があっても、サブリソースは別です。確認はログではなく、権限の問い合わせで行ってください。

権限の境界の検証表を作る

/root/op/install/out/permissions.jsonを作成してください。最上位のキーはchecksで、各要素はverb、resource、expected(yes/no)を持ち、必要ならnamespaceを持ちます。namespaceを書かない場合は、全ネームスペースを基準に確認されます。最低6件以上である必要があり、expectedがnoの項目が2件以上である必要があり、そのうち1つは必ずresourceがsecretsでなければなりません。たとえば、list webservices(yes)、watch webservices(yes)、crd-labでのupdate webservices/status(yes)、crd-labでのcreate events(yes)、crd-labでのupdate webservices/finalizers(yes)、crd-labでのget secrets(no)、crd-labでのdelete webservices(no)、create clusterrolebindings(no)の8件で十分です。すべての項目のexpectedが、実際の応答と同じである必要があります。

できることだけを確認しても、境界を証明できません。できてはいけないことも一緒に入れて、実際の応答と照合してください。ネームスペースを書かなかった項目は、全ネームスペースを基準に確認されます。