ansible-ldap is very simple and useful¶
OpenLDAP と仲間たち Advent Calendar 2015 24日目、クリスマスイブですね。一昨日(12/22)にEngineer All Handsという社内のイベントでLTをすることになり、「LDAPと私」というネタで ansible-ldap というモジュールの話を軽くしました。ついでにブログにでもちゃんと書いておこうかなと思い、昨日アドベントカレンダーの予定を見てみたら、空いていたので参加してみました。
概要¶
この記事の要点としては以下のとおりです。これを読んで理解できる方はその後を読む必要はありません。モジュールのソースコード内のドキュメントを読みましょう。
ansible-ldapの
ldap_entry
およびldap_attr
モジュールだけで LDAPのエントリーの追加・削除、エントリーの属性の追加、削除、置換を冪等に行うことができるldapi://
にも対応しているので slapd-config(5)の設定もできるAnsible Galaxy には登録されてないのでパッチ書いてPR送った (mergeされるかは別の話)
あと、LDAPには直接関係ありませんが、
サンプルのPlaybookを作っている際、サンプルデータをYAMLで記述するのが面倒になって、 CSVから変数を読み込めるAnsibleモジュール を作った
ansible-ldapとは¶
django-otp 、 django-auth-ldap 、 mockldap などの作者の Peter Sagerson 氏が開発された Ansib用の LDAP モジュールです。django-auth-ldapやmockldapには前職でとてもお世話になってました。 1
ldap_entry
と ldap_attr
の2つのモジュールがあります。
前者はLDAPのDITに対し、entryを追加または削除を、後者はすでに存在するエントリーの属性を追加、削除、もしくは置き換えを行うためのモジュールです。
fumiyasuさん が一日目にslapd.conf(5)を使ったPlaybookの 記事 を書かれていますが、ansible-ldapモジュールを使う場合は、slapd-config(5)を使います。DebianやUbuntuのOpenLDAPサーバのパッケージである、slapdパッケージはslapd-config(5)がデフォルトです。slapd-config(5)絡みの記事は 以前書いたもの も ある ので、そちらもご参照ください。
使い方¶
これらのモジュールを使った サンプルのPlaybook を作ったのでそれを例にして説明します。
ldap_entry¶
まずはシンプルに memberof
モジュールを追加する例です。
- name: be sure memberof module
ldap_entry:
dn: cn=module{1},cn=config
state: present
objectClass: olcModuleList
olcModulePath: /usr/lib/ldap
olcModuleLoad: memberof.la
dn
必須項目です。エントリーの追加時、
{1}
とあるindex番号は必要に応じて自動的に付加されます。しかし、cn=module,cn=config
とcn=module{1},cn=config
は別のdnになります。そのためエントリー追加後、同様にindexを指定しないで属性の変更や、エントリーの削除を行おうとすると、エントリーが見つからないためエラーになります。変更するエントリーのindexを把握しておく必要があるのでサンプルで明示的に指定しています。state
デフォルトは
present
で存在しなければ作成し、存在すれば何もしません。削除するときはabsent
を使います。これはAnsibleの他モジュールと同じ挙動です。ldap_entry
には変更という操作はありません。つまりldapadd
コマンドとldapdelete
コマンドに相当する操作だけです。ldapmodify
コマンドに相当する操作はldap_attr
で行います。objectClass
及びその他の属性必要な場合は設定します。 slapd-config(5)の属性の名称はslapd.conf(5) の設定オプション名とは微妙に異なるので、
man slapd-config(5)
でGLOBAL CONFIGURATION OPTIONS
以降のセクションを参照しましょう。
次に、suffixが dc=example,dc=org
のLDAPディレクトリに対し、oganizational unitを追加する例を見てみます。
- ldap_entry:
dn: "ou={{ item }},{{ suffix }}"
objectClass: organizationalUnit
ou: "{{ item }}"
bind_dn: "cn=admin,{{ suffix }}"
bind_pw: "{{ admin_pw }}"
state: present
with_items:
- People
- Groups
- SUDOers
server_uri
この例では省略していますが、これはデフォルトでは
ldapi:///
になります。リモートホストのLDAPサーバを対象にする場合には、LDAPのURLを指定する必要があります。start_tls
StartTLS LDAPを使うときはこのオプションを
true
にします。デフォルトではfalse
です。bind_dn
とbind_pw
slapd-config(5) の設定の時は省略することで EXTERNAL mechanism でアクセスしますが、特定のDITの操作を行うには、デフォルトでは認証が必要になります。bind用のDNを
bind_dn
で、パスワードをbind_pw
で指定します。slapd自体の設定し、特定のsuffix のDITに対し変更を行う場合、つい忘れがちなので気をつけましょう。
ldap_attr¶
アクセス権限を設定する例を見てみます。
- name: olcAccess are absent.
ldap_attr:
dn: "olcDatabase={1}{{ backend | lower }},cn=config"
name: olcAccess
state: absent
values:
- '{0}to attrs=userPassword by self write by anonymous auth by * none'
- '{1}to attrs=shadowLastChange by self write by * read'
- '{2}to * by * read'
- name: olcAccess are present.
ldap_attr:
dn: "olcDatabase={1}{{ backend | lower }},cn=config"
name: olcAccess
state: present
values:
- '{0}to attrs=userPassword,shadowLastChange
by self write
by anonymous auth
by dn="cn=admin,{{ suffix }}" write
by * none'
- '{1}to dn.base=""
by * read'
- '{2}to *
by dn="cn=admin,{{ suffix }}" write
by * read'
- '{3}to dn.subtree="{{ suffix }}"
by self read
by * read'
- '{4}to *
by * none'
この2つのタスクでは、 absent
でslapdインストール時にデフォルトで設定されるアクセス設定を一度削除し、 present
で新しく設定しています。
このやり方は面倒ですね。代わりに exact
を使えばひとつのタスクで変更できます。
- name: override olcAccess exactly
ldap_attr:
dn: "olcDatabase={1}{{ backend | lower }},cn=config"
name: olcAccess
state: exact
values:
- '{0}to attrs=userPassword,shadowLastChange
by self write
by anonymous auth
by dn="cn=admin,{{ suffix }}" write
by * none'
- '{1}to dn.base=""
by * read'
- '{2}to *
by dn="cn=admin,{{ suffix }}" write
by * read'
- '{3}to dn.subtree="{{ suffix }}"
by self read
by * read'
- '{4}to *
by * none'
name
で 変更する属性を指定し、 values
で一つもしくは一つ以上の値を指定します。複数設定できるか否かは、設定するattributeのスキーマ次第です。他のパラメータは基本的には ldap_entry
と同じです。
ansible-ldapのインストール方法¶
現状、ansible-ldapは Ansible Galaxyには登録されてません。また、Ansible Galaxyで公開できる形式になっていないため、requirements.yml に
- src: https://bitbucket.org/psagers/ansible-ldap
name: ldap
scm: hg
のように記述し、 ansible-galaxy install -p library -r requirements.yml
と実行してもインストールできません。手動で hg clone
を実行し、playbookのlibraryディレクトリを以下にモジュールをコピーする必要があります。とても面倒です。ということで、パッチ書いてPRを送っておきました。
マージされるまでの間 2 は、下記のように記述することで ansible-galaxy install
コマンドでインストールすることができます。ただし、 --no-deps
オプションが必要ですので気をつけましょう。
- src: https://bitbucket.org/mkouhei/ansible-ldap
name: ldap
scm: hg
version: for-ansible-galaxy
さらにもしAnsible Galaxyに登録されたら、おそらくこんな記述になることでしょう。
- src: psagers.ldap
C bindingとPure Python¶
今回紹介した django-auth-ldap、mock-ldapは OpenLDAPライブラリの C bindingと実装された Python-LDAP やそのPython3対応としてのforkの pyldap に依存してします。 ansible-ldapもPython-LDAPに依存しています。 3 Pure PythonでのLDAPクライアントの実装としての ldap3 は使用されていません。今回紹介する ansible-ldap も やはり Python-LDAPに依存しています。
今までに何度かLDAP用のAnsibleモジュールを書こうかな、と思ったことも何度かあったのですが 4 、slapdの設定変更に必要なのは ldapi://
(LDAP over IPC) でアクセス、操作できることなので、少なくともこの10月末まではC bindingのPython-LDAP / pyldapしかその機能があるPythonモジュールはありませんでした。 Pure Pythonのldap3では本当にこの最近(2015-11-15)、 v0.9.9.3 として LDAPIの機能が実装された ようです。
一方、このansible-ldapは 昨年の11月に基本機能を実装して公開されていた いたので、ldap3を使っていないのは当然といえます。
DebianシステムではPython-LDAPは python-ldap
パッケージとして提供されていますが、pyldapはDebianパッケージとして提供されていません。ldap3 は python-ldap3
(Python2版) および python3-ldap3
(Python3版) として提供されています。Ansible は Python3はまだ正式対応されていないので現状では playbook の中で、
- apt: pkg=python-ldap state=present
と python-ldap
パッケージをインストールすればよいですが 5 、Ubuntu の次のLTSではPython3だけになるので、pyenvなどでPython2.7を構築するタスクを書いた上 6 で
- apt:
pkg={{ item }}
state=present
with_items:
- build-essential
- libldap2-dev
- pip: name=Python-LDAP
として、slapdを動かすホスト上でPython-LDAPのコンパイルも必要な上、ansible-ldapでは現状任意の PYTHONPATH
を指定することができないので、 /usr/local/lib/python2.7/dist-packages
の下にPython-LDAPをインストール必要があります。
(おまけ) CSVからvarsを読み込むモジュールを作りました¶
サンプルのPlaybookではユーザーの作成や、SSH公開鍵を登録するための タスク もあるのですが、ユーザー作成用のパラメータや公開鍵をいちいちYAMLで記述するのはとても億劫です。なので、CSVで記述したものをvarsとして読み込むことのできる include_csv というモジュールもついでに作りました。使い方としては、コアモジュールの include_vars のような使い方になります。詳しくはAnsible GalaxyのREADMEのページを参照してください。
まとめ¶
今までは、Ansibleらしくない書き方でslapdの構築を行い、それが故に冪等にすることが難しいため変更はAnsibleで行わない、という運用になってしまっていましたが、このansible-ldapモジュールのおかげで冪等性を保つことができるようになりました。個人的には ldapvi コマンド、Python-LDAPに続く、LDAPの運用・利用が非常に楽になるツールが登場したと思ってます。勝手に三種の神器と呼びたい。
また、include_csv も便利そうという意見ももらったので結構うれしいですね。 7
footnotes
- 1
11月からRuby on Railsの仕事をすることになり、業務では現時点ではPythonもLDAPも使っていません。
- 2
マージされるかはわかりませんが。
- 3
ansible 2.0.0-0.8.rc3 も試してみましたが、現状ではまだ
ansible-galaxy
コマンドが Python3 に対応していませんでした。- 4
頻度の問題で、結局作らずに済ませてしまってきたのですが…。
- 5
Debianシステムの場合。
- 6
今回の話と少しずれるので省略します。
- 7
ちなみに今回、初Ansible Galaxy、つまり初のAnsible モジュール作成、初のアカウント作成、初のRole登録、初の
ansible-galaxy
コマンド利用、と初ものづくしでした。
Changed the display manager to slim instead of gdm3¶
一週間ほど前(2015/10/12)から、stretch/sidでパッケージのアップデートを行うと、次回のOS起動時にDisplay managerが起動せず、ttyのログインプロンプトが延々と明滅してまともにログインすらできない問題に遭遇しました。
私物のMacBook ProのVirtualBox上で動かしているSidは、VirtualBox用のカーネルモジュールを再作成してやることで回避できました。
$ sudo dpkg-reconfigure virtualbox-guest-dkms
しかし、仕事で使っているMacBook ProのVirtualBox上のSid 1 ではこれでは解決せず、recovery modeで/var/log/Xorg.0.logを確認してみたところ、
(EE) dbus-core: error connecting to system bus: org.freedesktop.Dbus.Error.FileNotFound (Failed to connect to socket /var/run/dbus/system_bus_socket: No such file or directory
というログが出ていたのでパッケージを確認してみたら、 libdbus-c++-10v5
パッケージが無く、これをインストールすると、 startx
コマンドでX Window systemが起動するようにはなりました。 2
ところが、これでもgdm3は起動しません。そこで、現在Debianでサポートされている Display manager を確認して、slimに変更してみました。これは正常に起動できました。
デフォルトのセッションがawesomeではなかったので、 /etc/slim.conf
の login_cmd
を
diff --git a/slim.conf b/slim.conf
index c82f73b..413e111 100644
--- a/slim.conf
+++ b/slim.conf
@@ -33,8 +33,9 @@ authfile /var/run/slim.auth
# NOTE: if your system does not have bash you need
# to adjust the command according to your preferred shell,
# i.e. for freebsd use:
-# login_cmd exec /bin/sh - ~/.xinitrc %session
-login_cmd exec /bin/bash -login /etc/X11/Xsession %session
+#login_cmd exec /bin/sh - ~/.xinitrc %session
+#login_cmd exec /bin/bash -login /etc/X11/Xsession %session
+login_cmd exec /bin/bash -login /etc/X11/Xsession awesome
# Commands executed when starting and exiting a session.
# They can be used for registering a X11 session with
と直接指定しています。 3
普段、Gnomeではなくawesomeしか使っていないので、別に gdm3を使う理由も無いので、とりあえずこれで良しとします。問題のなかった私物の環境の方もslimに変更しようかな。
footnotes
Rewrote the sctipt of OpenSSH AuthorizedKeysCommand for LDAP public key¶
OpenSSH 6.2以上で実装された AuthorizedKeysCommand
を使って、LDAPでの公開鍵認証用のシェルスクリプトを Golang で書き換えました。その理由などはいいからコードはよ、という方は、
次のリンク先からドキュメントと合わせてどうぞ。
背景¶
現状、下記のようなシェルスクリプトを /etc/ssh/searchkey.sh
として用意し、
#!/bin/sh -e
uri="ldap://ldap.example.org"
search_base="ou=People,dc=example,dc=org"
uid=$1
search_filter="(&(objectClass=posixAccount)(uid=${uid})(description=limited))"
ldapsearch -x -LLL -H $uri -b $search_base $search_filter sshPublicKey |\
grep -v '^dn:' | sed '
s/sshPublicKey: //g
s/^ //g
' | tr -d '\n'
このスクリプトのパスを sshd_config
の AuthorizedKeysCommand
に設定することで、 openssh-lpk.schema を使ったLDAPでの公開鍵認証を行っています。ちなみにその前(Ubuntu PreciseやDebian Wheezy)では OpenSSHのソースパッケージにopenssh-lpkパッチを適用して、カスタムビルドパッケージを配布していました。 [LPK0] [LPK1] [LPK2]
問題発生¶
Trusty, Jessieから AuthorizedKeysCommand
でのシェルスクリプトに切り替えてから、しばらく問題がなかったのですが、あるタイミングから公開鍵認証ができなくなりました。 1
原因は2つ。一つは sshPublicKey
の値が base64 encodeされている場合。上記のスクリプトだとdecodeしていないため、authorized keysとして機能しません。
もうひとつは、複数の公開鍵が登録されている場合。複数行あった場合の処理も行っておらず、複数のエントリがあった場合は一行に連結されてしまいます。
対応検討¶
最初、シェルスクリプトで対応しようかと思ったのですが、厄介なのが、複数キーを登録しているユーザで、ある鍵は base64 encodeされておらず、ある鍵はencodeされている、というケースが存在すること。シェルスクリプトだとテストコードも書きづらかったこともあり、ピュアGoで書かれている go-ldap を Gosh で試してみたら、base64 encodedの値も自動的にdecodeしてくれるので、Golangで書き換えることにしました。
openssh-ldap-pubkey での実装¶
go-ldapでエラー処理など省いて書くと実質的には下記のようになります。
c, _ := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "<host>", "<port>"))
defer c.Close()
bindRequest := ldap.NewSimpleBindRequest("", "", nil)
c.SimpleBind(bindRequest)
searchRequest := ldap.NewSearchRequest(
l.base, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
0, 0, false,
fmt.Sprintf("<filter>", "<uid>"), []string{sshPublicKeyName}, nil)
sr, _ := c.Search(searchRequest)
for _, pubkey := range sr.Entries[0].GetAttributeValues("sshPublicKey") {
fmt.Println(pubkey)
}
ただ、Goで書き換えても、 openssh-ldap-pubkey
コマンドのラッパースクリプトを用意して、LDAPサーバや search baseを指定するのはひと手間増えます。これは避けたいなと思いました。通常LDAPを使っている環境では nslcd
も併用していることが多いので、初期リリースとしては /etc/nslcd.conf
があればそこから設定を読み込むようにしました。 nslcd.confとopenssh-ldap-pubkeyコマンドのオプションの対応表はドキュメントに記載 してあります。なお、もしnslcd を利用していない場合はラッパースクリプトを用意する必要があります。この場合も ドキュメントに記載 しておきました。
RHEL系は?¶
皆大好きRHELベースのディストロでは、そもそも openssh-ldap というパッケージが用意されているので、 AuthorizedKeysCommand
を使う必要がありません。設定を合わせたい、という場合には同じやり方でできます。
SSSDの対応は?¶
DebianシステムでもSSSDパッケージは用意されています。ただ、デフォルトではSSSDになっておらず、個人的にもまだ使っていないので、こちらは未対応です。要望があれば対応するかも or パッチウェルカムです。
IPv6 only host in Dual stack でハマった¶
今の環境は、IPv4/IPv6の dual stackの構成で、IPv6がデフォルトで、ホストはIPv6 onlyまたはIPv4/IPv6のdual stackを選べるようになっています。なのでDNSは基本IPv6のアドレスに問い合わせする構成にしています。LDAPサーバはIPv4しか使えないネットワーク機器も利用するので、例えばldap.example.orgはAAAAレコードとAレコードを設定しています。通常、ldapsearchを行うと基本IPv6経由で行ってくれるので問題ありませんが、今回使ったgo-ldapはdual stackの場合、IPv4で名前解決をしてしまい、アクセスできないとそこで処理を中断してしまうことが分かりました。go-ldapの方を修正して、パッチを投げるのが筋でしょうが、とりあえず、今回はopenssh-ldap-pubkey側で net.LookupHost を使って、IPv6 onlyのホストでも正常に接続できるように 回避策 をとりました。
まとめ¶
やっつけで作るときはシェルスクリプトやAWK, Sedスクリプトは楽なのですが、メンテナンスを考えると結構しんどいので、テストコード書けるプログラミング言語で実装するほうがやはり良いですね。 2年位前だったら、今回のような場合、Pythonで実装していたと思いますが、クライアントに配布するようなユーティリティで、ディストリビューションの公式パッケージにしていない場合、Golangはシングルバイナリで配布できるのでやはり圧倒的に便利です。
あと、今回のユーティリティは主にDebianシステムが対象なのと、go-ldapは 公式Debianパッケージになって いるのでDebianパッケージ化しておくと、Ubuntu 16.04やDebian Stretch以降で便利です。気が向いたら ITPするかもしれません。
参考文献¶
footnotes
- 1
と言ってもカスタムビルドパッケージを使ったPreciseやWheezyでは問題が無く、現環境ではCentOSに比べ、Ubuntuを使っている人そもそも少ないこと、後述のbase64 encodeされているのも一部のユーザであるため、気づいている人が少ないのが現状でした。
Upgraded to OS X El Capitan¶
会社用のMacBook Pro(Retina, 15-inch, Early 2013)と私物のMacBook Pro 8,2(15-inch)をEl Capitanにアップグレードしてみました。 一部、騒がれているような問題にも特に遭遇することもなく、2台とも正常にアップグレードできました。
メイン環境のVirtualBox上のSidさんは今日も日常通りです。VirtualBoxをフルスクリーンでしか基本使ってないのですけどね。
VirtualBox入れてない、Mac miniは問題が落ち着いた頃にアップグレードしてみます。
そろそろ私物のMacBook Pro (15-inch)、新しいの欲しいなぁ。
Mac miniといえば¶
先日、ようやく妻の古いWindowsからiTunesのデータをMac miniに移行しました。まだiTunesに登録してないCDが結構あるので、USB接続のDVDドライブ見てみたら、(使用頻度を考えると)結構高いのですね。うーん。
売り上げランキング: 48
I have user registration to HP Helion Public Cloud in order to use the Swift¶
自分で作っているバックアップ用のツール(backup2swift)とそのライブラリ(swiftsc)のIdentity API v3対応のため、KeyStoneとSwiftの環境を作るのが面倒だったのでHP Helion Public Cloudに登録してみました、という使ってみた系のお話です。
結論からいうと、 OpenStack Days Tokyo 2015 で個人情報と引き換えにもらった
売り上げランキング: 40,426
この本の最初の方で紹介されているHP Helion Public Cloudの登録方法を見れば分かるはずだと思います。 1
手間取った点¶
ユーザーアカウント登録時¶
一つは、ユーザーアカウント登録の時。まず、contact infoの登録で住所の部屋番号を間違えて登録した記憶がありました。 その後支払い情報を登録するときにも、住所を登録する必要があります。この時には正しく入力しました。 その登録直後に表示されたメッセージには、セキュリティ上の問題があるやらなんやらで確認するのにしばらくかかるよ、みたいな内容が表示されました。なので住所間違えたのがまずかったかな?と思ったわけですが、しばらく(10分くらい?)したらaccount activatedのメールが来たので、メール来るまでの間、なんか面倒なことになってないだろうか嫌だなぁ、ともやもやしていただけで、結果的には杞憂に過ぎませんでした。 2
Identity API用の情報確認¶
Identity API v2.0 とv3とで、エンドポイント以外に、認証に必要な情報が異なるため、Helionのダッシュボードから探すのに手間取りました。
前者ではユーザー名はユーザーアカウント登録時に設定した User Name
、パスワード、および自動生成される Project Name
、後者では User Nameと対で自動生成される User ID
と自分で設置したパスワード、および やはり自動生成される Project ID
となんか微妙に異なります。ぱっとみ Project Name
は Project ID
に -Project
というSuffixを付与すればええんだな、と思ってよく見たら実は違うし。
そういえば、Horizonの画面触ったのはEssex以来で、今同じ部門で進んでいるOpenStackの導入にはまーったく絡んでいないですし 3 。 あとIdentity API v3を触ってたのも v3 がリリースされる前の時だったしなぁ、という言い訳をしておきます。
ConoHaは?¶
Helion登録する前に ConoHa by GMO でもよいかなと思ったのですが、こちらは まだ Identity API v2.0のみでv3には対応していません でした。残念。
気になっている点¶
アカウント削除後、再登録できるのか?¶
Helion は3ヶ月は無料となっているのですが、既に 対応 は 済んだ ので、ほったらかして忘れてしまい課金されてた!というのを避けるために、もう解約しようと思っているのですが、また何らかの理由で必要になったときに、再度新規登録すればまた使えるのかなぁ、というところですね。無料試用期間も復活するとさらに良いのですが。
そういえば日本語情報無いですね¶
HP Helion関係の日本人エンジニアの方、いると思っているのですが日本語ブログとか無いなぁ、と気づいたのですが、HP Helion Pubilc Cloudを売るのが仕事ではないからなんでしょうかね、と勝手にトスを上げてみて、終わります。
footnote