Shibboleth IdPをAnsibleでインストールしてみた

# はじめに
インフラの構築は練り上げた手順書を使い、チェックシートを持った担当者に見守られながら、コンソールに指差し確認をして１つ１つコマンドを打ち込んでいくのが、古き良き時代の習わしでした。
しかし、近代化の波はDevOptsという造語とともにやってきて、私を含めたおじさんエンジニアは翻弄される今日この頃です。

ちょっと、冗談めいた出だしでしたが我が社でも少しずつツールを利用しながら、何度も繰り返す作業は効率のよい方法に置き換えていくことを進めています。
Ansibleはそのひとつで、弊社のプロダクトもひととおり自動で構築できるような体制が整っています。

今回は弊社製品ではないのですが、シングルサインオンに関わるプロダクトの１つであるShibboleth IdPをインストールするyamlの記述を実際に解説していこうと思います。

# Ansibleのディレクトリ構成
Shibbolethの処理を説明する前にAnsibleのディレクトリ構成ですが、以下のように用意しました。

![qiita-ansible-tree.png](https://qiita-image-store.s3.amazonaws.com/0/316107/7767ca9b-bff1-0cbe-d3dd-3e5ce04265b0.png)

inventoriesは変数の定義したファイルを保存します。
localは環境を指していて、stagingやproductionなど状況に応じて環境別の設定が出来るようにしています。

group_varsはサーバ分類ごとのグループを指しています。
今回は「shibboleth-idp_groups」だけです。
このファイルにShibboleth IdPに関するほとんどの変数を定義しています。

host_varsはサーバホストごとの変数です。
今回は「shibb1」というホスト名で1台のみですが、冗長化する場合など複数台構成時にこのホストはマスターでこのホストはスレーブだというような振る舞いを変更したい場合に変数を定義したりします。

kvmはAnsibleと特に関係ありません。私がkvm上で仮想マシンを作成するためのファイル等を保存しています。

rolesが実際のサーバグループの構築処理が記載されています。
commonはサーバ共通で用意している処理ですが、内容は割愛します。
hostsファイルや時刻同期、ファイアウォールなどの設定を通常は記載しています。

shibboleth-idpが実際の構築処理ですが、細分化しています。
install: Shibboleth IdPのパッケージインストール
setup-idp: Shibboleth IdPのセットアップ
setup-mariadb: MariaDBのセットアップ
setup-middle: ミドルウェアのセットアップ
各ディレクトリにfilesやtemplatesがありますが、実際に格納されているファイルの表示は割愛しています。処理内容の詳細を見て準備してください。

ディレクトリ直下のファイルですが、
ansible.cfgがAnsibleの環境設定です。
README.mdはこのAnsible Playbookの内容説明です。
shibboleth-idp.ymlがrolesの実行手順が記載されています。

# Shibboleth IdPの構築手順
では、実際に処理の記載方法を見ていきますしょう。

まず、Ansibleの実行コマンドは以下の通りです。

```
 ansible-playbook -i inventories/local shibboleth-idp.yml
```
inventories/localの変数定義を使ってshibboleth-idp.ymlを実行します。
shibboleth-idp.ymlの内容は以下の通りです。

```
- hosts: shibboleth-idp_groups
  roles:
    - common
    - shibboleth-idp/install
    - shibboleth-idp/setup-middle
    - shibboleth-idp/setup-mariadb
    - shibboleth-idp/setup-idp
```

hostsはshibboleth-idp_groupsのグループ定義を使って、
roles以下の処理を順番に実行するという内容です。
次にhostsファイルの内容を見てみます。

```
[shibboleth-idp_groups]
shibb1 ansible_host=10.0.119.131

[local:children]
shibboleth-idp_groups

[local:vars]
domain=test.osstech.co.jp
env=local
project_name=Ansible_Template

```
[shibboleth-idp_groups]にはshibb1というホスト名で10.0.119.131のIPアドレスである記載があります。複数台のときはホスト行を増やして記載します。
[local:children]にはlocal環境にshibboleth-idp_groupsが属していることを記載します。
[local:vars]はlocal環境共通で利用する変数を記載します。

次にshibboleth-idp/installのロール処理です。

```
- name: Install Apache Java Tomcat rsync
  yum:
    name: "{{item}}"
    state: latest
  with_items:
    - httpd
    - mod_ssl
    - java-1.8.0-openjdk
    - jakarta-taglibs-standard
    - tomcat
    - rsync
  tags:
    - install-packages

- name: Send Shibboleth IdP Packages
  copy:
    src: "{{product_version.shibboleth_idp}}.tar.gz"
    dest: "{{workdir}}/{{product_version.shibboleth_idp}}.tar.gz"
    mode: "0644"
  tags:
    - install-packages

- name: Send MySQL Driver Packages
  copy:
    src: "{{product_version.mysql}}.tar.gz"
    dest: "{{workdir}}/{{product_version.mysql}}.tar.gz"
    mode: "0644"
  tags:
    - install-packages

- name: Extract Shibboleth IdP Packages
  shell: >
    cd {{workdir}} && tar xvf "{{product_version.shibboleth_idp}}.tar.gz";
  tags:
    - install-packages

- name: Jakarta taglibs link
  shell: >
    ln -s /usr/share/java/jakarta-taglibs-core.jar /usr/share/tomcat/lib/jakarta-taglibs-core.jar;
    ln -s /usr/share/java/jakarta-taglibs-standard.jar /usr/share/tomcat/lib/jakarta-taglibs-standard.jar;
  register: taglibs_link
  failed_when: not "'File exists' in taglibs_link.strerr"
  tags:
    - install-packages

- name: Install MariaDB
  yum:
    name: "{{item}}"
    state: latest
  with_items:
    - mariadb
    - mariadb-server

- name: Extract MySQL Driver Packages
  shell: >
    cd {{workdir}} && tar xvf "{{product_version.mysql}}.tar.gz";
  tags:
    - install-packages

- name: Deploy MySQL Driver
  shell: >
     cp "{{workdir}}/{{product_version.mysql}}/{{product_version.mysql}}-bin.jar" "{{ tomcat.sharelibdir }}/";
     chown tomcat:tomcat "{{ tomcat.sharelibdir }}/{{product_version.mysql}}-bin.jar";
  tags:
    - install-packages
```
内容はyumモジュールでApahce,Java,Tomcatなどのインストールをして、
shellモジュールでShibboleth IdP、MySQLコネクタのjavaドライバを送信してインストールしています。

次にshibboleth-idp/setup-middleのロール処理です。

```

- name: Check httpd.conf
  stat: path=/etc/httpd/conf/httpd.conf.org
  register: httpd_conf
- name: Backup httpd.conf
  shell: mv /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
  when: httpd_conf.stat.md5 is not defined

- name: Check conf.d
  stat: path=/etc/httpd/conf.d.org
  register: httpd_confd
- name: Backup conf.d
  shell: mv /etc/httpd/conf.d /etc/httpd/conf.d.org
  when: not httpd_confd.stat.exists

- name: Mkdir conf.d
  file: path=/etc/httpd/conf.d state=directory owner=root group=root mode=0755

- name: Deploy httpd.conf
  template:
    src: httpd.conf.j2
    dest: /etc/httpd/conf/httpd.conf
    mode: "0644"
    owner: "root"
    group: "root"

- name: Deploy shibboleth.conf
  template:
    src: shibboleth.conf.j2
    dest: /etc/httpd/conf.d/shibboleth.conf
    mode: "0644"
    owner: "root"
    group: "root"

- name: Deploy ssl.conf
  template:
    src: ssl.conf.j2
    dest: /etc/httpd/conf.d/ssl.conf
    mode: "0644"
    owner: "root"
    group: "root"

- name: Change tomcat.conf
  shell: >
    sed -i '$ a \\nCATALINA_OPTS="-Xmx2048M -Xms2048M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M"' /usr/share/tomcat/conf/tomcat.conf;
  register: change_tomcat_conf
  changed_when: false
- name: Change tomcat.conf Result
  debug: var=change_tomcat_conf.stdout_lines

- name: Deploy LDAP Certificate
  copy:
    src: "{{certificate.ldap_cacertfile}}"
    dest: "{{certificate.ca_anchors_dir}}/"
    mode: "0644"
    owner: root
    group: root

- name: LDAP Certificate to OS
  shell: >
    update-ca-trust extract;

- name: Deploy server.xml
  template:
    src: server.xml.j2
    dest: /usr/share/tomcat/conf/server.xml
    mode: "0644"
    owner: "root"
    group: "tomcat"

- name: Change context.xml
  shell: >
    sed -i /usr/share/tomcat/conf/context.xml -e 's/<Context>/<Context useHttpOnly="true">/';
  register: change_context_xml
  changed_when: false
- name: Change context.xml Result
  debug: var=change_context_xml.stdout_lines

- name: Stop Tomcat Logrotate
  shell: >
    sed -i /usr/share/tomcat/conf/logging.properties -e 's/1catalina.org.apache.juli.FileHandler.prefix = catalina./1catalina.org.apache.juli.FileHandler.prefix = catalina.\n1catalina.org.apache.juli.FileHandler.rotatable = false/';
    sed -i /usr/share/tomcat/conf/logging.properties -e 's/2localhost.org.apache.juli.FileHandler.prefix = localhost./2localhost.org.apache.juli.FileHandler.prefix = localhost.\n2localhost.org.apache.juli.FileHandler.rotatable = false/';
    sed -i /usr/share/tomcat/conf/logging.properties -e 's/3manager.org.apache.juli.FileHandler.prefix = manager./3manager.org.apache.juli.FileHandler.prefix = manager.\n3manager.org.apache.juli.FileHandler.rotatable = false/';
    sed -i /usr/share/tomcat/conf/logging.properties -e 's/4host-manager.org.apache.juli.FileHandler.prefix = host-manager./4host-manager.org.apache.juli.FileHandler.prefix = host-manager.\n4host-manager.org.apache.juli.FileHandler.rotatable = false/';
    cat /usr/share/tomcat/conf/logging.properties;
  register: stop_tomcat_logrotate
  changed_when: false
- name: Stop Tomcat Logrotate Result
  debug: var=stop_tomcat_logrotate.stdout_lines

- name: Change Logrotate
  shell: >
    sed -i /etc/logrotate.d/tomcat -e 's/rotate 52/rotate 30/';
    sed -i /etc/logrotate.d/tomcat -e 's/weekly/daily/';
    cat /etc/logrotate.d/tomcat;
  register: change_logrotate
  changed_when: false
- name: Change Logrotate Result
  debug: var=change_logrotate.stdout_lines

- name: Deploy httpd Logrotate
  template:
    src: httpd.j2
    dest: /etc/logrotate.d/httpd
    mode: "0644"
    owner: "root"
    group: "root"

- name: Tomcat Start and AutoRun
  service: name=tomcat state=started enabled=yes

- name: Apache Start and AutoRun
  service: name=httpd state=started enabled=yes

- name: Mariadb Start and AutoRun
  service: name=mariadb state=started enabled=yes
```
内容はApacheやTomcatの設定ファイルの配置や変更をして、各サービスを起動しています。

次にshibboleth-idp/setup-mariadbのロール処理です。

```
- name: Deploy SQL
  template:
    src: "{{ item }}"
    dest: "{{ ansible_work }}/"
    mode: "0644"
    owner: root
    group: root
  with_items:
    - add_priv.sql
    - add_db.sql
    - SService_table.sql
    - SId_table.sql

- name: Execute SQL
  shell: >
    mysql < {{ ansible_work }}/add_priv.sql;
    mysql  -u{{ mysql.admin_id }} -p{{ mysql.admin_password }} < {{ ansible_work }}/add_db.sql;
    mysql StorageService -u{{ mysql.admin_id }} -p{{ mysql.admin_password }} < {{ ansible_work }}/StorageService_table.sql;
    mysql StoredID -u{{ mysql.admin_id }} -p{{ mysql.admin_password }} < {{ ansible_work }}/StoredId_table.sql;
```
内容はMariaDBのデータベースやテーブル作成、また管理者ユーザーの設定です。

次にshibboleth-idp/setup-idpのロール処理です。

```
- name: Deploy build.xml
  template:
    src: build.xml.j2
    dest: "{{workdir}}/{{product_version.shibboleth_idp}}/bin/build.xml"
    backup: yes
    mode: "0644"
    owner: "root"
    group: "root"

- name: Install Shibboleth IdP
  shell: >
    cd {{workdir}}/{{product_version.shibboleth_idp}}/bin;
    export JAVA_HOME="/usr/lib/jvm/jre-1.8.0";
    ./install.sh \
     -Didp.src.dir={{workdir}}/{{product_version.shibboleth_idp}} \
     -Didp.target.dir=/opt/shibboleth-idp \
     -Didp.host.name={{shibboleth.site.fqdn}} \
     -Didp.scope={{domain}} \
     -Didp.sealer.password={{ shibboleth.sealer.keyPassword }} \
     -Didp.keystore.password={{ shibboleth.sealer.storePassword }} \
     -Didp.noprompt=true;

- name: Shibboleth IdP WAR deploy
  copy:
    remote_src: true
    src: "{{shibboleth.deploy_dir}}/war/idp.war"
    dest: /usr/share/tomcat/webapps/idp.war
    mode: "0644"
    owner: "tomcat"
    group: "tomcat"

- name: Shibboleth IdP Dir Permission
  shell: >
      chown -Rh tomcat:tomcat {{shibboleth.deploy_dir}};

- name: Shibboleth IdP Log Dir
  shell: >
      rmdir {{shibboleth.deploy_dir}}/logs;
      mkdir /var/log/shibboleth;
      chown tomcat:root /var/log/shibboleth;
      chmod 770 /var/log/shibboleth;
      ln -s /var/log/shibboleth {{shibboleth.deploy_dir}}/logs;
  failed_when: not "'File exists' in res.stderr"

- name: Deploy Shibboleth Logrotate
  template:
    src: shibboleth.j2
    dest: /etc/logrotate.d/shibboleth
    mode: "0644"
    owner: "root"
    group: "root"

- name: Check Certificate
  stat: path="{{shibboleth.deploy_dir}}/credentials/{{certificate.certfile}}"
  register: idpcert_file

- name: Shibboleth IdP Metadata Certificate
  shell: >
    cd {{shibboleth.deploy_dir}}/credentials;
    openssl req -new -x509 -out {{certificate.certfile}} -subj "{{certificate.subject}}" -days {{certificate.validdays}} -newkey rsa:2048 -keyout {{certificate.privatefile}} -nodes -sha512;
    chmod 440 {{shibboleth.deploy_dir}}/credentials/{{certificate.certfile}};
    chmod 440 {{shibboleth.deploy_dir}}/credentials/{{certificate.privatefile}};
    chown root:tomcat {{shibboleth.deploy_dir}}/credentials/{{certificate.certfile}};
    chown root:tomcat {{shibboleth.deploy_dir}}/credentials/{{certificate.privatefile}};
  when: [ idpcert_file.stat.md5 is not defined,
          node == "master" ]

- name: Download Certificate
  synchronize:
    mode: pull
    src: "{{ item }}"
    dest: ./files/
  with_items:
    - "{{shibboleth.deploy_dir}}/credentials/{{certificate.certfile}}"
  when: [ idpcert_file.stat.md5 is not defined,
          node == "master" ]

- name: Deploy Certificate to Slave
  copy:
    src: "{{ item }}"
    dest: "{{shibboleth.deploy_dir}}/credentials/"
    mode: "0440"
    owner: root
    group: tomcat
  with_items:
    - "{{certificate.certfile}}"
  when: [ idpcert_file.stat.md5 is not defined,
          node == "slave" ]

- name: Shibboleth IdP Metadata Certificate Cash
  shell: cat {{shibboleth.deploy_dir}}/credentials/{{certificate.certfile}} | grep -v CERTIFICATE;
  register: idp_cert_cash
  changed_when: false
  when: node == "master"
- name: Generate Master Password Hash Result
  debug: var=idp_cert_cash.stdout_lines
  when: node == "master"

- name: Deploy idp.properties
  template:
    src: idp.properties.j2
    dest: "{{shibboleth.deploy_dir}}/conf/idp.properties"
    backup: yes
    mode: "0770"
    owner: tomcat
    group: tomcat

- name: Deploy ldap.properties
  template:
    src: ldap.properties.j2
    dest: "{{shibboleth.deploy_dir}}/conf/ldap.properties"
    backup: yes
    mode: "0770"
    owner: tomcat
    group: tomcat

- name: Deploy global.xml
  template:
    src: global.xml.j2
    dest: "{{shibboleth.deploy_dir}}/conf/global.xml"
    mode: "0770"
    owner: tomcat
    group: tomcat

- name: Deploy attribute-resolver.xml
  template:
    src: attribute-resolver.xml.j2
    dest: "{{shibboleth.deploy_dir}}/conf/attribute-resolver.xml"
    mode: "0770"
    owner: tomcat
    group: tomcat

- name: Deploy Conf
  copy:
    src: "{{item}}"
    dest: "{{shibboleth.deploy_dir}}/conf"
    mode: "0770"
    owner: tomcat
    group: tomcat
  with_fileglob:
    - "files/conf/*.xml"

- name: Deploy Conf/Intercept
  copy:
    src: "{{item}}"
    dest: "{{shibboleth.deploy_dir}}/conf/intercept"
    mode: "0770"
    owner: tomcat
    group: tomcat
  with_fileglob:
    - "files/conf/intercept/*.xml"

- name: Deploy Medadata XML File
  template:
    src: "metadata/{{ item.value.filename }}-metadata.xml"
    dest: "/opt/shibboleth-idp/metadata"
    mode: "0640"
    owner: tomcat
    group: tomcat
  with_dict: "{{ shibboleth.metadata.entity }}"

- name: Deploy Message
  copy:
    src: "{{item}}"
    dest: "{{shibboleth.deploy_dir}}/system/messages"
    mode: "0770"
    owner: tomcat
    group: tomcat
  with_fileglob:
    - "system/messages/*.properties"

- name: Tomcat Start and AutoRun
  service: name=tomcat state=started enabled=yes

- name: Apache Start and AutoRun
  service: name=httpd state=started enabled=yes
```
内容はbuild.xmlを置き換えてShibbolethセットアップの実行と、
各設定ファイルのデプロイや証明書の配置して、ミドルウェアを再起動しています。

この処理の冒頭のbuild.xmlですが、元々のSbibbolethパッケージ/bin/build.xmlを以下のように修正したものを配置しています。
※記載しているのは該当部分のみです。

```
(修正前)
    <target name="getentityid" if="idp.ask.entityid" depends="checkproperties">
        <TGT>getentityid</TGT>
        <fail if="idp.noprompt">Input needed, silence demanded</fail>
        <local name="entityid" />
        <input message="SAML EntityID:" addproperty="entityid" defaultvalue="https://${idp.host.name}/idp/shibboleth" />
        <property name="idp.uri.subject.alt.name" value="${entityid}" />
        <echo file="${idp.merge.properties}" append="yes">
            idp.entityID=${entityid}
        </echo>
    </target>
```

以下に修正しています。

```
(修正後)
    <target name="getentityid" if="idp.ask.entityid" depends="checkproperties">
        <TGT>getentityid</TGT>
        <local name="entityid" />
        <property name="idp.uri.subject.alt.name" value="${entityid}" />
        <echo file="${idp.merge.properties}" append="yes">
            idp.entityID=${entityid}
        </echo>
    </target>
```
対話式のセットアップでエラーになるために修正しています。

これでShibboleth IdPの構築手順が終わりです。
実際にやってみてはまったのは、Shibbolethのセットアップは対話式で実行するのですが、
これをコマンドで実行するのに必要なオプションを調べることでした。
また、必要なオプションだけではエラーとなり失敗してしまいました。
検索しても有効な情報がヒットせずに試行錯誤して成功しました。

# おわりに
ここで記載したものは私が積み上げてきたAnsibleのやり方ですので、これがベストプラクティスというわけではありません。
皆さんがこれを参考にShibboleth IdPのAnsible作成の一助となれば幸いです。