NAMESPACEをプロビジョニングするOPERATORを作ってみる(ユーザー権限編)

NamespaceをプロビジョニングするOperatorがあったら、マルチテナントでクラスター運用するのが楽になるかなぁと思い、Operatorを作成しています。本日はNamespaceにおけるユーザーアクセス権限のプロビジョニング機能を作成します。

  • はじめに
  •  Operatorを作成する
    • CRDを作成する
    • Controllerを追加する
  • Operatorをビルドする
  • Operatorをデプロイする
  • Operatorを確認する
    • ユーザーを作成する
    • Contextを作成する
    • CRを作成する
    • 権限を確認する

はじめに


最近、NamespaceをプロビジョニングするOperatorを作成しています。以下の関連記事にあるように、これまでResourceQuotaやNetworkPolicyをプロビジョニングするOperatorを作成しました。

本記事では、このOperatorにアクセス権限をプロビジョニングする機能を追加します。具体的には、CR(Cutom Resouce)に指定されたGroupに所属するユーザーが、Namespaceにアクセスできるように設定します。システムやプロジェクト単位でNamespaceを分割する際、関係ないユーザーが勝手に操作することを防げるので、必ずと言っていいほどインフラチームが実施する設定です。

Operatorを作成する


CRDを作成する


CRで設定できる設定項目を定義します。今回は、pkg/apis/nspro/v1alpha1/nspro_types.goのNsproSpecにAllowedGroup []string `json:”allowedGroup”`を加えて、Namespaceにアクセス可能なグループを指定できるようにします。

Operator SDKのインストール方法は、Operator SDK をインストールしてOperatorを作成する(1/2)で紹介していますので、よろしければご参照ください。

type NsproSpec struct {
    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
    // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
    // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html
    NsSize string `json:"nssize"`
    Managed bool `json:"managed"`
    AllowedGroup []string `json:"allowedGroup"`
}

変更を加えたら、以下のコマンドで必要なコードを再生成します。

$ operator-sdk generate k8s
INFO[0006] Running deepcopy code-generation for Custom Resource group versions: [nspro:[v1alpha1], ]
INFO[0011] Code-generation complete.

Controllerを追加する


ControllerのReconcile関数には、newRolePolicyForNSとnewRoleBindingForNSを呼び出してRole, RoleBindingリソースを作成する処理を記述します。

func (r *ReconcileNspro) Reconcile(request reconcile.Request) (reconcile.Result, error) {
    ~~省略~~
    role := r.newRoleForNS(instance)
    err = r.client.Create(context.TODO(), role)

    group_names := instance.Spec.AllowedGroup
    for i := range group_names {
      rb := r.newRoleBindingForNS(instance, group_names[i])
      err = r.client.Create(context.TODO(), rb)
    }
    ~~省略~~
}

newRoleforNSは以下の通りで、CRで作成するNamespaceの全権限(クラスター権限を除く)を付与します。作成方法は他のリソースと同じですが、RoleとRoleBindingの定義は、vender/k8s.io/api/rbac/v1/type.goに記述されています。

func (r *ReconcileNspro) newRoleForNS(cr *nsprov1alpha1.Nspro) *rbacv1.Role {
  nsname  := cr.Name
  role := &rbacv1.Role {
    ObjectMeta: metav1.ObjectMeta {
      Name:      nsname + "-role",
      Namespace: nsname,
    },
    Rules: []rbacv1.PolicyRule {
      {
        APIGroups: []string{ rbacv1.APIGroupAll },
        Resources: []string{ rbacv1.ResourceAll },
        Verbs: []string{ rbacv1.VerbAll },
      },
    },
  }
  controllerutil.SetControllerReference(cr, role, r.scheme)
  return role
}

newRoleBindingForNSは以下の通りで、newRoleforNSで作成したRoleをCRに指定されたGroupに付与します。

func (r *ReconcileNspro) newRoleBindingForNS(cr *nsprov1alpha1.Nspro, group_name string) *rbacv1.RoleBinding {
  nsname  := cr.Name
  rb := &rbacv1.RoleBinding {
    ObjectMeta: metav1.ObjectMeta {
      Name:      nsname + "-rb",
      Namespace: nsname,
    },
    Subjects: []rbacv1.Subject {
      {
        Kind: "Group",
        Name: group_name,
        APIGroup: "rbac.authorization.k8s.io",
      },
    },
    RoleRef: rbacv1.RoleRef {
      Name: nsname + "-role",
      Kind: "Role",
      APIGroup: "rbac.authorization.k8s.io",
    },
  }
  
  controllerutil.SetControllerReference(cr, rb, r.scheme)
  return rb
}

Operatorをビルドする


作成したOperatorのコンテナイメージをビルドします。

$ operator-sdk build 192.168.64.2:32000/nspro-operator:v1

以下のコマンドでレジストリに登録します。今回はMicroK8s上のコンテナレジストリ上に登録するので、[WorkerNodeのIPアドレス]:[NodePort]/[イメージ名]:[タグ名]のように登録しておきます。

$ docker push 192.168.64.2:32000/nspro-operator:v1

Operatorをデプロイする


Operatorをデプロイします。Operatorの前にCRDを作成しないとapiVersionが利用できないので、CRDから作成します。

$ kubectl create -f deploy/crds/nspro_v1alpha1_nspro_crd.yaml
customresourcedefinition.apiextensions.k8s.io/nspros.nspro.example.com created

続いてdeploy/配下にあるyamlを作成します。operator.yamlだけイメージ名を変更する必要があります。

$ kubectl delete -f deploy/operator.yaml 
deployment.apps "nspro-operator" deleted
$ kubectl create -f deploy/operator.yaml -n nspro-operator
deployment.apps/nspro-operator created
$ kubectl create -f deploy/service_account.yaml -n nspro-operator
serviceaccount/nspro-operator created
$ kubectl create -f deploy/role.yaml -n nspro-operator
role.rbac.authorization.k8s.io/nspro-operator created
$ kubectl create -f deploy/role_binding.yaml -n nspro-operator
rolebinding.rbac.authorization.k8s.io/nspro-operator created

デプロイすると以下のエラーが発生します。

E0701 15:06:25.610185 1 reflector.go:134] sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go:126: Failed to list *v1alpha1.Nspro: nspros.nspro.example.com is forbidden: User "system:serviceaccount:nspro-operator:nspro-operator" cannot list resource "nspros" in API group "nspro.example.com" in the namespace "nspro-operator"

アクセス権限を付与するために、以下の記事でRBAC機能を有効化したことによってOperatorに権限がなくなったことが原因です。

MicroK8sへのユーザー追加と権限付与でハマった話

従って、OperatorにCluster権限を与える必要があります。以下のyamlを使ってnspro-operator ServiceAccountにcluster-adminを付与します。

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: admin-for-nspro-operator
subjects:
- kind: ServiceAccount
  name: nspro-operator
  namespace: nspro-operator
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

Operatorを確認する


CRを作成してOperatorの挙動を確認します。

ユーザーを作成する


Operator確認用に、dev1グループに所属するdev1-user01とdev2グループに所属するdev2-user01を作成します。

basic_auth.csv に以下の行を追加します。今回はMicroK8sを利用しているので/var/snap/microk8s/current/credentials/配下にあります。環境毎にパスが異なるので注意してください。

[dev1-user01のパスワード],dev1-user01,dev1-user01,"system:authenticated,developer,dev1"
[dev2-user01のパスワード],dev2-user01,dev2-user01,"system:authenticated,developer,dev2"

Contextを作成する


~/.kube/config を編集して、dev1-user01とdev2-user01で操作する用のContextを作成します。

apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://127.0.0.1:16443
  name: microk8s-cluster
contexts:
- context:
    cluster: microk8s-cluster
    user: admin
  name: microk8s
- context:
    cluster: microk8s-cluster
    user: dev1-user01
  name: microk8s-dev1
- context:
    cluster: microk8s-cluster
    user: dev2-user01
  name: microk8s-dev2
current-context: microk8s
kind: Config
preferences: {}
users:
- name: admin
  user:
    password: [adminのパスワード]
    username: admin
- name: dev1-user01
  user:
    password: [dev1-user01のパスワード]
    username: dev1-user01
- name: dev2-user01
  user:
    password: [dev2-user01のパスワード]
    username: dev2-user01

CRを作成する


CRを作成してNamespaceをプロビジョニングします。

dev1グループがアクセスできるdev1-nsとdev2グループがアクセスできるdev2-nsを以下のyamlを使って作成します。

apiVersion: nspro.example.com/v1alpha1
kind: Nspro
metadata:
  name: dev1-ns
spec:
  nssize: large
  managed: true
  allowedGroup: ["dev1"]
apiVersion: nspro.example.com/v1alpha1
kind: Nspro
metadata:
  name: dev2-ns
spec:
  nssize: large
  managed: true
  allowedGroup: ["dev2"]
$ kubectl create -f deploy/crds/dev1-ns.yaml -n nspro-operator
nspro.nspro.example.com/dev1-ns created
$ kubectl create -f deploy/crds/dev2-ns.yaml -n nspro-operator
nspro.nspro.example.com/dev2-ns created
$ kubectl get ns dev1-ns dev2-ns
NAME      STATUS   AGE
dev1-ns   Active   82s
dev2-ns   Active   74s

権限を確認する


dev1-user01とdev2-user01のコンテキストに切り替えて、それぞれの権限を確認すると、以下のようにCRのallowedGroupに指定されたNamespaceのみアクセスできることがわかります。

$ kubectl config use-context microk8s-dev1
Switched to context "microk8s-dev1".
$ kubectl auth can-i get pods -n dev1-ns
yes
$ kubectl auth can-i get pods -n dev2-ns
no
$ kubectl config use-context microk8s-dev2
Switched to context "microk8s-dev2".
$ kubectl auth can-i get pods -n dev1-ns
no
$ kubectl auth can-i get pods -n dev2-ns
yes

以上です。段々Operatorの使い方も慣れてきました。