SSHをLDAP公開鍵認証 + TOTP(Google Authenticator)の多要素認証にする


## 環境

- CentOS 6.5

## Overview

SSHを多要素認証にしたい。
公開鍵認証が端末の紛失などによって突破された場合の保険としてTOTP(Time-Based One Time password)のGoogle Authenticator認証を追加する。

## Requirements

- pam_google_authenticator
- pam_ldap

## Install

- 依存パッケージのインストール

```
yum install openldap-clients nss-pam-ldapd pam_ldap openssh-ldap
```

- [pam_google_authenticator](https://code.google.com/p/google-authenticator/downloads/list)のDL & インストール

```
tar jxf libpam-google-authenticator-1.0-source.tar.bz2
cd libpam-google-authenticator-1.0
make && make install
```



- LDAPクライアントの設定

```/etc/pam_ldap.conf
authconfig-tui

・ユーザ情報
「LDAPを使用」にチェック
・認証
「シャドウパスワードを使用」
「LDAP認証を使用」
「ローカル認証は十分です」
にチェック
・LDAP設定
「サーバー」「baseDN」を入力しOKを押す。
```

```/etc/ssh/ldap.conf 
uri ldap://210.129.173.120/
base dc=tako,dc=asia
# host 210.129.173.120
binddn cn=Directory Manager
bindpw password

ssl no
```

- PAMの設定

/etc/pam.d/sshd

```/etc/pam.d/sshd
#%PAM-1.0
auth       required     pam_sepermit.so
auth       required     pam_google_authenticator.so
# auth       include      password-auth

account    required     pam_nologin.so
account    include      password-auth
password   include      password-auth
# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    optional     pam_keyinit.so force revoke
session    include      password-auth
session     required      pam_mkhomedir.so skel=/etc/skel umask=0022
```

`auth       required     pam_google_authenticator.so`を追記します。
ここで`auth include password-auth`をコメントアウトし無効にするのがポイントです。これをしないとパスワードログインが有効であると認識されます。

/etc/pam.d/system-auth

```/etc/pam.d/system-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth        required      pam_env.so
auth        sufficient    pam_unix.so nullok try_first_pass
auth        requisite     pam_succeed_if.so uid >= 500 quiet
auth        sufficient    pam_ldap.so use_first_pass
auth        required      pam_deny.so

account     required      pam_unix.so broken_shadow
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 500 quiet
account     [default=bad success=ok user_unknown=ignore] pam_ldap.so
account     required      pam_permit.so

password    requisite     pam_cracklib.so try_first_pass retry=3 type=
password    sufficient    pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password    sufficient    pam_ldap.so use_authtok
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
session     optional      pam_mkhomedir.so skel=/etc/skel umask=077
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so
session     optional      pam_ldap.so
```

/etc/pam.d/password-auth

```/etc/pam.d/password-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth        required      pam_env.so
auth        sufficient    pam_unix.so nullok try_first_pass
auth        requisite     pam_succeed_if.so uid >= 500 quiet
auth        sufficient    pam_ldap.so use_first_pass
auth        required      pam_deny.so

account     required      pam_unix.so broken_shadow
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 500 quiet
account     [default=bad success=ok user_unknown=ignore] pam_ldap.so
account     required      pam_permit.so

password    requisite     pam_cracklib.so try_first_pass retry=3 type=
password    sufficient    pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password    sufficient    pam_ldap.so use_authtok
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
session     optional      pam_mkhomedir.so skel=/etc/skel umask=077
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so
session     optional      pam_ldap.so
```


- sshd_configの設定

以下の項目を設定する。

```/etc/ssh/sshd_config
RSAAuthentication yes
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication yes

RequiredAuthentications2 publickey,keyboard-interactive

AuthorizedKeysCommand  /usr/libexec/openssh/ssh-ldap-wrapper
AuthorizedKeysCommandRunAs nobody

UsePAM yes
```

解説: `RequiredAuthentications2`はRHEL系のOpenSSHのみ有効。
Debian系はOpenSSH6.2以上で同じ意味の`AuthenticationMethods`が使える。
`RequiredAuthentications2 publickey,keyboard-interactive`で認証方法を2つ指定している。

- LDAP用のスキーマをインポートする

```/usr/share/doc/openssh-ldap-5.3p1/openssh-lpk-openldap.schema
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
        DESC 'MANDATORY: OpenSSH Public key'
        EQUALITY octetStringMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )

# printableString SYNTAX yes|no
objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
        DESC 'MANDATORY: OpenSSH LPK objectclass'
        MUST ( sshPublicKey $ uid )
        )
```

- LDAPに公開鍵を登録。

```
dn: uid=test,ou=people,dc=tako,dc=asia
givenName: test
objectClass: person
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: posixAccount
objectClass: top
objectClass: ldapPublicKey
uid: test
cn: test
sn: user
userPassword:: e1NTSEF9VjlzaDEvd1pmQ0Z0Vjc1NGdGZGdKN2tlNitFNk9PRDJUTnRWcHc9PQ=
 =
homeDirectory: /home/test
sshPublicKey: ssh-rsa ...... hoge@localhost
uidNumber: 100
gidNumber: 500
```

上記の/etc/ssh/sshd_configで設定した

```
AuthorizedKeysCommand  /usr/libexec/openssh/ssh-ldap-wrapper
```

が
実際にLDAPサーバを検索するときにかけるフィルタは

```
(&(objectClass=posixAccount)(objectClass=ldapPublicKey))
```

なのでこの2つのオブジェクトクラスが必要。openssh-ldapを使わない場合はAuthorizedKetsCommandにLDAPサーバを検索して公開鍵を返すスクリプト、例えば以下のようなスクリプトで検索し、posixAccountクラスは不要。

```
#!/bin/bash

uri=ldap://127.0.0.1/
binddn="cn=Directory Manager"
bindpw=password
base="dc=example,dc=com"
uid=$1

/usr/bin/ldapsearch -LLL -H ${uri} -w "${bindpw}" -D "${binddn}" -b "${base}" "(& (objectClass=ldapPublicKey) (uid=${uid}))" "sshPublicKey" | tail -n +2 | sed -e "s/sshPublicKey\: //g" | sed -e 's/^[ ]*//g' | awk '{printf $0}'
```



- アカウント情報を引けるか確認

```
[root@hoge]# id test
uid=100(test) gid=500(vagrant) groups=500(vagrant)
```

引けない場合は/etc/pam_ldap.confにある設定が正しいか確認。

- ユーザを作成

```
adduser test
```

- google-authenticator用のシークレットファイルを作る

```bash
$ su - test
$ google-authenticator -t -D -f -u -W
バーコードのURLが表示されるのでブラウザで開き、iPhoneまたはAndroidの「Google Authenticator」で検索して出てくるアプリで読み取ってtokenを登録する
```

- 確認

```bash
$ ssh example.com -l test
Authenticated with partial success.
Verification code: <<Google AuthenticatorのOTPを入力>>
```

ここにモバイル端末のGoogle Authenticatorアプリで表示されているワンタイムパスワードを入力し、無事ログイン出来れば完了です。


参照: [OpenSSH 6.2を使って公開鍵認証もLDAPで行いたい。 - Qiita](http://qiita.com/harukasan/items/0784e1e45083d1e0a602)