CL LAB

HOME > CL LAB > Mirantis Kubernetes Engine (MKE)でGitLab Runnerを動かそう 〜 RBACやPSPの観点から #mirantis #kubernetes #k8s #rbac #podsecuritypolicy #docker #gitlab

Mirantis Kubernetes Engine (MKE)でGitLab Runnerを動かそう 〜 RBACやPSPの観点から #mirantis #kubernetes #k8s #rbac #podsecuritypolicy #docker #gitlab

 ★ 0

Mirantis Kubernetes Engine (MKE) (旧Docker Enterprise & Universal Control Plane (UCP))とは、KubernetesとSwarmのどちらか一方または両方を利用可能なコンテナオーケストレーションプラットフォームです。

本稿では、MKEにGitLab Runnerをデプロイし、GitLab CI/CDのジョブを実行するための手順と注意点を、MKEのセキュリティ機構、KubernetesのRBACKubernetesの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)をご検討いただければ幸いです。

CL LAB Mail Magazine

CL LABの情報を逃さずチェックしよう!

メールアドレスを登録すると記事が投稿されるとメールで通知します。

メールアドレス: 登録

※登録後メールに記載しているリンクをクリックして認証してください。

Related post

Neo4j[ホワイトペーパー]CCPA