ネタ振り
この記事で目標とするサービスの概要は、以下の通りです。
環境: Debian GNU/Linux 9 (stretch) on Google Cloud Platform
この他、DNSとしてBIND9を立てます。
以下の説明では、${xxx}となっているところは、xxxに従って、自分の環境に合わせて設定してください。また、説明が不十分だと思われた方は、https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_ldap.html をみると良いかもしれません。
では、いってみよう。
インストール
Kerberos関係
apt install krb5-admin-server krb5-kdc krb5-kdc-ldap
krb5-admin-server/stable,now 1.15-1+deb9u1 amd64 [installed]
MIT Kerberos master server (kadmind)
krb5-kdc/stable,now 1.15-1+deb9u1 amd64 [installed]
MIT Kerberos key server (KDC)
krb5-kdc-ldap/stable,now 1.15-1+deb9u1 amd64 [installed]
MIT Kerberos key server (KDC) LDAP plugin
(apt searchの結果より引用)
OpenLDAP関係
apt install ldap-utils slapd
ldap-utils/stable,now 2.4.44+dfsg-5+deb9u2 amd64 [installed]
OpenLDAP utilities
slapd/stable,now 2.4.44+dfsg-5+deb9u2 amd64 [installed]
OpenLDAP server (slapd)
(apt searchの結果より引用)
インストール時に色々設定を聞かれますが、以下のように答えてください。
- DNS dmain name: ${domain}
- Organization name: ${org}
- Administrator password: ${admin passwd}
- Database background: MDB
設定を間違えたときは、
dpkg-reconfigure slapd
で変更できます。
BIND9関係
apt install bind9
OpenLDAPの設定
OpenLDAPの設定方法が最近変更されました。古い記事では、slapd.confで設定しているのですが、現在は非推奨です。この記事では、現在推奨されている、dynamic runtime configuration engineを使う方法で設定していきます。
(参考: https://www.openldap.org/doc/admin24/slapdconf2.html)
この方法では、設定を(少なくともクライアントからは)他のデータと同じようにデータベースに格納します。そのときに使われるAttributeはolc(OpenLDAP Configuration)というprefixで始まることが多いです。
TLSの有効化
まずは、TLSに使う証明書を作成します。Debian系のLinuxのパッケージからOpenLDAPをインストールすると、OpenLDAPはOpenSSLでなくGnuTLSを使ってコンパイルされていることが多いので、今回はGnuTLS系のコマンドのcertutilを使って鍵を作成します。
TLSの設定に関しては、
https://qiita.com/JhonnyBravo/items/b18341b4cf20b9d865e4#tls
または、
https://www.openldap.org/doc/admin24/tls.html
を見てください。
参考までに、僕が使ったldifファイルを置いておきます。
dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/ca.crt
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/ldap.crt
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/ldap.key
-
add: olcTLSDHParamFile
olcTLSDHParamFile: /etc/ldap/dhparam.pem
-
add: olcTLSVerifyClient
olcTLSVerifyClient: allow
上手くいかないときは、以下のポイントを確認してみてください。
- Subjectのcnがサーバーのドメインと一致しているか
例えば、
openssl x509 -text -noout -in ${server cert} | grep Subject
# Subject: CN = ${domain}, ...
のとき、${domain}がサーバーのドメインと一致していればOK。
- CAの公開鍵のKey Usageにcaが入っているか
例えば、
openssl x509 -text -noout -in ${ca cert} | grep CA
# CA=True
ならOK。
- Server証明書がSelf-signedでないか
例えば、
openssl x509 -text -noout -in ${server cert} | grep Issuer
openssl x509 -text -noout -in ${server cert} | grep Subject
の結果が一致していなければOK。
本当は、TLSでClient認証もできるらしいのですが、gnu_tls周りの処理が上手くいかなかった*ので省略します。Client認証では、Client証明書を電子署名に使うので、Key Usageにdigital signitureを追加しておくと良いです。
* サーバー側で出るgnutls_certificate_verify_peers2 failed -49
というエラーの解決法が分かりませんでした。調べたところ、ldapsearch等のクライアントが証明書をサーバーに渡していないようなので、サーバー側の問題ではなく、クライアントの問題のようです。そこまでは分かったのですが、詳細は不明です。手がかりとなる情報を求めています。jxplorerで試そうとも思いましたが、鍵の登録がうまくいかず、結局断念しました。
LDAPサーバーの起動
systemctl enable slapd # 次回以降のブート時に起動
systemctl start slapd # 今すぐ起動
を実行してください。
ファイヤーウォール等を設定している人は、設定の変更をしてください。(OpenLDAPに限らず)LDAPサーバーが用いるスキーマにはldapとldaps(ldap over TLS)およびldapiがあります。
schema | port |
---|---|
ldap | 389 |
ldaps | 636 |
この記事ではTLSを有効化した上でOpenLDAPと接続するので、636番ポートはクライアントからアクセスできるようにしてください。
ldapiスキーマについては、余談 ldapiスキーマを参照してください。
LDAPクライアントのインストール
OpenLDAPにデータを登録するのは、コマンドラインからでもできるのですが、GUIクライアントもたくさんあるので、ここではGUIで登録を行います。そのために、まずクライアントソフトウェアをインストールします。
クライアントはなんでも良いのですが、僕はjxplorerを選びました。ちなみに、僕のクライアント用環境はmacOS High Sierraです。jxplorerの使い方についての詳細は割愛しますが、接続するときに使うユーザーについてだけ書いておきます。
OpenLDAPクライアントに接続するユーザーはDN(distinguished name)で選択するのですが、それは
sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b cn=config olcDatabase={1}mdb 2>/dev/null | grep olcRootDN
# olcRootDN: ${admin dn}
を実行したときに表示される${admin dn}を用いてください。パスワードはインストール時に設定した${admin passwd}になります。
Kerberos用schemaの読み込み
sudo ldapadd -H ldapi:/// -Y EXTERNAL -f /etc/ldap/schema/kerberos.ldif
Kerberos用エントリーの作成
KerberosサーバーがOpenLDAPにアクセスするときに使う(bindする)エントリーを作成します。adminサーバー用のエントリー(dn: ${kadmin dn})と、kdcサーバー用のエントリ(dn: ${kdc dn})を作成します。それぞれのエントリーが満たすべき条件は、
- simpleSecurityObject ObjectClassを含むこと
- userPassword attributeが設定されていること
です。ここで設定したuserPasswordはKerberosを設定するときに使います。
また、Kerberosが情報を格納するのに使う、containerを作っておきます。要件は、
- krbContainer ObjectClassを含むこと
です。
さらに、後でユーザーを追加したときにそのユーザーの情報が格納される場所を作ります。要件は、
- containerっぽいObjectClassを含むこと(organizationとか)
です。
ACL(Access ControL)の設定
ACL設定を書き込むためのldifファイルを作ってください。その際、先ほど作った${kadmin dn}と${kcd dn}がKerberos containerとrealmのsubtreeに対するwrite権限を持つようにしてください。
ACL設定の評価方法としては、一番初めにマッチしたものが採用されます。このことに注意してファイルを作ってください。複雑な設定を作ろうとすると結構ハマります。
例えば、最も簡単な場合は、以下のようなファイルになります。
dn: olcDatabase={1}mdb,cn=config
changetype: delete
delete: olcAccess
dn: olcDatabase={1}mdb,cn=config
changetype: add
add: olcAccess
olcAccess: {0}to attrs=shadowLastChange
by self write
by * read
olcAccess: {1}to dn.subtree="${krb container}"
by dn.exact="${kadmin dn}" write
by dn.exact="${kdc dn}" write
by * none
olcAccess: {2}to attrs=userPassword
by self write
by anonymous auth by * none
olcAccess: {3}to * by * read
これを任意の場所に拡張しldifを付けて保存します(${ldif file})。次に、以下のコマンドを実行し、LDAPデータベースを更新します。
sudo ldapmodify -H ldapi:/// -Y EXTERNAL -f ${ldif file}
以上でACLの設定は終わりです。参考までに、僕のLDAPエントリを載せておきます。
# sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b cn=config olcDatabase={1}mdb
dn: olcDatabase={1}mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=${domain}
olcLastMod: TRUE
olcRootDN: cn=admin,dc=${domain}
olcRootPW: {SSHA}xxxxxxxxxxxxxxxxxxxxxxx
olcDbCheckpoint: 512 30
olcDbIndex: objectClass eq
olcDbIndex: cn,uid eq
olcDbIndex: uidNumber,gidNumber eq
olcDbIndex: member,memberUid eq
olcDbMaxSize: 1073741824
olcAccess: {0}to attrs=shadowLastChange by self write by * read
olcAccess: {1}to attrs=userPassword
by self write by anonymous auth
by * none
olcAccess: {2}to dn.base="dc=gc"
by dn.exact="cn=admin_server,dc=gc" write
by dn.exact="cn=kdc,dc=gc" write
by * break
olcAccess: {3}to dn.subtree="cn=kerberos,dc=gc"
by dn.exact="cn=admin_server,dc=gc" write
by dn.exact="cn=kdc,dc=gc" write
by * break
olcAccess: {4}to dn.subtree="o=main,dc=gc"
by dn.exact="cn=admin_server,dc=gc" write
by dn.exact="cn=kdc,dc=gc" write
by * break
olcAccess: {5}to dn.subtree="o=test,dc=gc"
by dn.exact="cn=admin_server,dc=gc" write
by dn.exact="cn=kdc,dc=gc" write
by * break
olcAccess: {6}to * by * read
ACLについては、OpenLDAP Software 2.4 Administrator's Guideを参照。breakはそこには書いてないので、https://www.openldap.org/faq/data/cache/454.html を参照。
BINDの設定
named.conf
僕の環境では、デフォルトでnamed.confはいくつかのファイルに別れており、named.confファイルでは、それらのファイルをincludeして纏めているようでした。
ファイル名と設定内容の組み合わせは以下の通りにしました。
ファイル名 | 設定内容 |
---|---|
named.conf.options | global option |
named.conf.locals | zone情報 |
named.conf.acl | access control(自作) |
named.conf.logging | log設定(自作) |
セキュリティ上、気にすべき点がいくつかあります。
- DNS cache poisoning対策
- DNSSEC
- 返答portのランダム化
- recursionリクエスト送信元の限定
- DNS amplified Dos対策
- リクエスト送信元の限定
個人でBINDを立ち上げるときは、ローカルネットワーク用であることが多いと思いますので、そもそも外部公開しないようにするのも効果があります。また、好き勝手ドメイン名を決められるので、トップレベルドメインを操作することもできなくないため、DNSSEC対応が簡単です。
ローカルネットワークに限定する方法として、BIND9には受信NIC(正確にはリクエストが送られてきたIPアドレス)によるアクセス制御ができるので、ローカルネットワークに接続しているNICからのみリクエストを許可するといいでしょう。このような仕組みを提供するオプションは、基本的に"on"で終わる名前です。
受信NICによるアクセス制御について、もう少し深掘りしてみました。(余談 BIND9 受信IP)
DNSSECはまだ導入していないので、ここでは省略します。
zone情報
サーバーのドメイン情報を登録します。ここでは、AレコードとSRVレコードを登録します。SRVレコードについて、サービス名はIANAのサイトで検索してみてください。
以下に、僕が使っているゾーン情報を載せておくので、テンプレート代わりにしてください。注意点としては、mainとldap-master、krb-masterのIPアドレスが同じだからといって、CNAMEにしてはならないことです。SRVレコードに使うドメイン名がCNAMEであることは禁止されています(ただし、実際には動くことが多いようですが)。
$TTL 604800
@ IN SOA ns root.gc. (
1 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns
ns IN A 172.16.0.2
main IN A 172.16.0.2
ldap-master IN A 172.16.0.2
krb-master IN A 172.16.0.2
_ldap._tcp IN SRV 0 5 389 ldap-master
_ldaps._tcp IN SRV 0 5 636 ldap-master
_kerberos._tcp IN SRV 0 5 88 krb-master
_kerberos-adm._tcp IN SRV 0 5 749 krb-master
起動
systemctl enable bind9
systemctl start bind9
使用するDNSサーバーの設定
どのDNSサーバーを使うかの設定は、今までresolv.confファイルで行なっていましたが、最近はNetworkManagerの台頭とともに、nmcliコマンドで行うことも多くなりました。そこで、この記事ではnmcliコマンドで行おうと思います。とはいえ、普通は簡単ですが。。
nmcli c down ${nic}
nmcli c m ipv4.ignore-auto-dns yes ipv4.dns ${dns IP-addr}
nmcli c up ${nic}
service network-manager restart
以上です。resolv.confファイルを見てみて、上手く設定できているか確認してください。
上手く設定できていない方は、普通とは違う特別コースを行く必要があります。例えば、先ほどのコマンドの${nic}という部分が実はnicの名前ではないとかいうことを意識する必要があるかもしれません。それについては、nmcliコマンドの隅をつつくを参考にしてください。
Kerberosの設定
さて、やっと最後の設定です。
krb5.conf
重要なところだけピップアップしました。詳しくは、https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html を見てください。
[libdefaults]
default_realm = ${default domain}
[realms]
${realm domain} = {
kdc = ${kdc domain} # なくても良い
admin_server = ${kadmin domain}
database_module = openldap_ldapconf
default_domain = ${domain}
}
[domain_realm]
{domain} = {realm domain}
[dbmodules]
openldap_ldapconf = {
db_library = kldap
ldap_kdc_dn = ${kdc dn}
ldap_kadmind_dn = ${kadmin dn}
ldap_kerberos_container_dn = ${krb container dn}
ldap_service_password_file = /etc/krb5kdc/service.keyfile
ldap_servers = ldaps://{ldap domain}
ldap_conns_per_server = 5
}
realmに関する設定のうち、kdcのドメイン名は設定しなくても良いです。これは、BIND9についての説明通りSRVレコードを設定していれば、そのレコードを使って自動取得してくれるからです。ただし、そのためにはrealmの名前とドメイン名とが[domain_realm]で対応づけられている必要があります。
理論的にはadmin_serverのほうも不要ですが、Kerberosの実装が不完全らしく、現在のところは書かなくてはならないそうです。
Note that the admin_server entry must be in the krb5.conf realm information in order to contact kadmind, because the DNS implementation for kadmin is incomplete.
(https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html より)
krb5kdc.conf
[kdcdefaults]
kdc_ports = 750,88
[realms]
${realm domain} = {
admin_keytab = FILE:/etc/krb5kdc/kadm5.keytab
key_stash_file = /etc/krb5kdc/stash
acl_file = /etc/krb5kdc/kadm5.acl
max_life = 10h 0m 0s
max_renewable_life = 7d 0h 0m 0s
default_principal_flags = +preauth
}
とくに説明はいらないでしょう。詳しくは、https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/kdc_conf.html を見てください。
LDAPサーバーに対する認証
KerberosサービスがLDAPにアクセスするときに、いちいちパスワードを入力するのはあまりに不便なので、事前にパスワードをstashして(Kerberosサーバーに保持させて)おきます。
kdb5_ldap_util stashsrvpw ${kadmin dn}
kdb5_ldap_util stashsrvpw ${kdc dn}
Realmの作成
kdb5_ldap_util -D ${admin dn} create -subtrees ${princ container dn} -r ${realm domain} -s
${admin dn}はLDAPデータベースの管理ユーザーのDNです。
今作ったrealmに新しくprincipalを作ると、${princ container dn}以下にそのprincipalの情報が保存されます。
管理者principalの作成
kadmin.localコマンドを使って、Kerberosの管理者用principalを作ります。似たコマンドに、kadminというのがありますが、これはリモート用で、ローカルの場合はkadmin.localを使うという使い分けをします。管理者用principalを設定する前は、kadminは使えないと思います。
まず、管理者用principalを作ります。
kadmin.local # 対話モードで起動
kadmin.local: add-princ ${name}/admin
kadmin.local: exit
kadm5.acl
次に、今作ったprincipalの権限を設定します。それには、kadm5.aclファイルを書き変えます。
*/admin@* *
この設定で、全てのrealmにおいて、*/adminにマッチするprincipalはそのrealmにおける全権限をもつということを設定できました。つまり、管理者ということです。
firewallの設定
firewallを使用している場合は、次のポートを開けてください。
port | サービス名(IANA) | 用途 |
---|---|---|
88 | kerberos | kdc server |
749 | kerberos-adm | admin server |
464 | kpasswd | kpasswd server |
サーバーの起動
kadminサーバーとkdcサーバーを起動させます。
sudo systemctl enable krb5-kdc
sudo systemctl start krb5-kdc
sudo systemctl enable krb5-admin-server
sudo systemctl start krb5-admin-server
接続をテストします。
kadmin -p ${name}/admin
対話モードに入れたら成功です。
これで、サーバーのセットアップは終了です。長くなってしまったので、この記事はここまでとします。サービスに対するkerberos認証の提供方法は、また今度の記事でということにします。
余談
ldapiスキーマ
記事の中で、ldapスキーマとldapsスキーマについては触れたが、ldapiスキーマについては触れていない。だが、記事で最も多く登場したのがldapiスキーマだと思います。
sudo ldapsearch -H ldapi:/// -Y EXTERNAL ...
ここに登場してたのです。これはどういうスキーマかというと、ldap通信をUnixソケットを使って行うものです。実は、デフォルトの状態では、通常のTCPを使う方法でcn=config下の設定ファイルを変更することができません。必ずldapiスキーマを使う必要があります。さらに、かならずrootとして実行する必要があります。
それを確認するために、まずはcn=config下のデータベースのACLがどうなっているかを確認しましょう。それには、
sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b olcDatabase={0}config,cn=config -s base
を実行してみます。すると、
dn: olcDatabase={0}config,cn=config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external
,cn=auth manage by * break
olcRootDN: cn=admin,cn=config
のような結果が得られます。このうち、olcAccess attributeがconfig下のアクセス制御を行なっています。見ると、DNがgidNumber=0+uidNumber=0,cn=peercred,cn=external
,cn=authであるユーザーはmanage権限(全権)を持つことがわかります。このDNこそ、ldapiスキーマでrootとしてアクセスしたときに付与されるDNです。このことは、先ほどのコマンドを実行したときに、
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
と表示される(今までは省略していましたが)ことから分かります。そして、olcAccess attributeはそのDNにのみ権限を与えているので、それ以外の方法でcn=config下のデータベースにアクセスすることはできないと分かります(最後についているbreakについては記事に参考リンクを載せてあります)。
ldap-utilsに入っているクライアントとTLS
ldapsearch -H ldaps:/// -D ${admin dn} -W -b ${base dn}
としないように注意してください。これではサーバー認証が通りません。
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
と怒られます。エラーがあまりにざっくりとしているので、サーバー認証が通っていないことが分からず、デバッグに苦戦することが多いです(実体験)。
また前でも書きましたが、ldap-utilsに入っているクライアントは、TLSを用いたクライアント認証時に、なぜかクライアント認証鍵をサーバーに送ってくれないという現象に困っています。情報を持っている方はコメントをお願いします。
BIND9 受信IP
受信IPによるアクセス制御は、allow-query-on系とlisten-on系の二つがあります。
系 | 使える場所 | 弾かれた時 |
---|---|---|
allow-query-on | zone, view, global option | refusedが帰ってくる |
listen-on | global option | タイムアウトする |
ss -ul
で見ると分かりますが、listen-onで弾かれたNICはもはやlistenしていません。
気を付けないといけないのは、受信IPによるアクセス制御で127.0.0.1へのリクエストの許可を意識していないと、テストでびっくりすることがあることです(経験済)。dig @localhost domain
がタイムアウトする!!。
これと似たような関係のものに、deny-answer-addressとblackholeオプションがあります。こちらは、送信元のIPベースでアクセス制御を行います。