【2019年11月版】Concourse CI を Keycloak で SSO

# Concourse CI のログインは Keycloak で SSO したい

社内プロジェクトの CI に [Concourse CI](https://concourse-ci.org/) どうだろう？と試してみましたが、なかなか好感触だったのでもうちょっとしっかりと使いこんでみようかな、ということで [Concourse CI の豊富なログイン方式](https://concourse-ci.org/configuring-auth.html)を備えているので Keycloak と SSO をやってみたいと思います。
とは言え、Concourse CIもKeycloakも豊富な接続方式を持っていますので・・・まずはシンプルにOpenID Connect での SSO に挑戦してみたいと思います！

以下の Concourse CI の OpenID Connect のページを参考に、手順をご説明いたします。

- [Generic OIDC auth](https://concourse-ci.org/generic-oidc-auth.html)

## 1. Concourse CI、Keycloak の準備

今回も docker-compose でサクッと立ち上げてしまいます。

本家サイトの"Quick Start"にある `docker-compose.yml`をダウンロードし、サービス："keycloak" を加えました。

```yml:docker-compose.yml
version: '3'

services:
  concourse-db:
    image: postgres
    environment:
      POSTGRES_DB: concourse
      POSTGRES_PASSWORD: concourse_pass
      POSTGRES_USER: concourse_user
      PGDATA: /database

  concourse:
    image: concourse/concourse
    command: quickstart
    privileged: true
    depends_on: [concourse-db,keycloak]
    ports: ["8080:8080"]
    environment:
      CONCOURSE_POSTGRES_HOST: concourse-db
      CONCOURSE_POSTGRES_USER: concourse_user
      CONCOURSE_POSTGRES_PASSWORD: concourse_pass
      CONCOURSE_POSTGRES_DATABASE: concourse
      CONCOURSE_EXTERNAL_URL: http://cc.10.143.60.16.xip.io:8080/
      CONCOURSE_ADD_LOCAL_USER: test:test
      CONCOURSE_MAIN_TEAM_LOCAL_USER: test
      CONCOURSE_WORKER_BAGGAGECLAIM_DRIVER: overlay

  keycloak:
    image: jboss/keycloak:7.0.1
    container_name: keycloak
    restart: always
    ports:
      - 8180:8180
    environment:
      - KEYCLOAK_USER=kcadmin
      - KEYCLOAK_PASSWORD=kcadmin
    command: >
      -b 0.0.0.0
      -Djboss.http.port=8180
    tty: true
```

`CONCOURSE_EXTERNAL_URL` に [ワイルドカードDNSのxip.io](http://xip.io/) を使ってローカルアドレスに無理やりドメイン名を与えてしまいます。`cc.10.143.60.16.xip.io`というドメインから`10.143.60.16` を解決してくれるありがたいサービスです。
このIPアドレスは実行時のホストのIPアドレスに差し替えてください。

これを `docker-compose up -d` で立ち上げます。

```shell
$ docker-compose up -d
```
はい、サーバーの起動は以上です!

ちなみに keycloak は 8.0.0 がリリースされていますが、`jboss/keycloak:8.0.0.` はエントリーポイントのスクリプトのエラーで立ち上がりませんでした。。。何か、設定が変わっているのでしょう・・・

## 2. Keycloak でレルム定義

続いて OpenID Connect 用のレルム定義を、Keycloak で行います。

以下の記事などを参考に、クライアントの"Access Type"が"Credential"で繋がるように設定を行います。

- [KeycloakでOpenID Connectを使ってシングルサインオンをしてみる（認可コードフロー(Authorization Code Flow)編）](https://qiita.com/rawr/items/d4f45e094c39ef43cbdf)

### 2-1. レルムの追加

新規のレルムとして `concourse-ci` としてレルムを追加します。この名前は適当で大丈夫です。

作成後の詳細画面でエンドポイントが確認できます。今回は `issuer` のエントリポイントのみを使用します。
また他のログインに対応するにはここからエンドポイントのURLをコピペしてください。

### 2-2. クライアントの追加

クライアントの新規追加から `concourse-ci` を追加し、"アクセスタイプ" を`confidential` に変更します。

ここでリダイレクト先のURLにはConcourseのURL + "/sky/issuer/callback"を宛先とします。

今回の場合は、"http://cc.10.143.60.16.xip.io:8080/sky/issuer/callback" となります。

設定が完了したら、"save" ボタンをクリックしてください。

### 2-3. 認証キーの取得

アクセスタイプを"confidential"に変更したことで、"Credential"のタブが表示されるようになったはずです。

"Credential"のタブをクリックして認証キーをコピペしておきましょう。

### 2-4. ロールの追加

クライアント`concourse`のロールということで、今回は`user`ロールを追加しましょう。

### 2-5. マッパーの追加と編集

`Mapper` タブから"Add builtin"をクリックします。
Mapperの一覧が表示されるので、"client roles" と "username" を選択し、"Add Selected" をクリックでマッパーを追加します

![Keycloak Admin Console_Add_Mapper.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/1f7f6735-7f67-7b4d-5dc3-a9869c7c0188.png)

続いて、"username"の"Token Claim Name"を "username"に変更します。
![Keycloak Admin Console_Edit_Username_Mapper.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/d55beb62-86df-579a-8d25-49175eed5cbf.png)

続いて、"client roles"の"Client ID"にconcourse-ci"とし、"Token Claim Name"を "groups"に変更します。
![Keycloak Admin Console_Edit_ClientRole_Mapper.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/385e8caf-50bb-78cf-96f1-eafbbf4f7dc7.png)
また、ID Token、Access Token、Userinfoのどれにも追加されるようにADDのチェックをそれぞれ入れます。

### 2-6. ユーザーの追加とロールのアサイン

今回は "cc001" というユーザを追加し、`concourse-ci`のクライアントロール `user` にアサインいたしました。

Keycloak での作業は以上です。

## 3. Concourse の環境変数変更

Concouse CI が起動するときの環境変数で、Keycloak を OIDC サーバーとして参照するように設定します。

以下のように docker-compose.yml に環境変数を追加します。

```
...
  concourse:
    image: concourse/concourse
...
    environment:
      CONCOURSE_POSTGRES_HOST: concourse-db
      CONCOURSE_POSTGRES_USER: concourse_user
      CONCOURSE_POSTGRES_PASSWORD: concourse_pass
      CONCOURSE_POSTGRES_DATABASE: concourse
      CONCOURSE_EXTERNAL_URL: http://cc.10.143.60.16.xip.io:8080/
      CONCOURSE_ADD_LOCAL_USER: test:test
      CONCOURSE_MAIN_TEAM_LOCAL_USER: test
      CONCOURSE_WORKER_BAGGAGECLAIM_DRIVER: overlay
      CONCOURSE_OIDC_DISPLAY_NAME: ConcourseCI
      CONCOURSE_OIDC_CLIENT_ID: concourse-ci
      CONCOURSE_OIDC_CLIENT_SECRET: xxxxxxxx-xxxx-xxxx-xxx-xxxxxxxxxx
      CONCOURSE_OIDC_ISSUER: http://10.145.61.6.xip.io:8180/auth/realms/concourse-ci
...
```
まず、`CONCOURSE_OIDC_DISPLAY_NAME` はログイン画面で表示されるOIDCでログインする際のボタンのラベルです。これはわかりやすければなんでもOKかと思います。

続いて`CONCOURSE_OIDC_CLIENT_ID`と、`CONCOURSE_OIDC_CLIENT_SECRET`はKeycloakで追加したクライアント`concourse-ci`のクライアントIDと、"Credential"のタブで表示されていた認証キーです。

最後の`CONCOURSE_OIDC_ISSUER`が`issuer` のエントリポイントのURLとなります。

docker-compose.ymlの修正後、再度 `docker-compose up -d`を実行してコンテナを再作成させてください。

`docker-compose ps` などで concourse コンテナが止まっていた場合は `docker-compose logs concourse`などでエラーメッセージをチェックしてみてください。
`http://10.145.61.6.xip.io`にアクセスできないエラーが出ている場合は、`nslookup`を一度実行すれば、ちゃんと名前解決できるようになります。

## 4. チームの追加

Keycloak でログインするユーザーを受け付ける"チーム"を作成します。

まず以下のコマンドで `test` ユーザーでログインします。

```shell
$ fly login -t hello -u test -p test -c http://localhost:8080
logging in to team 'main'


target saved
```

続いて、Keycloakで設定したロール`user`に`myAppDevTeam`を割り当ててみます。

```shell
$ fly -t hello set-team -n myAppDevTeam --oidc-group user
setting team: myAppDevTeam

role owner:
  users:
  - local:test

  groups:
    none

apply team configuration? [yN]: y
team created
```

これで、`user`ロールでログインしたユーザーは`myAppDevTeam`チームに配属されることとなりました。

ちなみに、環境変数 `CONCOURSE_MAIN_TEAM_OIDC_GROUP` で特定のロールを丸ごと`main`チームに割り当てることも可能です。

```yml:docker-compose.yml
  concourse:
...
    environment:
...
      CONCOURSE_MAIN_TEAM_OIDC_GROUP: admin
...
```
などと設定すると、Keycloak で `admin`ロールにアサインしたユーザーは、自動的に`main`チームに参加いたします。

## 5. ログイン

いよいよログインの時です！

ブラウザで `http://cc.ホストのIPアドレス.xip.io:8080/` を開き `login here` からログイン画面へ移動してください。
![Concourse_Login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/0accc09d-20c7-0e84-3af3-cc10976f77c6.png)
`CONCOURSE_OIDC_DISPLAY_NAME`で設定した "ConcourseCI" ボタンが表示されているかと思います。~~(今更ですが、命名間違えたな・・・)~~
ここをクリックするとKeycloakのログイン画面へ移ります。
![KeyCloak_Log in to concourse-ci.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/4db558aa-e5b6-7d30-5e03-ddbc49a9e759.png)
はい、ここで `cc001` ユーザーでログインします。
![Password Reset Log in to concourse-ci.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/843c047a-76d8-c439-65c3-27f124c3c129.png)
初回はパスワードリセットが走ると思います。うまくいくと・・・
![LoginSuccess_Dashboard - Concourse.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/11010277-52f3-d351-68ef-d8d5532bdece.png)
`cc001`ユーザーが無事に`myAppDevTeam`に参加できました！

## 6. `fly` でログイン

Concourse CI は基本的な操作を `fly` コマンドから行います。つまり、`fly`コマンドでもログイン必要、ですよね・・・？

という場合もOKです。以下のように `fly login` が使えます。(ブラウザの方からは一旦ログアウトしておいたほうがいいかもしれません。)

```shell
$ fly -t hello login -n myAppDevTeam -c http://cc.10.4.97.184.xip.io:8080/
logging in to team 'myAppDevTeam'

navigate to the following URL in your browser:

  http://cc.10.4.97.184.xip.io:8080/login?fly_port=58077

or enter token manually: 
```

で、表示されるURLをブラウザから開くと先ほどと同様にログイン方法の選択からログインの流れが行えて・・・
![Fly Login Success- Concourse.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/36d68557-5c7f-1293-ae0b-93a92489d2f9.png)
とブラウザに表示されます。
ターミナルの方では、

```shell
...
or enter token manually: 
target saved
```
と表示され、keycloakでユーザーがログインした状態が`-t hello`に保持され、これ以降は、`cc001`
ユーザーとして `fly` コマンドが利用可能になります！

## まとめ

Concourse CI は本体のユーザー管理機能がほとんど使えない代わりに非常に接続方法が充実しています。
Concourse CI にもロールやグループといった概念は(編集機能がCLIしかない割には)ガッツリ実装されているので、そのマッピングをちゃんとやらなければなりませんが、LDAPやSAMLも使えるのでかなり企業向けの機能もしっかりしているかと思います。

本記事で作成した`docker-compose.yml`は以下のリポジトリに上げました。ご参考にどうぞ。

- [concourse-smee-ci | github.com](https://github.com/Yoshinori-Koide-PRO/concourse-smee-ci)

Concourse CI ネタ、続きます！w