MAMP上でsimpleSAMLphpのSPとIdPを構築する

# はじめに
MAMPとは何か、等については次節のリンクで説明しているので割愛します。simpleSAMLphpは簡単に言うとシングルサインオンを実装するためのオープンソースソフトウェアです。ネットに転がっているサイトではだいたいVMを使って実装されておりMAMPを使った実装は見かけたことがなかったこと、およびSPとIdP両方についてひとつのサイトで説明しているものが少ないことから記事にしてみました。

わざわざ当サイトを見に来るような人はおそらくsimpleSAMLphpやSP・IdPといった知識は十二分に持っていらっしゃると思ったので説明ははしょっています。

環境
OS：macOS Mojave(バージョン10.14.1)
MAMP：Version 5.3(367)
PHP:Version 7.3.1
SimpleSAMLphp：Version 1.17.2

※MAMPのApacheのバージョンはどこから確認できるかわかりませんでした…。わかりましたら追記いたします。

# MAMPのWebサーバー構築
下記記事参照
https://qiita.com/come340/items/6cb175918075f398a41a

# simpleSAMLphpのインストール
https://simplesamlphp.org/download
から最新のものをダウンロード
VMならwgetコマンドを使うといいけれど今回はMAMP(すなわちMacOS上)でやるので普通にダウンロードします。

ダウンロードしたtarファイル(simplesamlphp-X.X.X.tar.gz)は/Applications/MAMP/htdocsに置いて展開します。

筆者は複数のSPおよびIdPを構築したかったのでとりあえず展開したファイルは置いておいてSP・IdPごとにコピーしました。

# simpleSAMLphpの設定
ここはSP・IdPともに同様の設定を行うので、とりあえずSPについて説明します。
先に展開したsimplesSAMLphpのファイルをコピーしてsaml_sp1と名前をつけました。(これは何でもいいです。)

### 複数のローカルホストをたてられるように設定変更
/Applications/MAMP/apache/httpd.confを編集

```httpd.conf
# Virtual hosts
#Include /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf
```

からコメントアウトを削除。

```httpd.conf
# Virtual hosts
Include /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf
```

### ローカルホストの設定

/Applications/MAMP/conf/apache/extra/httpd-vhost.confを編集
最終行の後に追加して以下を記述

```httpd-vhost.conf
# SimpleSAMLphp SP1
Listen 8001
<VirtualHost *:8001>
    SetEnv SIMPLESAMLPHP_CONFIG_DIR /Applications/MAMP/htdocs/saml_sp1/config

    DocumentRoot "/Applications/MAMP/htdocs/saml_sp1/www"
    ServerName saml_sp1.local:8001
    Alias /simplesaml "/Applications/MAMP/htdocs/saml_sp1/www"

    <Directory "/Applications/MAMP/htdocs/saml_sp1/www">
            <IfModule mod_authz_core.c>
            # For Apache 2.4:
            Require all granted
            AllowOverride All
            </IfModule>
    </Directory>
</VirtualHost>
```

8001はポート番号、設定したいローカルホストごとに異なる番号を振り分けるとよいです。
simpleSAMLphpの設定について調べているといろんなサイトで似たような設定の仕方が記述されていますが、コピペしないでちゃんと自分のホストとの整合性を確認したほうがいいと思います。チェックすべきポイントは

- SetEnv
- DocumentRoot
- Alias
- \<Directory "">

あたりだと思います。Aliasは詳しくは説明しませんが/simplesamlの所だけは変えてしまうとうまくいかないのでそのままにしましょう。

ここまで設定できたらMAMPのStop ServersおよびStart Serversを押してサーバーを再起動のち、ブラウザに"localhost:8001"を入力してみたら以下のようなページが表示されるはずです。

<img width="817" alt="スクリーンショット 2019-05-02 15.34.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404657/c4e6740f-373d-d86a-0a6c-b169a3d5640c.png">

### adminユーザパスワード設定
これは下記記事を参考に
https://qiita.com/haya43/items/c74d2710cd9b57d2cbb4

/Applications/MAMP/htdocs/saml_sp1/config/config.phpを編集

```php:config.php
'auth.adminpassword' => 'test',
```

シークレットソルトを設定。下記コマンドでランダム文字列を生成。

```
tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' </dev/urandom | dd bs=32 count=1 2>/dev/null;echo
```

VMで動かしたときはこれでよかったのですが、Macのターミナルではtr: Illegal byte sequenceというエラーを吐いてうまくいかなかったので調べてみたところ、以下コマンドでうまくいきました。

```
LC_CTYPE=C tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' </dev/urandom | dd bs=32 count=1 2>/dev/null;echo
```

生成した文字列をコピーしてconfig.phpをさらに編集

```php:config.php
'secretsalt' => '(生成した文字列)',
```

再びlocalhost:8001にブラウザからアクセスして管理者でログインできるか確認しましょう。

## IdPの設定
筆者はIdPのポート番号は8011にしています。上記の設定で利用していたファイル名saml_sp1に当たるものはsaml_idp1としています。ブラウザから見るときはlocalhost:8011でアクセスします。

### saml20-idpを有効化する

```php:config.php
'enable.saml20-idp' => true,
```

SimpleSAMLphp設定ページの設定タブでSAML 2.0 IdPに緑のチェックマークがついているか確認しましょう。

### 自己証明書を作る
cdコマンドで/Applications/MAMP/htdocs/saml_idp1/cert配下に行きます。
以下コマンドで証明書を作成。

```
openssl req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem
```

いろいろ聞かれますが、以下のように答えます。割と適当なので問題あればご指摘ください。

```
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:(空白)
Organization Name (eg, company) [Internet Widgits Pty Ltd]:(空白)
Organizational Unit Name (eg, section) []:(空白)
Common Name (e.g. server FQDN or YOUR name) []:saml_idp1.local
Email Address []:(空白)
```

これで/cert配下にserver.crtとserver.pemが作成されます。一応lsコマンドで確認するといいです。デフォルトの設定でserver.crtとserver.pemというものが書かれているはずなのでその他のファイルは編集せずこのまま使えます。
もし異なる名称で作成した場合、metadata/saml20-idp-hosted.phpを編集します。

```php:saml20-idp-hosted.php
    'privatekey' => '(設定した名称).pem',
    'certificate' => '(設定した名称).crt',
```

## SPの設定
### SPでもサーバー証明書を作成
/Applications/MAMP/htdocs/saml_sp1/certに移動して以下コマンド実行。あんまり意味があるかわかりませんが出力ファイルの名称をsaml_sp1.local.crt、saml_sp1.local.pemのように変えています。

```
openssl req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out saml_sp1.local.crt -keyout saml_sp1.local.pem
```

IdPの設定時と同様に質問に答えていきます。Common Nameだけはsaml_sp1.localに変えておきます。

### SPの設定ファイルを編集
/Applications/MAMP/htdocs/saml_sp1/config/authsources.php

```php:authsources.php
 'default-sp' => [
        'saml:SP',

        'privatekey' => 'saml_sp1.local.pem', # 追記
        'certificate' => 'saml_sp1.local.crt', # 追記

        // The entity ID of this SP.
        // Can be NULL/unset, in which case an entity ID is generated based on the metadata URL.
        'entityID' => 'http://localhost:8001', # 変更

        // The entity ID of the IdP this SP should contact.
        // Can be NULL/unset, in which case the user will be shown a list of available IdPs.
        'idp' => 'http://localhost:8011/simplesaml/saml2/idp/metadata.php', # 変更

        // The URL to the discovery service.
        // Can be NULL/unset, in which case a builtin discovery service will be used.

(以下省略)
```

entityIDは設定しているSPへのアクセスに利用しているURLに当たるもの(?)を、idpはIdPのエンティティIDを入力します。エンティティIDはIdP側のSimpleSAMLの設定ページの連携タブにある、SAML2.0 IdPメタデータの欄にあるEntity IDを入力しています。ちなみにコメントで記入されていますが、複数のIdPを利用したい場合はnullにセットします。こうすることで、登録した複数のIdPからプルダウンで選択できるようになります。
privatekeyとcertificateの欄はデフォルトでは書いていないので自分で追記します。

# IdP・SPのメタデータ登録
## IdPのメタデータをSPに登録
IdPの設定ページで'連携'タブを開き、SAML 2.0 IdPメタデータの欄から'メタデータを表示'を押してもらうと以下のような画面が表示されます。
<img width="812" alt="スクリーンショット 2019-05-03 13.02.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404657/64061535-2786-c73a-864a-3ce9d4dfc56d.png">
このメタデータXMLフォーマットをコピーして、今度はSPの設定ページに行きます。(IdPのページでもできる気がしますがなんとなくその方が良さそうなのでSP側でやります。)
'連携'タブを開いてツールの'XMLをSimpleSAMLphpメタデータに変換'をクリック。メタデータパーザの入力枠内に先程コピーしたXMLデータを入力し'パース'ボタンを押します。するとXMLフォーマットをSimpleSAMLphpのメタデータフォーマットに変換してくれるので再びコピー。

次に、/saml_sp1/metadata/saml20-idp-remote.phpを編集。先程コピーしたものをそのままペーストすれば良いです。

```php:saml20-idp-remote.php
<?php
/**
 * SAML 2.0 remote IdP metadata for SimpleSAMLphp.
 *
 * Remember to remove the IdPs you don't use from this file.
 *
 * See: https://simplesamlphp.org/docs/stable/simplesamlphp-reference-idp-remote
 */

$metadata['http://localhost:8011/simplesaml/saml2/idp/metadata.php'] = array (
  'entityid' => 'http://localhost:8011/simplesaml/saml2/idp/metadata.php',
  'contacts' =>
  array (
  ),
  'metadata-set' => 'saml20-idp-remote',
  'SingleSignOnService' =>
  array (
    0 =>
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
      'Location' => 'http://localhost:8011/simplesaml/saml2/idp/SSOService.php',
    ),
  ),
(以下省略)
```

'<?php'のPHPタグをつけるのを忘れずに。(筆者は忘れていてエラーを吐かれました)

## SPのメタデータをIdPに設定
SPへの設定と同様に、SPのXMLメタデータを変換しコピーします。こちらでは変換されたメタデータが'shib13-sp-remote'と'saml20-sp-remote'の二種類出てくるのでここでは後者を使用します。
/saml_idp1/metadata/saml20-sp-remote.phpを編集します。こちらも先程と同様にコピーしたものをそのままペーストします。PHPタグも忘れずに。

```php:saml20-sp-remote.php
<?php
$metadata['http://localhost:8001'] = array (
  'entityid' => 'http://localhost:8001',
  'contacts' =>
  array (
  ),
  'metadata-set' => 'saml20-sp-remote',
  'AssertionConsumerService' =>
  array (
    0 =>
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
      'Location' => 'http://localhost:8001/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp',
      'index' => 0,
    ),
    1 =>
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post',
      'Location' => 'http://localhost:8001/simplesaml/module.php/saml/sp/saml1-acs.php/default-sp',
      'index' => 1,
    ),
(以下省略)
```

IdPの設定ではさらに、認証モジュールの設定をします。詳しくは公式文書の3番を読んでください。
https://simplesamlphp.org/docs/stable/simplesamlphp-idp
今回はこの例にならってexampleauthモジュールを使用します。

・モジュールの有効化

```
cd /Applications/MAMP/htdocs/saml_idp1/
touch modules/exampleauth/enable
```

・/saml_idp1/config/authsources.phpを編集
以下のユニット前後のコメントアウト(/*, */)を削除

```php:authsources.php
'example-userpass' => [
        'exampleauth:UserPass',

        // Give the user an option to save their username for future login attempts
        // And when enabled, what should the default be, to save the username or not
        //'remember.username.enabled' => false,
        //'remember.username.checked' => false,

        'student:studentpass' => [
            'uid' => ['test'],
            'eduPersonAffiliation' => ['member', 'student'],
        ],
        'employee:employeepass' => [
            'uid' => ['employee'],
            'eduPersonAffiliation' => ['member', 'employee'],
        ],
    ],
```

これで、IdPにユーザが登録されます。studentユーザはユーザID'test'、パスワード'studentpass'で、employeeユーザはユーザID'employee'、パスワード'employeepass'でログインできるようになります。

# 接続テスト
### SP側のテスト
SP側の設定ページで'認証'タブを開いて'設定されている認証元をテスト'をクリック。default-spを選択してユーザ名、パスワードを入力。うまくいけば以下のような画面が表示されます。
<img width="811" alt="スクリーンショット 2019-05-03 13.31.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404657/8f0eec96-1037-f93f-b6a4-deb73a42f42f.png">

### IdP側のテスト
SP同様に'設定されている認証元をテスト'をクリック。default-spまたはexample-userpassを選択するとSP側同様の画面が表示されます。

# 終わりに
書きたいことを全部まとめて書いてしまったため長くなってしまいましたが、とりあえずここまででなんとなくSPとIdPを動かせるようになると思います。筆者自身MAMPもシングルサインオンもド初心者ですのでなにか問題がありましたらご指摘いただけると幸いです。実際の運用についても今後追記できたらいいなと思っております。

本記事の投稿に当たって以下のサイトを参考にさせていただきました。
https://suer.hatenablog.com/entry/2015/11/20/141941