ArgoCDのアカウントにロールを割り当てて権限設定する


ArgoCDを複数ユーザで使う場合に権限を分けたい。
具体的には参照可能なApplicationリソースを以下のように分離したい。

|     | Projectが同一    | Projectが異なる|
| --- | --- | --- |
|ゲスト| Read|Read|
|開発者|CRUD+Sync|なし|
|管理者|CRUD+Sync|CRUD+Sync|

※CRUD: Create, Read(Get), Update, Delete

これを実現する方法を検討した時のメモ。\
なお、確認に利用したArgoCDのバージョンはv2.4.7であり、異なるバージョンで試す場合は方法が変わる可能性が有る点には注意すること。

## やること
以下の仮想ユーザを用意し、それぞれ権限が適切に分けて付与できるかを確認する。
![1658741598885.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/08a03fdf-607f-599e-6cb2-fabe67820dd0.png)

確認手順は以下となる。
1. 前準備(Applicationが管理するManifestの準備)
2. ArgoCD用ユーザアカウントの作成
3. ArgoCDのProjectとRoleの作成
4. `kind: Application`の適用
5. 検証

## 前準備

ArgoCDのアプリケーション(`game_app`、`book_app`)が監視するManifestを作成してGitにpushする。

空のリポジトリを作成してcloneする。
```
export $MYREPO=https://mygitlab.info/myapp/apps.git
git clone $MYREPO
```
各プロジェクトで利用するManifestを格納するディレクトリを作成する。
```
mkdir -p apps/{game,book}
```
今回はアプリケーションそのものには価値を求めないため、ダミーでnginxが起動するManifestを作成する。
それぞれのディレクトリにダミーのManifestを作成してpushする。
```
NAME=game
kubectl run --image nginx nginx-$NAME -o yaml --dry-run=client  | kubectl neat -f - > apps/$NAME/nginx-$NAME.yaml
NAME=book
kubectl run --image nginx nginx-$NAME -o yaml --dry-run=client  | kubectl neat -f - > apps/$NAME/nginx-$NAME.yaml
git add *
git commit -m "nocomment"
git push
```

構成としては以下のようになる。
```
apps
├── README.md
├── book
│   └── nginx-book.yaml
└── game
    └── nginx-game.yaml
```

## ユーザアカウントの作成
ArgoCDはv2.4時点ではアカウントの作成がCLI/GUIから作成できず、ConfigMapで指定することでのみ作成できる。\
作成するConfigMapも指定があり、`argocd-cm`というConfigMapを必ず使うことになっているので、こちらを使用する。\
なお、デフォルトでインストールされている`argocd-cm`は初期状態では空なので、上書きしてもらって問題ない。

ArgoCDの[ユーザ管理のオフィシャルページ](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/)を見つつ、アカウントの元となるyamlを作成し、applyする。
```yaml
cat << EOF > ./argocd-account-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:
  accounts.alice: apiKey, login
  accounts.bob: apiKey, login
  accounts.mike: apiKey, login
EOF
kubectl apply -f argocd-account-cm.yaml
```
ユーザアカウント名は`data`フィールドに`accounts.<ユーザアカウント名>`で記載することで作成される。apiKeyはTokenによるアクセスを許可するかどうかであり、不要であれば外してもらって構わない。

反映されたことを確認する。
```
$ argocd account list
NAME   ENABLED  CAPABILITIES
admin  true     login
alice  true     apiKey, login
bob    true     apiKey, login
mike   true     apiKey, login
```
パスワードは設定されていないので、コマンドで変更する。なお、`--current-password`にはadminのパスワードを指定する。
```
export ADMIN_PASSWORD='admin-password'
export PASSWORD='argocd-password'
argocd account update-password --account alice --current-password $ADMIN_PASSWORD  --new-password $PASSWORD
argocd account update-password --account bob --current-password $ADMIN_PASSWORD  --new-password $PASSWORD
argocd account update-password --account mike --current-password $ADMIN_PASSWORD  --new-password $PASSWORD
```

## ProjectとRoleの作成

次にProjectの作成とroleの作成をセットで行う。なおコマンドでProjectやroleも[作成可能](https://argo-cd.readthedocs.io/en/stable/user-guide/projects/)だが、Manifestで設定した方が全体を見渡せやすいので、今回はManifestにて作成する。\
なお、Built-inのroleが[存在する](https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/)が、今回は利用しない（書き方の参考にはした）。\
その他書き方を参考にしたページは[これ](https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/argocd-rbac-cm.yaml)と[これ](https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/rbac.md)になる。

以下、Project:`game`のManifest適用手順となる。`book`に対しても同様に適用する（環境変数を`book`に変えて再実行する）。
```yaml
export APP_PROJECT=game
cat <<EOF > ./${APP_PROJECT}-project.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: ${APP_PROJECT}
  namespace: argocd
spec:
  clusterResourceWhitelist:
  - group: '*'
    kind: '*'
  destinations:
  - namespace: '*'
    server: '*'
  sourceRepos:
  - '*'
  roles:
  - name: guest
    description: "guest role"
    policies:
    - p, proj:${APP_PROJECT}:guest, applications, get, */*, allow
  - name: developer
    description: "developer role"
    policies:
    - p, proj:${APP_PROJECT}:developer, applications, create, ${APP_PROJECT}/*, allow
    - p, proj:${APP_PROJECT}:developer, applications, get, ${APP_PROJECT}/*, allow
    - p, proj:${APP_PROJECT}:developer, applications, update, ${APP_PROJECT}/*, allow
    - p, proj:${APP_PROJECT}:developer, applications, delete, ${APP_PROJECT}/*, allow
    - p, proj:${APP_PROJECT}:developer, applications, sync, ${APP_PROJECT}/*, allow
  - name: operator
    description: "operator role"
    policies:
    - p, proj:${APP_PROJECT}:operator, applications, create, */*, allow
    - p, proj:${APP_PROJECT}:operator, applications, get, */*, allow
    - p, proj:${APP_PROJECT}:operator, applications, update, */*, allow
    - p, proj:${APP_PROJECT}:operator, applications, delete, */*, allow
    - p, proj:${APP_PROJECT}:operator, applications, sync, */*, allow
EOF
kubectl apply -f ./${APP_PROJECT}-project.yaml
```
`clusterResourceWhitelist`、`destinations`、`sourceRepos`は今回チェックの対象外としている。必要な場合（クラスタやデプロイ先、利用するリポジトリに制限をかけたい場合）は
[こちら](https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup)を参照するとよい。

なお、`policies`で指定しているポリシーの意味は以下となる。
- ポリシールールを定義：
```
p, subject, resource, action, object, effect
```
例）
```
p, my-org:team-alpha, applications, sync, my-project/*, allow
```

- ユーザとロールをbinding：
```
g, subject, inherited-subject
```
例）
```
g, my-org:team-beta, role:admin
```

ここではbindingまでは含めておらず、ロールの定義のみ実施しているため、`g, xxxx`は記載していない。


適用後、argocd cliからProjectとRoleは以下のように見える。
```
$ argocd proj list
NAME         DESCRIPTION  DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
book                      *,*           *        */*                         <none>                        <none>          disabled
default                   *,*           *        */*                         <none>                        <none>          disabled
game                      *,*           *        */*                         <none>                        <none>          disabled
```
```
$ argocd proj role list game
ROLE-NAME  DESCRIPTION
guest      guest role
developer  developer role
operator   operator role
```
```
$ argocd proj role get game developer
Role Name:     developer
Description:   developer role
Policies:
p, proj:game:guest, projects, get, game, allow
p, proj:game:guest, applications, get, */*, allow
p, proj:game:developer, projects, get, game, allow
p, proj:game:developer, applications, create, game/*, allow
p, proj:game:developer, applications, get, game/*, allow
p, proj:game:developer, applications, update, game/*, allow
p, proj:game:developer, applications, delete, game/*, allow
p, proj:game:developer, applications, sync, game/*, allow
p, proj:game:operator, projects, get, game, allow
p, proj:game:operator, applications, create, */*, allow
p, proj:game:operator, applications, get, */*, allow
p, proj:game:operator, applications, update, */*, allow
p, proj:game:operator, applications, delete, */*, allow
p, proj:game:operator, applications, sync, */*, allow
JWT Tokens:
ID  ISSUED-AT  EXPIRES-AT
```

次に[オフィシャルサイトの記述](https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/#tying-it-all-together)を参考にユーザとロールをConfigMapを使ってbindingする。\
こちらもアカウント作成時と同様にConfigMapに指定があり、`argocd-rbac-cm`というConfigMapに`policy.csv`というdataフィールドに追加することでbindingする。
適用したManifestは以下となる。
```yaml
cat <<EOF > ./argocd-rbac-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  #policy.default: role:readonly
  policy.csv: |
    g, alice, proj:game:guest
    g, alice, proj:book:guest
    g, bob, proj:game:developer
    g, mike, proj:game:operator
    g, mike, proj:book:operator
EOF
kubectl apply -f ./argocd-rbac-cm.yaml
```

今回はroleは作成済みなので、policyの記載ではbindingに関するルールの`g, xxxx`の方のみ記載した。
また、defaultのpolicyは検証の邪魔になるためコメントアウトした。

## `kind: Application`の適用
先程作成したManifestをデプロイする`kind: Application`を作成する。\
以下、Project:`game`で動くアプリケーションとなる。Project:`book`については環境変数を変更して流し直す。
```yaml
export APP_PROJECT=game
cat <<EOF > ./${APP_PROJECT}-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ${APP_PROJECT}-app
  namespace: argocd
spec:
  destination:
    namespace: nginx-${APP_PROJECT}
    server: https://kubernetes.default.svc
  project: ${APP_PROJECT}
  source:
    path: ${APP_PROJECT}/
    repoURL: https://mygitlab.info/myapp/apps.git
    targetRevision: HEAD
  syncPolicy:
    syncOptions:
    - CreateNamespace=true
EOF
kubectl apply -f ./${APP_PROJECT}-app.yaml
```
AutoSyncを書き忘れていたので、手動でsyncする。
```
argocd app sync game-app
argocd app sync book-app
```
実行後は以下となる。
```
$ argocd app list
NAME      CLUSTER                         NAMESPACE   PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS  REPO                                  PATH   TARGET
book-app  https://kubernetes.default.svc  nginx-book  book     Synced  Healthy  <none>      <none>      https://mygitlab.info/myapp/apps.git  book/  HEAD
game-app  https://kubernetes.default.svc  nginx-game  game     Synced  Healthy  <none>      <none>      https://mygitlab.info/myapp/apps.git  game/  HEAD
```

## 検証
 作成したアカウントに適切に権限が設定されるかを確認する。\
冒頭に載せた表をアカウント名に置き換えたものを再掲する。

|     | Projectが同一    | Projectが異なる|
| --- | --- | --- |
|alice| Read|Read|
|bob|CRUD+Sync|なし|
|mike|CRUD+Sync|CRUD+Sync|

それぞれのアカウントでGUIからログインし、権限が適切に割り当たっていることを確認する。

### alice(ゲスト) 
それぞれのApplicationは表示される。
![1658740889288.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/deee90ff-dba8-506c-f707-5ee1f9d6add9.png)
Syncしようとすると"permission denied"のエラーが表示される。
![1658740924869.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/e84e3baf-d539-0ffd-9583-c3953faef052.png)


### bob(開発者)
画面上にはgameのProjectのApplicationしか表示されず、リストも取得できない。
![1658740731873.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/a8407189-fca6-6855-2cf5-f41225e2bf45.png)


Syncは成功する。

### mike(管理者)
画面上にはそれぞれのApplicationが表示される。
![1658741007472.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/6642f63d-cb6f-8ff7-b354-f895f00cb3df.png)
Syncはそれぞれ成功する。

## まとめ
設定がCLIから出来ないところがあるのと、設定ルールを学習する必要があること、またドキュメントが散らばっていて分かりづらいところがあるのがやや難ではあるが、最低限のアクセスコントロールが出来ることは確認できた。