前回記事の続きです。
モチベーション
前回はサーバーのセットアップまで完了しました。ここでは、その環境を使ってSSO(Single Sign On)を実現していきます。
完成予定図です。
大量に作ったVMをSSOを使って簡単に管理しようという魂胆です。
環境は、KDCとVM達はGoogle Cloud Platform上のDebian GNU/Linux 9 (stretch)を使っています。また、ClientはMacOS High Sierraです。
Kerberosについての簡単な説明
前回の記事では環境構築をひたすらやったので、それぞれのサーバーの役割をあまり説明しませんでした。なので、ここでごく簡単な説明を入れます。
先ほどの図で、KDCやticket、keytabといった見慣れない単語が出てきたと思います。これらはKerberos認証と呼ばれる認証方式を実現するのに必要なものです。
Kerberos認証は双方向認証(クライアント認証+サーバー認証)です。かの有名なActiveDirectoryでもKerberos認証を使うことができます。Kerberosは認証しかしないので、認証対象のユーザーの情報の保存は別に考えなくてはいけません。
KDCはKey Distribution Centerの略で、AS(Authentication Server)とTGS(Ticket Granting Server)から成ります。クライアントはASに認証してもらった後、TGSからticketを受け取り、それを相手に提示することで自分の身分を証明します。keytabは、逆に相手が身分を証明するために用いるものです。このようにして、お互いに身分を証明し合うことにより、安心して通信を行うことができます。
サーバーにKerberos認証を使ってアクセスする際の、認証の詳しい手順は以下の通りです。
暗号鍵の生成
パスワードを使う場合
- ユーザーがユーザー名と所属realm、パスワードを入力します。
- クライアントプログラムがそれらの情報(credential)から共通鍵を生成します。
keytabを使う場合
- ユーザーはあらかじめcredentialをクライアントプログラムに登録しておきます。クライアントプログラムはそこから共通鍵を生成し、keytabというファイルに保持します。keytabからcredentialを復元することはできません。
この時点で、クライアントはASとの通信に使う共通鍵を持っています。
クライアント認証
ASがクライアントが本人であるか確認します。
-
クライアントはASにユーザーIDを送り、認証を要求します。
-
ASはデータベース上に送られてきたユーザーIDに対応するユーザーがいることを確認し、データベース上からcredentialを取ってきて共通鍵を生成します。そして、以下の二つのメッセージをクライアントに返します。
-
- A: Client/TGS Session Key [共通鍵で暗号化]
- B: TGT(Ticket Getting Ticket) [TGSの暗号鍵で暗号化]
-
クライアントはメッセージAを共通鍵で復号し、Client/TGS Session Keyを取り出します。
この時点で、クライアントは以下の二つの情報を持っています。
- Client/TGS Session Key
- TGT [TGSの暗号鍵で暗号化]
クライアント認可
TGSがクライアントにサーバーへのアクセス権(券)を与えます。
- クライアントはTGSにサーバーへのアクセス権を要求します。そのさい、以下の二つをTGSに渡します。
- メッセージC: TGSの暗号鍵で暗号化されたTGTおよびアクセスを要求するサービスのID
- メッセージD: ユーザーIDおよびタイムスタンプ [Client/TGS Session Keyで暗号化]
- TGSはメッセージCからTGSの暗号鍵で暗号化されたTGTを取り出して復号し、TGTを得ます。そしてTGSから、Client/TGS Session Keyを取り出します。この鍵を使って、メッセージDを復号し、そこに含まれるユーザーIDとTGTから得られるユーザーIDが一致するか確認します。一致した場合、次の二つのメッセージを返します。
- メッセージE: ticket [サーバーの暗号鍵で暗号化]
- メッセージF: Client/Server Session Key [Client/TGS Session Keyで暗号化]
- クライアントはメッセージFを復号して、Client/Server Session Keyを得ます。
この時点でクライアントは、以下の二つの情報を持っています。
- ticket [サーバーの暗号鍵で暗号化]
- Client/Server Session Key
サーバーへの接続
- クライアントはサーバーに接続を要求します。そのさい、以下の二つのメッセージを渡します。
- メッセージE: ticket [サーバーの暗号鍵で暗号化]
- メッセージG: ユーザーIDとタイムスタンプ [Client/Server Session Keyで暗号化]
- サーバーは事前に用意されたkeytabから自身の暗号鍵を取り出し、メッセージEを復号して、ticketを得る。さらに、ticketからClient/Server Session Keyを取り出し、メッセージGを復号することで、ユーザーIDを得る。このユーザーIDとticketに含まれるユーザーIDが一致するか調べ、一致する場合は次のメッセージを返す。
- メッセージH: メッセージGから取り出したタイムスタンプ [Client/Server Session Keyで暗号化]
- クライアントはメッセージHを復号し、タイムスタンプが送ったものと一致するか確認する。一致した場合は、サーバーに接続する。
以上です。この内容は、wikipediaに書いてあります。
クライアントがどうして偽れないのか、サーバーがどうして偽れないのかを考えてみると良いかと思います。
前提
前回記事でやったことを前提とします。
- krb5-admin-server(adminサーバー)が動いている
- krb5-kdc(master KDCサーバー)が動いている
- 管理者用principalがすでに存在する
- firewallが適切に設定されている
前回はKerberosのバックグラウンドにOpenLDAPを使いましたが、今回の内容はバックグラウンドに関わらず使えると思います。
principalの追加
前回の記事では、管理者principalの追加までやったので、これからはkadmin.localではなくkadminを使っていきましょう。
一般ユーザー用principal(usagi)を追加します。名前のつけ方にはルールがあって、サービスの場合は、<service-name>/<hostname>@<realm>
に従ってください。ユーザー用principalの場合は、<user-name>@<realm>
です。@<realm>
は省略可能で、省略した場合、デフォルトrealmが使われます。
sudo kadmin -p ${admin}/admin -q 'addprinc usagi'
これまではkadminを対話モードで使っていましたが、このように対話モードに入らなくても設定ができます。
サービス用principal(http/www)を追加します。これは、サービスをホストする機器で行います。当然、その機器にはkadminが適切にインストールされている必要があります。前回の記事が参考になるかもしれません。
sudo kadmin -p ${admin}/admin
kadmin: addprinc http/www -randkey
kadmin: ktadd -k ${service keytab file} usagi # keytabを作る
kadmin: exit
一般ユーザーのkeytabも作りたくなるかもしれませんが、ktaddで作ることはできません。ktaddでkeytabを作ると、以前のパスワードは使えなくなるからです。
Each principal’s keys are randomized in the process.
(https://web.mit.edu/kerberos/krb5-1.12/doc/admin/admin_commands/kadmin_local.html より)
その代わり、ktutilというコマンドで作ります。
sudo ktutil
ktutil: read_kt /etc/krb5.keytab
ktutil: add_entry add_entry -password -p ${princ} -k ${kvn} -e ${encry}
ktutil: write_kt /etc/krb5.keytab
/etc/krb5.keytab
というのはデフォルトのkeytabファイルで、kadmin -k
で使われるものです。もちろん、他のファイルを指定しても構いません。
principalのパスワードの変え方
これはmiscですが、大事なので書いておきます。パスワードの変更は、
kpasswd ${principal}
で出来ます。ただ、この方法は変更前のパスワードを知っている必要があるので、パスワードを忘れた場合には使えません。そのような時は、
kadmin -p ${admin}/admin -q ${princ}
を使います。元のパスワードを知らなくても変更できるので、忘れた時などにも使えます。
SSHでKerberos認証を使う
では最後に、実際にkerberos認証を使ってみます。
sshd_configの設定
まずは、sshサーバーのdaemonであるsshdの設定ファイルsshd_configを変更します。
...
PasswordAuthentication no
...
GSSAPIAuthentication yes
...
としてください。
systemctl restart sshd
でdaemonの再起動を行います。
SSHサーバー用principalの作成
kadmin -p ${admin}/admin@${REALM}
# sshログインユーザー用principal
kadmin: add_principal ${username}@${REALM}
# sshサーバー用principal
kadmin: add_principal -nokey host/${FQDN}@${REALM}
サーバー用principalについては、あとでktaddによって暗号鍵を生成するので、ここではnokeyとしています。
ここで、注意しないといけないのが、sshサーバー用principalの名前です。
上で見た通り、sshクライアントはticketをもらう時に、sshサーバーprincipalを知っておく必要があります。sshクライアントは、おそらく、
ssh ${FQDN}
と要求された場合、sshサーバーのprincipalとしてhost/${FQDN}@${default realm}を想定するのでしょう。そのため、sshサーバー用principalの名前は自由に決められません。(要検証)
また${FQDN}は、SSHサーバー上で
hostname --fqdn
を実行したときの結果と一致している必要があります。サーバーのFQDNを変更するには、/etc/hosts
ファイルを変更すれば良いです。詳しいやり方はman hostname
を見るなり、ググるなりして、調べて見てください。
蛇足ですが、sshサーバーがどうやってログインユーザーを決めているかというと、ticketにユーザーIDが含まれるので、そのIDを用いてログインユーザーを決定しているのでしょう。(要検証) ただ、ユーザーIDとuidのバインディングを設定するファイルは見当たりませんでした。どうやって、バインディングしているのかは不明です(ユーザー名?)。ご存知の方はコメントをお願いします。
keytabの登録
SSHサーバーで、
kadmin -p ${admin}/admin@${REALM} -q ktadd host/${FQDN}@${REALM}
を実行してください。サーバーにkeytabが登録されます。
SSHクライアントの設定
~/.ssh/configファイルに、以下を追記します。
Host ${target name}
HostName ${FQDN}
User ${login name}
KerberosAuthentication yes
KerberosTicketCleanup yes
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
ここで、principalの設定がないことに気づきます。が、これできちんと動きます。理由は、余談 複数のprincipalを使い分けるを見てください。
TGT(Ticket Granting Ticket)の取得
TGTを取得するには、以下のコマンドを実行してください。
kinit ${princ name}@${REALM}
取得したTGT(とその他のticket)は、
klist
で確認できます。TGTは、
krbtgt/${REALM}@${REALM}
と表示されるはずです。
~/.k5loginの編集
名前が${login name}@${default realm}のprincipalは特別な設定なくログインできます。そうでない場合(以下の条件を満たす場合)は設定が必要です。
-
kinit
で指定した${princ name}が~/.ssh/conf
で設定した${login name}と一致しない場合 -
kinit
で指定した${REALM}がsshサーバーのデフォルトrealmでない場合
この二つの条件のうち一つでも当てはまる場合は、sshサーバー上のユーザー${login name}のホームディレクトリに.k5loginファイルを作り、以下の内容を書き込んでください。
${princ name}@{REALM}
また、.k5loginファイルを作成した場合、デフォルトの設定が無効となりますので、名前が${login name}@${default realm}のprincipalも、そのprincipalでログインしたいならば.k5loginファイルに書き込む必要があります。
* k5login_authoritativeはkrb5.confファイルで設定できます。 (http://web.mit.edu/Kerberos/krb5-devel/doc/user/user_config/k5login.html より引用)With no .k5login file, or with k5login_authoritative* set to false, a default rule would permit the principal alice [${princ name}] in the machine’s default realm to access the alice [${login name}] account.
接続確認
ssh ${target name}
パスワードを要求されずに接続できれば成功です。
余談
複数のprincipalを使い分ける
あるprincipalのTGTをもらうには、
kinit ${princ}
とします。この時、別のprincipalのTGTを
kinit ${another princ}
で要求すると、先ほどもらったTGTが上書きされます。このことは、
klist
が常に最大一つまでのkrb/${REALM}@${REALM}チケットしか持たないことから推察できます。
よって、一人のユーザーが持ちうるTGTは(kinitを使う限りは)一つだけであり、そのため、ssh接続するときに使用するprincipalを指定する必要がないのです。
SSHの再インストール (SSH1を使いたい人向け)
<重要> ssh kerberos
と検索したときに上位に出てくるhttps://www.oreilly.com/library/view/linux-security-cookbook/0596003919/ch04s14.html やhttps://nnc3.com/mags/Networking2/ssh/ch11_04.htm には、SSHを--with-kerberos5付きでコンパイルするよう書いてありますが、不要です。SSH1でKerberos認証を行うときには、サーバーで
KerberosAuthentication yes
という設定をします。この設定を使うには--with-kerberos5が必要なのでしょうが、SSH2ではその代わりに
GSSAPIAuthentication yes
という設定が使えます。そして、これでもKerberos(を使った)認証ができます。この方法は、少なくとも僕の環境ではデフォルトで使えました。
とはいえ、僕はそれに気付かずにSSHの再インストールを行なったのでその時のメモは以下に残しておきます。
標準でついてくるsshやsshdはKerberos付きでコンパイルされていないことが多いため、自分でコンパイルしたものを代わりに使う必要があります。
# 依存するライブラリ等のインストール
sudo apt install gcc zlib1g-dev libssl-dev libpam0g-dev libkrb5-dev
# ソースコードのダウンロード
wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-7.9p1.tar.gz
# 解凍
tar xf openssh-7.9p1.tar.gz
最新のソースコードは、https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/ とかで配布されています。必ずportableの物を使ってください。
コンパイルして、インストールします。
cd openssh-7.9p1
# autoconf
./configure --with-kerberos5 --with-pam
make
# 既存のプログラムを必要なら消す
sudo rm /usr/bin/ssh* /usr/sbin/sshd
sudo make install
# systemd unitファイルの書き換え
sudo -e /lib/systemd/system/ssh.service