LoginSignup
5
8

More than 5 years have passed since last update.

KerberosのバックグラウンドにOpenLDAPを使う

Last updated at Posted at 2019-01-25

ネタ振り

この記事で目標とするサービスの概要は、以下の通りです。

環境: 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ファイルを置いておきます。

tls.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であることは禁止されています(ただし、実際には動くことが多いようですが)。

db.gc
$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ファイルを書き変えます。

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ベースでアクセス制御を行います。

5
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8