mrubyアドベントカレンダー、21日目です!!! 前回は、harasouさんによる超便利ツールでした 。
ぼくも難しい記事は無理なのですが、今日は、あんちぽさんが以前スパイクで作っていた libpam-mruby をいじってみて、OS認証のmruby化について実験してみることにします。
PAMについてのおさらい
PAMとは、 Pluggable Authentication Module
の略で、各種の認証処理を実行するモジュール群と、それを利用するための標準的なAPIを備えたライブラリによる認証システムの呼称です。
代表的には、LDAP、SMBなどでのLinuxのユーザ認証に利用されているため、Linuxのユーザ認証のためのプラガブルな認証システムのことという文脈で使われることが多いようです。
Sambaユーザーのパスワード管理 PAMを利用して認証を行う より、図を引用させていただきます。
libpam-mrubyは、そのような認証の中身のロジックをmrubyを用いて記述することができるPAMミドルウェアです。
まずはビルドを通す
環境はCentOS 7.1です。
git clone https://github.com/pepabo/libpam-mruby.git
cd libpam-mruby/
sudo yum groupinstall "Development Tools"
sudo yum install rake pam-devel
rake
sudo install build/mruby.so /usr/lib64/security/pam_mruby.so
ここまでで一応ビルドはできるのですが、実は記事執筆直前の段階 0bd52ea
では、このままではロードしてくれませんでした。syslog(/var/log/secure
)を見ると、以下のようなエラーが出ています。
PAM unable to dlopen(/usr/lib64/security/pam_mruby.so): /usr/lib64/security/pam_mruby.so: undefined symbol: mrb_str_new_cstr
PAM adding faulty module: /usr/lib64/security/pam_mruby.so
どうもmrubyのライブラリなどのリンクに失敗しているようです。
ビルドスクリプトを変更する
リンクを上手く行かせるために、以下のように最後の pam_mruby.so
を生成するコマンドを変更します(成果物の名前もpamの規約に沿って変えておきます)。
--- a/Rakefile
+++ b/Rakefile
@@ -21,7 +21,7 @@ end
desc 'build'
task :build => [:build_mruby_c, :build_pam_c] do
- sh 'ld -x --shared -o build/mruby.so build/mruby.o build/pam.o'
+ sh 'gcc -shared -Wl,-soname=pam_mruby.so.1 -lm -lpam -o build/pam_mruby.so build/mruby.o build/pam.o mruby/build/host/lib/libmruby.a'
end
desc 'install'
また、 build_config.rb
も以下のように変えます。
--- a/build_config.rb
+++ b/build_config.rb
@@ -1,4 +1,8 @@
MRuby::Build.new do |conf|
toolchain :gcc
conf.gembox 'default'
+
+ conf.cc do |cc|
+ cc.flags << '-fPIC'
+ end
end
この上でビルドをやり直すと、リンクがいい感じになり、mrubyも pam_mruby.so
の中にちゃんと組み込まれるようになります。
[vagrant@log libpam-mruby]$ ldd build/pam_mruby.so
linux-vdso.so.1 => (0x00007fffa8973000)
libm.so.6 => /lib64/libm.so.6 (0x00007fe657b65000)
libpam.so.0 => /lib64/libpam.so.0 (0x00007fe657956000)
libc.so.6 => /lib64/libc.so.6 (0x00007fe657594000)
libaudit.so.1 => /lib64/libaudit.so.1 (0x00007fe65736e000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fe65716a000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe65810b000)
パスワードをmrubyで認識できるようにする
ここまでで、mrubyで認証が可能になったのですが、実はこの段階ではmrubyのスクリプトではユーザ名しか認証に利用できないので、パスワードを認識できるようにコードを変更してみます。
以下のようにmruby周りの定義を変更し、 check
メソッドの引数を1つから2つにします。
diff --git a/src/mruby.c b/src/mruby.c
index 9606465..60c1a52 100644
--- a/src/mruby.c
+++ b/src/mruby.c
@@ -1,12 +1,14 @@
#include "mruby.h"
-int pam_mruby_check(FILE *rbfile, const char *name)
+int pam_mruby_check(FILE *rbfile, const char *name, const char *passwd)
{
mrb_value ret;
mrb_state *mrb = mrb_open();
mrb_load_file(mrb, rbfile);
- ret = mrb_funcall(mrb, mrb_top_self(mrb), "check", 1, mrb_str_new_cstr(mrb, name));
+ ret = mrb_funcall(mrb, mrb_top_self(mrb), "check", 2,
+ mrb_str_new_cstr(mrb, name),
+ mrb_str_new_cstr(mrb, passwd));
mrb_close(mrb);
if (mrb_type(ret) == MRB_TT_TRUE)
diff --git a/src/mruby.h b/src/mruby.h
index 8075ed5..6a1828e 100644
--- a/src/mruby.h
+++ b/src/mruby.h
@@ -7,6 +7,6 @@
#include <stdio.h>
-int pam_mruby_check(FILE *rbfile, const char *name);
+int pam_mruby_check(FILE *rbfile, const char *name, const char *passwd);
#endif
そうしたら、PAM認証の肝である pam_sm_authenticate
内部で、パスワードを取得するコードを追加します。 pam_get_authtok
という関数を利用できるようです。
--- a/src/pam.c
+++ b/src/pam.c
@@ -46,7 +46,12 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,int argc, const
}
if (debug) pam_syslog(pamh, LOG_DEBUG, "username is %s", username);
- ret = pam_mruby_check(rbfile, username);
+ if (pam_get_authtok(pamh, PAM_AUTHTOK, &password, NULL) != PAM_SUCCESS) {
+ pam_syslog(pamh, LOG_ERR, "couldn't get password from PAM stack");
+ return PAM_AUTH_ERR;
+ }
+
+ ret = pam_mruby_check(rbfile, username, password);
fclose(rbfile);
if (ret > 0)
auth required pam_env.so
# この行を追加
auth sufficient pam_mruby.so rbfile=/etc/pam-mruby.d/auth.rb debug try_first_pass
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 500 quiet
auth required pam_deny.so
また、このままだと PAM unable to resolve symbol: pam_sm_setcred
というログが(実害はないようですが)出続けてしまうので、何もしない pam_sm_setcred
を定義する必要があります。
diff --git a/src/pam.c b/src/pam.c
index 5d2abe2..ff7a9f5 100644
--- a/src/pam.c
+++ b/src/pam.c
@@ -59,3 +59,8 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,int argc, const
else
return PAM_AUTH_ERR;
}
+
+PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
+{
+ return PAM_SUCCESS;
+}
これで、一通りの変更が終わりましたので、ビルドとインストールをやり直しておきます。
mrubyをつかってユーザ認証をする
あとは、以下のエントリを /etc/pam.d/system-auth-ac
に追加し、
auth required pam_env.so
# ここを追加
auth sufficient pam_mruby.so rbfile=/etc/pam-mruby.d/auth.rb debug try_first_pass
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 500 quiet
auth required pam_deny.so
#...
以下のような認証のためのmrubyスクリプトを用意して確認してみましょう。
(追記: 最新のバージョンでは authenticate
というメソッドを定義する必要があります)
def check(username, password)
if username == 'udzura'
password == 'p@ssw0rd'
else
true
end
end
$ sudo adduser udzura
## 雑にUNIXパスワードを潰す
$ echo "udzura:$(uuidgen)" | sudo chpasswd
$ su - udzura
パスワード: # <- p@ssw0rd を入力!
最終ログイン: 2015/12/22 (火) 15:23:48 JST日時 pts/0
[udzura@mruby ~]$
見事にmrubyでユーザログインができました!
認証手段にmrubyを利用できることで、例えば外部APIとの連携など、様々な応用が考えられると思います。
今回のコードの最終的なdiffは こちらのPR にあります。
PAMの拡張について
PAMの拡張の書き方は、The Linux-PAM Module Writers' Guide の3章によると、以下に大別されるそうです。
- 3.2 Authentication management
- 3.3 Account management
- 3.4 Session management
- 3.5 Password management
これは、 pam.d 配下のファイルの auth/account/session/password
の項目に対応しています。LDAPでの認証のように、自動でLDAPのデータベースからアカウントを作成するような処理は Account management
で行われるようですが、今回は Authentication management
のみを行うので、既存のユーザの認証方法を操作できるということでした。
その他の項目についても実装すれば、より便利になるかもしれません。
なお、このサンプルは、あくまでもサンプルとして、セキュリティなどに関しては不十分な箇所もある(認証用のmrubyファイルが丸見え、とか...)ことをご留意ください。
明日(今日...)は、 yasuyuki さんです。