CI/CD関連ツールのユーザー情報をLDAPで一元管理してみる

前回はSpring Security使ってLDAP認証できるよ〜というネタでしたが・・・今回は、CI/CDに関わるツール達（直接関係ない子もw）にログインするためのユーザー情報（認証情報）をLDAPで管理する方法をメモっておく。なお、各環境の構築にはDocker（Docker Compose）を使用した。

> **WARNING:**
>
> 本エントリーでは各ツールへアクセスする際にhttpを利用する前提にしていますが、パスワードなどの秘匿情報や個人情報を扱う可能性があるのでhttpsでアクセスするのが望ましい。（完全にクローズな環境で悪いことする人がいない前提ならhttpでもよいかもしれませんが・・・）

# 対象ツール

今回は・・・実案件で使う可能性が高い以下のツールでLDAP認証を試した。

| ツール名 | ツール概要 | 利用バージョン |
| ------ | -------- | ----------- |
| GitLab | Gitリポジトリのホスティングツール(GitHubのクローン)。「Issue管理」「マージリクエスト(GitHubで言う所のプルリクエスト)」「Wiki」などのGUI機能が提供されている。 | 10.1.5-ce.0 |
| Redmine | プロジェクト管理ツール。「タスク管理（チケット管理）」「進捗管理」「情報共有」などの機能が提供されており、Gitなどのバージョン管理システム（＋Gitのホスティングツール）と連携することもできる。 | 3.4.3 |
| Jenkins | CI管理ツール。プログラムの修正を検知して「テストの実施」「成果物(=リリース物、レポートなど)の生成」「成果物のリリース」を自動で行うための機能が提供されている。 | 2.95 |
| SonarQube | プログラムの品質管理ツール。プログラムの静的解析結果やテスト結果（カバレッジ）などのレポートを出力することができる。 | 6.7 |
| Nexus | ライブラリ(成果物)管理ツール。ソースコードから生成した成果物(Jar or War)を管理するための機能が提供されており、Maven Centralなどのパブリックリポジトリへのプロキシーとして使うことができる。 | 3.6.2 |
| Mattermost | チャットツール(Slackのクローン)。CIのビルド結果を専用のチャットルームに通知することで、CI結果を関係者に素早く知らせることができる。 | 4.5 |

> **NOTE:**
>
> 最近のGitLabはMattermostを同梱しているが、あえて同梱版を使用しない方法にした。もちろん同梱版でも同じことを実現することはできる。

# サンプル

本エントリーで作成・編集したファイルは、以下のGitHubリポジトリで公開済み。（Spring Bootアプリ用のMavenプロジェクト構成になっていますが、そこは気にせずスルーしてください！！）

* https://github.com/kazuki43zoo/cicd-demo/tree/authentication

# 管理イメージ

OpenLDAPでユーザ情報（認証情報）を管理し、各ツールの認証はLDAP経由を介して行う。Mattermost OSS版はLDAP認証をサポートしていないが、OAuth 2.0の認可コードグラントフローを利用したSSO機能を利用することで、MattermostのOSS版でもGitLabを介して（＝間接的に）LDAP認証を行うことができる。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/21022ed4-de8d-dd9d-8b13-3f506814662b.png)


# LDAPサーバ

OpenLdap 2.4.44を使用してLDAPサーバを作成し、以下のLDIFファイルをインポートして４つのグループと２人分のユーザー情報を作成する。

| ユーザ名 | パスワード | 氏名 | 所属するグループ |
| -------- | -------- | --------------- | ------ |
| kazuki | password | Kazuki Shimizu | admin, user, sonar-administrators, nx-admin |
| user | password | Taro Yamada | user |

```text:openldap/schema.ldif
version: 1

dn: ou=groups,dc=example,dc=com
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: cn=admin,ou=groups,dc=example,dc=com
cn: admin
objectclass: groupOfUniqueNames
objectclass: top
uniquemember: uid=kazuki,ou=people,dc=example,dc=com

dn: cn=sonar-administrators,ou=groups,dc=example,dc=com
cn: sonar-administrators
objectclass: groupOfUniqueNames
objectclass: top
uniquemember: uid=kazuki,ou=people,dc=example,dc=com

dn: cn=nx-admin,ou=groups,dc=example,dc=com
cn: nx-admin
objectclass: groupOfUniqueNames
objectclass: top
uniquemember: uid=kazuki,ou=people,dc=example,dc=com

dn: cn=user,ou=groups,dc=example,dc=com
cn: user
objectclass: groupOfUniqueNames
objectclass: top
uniquemember: uid=user,ou=people,dc=example,dc=com
uniquemember: uid=kazuki,ou=people,dc=example,dc=com

dn: ou=people,dc=example,dc=com
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=kazuki,ou=people,dc=example,dc=com
cn: Kazuki Shimizu
mail: kazuki@example.com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
sn: Shimizu
givenName: Kazuki
uid: kazuki
userpassword: {CRYPT}7pnoyta7lRz7M


dn: uid=user,ou=people,dc=example,dc=com
cn: Taro Yamada
mail: user@example.com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
sn: Yamada
givenName: Taro
uid: user
userpassword: {CRYPT}SjPCVdR9pFLD6
```

以下のようなDockerfileを作成し、本エントリー用のイメージをビルドしてコンテナを作成するようにした。

```dockerfile:openldap/Dockerfile
FROM osixia/openldap:1.1.11
# 設定ファイルを上書き
COPY schema.ldif /container/service/slapd/assets/config/bootstrap/ldif/custom/schema.ldif
```

> **NOTE:**
>
> /container/service/slapd/assets/config/bootstrap/ldif/custom/にldifファイルを格納しておくと、コンテナ作成時にLdifファイルをインポートすることができる。

LDAPの管理ツール(phpLDAPadmin)でLDAPの中を覗くと、以下のようなディレクトリ構成になっていることが確認できる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/5abb39d4-a206-c0bb-a9fd-c6b66d1636dd.png)

adminグループの状態は以下のような感じ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/56e31b55-a93a-1e49-6649-8e34aab5f858.png)

uid=kazukiの状態は以下のような感じ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/d6fdb978-af81-d90f-8645-b7fe5ae2e397.png)

# GitLab

GitLabでLDAP認証を行う場合は、`/etc/gitlab/gitlab.rb`を以下のように編集する。

* `gitlab_rails['ldap_enabled']`を`true`にする
* `gitlab_rails['ldap_servers']`にmainのLDAPサーバーを(YAML形式で)指定する

```rb:gitlab/gitlab.rb
### LDAP Settings
###! Docs: https://docs.gitlab.com/omnibus/settings/ldap.html
###! **Be careful not to break the indentation in the ldap_servers block. It is
###!   in yaml format and the spaces must be retained. Using tabs will not work.**

gitlab_rails['ldap_enabled'] = true

###! **remember to close this block with 'EOS' below**
gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
  main: # 'main' is the GitLab 'provider ID' of this LDAP server
    label: 'LDAP'
    host: 'ldap'
    port: 389
    uid: 'uid'
    bind_dn: 'cn=admin,dc=example,dc=com'
    password: 'password'
    encryption: 'plain' # "start_tls" or "simple_tls" or "plain"
    verify_certificates: true
    ca_file: ''
    ssl_version: ''
    active_directory: false
    allow_username_or_email_login: false
    block_auto_created_users: false
    base: 'ou=people,dc=example,dc=com'
    user_filter: ''
    attributes:
      username: ['uid', 'userid', 'sAMAccountName']
      email:    ['mail', 'email', 'userPrincipalName']
      name:       'cn'
      first_name: 'givenName'
      last_name:  'sn'
#     ## EE only
#     group_base: ''
#     admin_group: ''
#     sync_ssh_keys: false
#
#   secondary: # 'secondary' is the GitLab 'provider ID' of second LDAP server
#     label: 'LDAP'
#     host: '_your_ldap_server'
#     port: 389
#     uid: 'sAMAccountName'
#     bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
#     password: '_the_password_of_the_bind_user'
#     encryption: 'plain' # "start_tls" or "simple_tls" or "plain"
#     verify_certificates: true
#     ca_file: ''
#     ssl_version: ''
#     active_directory: true
#     allow_username_or_email_login: false
#     block_auto_created_users: false
#     base: ''
#     user_filter: ''
#     attributes:
#       username: ['uid', 'userid', 'sAMAccountName']
#       email:    ['mail', 'email', 'userPrincipalName']
#       name:       'cn'
#       first_name: 'givenName'
#       last_name:  'sn'
#     ## EE only
#     group_base: ''
#     admin_group: ''
#     sync_ssh_keys: false
EOS
```

> **NOTE:**
>
> コメントにも記載がありますが、YAML形式で指定するのでインデントは気をつける必要がある。

以下のようなDockerfileを作成し、本エントリー用のイメージをビルドしてコンテナを作成するようにした。

```dockerfile:gitlab/Dockerfile
FROM gitlab/gitlab-ce:10.1.5-ce.0
# 設定ファイルを上書き
COPY gitlab.rb /etc/gitlab/gitlab.rb
```

設定を反映した状態でGitLabを起動した後にサインイン画面を開くと、「LDAP」用のログインフォームが表示される。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/1458ef3f-c44d-e4cf-a18a-fe18939cbe1d.png)


# Redmine

RedmineでLDAP認証を行う場合は、

* 管理者ユーザでログイン
* トップメニューバーの「Administration」をクリック
* レフトメニューバーの「LDAP authentication」をクリック
* メイン表示領域右上の「New authentication mode」リンクをクリック

すると、以下のような入力画面が表示されるので、入力フォームにLDAP認証に必要な情報を入力して「Save」ボタンを押下する。
(画面キャプチャは登録後の編集画面)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/892836b3-b16d-6c33-db27-c0709e53b31f.png)

| 項目 | 設定値 |
| ---- | ----- |
| Name | Main LDAP Server |
| Host | ldap |
| Port | 389 |
| Account | cn=admin,dc=example,dc=com |
| Password | password |
| Base DN | ou=people,dc=example,dc=com |
| LDAP filter | |
| Timeout | |
| On-the-fly user creation | ON |
| Login attribute | uid |
| Firstname attribute | givenName |
| Lastname attribute | sn |
| Email attribute | mail |


# Jenkins

JenkinsでLDAP認証を行う場合は、

* Jenkins初期セットアップ時にLDAP Pluginをインストール(後からインストールすることも可能)
* 管理者ユーザでログイン
* レフトメニューバーの「Manage Jenkins」をクリック
* メニューから「Configure Global Security」を選択
* Access Control > Security Realmを「LDAP」に選択
* 「Advanced Server Configuration...」ボタンをクリック

すると、以下のような入力画面が表示されるので、入力フォームにLDAP認証に必要な情報を入力して「Save」ボタンを押下する。

> **NOTE:**
>
> 「Test LDAP settings」ボタンをクリックすると、設定内容が正しいか確認することができる。画面キャプチャでは、uid=kazukiを使って接続テストした後の状態になっている。



![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/7ba176f1-2452-feeb-22d8-983a306056a3.png)

| 項目 | 設定値 |
| ---- | ----- |
| Server | ldap |
| root DN | dc=example,dc=com |
| User search base | ou=people |
| User search filter | uid={0} |
| Group search base | ou=groups |
| Group search filter | |
| Group membership | Search for LDAP groups containing user |
| Group membership filter | (uniquemember={0}) |
| Manager DN | cn=admin,dc=example,dc=com |
| Manager Password | password |
| Display Name LDAP attribute | cn |
| Email Address LDAP attribute | mail |


# SonarQube

SonarQubeでLDAP認証を行う場合は、

* Sonar LDAP Pluginのインストール (wgetしてpluing用のディレクトリへ格納)
* `/opt/sonarqube/conf/sonar.properties`にLDAP認証情報の設定

を行う。

```properties:sonarqube/sonar.properties
# LDAP configuration
# General Configuration
sonar.security.realm=LDAP
ldap.url=ldap://ldap:389
ldap.bindDn=cn=admin,dc=example,dc=com
ldap.bindPassword=password

# User Configuration
ldap.user.baseDn=ou=people,dc=example,dc=com
ldap.user.request=(&(objectClass=inetOrgPerson)(uid={login}))
ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail

# Group Configuration
ldap.group.baseDn=ou=groups,dc=example,dc=com
ldap.group.request=(&(objectClass=groupOfUniqueNames)(uniquemember={dn}))
```

以下のようなDockerfileを作成し、本エントリー用のイメージをビルドしてコンテナを作成するようにした。

```dockerfile:sonarqube/Dockerfile
FROM sonarqube:6.7
# Pluginのインストール
RUN set -x \
    && wget -O /opt/sonarqube/extensions/plugins/sonar-ldap-plugin-2.2.0.608.jar "https://sonarsource.bintray.com/Distribution/sonar-ldap-plugin/sonar-ldap-plugin-2.2.0.608.jar"
# 設定ファイルを上書き
COPY sonar.properties /opt/sonarqube/conf/sonar.properties
```

# NEXUS

NEXUSでLDAP認証を行う場合は、

* 管理者ユーザでログイン
* トップメニューバーの設定アイコン（ツールチップ「Server administration and configuration 」）をクリック
* レフトメニューバーの「Security > LDAP」をクリック
* 「+ Create Connection」ボタンを押下

すると、以下の項目を設定するための入力画面が表示されるので、入力フォームにLDAP認証に必要な情報を入力する。
（画面キャプチャは登録後の編集画面）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/c5b3fb96-9924-fc91-9642-b527226daadc.png)

| 項目 | 設定値 |
| ---- | ----- |
| Name | Maon LDAP Server |
| LDAP server address | ldap://ldap:389 |
| Search base | dc=example,dc=com |
| Authentication method | Simple Authentication |
| Username or DN | cn=admin,dc=example,dc=com |
| Password | password |

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/57a7ae90-fafc-a1d3-f86c-9fbfa6a65cdf.png)

| 項目 | 設定値 |
| ---- | ----- |
| Base DN | ou=people |
| User subtree | ON |
| Object class | inetOrgPerson |
| User filter | |
| User ID attribute | uid |
| Real name attribute | cn |
| Email attribute | mail |
| Password attribute | |
| Map LDAP groups as roles | ON |
| Group type | Static Groups |
| Group base DN | ou=groups |
| Group subtree | ON |
| Group object class | groupOfUniqueNames |
| Group ID attribute | cn |
| Group member attribute | uniquemember |
| Group member format | uid=${username},ou=people,dc=example,dc=com |

「Verify user mapping」ボタンを押下すると、入力したLDAP認証情報が正しいか確認することができる。LDAP認証情報が正しいことが確認できたら「Save」ボタンを押下する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/6ddb4129-6aa3-aa47-37b3-5696545a2ae9.png)


# Mattermost

MattermostのOSSバージョンでは、残念ながらLDAP認証をサポートしていないが・・・
OAuth 2.0の認可コードグラントフローを利用したSSO機能がサポートされているので、MattermostのOSS版でもGitLabを介して（＝間接的に）LDAP認証を行うことができる。

ということで・・・
まずは、GitLabにMattermostをクライアントとして登録する。

具体的には、

* 管理者ユーザでログイン
* メニューからの「Configure GitLab」の「Configure」ボタンを押下
* レフトメニューバーから「Applications」を選択
* 「New application」ボタンをクリック

すると、以下の項目を設定するための入力画面が表示されるので、入力フォームに必要な情報を入力してMattermostをクライアントとして登録する。

| 項目 | 設定値 |
| --- | ------ |
| Name | Mattermost |
| Redirect URI | http://localhost:18065/login/gitlab/complete<br>http://localhost:18065/signup/gitlab/complete |

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/e6c0f0c2-e829-6d44-591d-61003efad1a1.png)

Mattermost側でGitLabとのSSO機能を有効にするためには、`/mm/mattermost/config/config_docker.json`を以下のように編集する必要がある。

```json:mattermost/config_docker.json
{
    "ServiceSettings": {
        "SiteURL": "http://localhost:18065"
    },
    "GitLabSettings": {
        "Enable": true,
        "Secret": "9c15d9a31f71eb671205d1735c168a042f081817c575fa5899b98089dc08a01c",
        "Id": "ae434f150981fa03da073fbe747fe3efcede25289bcc2b4d7765bb58c4686e5e",
        "Scope": "",
        "AuthEndpoint": "http://localhost/oauth/authorize",
        "TokenEndpoint": "http://gitlab/oauth/token",
        "UserApiEndpoint": "http://gitlab/api/v4/user"
    }
}
```

> **NOTE:**
>
> 上記の設定例では説明に必要なところ（デフォルト値を変更する必要があるところ）だけ抜き出したものなので、そのままコピーしてファイルを上書きすると期待通りの動きにならない可能性がある事を補足しておきます。


| 項目 | 設定値 | 補足 |
| --- | ----- | ---- |
| ServiceSettings > SiteURL | http://localhost:18065 | Mattermost自身のサイトを開くためのURLを指定する。GitLab(認可エンドポイント)からMattermostへ戻るための（リダイレクトするための）URLを生成する際にこの設定値を参照している。 |
| GitLabSettings > Enable | true | GitLabとのSSO機能を有効化する。 |
| GitLabSettings > Secret | {クライアントシークレット} | アプリケーション登録時に生成されたクライアントシークレットを指定する。 |
| GitLabSettings > Id | {クライアントID} | アプリケーション登録時に生成されたクライアントIDを指定する。 |
| GitLabSettings > Scope |　 | SSO時にアクセスを許可してもらうスコープを指定する。（本例では未指定だが適切な値を指定するようにする） |
| GitLabSettings > AuthEndpoint | http://localhost/oauth/authorize | GitLabの認可エンドポイント（認可コードを取得するための）URL |
| GitLabSettings > TokenEndpoint | http://gitlab/oauth/token | GitLabのトークンエンドポイント（アクセストークンを取得するための）URL |
| GitLabSettings > UserApiEndpoint | http://localhost/oauth/authorize | GitLabのユーザー情報エンドポイント（ユーザー情報を取得するための）URL |

> **WARNING**
>
> 上記の設定例では、認可サーバ(GitLab）提供のエンドポイントにアクセスするためのURLがhttpになっているが、OAuthの仕様的にはhttps（=通信の暗号化）にする必要である事を補足しておく。 


以下のようなDockerfileを作成し、本エントリー用のイメージをビルドしてコンテナを作成するようにした。

```dockerfile:mattermost/Dockerfile
FROM mattermost/mattermost-preview:4.5
# 設定ファイルを上書き
COPY config_docker.json /mm/mattermost/config/config_docker.json
```

設定を反映した状態でMattermostを起動した後にサインイン画面を開くと、「GitLabとのSSO」用のリンクが表示される。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/2b1e269b-1d8c-bc8d-afed-bbda177c0108.png)

SSO用のリンクを押すと、（GitLabに未ログイン状態であれば）GitLabのログイン画面が表示されるので、LDAPで管理しているユーザーでログインすると・・・・

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/2967e916-57e9-956c-63f6-2047f41a9db1.png)

GitLabで管理しているユーザー情報（＝実態はLDAPで管理しているユーザー情報）へアクセスするための認可を求める画面が表示される。ここで「Authorize」ボタンを押下すれば、MattermostがGitLabのAPIを介してユーザー情報を取得して認証処理を行ないログイン状態になる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/80bda0cb-88c8-eb28-225b-8809f0bb35f5.png)

認証に成功しログイン状態になると、（初回アクセスであれば）以下のような画面が表示される。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/0364f979-508d-5dd8-c3a5-6024a7c77f2b.png)

# ツール達の起動（docker-compose.xml）

ツール達の起動は、以下のような`docker-compose.xml`を作成してdocker-composeの機能を使用した。

```yaml:docker-compose.yml
version: '2'

services:
  # LDAP
  openldap:
    build: ./openldap
    container_name: cicd-openldap
    environment:
      LDAP_ORGANISATION: "example"
      LDAP_DOMAIN: "example.com"
      LDAP_ADMIN_PASSWORD: "password"
    ports:
      - "10389:389"
  # LDAP Management Tool
  phpldapadmin:
    image: osixia/phpldapadmin:0.7.1
    container_name: cicd-phpldapadmin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "ldap"
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "10080:80"
    links:
      - "openldap:ldap"
  # VCS(SCM)
  gitlab:
    build: ./gitlab
    container_name: cicd-gitlab
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://localhost'
    ports:
      - '80:80'
      - '433:433'
    links:
      - "openldap:ldap"
  # Issue Tracker
  redmine:
    image: redmine:3.4.3
    container_name: cicd-redmine
    ports:
      - '13000:3000'
    links:
      - "openldap:ldap"
  # Chat
  mattermost:
    build: ./mattermost
    container_name: cicd-mattermost
    ports:
      - '18065:8065'
    links:
      - "gitlab:scm"
  # CI Management
  jenkins:
    image: jenkins/jenkins:2.95
    container_name: cicd-jenkins
    ports:
      - '18080:8080'
      - '50000:50000'
    links:
      - "openldap:ldap"
  # Repository Management
  nexus:
    image: sonatype/nexus3:3.6.2
    container_name: cicd-nexus
    ports:
      - '18081:8081'
    links:
      - "openldap:ldap"
  # Quality Management(& Report)
  sonarqube:
    build: ./sonarqube
    container_name: cicd-sonarqube
    ports:
      - '19000:9000'
    links:
      - "openldap:ldap"

```

> **NOTE:**
>
> （原因は解明していませんが・・・）再起動を繰り返すと、たまにMattermostのコンテナが起動しないことがあった。今回はLDAP環境構築が目的なので・・・コンテナを作りなおしてエラーを回避した。

# 参考サイト

* GitLab : https://docs.gitlab.com/ce/administration/auth/ldap.html
* Redmine : http://guide.redmine.jp/RedmineLDAP/
* Jenkins (Plugin) : https://wiki.jenkins.io/display/JENKINS/LDAP+Plugin
* NEXUS : https://books.sonatype.com/nexus-book/3.5/reference/security.html#ldap
* SonarQube (Plugin) : https://docs.sonarqube.org/display/PLUG/LDAP+Plugin
* Mattermost : https://docs.mattermost.com/deployment/sso-gitlab.html