Mirantis Kubernetes Engine (MKE)でGitLab Runnerを動かそう 〜 RBACやPSPの観点から #mirantis #kubernetes #k8s #rbac #podsecuritypolicy #docker #gitlab
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
Mirantis Kubernetes Engine (MKE) (旧Docker Enterprise & Universal Control Plane (UCP))とは、KubernetesとSwarmのどちらか一方または両方を利用可能なコンテナオーケストレーションプラットフォームです。
本稿では、MKEにGitLab Runnerをデプロイし、GitLab CI/CDのジョブを実行するための手順と注意点を、MKEのセキュリティ機構、KubernetesのRBAC、KubernetesのPodSecurityPolicyなどに焦点を当てつつ見ていきます。
環境の準備
GitLab
既にあるものを利用します。本稿ではこのGitLabの特定のプロジェクトに、Specific Runnerとして動作するGitLab RunnerをMKEにデプロイします。
MKE
Virtualbox上にVagrantとLaunchpadでMKEをインストールし、またHelmもインストールします。詳細な手順は以前のブログ「MKEとMSRをLaunchpadでVirtualbox/Vagrantにインストールしてみよう」「HelmをサポートしたMSRを試してみよう」をご覧ください。
今回用いたVagrantfileはこちら:
Vagrant.configure("2") do |config| config.vm.box = "centos/7" config.vm.box_check_update = false ( 1..3 ).each do |i| config.vm.define "node#{i}" do |cf| cf.vm.hostname = "node#{i}" cf.vm.network "private_network", ip: "192.168.123.20#{i}" cf.vm.provider "virtualbox" do |vb| vb.memory = 4096 end end end end
launchpad.yamlはこちらです:
apiVersion: launchpad.mirantis.com/mke/v1.4 kind: mke metadata: name: my-mke-cluster spec: hosts: - ssh: address: 192.168.123.202 user: vagrant port: 22 keyPath: ~/.ssh/id_rsa role: manager privateInterface: eth1 - ssh: address: 192.168.123.203 user: vagrant port: 22 keyPath: ~/.ssh/id_rsa role: worker privateInterface: eth1 mke: version: 3.4.5 adminUsername: admin adminPassword: adminadmin installFlags: - --force-minimums - --default-node-orchestrator=kubernetes - --pod-cidr 172.31.0.0/16 - --san=node2 - --san=192.168.123.202 mcr: version: 20.10.0 cluster: prune: false
- node1 : 192.168.123.201 : DNSサーバ 兼 Launchpad/MKE/Helmクライアント
- node2 : 192.168.123.202 : MKEマスター
- node3 : 192.168.123.203 : MKEワーカー
GitLab Runnerのデプロイ
node1にて、GitLab Runner Helm Chartの手順通りに、まず Helm レポジトリを設定します。
node1$ helm repo add gitlab https://charts.gitlab.io "gitlab" has been added to your repositories
GitLab Runner用のKubernetes Namespaceを作成します。ここではそのまま「gitlab-runner」という名前にしておきます。
node1$ kubectl create ns gitlab-runner namespace/gitlab-runner created
Helm設定ファイルの雛形を生成します。
node1$ helm inspect values gitlab/gitlab-runner > values.yaml.orig node1$ cp values.yaml.orig values.yaml
これからデプロイするGitLab Runnerを、Specific Runnerとして利用したいGitLabプロジェクトの設定を取得しておきます。具体的には次の2点です。
- gitlabUrl
- runnerRegistrationToken
GitLabプロジェクトが https://gitlab.com/[USERNAME]/[PROJECTNAME] であれば、左のサイドメニューから設定→CI/CDを開き、Runnerを展開するか、 https://gitlab.com/[USERNAME]/[PROJECTNAME]/-/settings/ci_cd を開くと Specific runners に必要な設定が表示されます。
values.yamlのgitlabUrlに「Register the runner with this URL」の値、runnerRegistrationTokenに「And this registration token」の値を書き加えます。また、rbac.createをfalseからtrueに変更し、RBAC関連のリソースを自動作成するようにします。さらに、configuration.html.configに「privileged = true」を追加し、docker:18.09-dindサービスを起動できるようにします。雛形との差分は次の通りです(gitlabUrlとrunnerRegistrationTokenは伏せています):
node1$ diff -u values.yaml.orig values.yaml --- values.yaml.orig 2021-09-16 15:06:04.511800734 +0900 +++ values.yaml 2021-10-07 11:17:36.732166648 +0900 @@ -37,13 +37,13 @@ ## The GitLab Server URL (with protocol) that want to register the runner against ## ref: https://docs.gitlab.com/runner/commands/README.html#gitlab-runner-register ## -# gitlabUrl: http://gitlab.your-domain.com/ +gitlabUrl: https://XXXXXXXXXXXXXXXXXXX/ ## The Registration Token for adding new Runners to the GitLab Server. This must ## be retrieved from your GitLab Instance. ## ref: https://docs.gitlab.com/ce/ci/runners/README.html ## -# runnerRegistrationToken: "" +runnerRegistrationToken: "XXXXXXXXXXXXXXXXXXXX" ## The Runner Token for adding new Runners to the GitLab Server. This must ## be retrieved from your GitLab Instance. It is token of already registered runner. @@ -108,7 +108,7 @@ ## For RBAC support: rbac: - create: false + create: true ## Define specific rbac permissions. ## DEPRECATED: see .Values.rbac.rules @@ -172,6 +172,7 @@ [runners.kubernetes] namespace = "{{.Release.Namespace}}" image = "ubuntu:16.04" + privileged = true ## Which executor should be used ##
ではHelmでGitLab Runnerをデプロイしましょう。
node1$ helm install -n gitlab-runner gitlab-runner -f values.yaml gitlab/gitlab-runner NAME: gitlab-runner LAST DEPLOYED: Thu Oct 7 11:14:26 2021 NAMESPACE: gitlab-runner STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: Your GitLab Runner should now be registered against the GitLab instance reachable at: "https://XXXXXXXXXXXXXXXXXXX/" Runner namespace "gitlab-runner" was found in runners.config template.
確認してみます。
node1$ kubectl -n gitlab-runner get all NAME READY STATUS RESTARTS AGE pod/gitlab-runner-gitlab-runner-7dbdbd96cd-6mwqq 1/1 Running 0 21s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/gitlab-runner-gitlab-runner 1/1 1 1 21s NAME DESIRED CURRENT READY AGE replicaset.apps/gitlab-runner-gitlab-runner-7dbdbd96cd 1 1 1 21s
問題なくGitLab Runnerがデプロイできたようです。
先ほど gitlabUrl と runnerRegistrationToken の値を取得したGitLabプロジェクトの設定ページを開くと、このGitLab RunnerがSpecific Runnerとして利用可能となっていることがわかります。Pod名に注目してください。
ジョブの実行
では早速GitLab CI/CDのジョブを実行してみましょう。今回対象としたGitLabプロジェクト(OmegaTイメージのビルドとテスト)では、次の .gitlab-ci.yml を利用しています。
image: docker:18.09 services: - docker:18.09-dind stages: - build - test before_script: - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY build: stage: build variables: DOCKER_DRIVER: overlay2 script: - docker build -t $CI_REGISTRY_IMAGE:4.3.2 . - docker push $CI_REGISTRY_IMAGE:4.3.2 test: stage: test script: - docker run --rm -v $(pwd)/test/project:/omegat $CI_REGISTRY_IMAGE:4.3.2 - cmp test/test.txt test/project/target/test.txt
失敗: pods "" is forbidden: non-admin user "gitlab-runner:gitlab-runner-gitlab-runner"
ところが失敗してしまいました。ジョブのログを見ると、次のようになっています。
Preparing the "kubernetes" executor Using Kubernetes namespace: gitlab-runner Using Kubernetes executor with image docker:18.09 ... Using attach strategy to execute scripts... Preparing environment ERROR: Job failed (system failure): prepare environment: setting up build pod: pods "runner-xphxzza-project-1154-concurrent-0" is forbidden: non-admin user "gitlab-runner:gitlab-runner-gitlab-runner" [service account "gitlab-runner:default"]. The configured privileged attributes access for non-admin users ("[]")("[]") and for service accounts ("[]")("[]") lack required permissions to use attributes [privileged] for resource . Check https://docs.gitlab.com/runner/shells/index.html#shell-profile-loading for more information
このエラーログは誰が出しているのか細かく見ていきましょう。
まず「ERROR: Job failed (system failure):」はGitLab Runner、「prepare environment:」と「Check https://docs.gitlab.com/runner/shells/index.html#shell-profile-loading for more information」もGitLab Runnerが出しているようです。
次に「setting up build pod:」はKubernetes Executorが出しているようです。
では、エラーログの大部分を占める「pods "runner-xphxzza-project-1154-concurrent-0" is forbidden: non-admin user "gitlab-runner:gitlab-runner-gitlab-runner" [service account "gitlab-runner:default"]. The configured privileged attributes access for non-admin users ("[]")("[]") and for service accounts ("[]")("[]") lack required permissions to use attributes [privileged] for resource」は誰が出しているのでしょうか? 公開されているソースコードを検索しても一切出てこない謎のメッセージです。
実はこのメッセージは、KubernetesのAPIサーバに対するリクエストを制御するAdmission Controllerのうち、MKE独自のAdmission Controllerである「UCPAuthorization」が発しているものです。
Works in conjunction with the built-in PodSecurityPolicies admission controller to prevent under-privileged users from creating Pods with privileged options.
訳: 組み込みのPodSecurityPolicy Admission Controllerと連携して動作し、非特権ユーザが特権オプションを使用したPodを作成することを防ぎます。
先のメッセージを端的に解釈すると、「gitlab-runner」Namespaceの非特権ServiceAccountである「gitlab-runner-gitlab-runner」は、docker:18.09-dindコンテナをPrivilegedで実行するための権限がない、となります。
すなわちMKEはセキュリティを強固にするために、非特権ユーザあるいはServiceAccountが特権Podを起動することを防ぐAdmission Controllerを独自に導入し、デフォルトで有効化しているということになります。
しかしこのままではMKE上で、privilegedが必要なdocker:dindサービスを起動するGitLab Runnerジョブを実行することができません。対策が必要です。
対策1: cluster-admin ClusterRoleをServiceAccountに結びつける
MKEのドキュメントを参照すると、次のような記述があります。
PSPs do not override security defaults built into the MKE RBAC engine for Kubernetes pods. These security defaults prevent non-admin users from mounting host paths into pods or starting privileged pods.
訳: PSPは、KubernetesのPod用にMKE RBACエンジンに組み込まれているデフォルトセキュリティを上書きしません。このデフォルトセキュリティは、非管理者ユーザがホストパスをPodにマウントしたり、特権Podを起動したりすることを防ぎます。
For cluster security, only MKE admin users and service accounts that are granted the cluster-admin ClusterRole for all Kubernetes namespaces via a ClusterRoleBinding can deploy pods with privileged options. This prevents a platform user from being able to bypass the Universal Control Plane Security Model.
訳: クラスタのセキュリティのため、ClusterRoleBindingによってすべてのKubernetes Namespaceに対してcluster-admin ClusterRoleが付与されているMKE管理者ユーザとServiceAccountのみが、特権オプションでPodをデプロイできます。これにより、一般ユーザがUCPのセキュリティモデルをバイパスすることを防ぎます。
以上により、まず最初に思いつくのはcluster-admin ClusterRoleを「gitlab-runner」Namespaceの非特権ServiceAccountである「gitlab-runner-gitlab-runner」に結びつけることです。もちろん、クラスタ管理者のロールであるcluster-admin ClusterRoleをむやみに一般ユーザやServiceAccountに結びつけることは、クラスタ全体のセキュリティを低下させるため避けるべき行為です。本稿ではそれを承知した上で、実験的な対策としてこれを実施します。
では、cluster-admin ClusterRoleを gitlab-runner-gitlab-runner ServiceAccountに結びつけるClusterRoleBindingを作成しましょう。YAMLは次のようになります。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: gitlab-runner-cluster-admin subjects: - kind: ServiceAccount name: gitlab-runner-gitlab-runner namespace: gitlab-runner roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io
これを kubectl apply し、GitLab CI/CDジョブを実行します。
Preparing the "kubernetes" executor Using Kubernetes namespace: gitlab-runner Using Kubernetes executor with image docker:18.09 ... Using attach strategy to execute scripts... Preparing environment Waiting for pod gitlab-runner/runner-xphxzza-project-1154-concurrent-0dzn8d to be running, status is Pending Waiting for pod gitlab-runner/runner-xphxzza-project-1154-concurrent-0dzn8d to be running, status is Pending ContainersNotReady: "containers with unready status: [build helper svc-0]" ContainersNotReady: "containers with unready status: [build helper svc-0]" Running on runner-xphxzza-project-1154-concurrent-0dzn8d via gitlab-runner-gitlab-runner-7dbdbd96cd-9glws... Getting source from Git repository : Job succeeded
無事ビルドに成功しました。
しかし前述の通り、cluster-admin ClusterRoleをむやみに一般ユーザやServiceAccountに結びつけることは避けるべきです。次項では別の対策を実施します。
対策2: 独自にPSPを作成し、ServiceAccountに結びつけ、Privileged権限を許可する
cluster-admin ClusterRoleを一般ユーザやServiceAccountに結びつけてはいけないならば、権限を絞ったルールを作成する必要があります。まず必要となるのは、
- Privilegedコンテナ実行の許可をはじめ、その他 GitLab Runner 実行に必要な権限も含むPodSecurityPolicy
- そのPodSecurityPolicy (PSP)を利用し、その他 GitLab Runner 実行に必要なルールを含むClusterRole
- そのClusterRoleと「gitlab-runner」Namespaceの非特権ServiceAccountである「gitlab-runner-gitlab-runner」を結びつけるClusterRoleBinding
となります。具体的なYAMLファイルは次の通りです。PSPはMKEに組み込みのunprivileged PSPを参考とし、GitLab Runner 実行に必要な spec.privileged と spec.allowPrivilegeEscalation を true としています。
apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: gitlab-runner-psp spec: privileged: true allowPrivilegeEscalation: true volumes: - '*' hostNetwork: false hostPorts: - min: 0 max: 65535 hostIPC: false hostPID: false runAsUser: rule: 'RunAsAny' seLinux: rule: 'RunAsAny' supplementalGroups: rule: 'RunAsAny' fsGroup: rule: 'RunAsAny'
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: gitlab-runner-psp-cr annotations: rbac.authorization.kubernetes.io/autoupdate: "true" rules: - apiGroups: ['policy'] resources: ['podsecuritypolicies'] verbs: ['use'] resources: - gitlab-runner-psp - apiGroups: ["*"] resources: ["*"] verbs: ["*"] - nonResourceURLs: ["*"] verbs: ["*"]
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: gitlab-runner-psp-crb subjects: - kind: ServiceAccount name: gitlab-runner-gitlab-runner namespace: gitlab-runner roleRef: kind: ClusterRole name: gitlab-runner-psp-cr apiGroup: rbac.authorization.k8s.io
これを kubectl apply し、GitLab CI/CDジョブを実行します。
Preparing the "kubernetes" executor Using Kubernetes namespace: gitlab-runner Using Kubernetes executor with image docker:18.09 ... Using attach strategy to execute scripts... Preparing environment ERROR: Job failed (system failure): prepare environment: setting up build pod: pods "runner-lfnhz3za-project-1154-concurrent-0" is forbidden: non-admin user "gitlab-runner:gitlab-runner-gitlab-runner" [service account "gitlab-runner:default"]. The configured privileged attributes access for non-admin users ("[]")("[]") and for service accounts ("[]")("[]") lack required permissions to use attributes [privileged] for resource . Check https://docs.gitlab.com/runner/shells/index.html#shell-profile-loading for more information
一番最初に遭遇したエラーに逆戻りしてしまいました。何が問題なのでしょうか? 少し前に引用した公式ドキュメントを再掲します。
PSPs do not override security defaults built into the MKE RBAC engine for Kubernetes pods. These security defaults prevent non-admin users from mounting host paths into pods or starting privileged pods.
訳: PSPは、KubernetesのPod用にMKE RBACエンジンに組み込まれているデフォルトセキュリティを上書きしません。このデフォルトセキュリティは、非管理者ユーザがホストパスをPodにマウントしたり、特権Podを起動したりすることを防ぎます。
つまり、今回定義した Privileged を許可する PSP では、MKE組み込みのデフォルトセキュリティをパスできない、ということです。どうすればよいかの答えは、やはり公式ドキュメントにあります。
Administrators can now give permission to use one or more privileged pod attributes (such as specifying host networking usage or host IPC) to groups of non-administrator user accounts or non-cluster-admin service accounts. Administrators can choose different sets of attributes for each group (MKE-8252).
訳: 管理者は、非管理者ユーザアカウントのグループや非クラスタ管理者ServiceAccountに、1つ以上の特権Pod属性(ホストネットワークの使用やホストIPCの指定など)を使用する権限を与えられるようになりました。管理者は、各グループごとに異なる属性セットを選択することができます (MKE-8252)。
MKE 3.4.2で実装されたこの機能により、MKE組み込みのデフォルトセキュリティに一定の許可を与えることができるようになりました。具体的にはMKEに管理者でログインし、左のサイドバーからadmin → Admin Settings → Orchestrationを開きます。
この「Servicve account privileges」のフォームに「gitlab-runner:gitlab-runner-gitlab-runner」(gitlab-runner Namespaceのgitlab-runner-gitlab-runner ServiceAccountの意)を入力し、「privileged」チェックボックスにチェックを入れ、「Save」ボタンをクリックします。
これでGitLab CI/CDジョブを実行します。
Preparing the "kubernetes" executor Using Kubernetes namespace: gitlab-runner Using Kubernetes executor with image docker:18.09 ... Using attach strategy to execute scripts... Preparing environment Waiting for pod gitlab-runner/runner-lfnhz3za-project-1154-concurrent-0sqtln to be running, status is Pending Waiting for pod gitlab-runner/runner-lfnhz3za-project-1154-concurrent-0sqtln to be running, status is Pending ContainersNotInitialized: "containers with incomplete status: [init-permissions]" ContainersNotReady: "containers with unready status: [build helper svc-0]" ContainersNotReady: "containers with unready status: [build helper svc-0]" Waiting for pod gitlab-runner/runner-lfnhz3za-project-1154-concurrent-0sqtln to be running, status is Pending ContainersNotReady: "containers with unready status: [build helper svc-0]" ContainersNotReady: "containers with unready status: [build helper svc-0]" Running on runner-lfnhz3za-project-1154-concurrent-0sqtln via gitlab-runner-gitlab-runner-7dbdbd96cd-6mwqq... Getting source from Git repository : Job succeeded
無事ビルドに成功しました。
まとめ
本稿ではMirantis Kubernetes Engine (MKE)にGitLab Runnerをデプロイし、MKEの組み込みセキュリティ機構に留意しつつRBACやPSPの設定を行い、動作できるようにするまでを見てみました。
GitLab Runnerのように、より上位の権限が必要な場合は本稿で紹介したような設定変更が必要になりますが、MKEのセキュリティ機構は一般ユーザがクラスタに対して危険な操作ができないようにデフォルトで設定されています。Kubernetesを安全に利用したい場合、Mirantis Kuberntes Engine (MKE)をご検討いただければ幸いです。