はじめに
私の所属する 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 が所属)
OpenLDAP の Pod を作成する
以下の 3 つのマニフェストを作成します。
- 上記のユーザ/グループの構成をするための ConfigMap
- LDAPサーバを起動するための Deployment
- LDAPサーバに接続するための Service
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 には、Project と Application という概念があります。
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/ |
マニフェストは以下の通りです。
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
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-cm
の dex.config
で認証に関する設定ができます。DexのLDAP接続に関するドキュメント を参照し、dex.config
以下にLDAPに関する情報を設定していきます。
設定例は以下の通りです。各フィールドの詳細はコメントで記載しています。
ここでは、動作検証用途のため TLS 通信や証明書の検証処理は無効化していることに注意してください。
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 で言及されています。
- LDAP groups from Active Directory #982
- user groups from Microsoft AD not returned using ldap dex connector #1655
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つの方法があります。
- ユーザやグループに直接ポリシーを付与する
- ロールにポリシーを付与し、ユーザやグループにロールを付与する
ポリシーの管理がしやすいように、基本的には 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名など) |
設定例は以下のとおりです。
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
の設定に誤りがあるかもしれないので、設定内容を見直してください。
LOGIN VIA LDAP をクリックすると、Dex が提供するログイン画面が表示されます。ここでは、ユーザ alice でログインしてみます。
正常にログインできたら、以下のように group1-application が表示されるはずです。
Application が何も表示されなかった場合、あるいは group2-application も表示されている場合は argocd-cm-rbac
の設定に誤りがあるかもしれないので、設定内容を見直してください。
User Info ページでユーザ情報を確認すると、alice が group1 に所属していることが確認できます。
同様に Bobで ログインすると以下の状態が確認できます。
- group2-application しか参照できない
- group2 に所属している
まとめ
本記事では、Argo CD における LDAP 認証の設定方法と、LDAP グループを利用した権限制御の設定方法をまとめました。
認可のポリシーはシンプルな形式で設定できるので、一度ベースとなる argocd-cm
を作ってしまえば、以降は設定を継ぎ足していくことで、所望の権限制御が実現できると思います。
今回は、ConfigMap を直接 apply してしまいましたが、argocd-cm
, argocd-cm-rbac
なども GitOps で管理することで Argo CD 自体の設定を管理・監査できるようになります。Argo CD を宣言的に管理する方法は 公式ドキュメント に記載があるので、参照してみてください。