Help us understand the problem. What is going on with this article?

Argo CD における LDAP 認証と、LDAP グループを利用した権限制御

はじめに

私の所属する aslead DevOps チームでは、Kubernetes のマニフェストファイルの適用の効率化、クラスタの状態を把握しやすくするツールとして、GitOps ツールである Argo CD の検証を進めています。

開発環境や本番環境において、クラスタを様々なチームで共用したり、インフラチームだけが特定の namespace を管理する(例:istio に関する namespace は開発者には触らせたくない)等のケースを想定すると、認証認可の仕組みが必要になってきます。しかし、Argo CD で LDAP による認証認可を実現しようとしたところ、公式ドキュメントには詳細な設定方法は書かれておらず、やや苦戦しました。

本記事では、Argo CD に組み込まれている Dex を使った LDAPによる認証、LDAPのグループによる認可 についてまとめていきます。

また、プロダクションのLDAPサーバに繋いで検証できないケースを想定し、OpenLDAPでテスト用のLDAPサーバを立てる手順を合わせて記載します。

本記事で使っている環境

  • Docker CE 19.03.8
  • kubectl v1.18.2
  • Kind v0.8.1
    • Nodeのイメージ: kindest/node:v1.18.2 (Kubernetes v1.18.2 のイメージ)
  • Argo CD
    • サーバ: v1.6.1+159674e
    • CLI: v1.6.2+3d1f37b
  • OpenLDAP 2.4.50 (osixia/openldap:1.4.0)

本記事の前提

  • k8s クラスタが構築済
  • Argo CD がインストール済
  • Argo CD サーバにブラウザからアクセスできること

LDAP サーバの構築

動作確認用のLDAPサーバを作成します。

ユーザとグループ

ユーザとグループの構成は以下の通り。

  • ユーザ
    • alice (パスワード: alice)
    • bob (パスワード: bob)
  • グループ
    • group1 (alice が所属)
    • group2 (bob が所属)

argocd-ldap-example.drawio.png

OpenLDAP の Pod を作成する

以下の 3 つのマニフェストを作成します。

  • 上記のユーザ/グループの構成をするための ConfigMap
  • LDAPサーバを起動するための Deployment
  • LDAPサーバに接続するための Service
openldap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ldap-sample-data
data:
  sample-data.ldif: |
    version: 1

    dn: cn=group1,dc=example,dc=org
    objectClass: groupOfNames
    objectClass: top
    cn: group1
    member: uid=alice,ou=users,dc=example,dc=org

    dn: ou=users,dc=example,dc=org
    objectClass: organizationalUnit
    objectClass: top
    ou: users

    dn: uid=alice,ou=users,dc=example,dc=org
    objectClass: inetOrgPerson
    objectClass: organizationalPerson
    objectClass: person
    objectClass: top
    cn: alice
    sn: alice
    uid: alice
    userPassword:: YWxpY2U=

    dn: uid=bob,ou=users,dc=example,dc=org
    objectClass: inetOrgPerson
    objectClass: organizationalPerson
    objectClass: person
    objectClass: top
    cn: bob
    sn: bob
    uid: bob
    userPassword:: Ym9i

    dn: cn=group2,dc=example,dc=org
    objectClass: groupOfNames
    objectClass: top
    cn: group2
    member: uid=bob,ou=users,dc=example,dc=org
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openldap
spec:
  selector:
    matchLabels:
      app: openldap
  replicas: 1
  template:
    metadata:
      labels:
        app: openldap
    spec:
      containers:
        - name: openldap
          image: osixia/openldap:1.4.0
          args:
          - --loglevel
          - debug
          - --copy-service
          ports:
            - containerPort: 389
              name: ldap
          volumeMounts:
            - name: config-volume
              mountPath: /container/service/slapd/assets/config/bootstrap/ldif/custom/sample-data.ldif
              subPath: sample-data.ldif
      volumes:
        - name: config-volume
          configMap:
            name: ldap-sample-data
---
kind: Service
apiVersion: v1
metadata:
  name:  openldap-service
spec:
  selector:
    app: openldap
  type:  ClusterIP
  ports:
    - name:  ldap
      port:  389
      targetPort:  389

クラスタに適用します。

kubectl apply -f openldap.yaml

Argo CD に Project と Application を作成する

Argo CD には、ProjectApplication という概念があります。
Application は、デプロイするアプリケーションを構成するマニフェストの集まりであり、ソースとしてのGitへの参照と、デプロイ先クラスタ、ネームスペース を定義します。Project は、Application をグルーピング する単位です。

k8s でのリソース名(kind)は Project が AppProject、Application は Application となります。

LDAP サーバに構成したグループに合わせ、以下の構成で Project と Application を作成します。

Project Application 参照先 Git リポジトリ 参照するパス
group1-project group1-application https://github.com/d-tsu/argocd-example.git ldap-rbac-example/group1/
group2-project group2-application https://github.com/d-tsu/argocd-example.git ldap-rbac-example/group2/

マニフェストは以下の通りです。

appproject.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: group1-project
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  description: group1 project
  sourceRepos:
  - '*'
  destinations:
  - namespace: group1-namespace
    server: https://kubernetes.default.svc
  clusterResourceWhitelist:
  - group: '*'
    kind: '*'
  orphanedResources:
    warn: false

---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: group2-project
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  description: group2 project
  sourceRepos:
  - '*'
  destinations:
  - namespace: group2-namespace
    server: https://kubernetes.default.svc
  clusterResourceWhitelist:
  - group: '*'
    kind: '*'
  orphanedResources:
    warn: false
application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: group1-project
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: group1-project
  source:
    repoURL: 'https://github.com/d-tsu/argocd-example.git'
    path: ldap-rbac-example/group1
    targetRevision: HEAD
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: group1-namespace
  syncPolicy:
    automated: {}
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: group2-application
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: group2-application
  source:
    repoURL: 'https://github.com/d-tsu/argocd-example.git'
    path: ldap-rbac-example/group2
    targetRevision: HEAD
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: group2-namespace
  syncPolicy:
    automated: {}

クラスタに適用します。

kubectl -n argocd apply -f project.yaml
kubectl -n argocd apply -f application.yaml

argocd-cm ConfigMap の作成

ここからが本題です。
LDAP サーバへの接続は argocd Namespace にある argocd-cm ConfigMap で設定します。

argocd-cm は Argo CD に関する各種設定を行うためのリソースであり、シングルサインオンの設定以外にも、Argo CD への Git リポジトリの登録やローカルユーザの設定などが可能です。

Argo CD は外部に認証を委任するために Dex を組み込んでおり、 argocd-cmdex.config で認証に関する設定ができます。DexのLDAP接続に関するドキュメント を参照し、dex.config 以下にLDAPに関する情報を設定していきます。

設定例は以下の通りです。各フィールドの詳細はコメントで記載しています。
ここでは、動作検証用途のため TLS 通信や証明書の検証処理は無効化していることに注意してください。

argocd-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
  labels:
    app.kubernetes.io/name: argocd-cm
    app.kubernetes.io/part-of: argocd
data:
  # argocd サーバの URL(SSO設定する際は必須)
  url: https://argocd.example.com

  # dex の設定
  dex.config: | 
    connectors:
      - type: ldap
        id: ldap
        name: LDAP
        config: 
          # LDAPサーバのホスト名とポート番号
          # ポート番号が省略された場合:
          # insecureNoSSL: true の場合は ポート 389 が使われ、
          # startTLS: true の場合は TLS 通信するまで ポート 389 が使われる
          # それ以外の場合は ポート 636
          host: openldap-service.default.svc.cluster.local:389

          # TLS 通信を使わない場合は true
          insecureNoSSL: true

          # サーバ証明書の検証をスキップする場合は true
          insecureSkipVerify: true

          # startTLS を利用する場合は 
          # TLS 通信が可能か判断するまでは ldap:// プロトコル で通信し、
          # もし可能だったら ldaps:// プロトコルで通信をする
          # 参考:https://ja.wikipedia.org/wiki/STARTTLS
          startTLS: false

          # LDAPサーバで検索するための認証情報
          bindDN: cn=admin,dc=example,dc=org
          bindPW: admin

          # Dex のログイン画面のユーザ名に相当するテキストボックスに付くラベル
          # デフォルトは Username
          usernamePrompt: user

          # ユーザ検索に関する設定
          # この設定に基づき、ユーザとパスワードの照合が行われる
          userSearch:
            baseDN: ou=users,dc=example,dc=org      # ユーザ検索対象とするツリーのルート要素
            filter: "(objectClass=inetOrgPerson)"   # ユーザ検索時のフィルタ
            username: uid   # ログインに使われるユーザ名
            idAttr: uid     # どこに使われているか把握できていない。usernameと同一にしておくのが無難か
            emailAttr: uid  # この値がユーザ名(表示名)になる模様
            nameAttr: uid   # どこに使われているか把握できていない。usernameと同一にしておくのが無難か

          # グループ検索に関する設定
          # この設定に基づき、ユーザが所属しているグループが検索される
          # グループの groupAttr で指定した属性と、ユーザの userAttr で指定した属性がマッチするか確認される
          # ここでは、ユーザの DN と グループの member を突き合わせている
          groupSearch:
            baseDN: dc=example,dc=org            # グループ検索対象とするツリーのルート要素
            filter: "(objectClass=groupOfNames)" # グループ検索時のフィルタ
            userAttr: DN                         
            groupAttr: member
            nameAttr: cn                         # グループ名(RBAC設定で利用する)

クラスタに適用します。

kubectl -n argocd apply -f argocd-cm.yaml

参考:Dex 公式ドキュメントの誤りについて

DexのLDAP接続に関するドキュメントgroupSearch フィールドの例では、以下のように userMatchers が含まれた形式になっています。
しかし、この設定ではグループ検索が動作しません。 これは Dex のいくつかの Issue で言及されています。

userMatcher 要素を取り除き、他の要素とフラットに記載することで正しく動作します。

# これは動作しない
    groupSearch:
      baseDN: cn=groups,dc=freeipa,dc=example,dc=com
      filter: "(objectClass=group)"
      userMatchers:
      - userAttr: uid
        groupAttr: member
      nameAttr: name

# これは動作する
    groupSearch:
      baseDN: cn=groups,dc=freeipa,dc=example,dc=com
      filter: "(objectClass=group)"
      userAttr: uid
      groupAttr: member
      nameAttr: name

argocd-cm-rbac ConfigMap の作成

RBAC の設定は argocd Namespace にある argocd-cm-rbac ConfigMap で設定します。
認可のポリシーの設定には、以下の2つの方法があります。

  1. ユーザやグループに直接ポリシーを付与する
  2. ロールにポリシーを付与し、ユーザやグループにロールを付与する

ポリシーの管理がしやすいように、基本的には 2. で設定することをおすすめします。

argocd-cm-rbac では、policy.csv 配下に以下の形式で認可のポリシーを定義できます。

Application の場合
p, <role/user/group>, <resource>, <action>, <project>/<object>

それ以外 の場合
p, <role/user/group>, <resource>, <action>, <object>

ロールを ロール/ユーザ/グループに紐付ける場合
g, <role/user/group>, <role>

また、ポリシー行の末尾に カンマ続きで allow あるいは deny を定義できるようですが、Argo CD の公式ドキュメントやサンプルコードには説明がありませんでした。基本的には、暗黙的な deny をベースとして allow で許可することで、最小限の権限を与える方針が良いと思います。

暗黙的な deny は policy.default によって実現できます。
policy.default は、デフォルトでユーザに与える権限を指定できるフィールドですが、policy.default を定義しない、あるいは 空文字列 "" を設定すると、ポリシーが何も与えられていない状態がデフォルトとなります。

# ロール名 role:group1-role に、 group1-project 配下の Application を参照する権限を許可する例
p, role:group1-role, applications, get, group1-project/*, allow

# ロール名 role:group1-role を、グループ group1 に付与する例
g, group1, role:group1-role

ポリシーを構成する各項目の意味と、設定できる値は以下のとおりです。

項目 意味 指定できる値
<role/user/group> ポリシーを割り当てる対象 ロール名 or ユーザ名 or グループ名
<resource> ポリシーの設定対象とするリソース clusters, projects, applications, repositories, certificates
<action> ポリシーで制御する操作 get, create, update, delete, sync, override, action
<project> プロジェクト プロジェクト名
<object> リソースの名前 リソース名(クラスタ名や、Application名など)

設定例は以下のとおりです。

argocd-cm-rbac.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
  labels:
    app.kubernetes.io/name: argocd-rbac-cm
    app.kubernetes.io/part-of: argocd
data:
  policy.csv: |
    p, role:group1-role, applications, get, group1-project/*, allow
    p, role:group1-role, certificates, get, *, allow
    p, role:group1-role, clusters, get, *, allow
    p, role:group1-role, projects, get, *, allow
    p, role:group1-role, repositories, get, *, allow

    p, role:group2-role, applications, get, group2-project/*, allow
    p, role:group2-role, certificates, get, *, allow
    p, role:group2-role, clusters, get, *, allow
    p, role:group2-role, projects, get, *, allow
    p, role:group2-role, repositories, get, *, allow

    g, group1, role:group1-role
    g, group2, role:group2-role

  policy.default: ""

クラスタに適用します。

kubectl -n argocd apply -f argocd-cm-rbac.yaml

動作確認

まずは、LDAPサーバへの接続設定が正常に構成されているかを確認します。
正常に構成できている場合、ログイン画面に LOGIN VIA LDAP というボタンが表示されます。
表示されなかった場合は argocd-cm の設定に誤りがあるかもしれないので、設定内容を見直してください。

Screenshot_2020-09-12 Argo CD - Help.png

LOGIN VIA LDAP をクリックすると、Dex が提供するログイン画面が表示されます。ここでは、ユーザ alice でログインしてみます。

Screenshot_2020-09-12 dex.png

正常にログインできたら、以下のように group1-application が表示されるはずです。
Application が何も表示されなかった場合、あるいは group2-application も表示されている場合は argocd-cm-rbac の設定に誤りがあるかもしれないので、設定内容を見直してください。
Screenshot_2020-09-12 Argo CD - Applications(1).png

User Info ページでユーザ情報を確認すると、alice が group1 に所属していることが確認できます。
Screenshot_2020-09-16 Argo CD - User Info.png

同様に Bobで ログインすると以下の状態が確認できます。

  • group2-application しか参照できない
  • group2 に所属している

まとめ

本記事では、Argo CD における LDAP 認証の設定方法と、LDAP グループを利用した権限制御の設定方法をまとめました。
認可のポリシーはシンプルな形式で設定できるので、一度ベースとなる argocd-cm を作ってしまえば、以降は設定を継ぎ足していくことで、所望の権限制御が実現できると思います。
今回は、ConfigMap を直接 apply してしまいましたが、argocd-cm, argocd-cm-rbac なども GitOps で管理することで Argo CD 自体の設定を管理・監査できるようになります。Argo CD を宣言的に管理する方法は 公式ドキュメント に記載があるので、参照してみてください。

dtn
CICD、自動テストなど、めんどくさいことを仕組み化する仕事をしています。
nri
NRIは「コンサルティング」「金融 ITソリューション」「産業 ITソリューション」「IT 基盤サービス」の4事業でお客様のビジネスや快適な社会、暮らしを支えています。※各記事の内容は個人の見解であり、所属する組織の公式見解ではありません。
https://www.nri.com/jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした