前回まででApacheにKerberos認証を入れた所までやりましたが、認可の設定がまだでした。
Apacheに乗っかっているWebアプリケーションで基本的なグループを使った認可をやりたいのですが、Kerberosはご存じの通り認証の技術であって認可はできません。今回の一連の取り組みではKerberos認証+LDAP認可の仕組みで作っているので、Webアプリケーションでの認可にはLDAPを使用します。
Webアプリごとにグループ情報にアクセスするためのDNやパスワードを設定しているとどこかで必ずミスして漏洩するので、ApacheでDNとパスワードを取得しておき環境変数経由でWebアプリに受け渡します。
まずは現在までの構成をおさらいです。
SSSD経由のときはKeberos認証と共にLDAPのグループを取得できるように設定されています。
...
# 認可用
ldap_group_search_base = ou=Groups,dc=home
ldap_id_mapping = false
...
ところがKeytabを使うサービスはGSSAPIで直接Keberosとやりとりしちゃうので、LDAPからの情報は自力で取得する必要があります。
グループ情報へのアクセス用ユーザーを作る
Webアプリケーションからグループ情報にアクセスするためのユーザーを作ります。アクセスするための情報は何らかの形でApacheやその先のWebアプリケーションに取り込むため、基本的には漏洩するものとして考えます。
少なくともWebアプリケーション開発者はDNとパスワードを取得できるので、そこから先も考慮にいれるとほぼ確実に漏洩します。
なので漏洩しても問題無い範囲しかアクセスできないユーザーでアクセスするという考え方になります。LDAP全体の管理者アカウントでのアクセスはダメ絶対。
それでは管理用ユーザーを格納するou=AdminUsers
というグループを作りcn=group_reader
というユーザーを作りましょう。今回はnsPersonを使いました(cnがキーになりuserPasswordを持っている最小objectClass)
※漏洩前提でも一応パスワードはしっかり設定しておきましょう
ACIを設定する
この記事のメインです。ウイザードの使い方が微妙にわかりにくかったのでメモしておこうと。
まず アクセスコントロール対象 のグループを選択しACIs設定ウィザードを実行します。
ACI追加ウィザードボタンで追加を開始します。コピペ等で手動でACIを追加するボタンで一気に設定することもできますし、後からACI編集ボタンで修正を行うこともできます。
ACIの名称とターゲットの設定(target)
ウィザードを実行すると名前と対象の入力を求められます。対象を選択してウィザードを立ち上げているので対象はたぶんそのままのはずです。設定名だけ決めちゃいましょう。
ターゲット属性(targetattr)
設定すると上手く動きませんでした_(:3」∠)_
空で進めると(targetattr="*")
が設定されます。
ターゲットフィルタ(targetfilter)
検索対象となるグループがもつobjectClassを設定します。
エントリの移動
なんですかねこれ(´・ω:;.:...
Gemini先生に解説をお願いしたけどよく分からなかったのでとりあえず空にしました。
アクセス権の設定(acl)
今回の目的、readとsearchを設定します。
バインドルール(userdn)
どのような権限をもってアクセスするかの設定、、、というとわかりにくいですが今回でいえば単に実行ユーザーの設定です。
グループ情報取得のために最初に作ったcn=group_reader,ou=AdnimUsers,dc=homeというユーザーを設定します。
検索ベースの横のリンクをクリックするとツリーで選択できるので便利です。後はエントリを検索の横の→ボタンを押すと利用可能なエントリにユーザー名がでてきますので、右に移してバインドルールの追加ボタンで確定させます。
日時制限
必要があれば。なければそのままNext。
確認画面
ここで追加されるACIのテキストが表示されるので、確認・修正ができます。
ACLの追加ボタンを押して確定させます。
最初GUIの使い方が分からず苦労しましたが、分かってしまえばざっと設定できて楽だし後から簡単に修正できるし便利ですね☺️
ldapsearchで確認
上手く設定できていればladapsearchコマンドでグループの情報が取得できるはずです。
ldapsearch -x -H ldaps://localhost:636 -D "cn=group_reader,ou=AdminUsers,dc=home" -W -b "ou=Groups,dc=home" -s sub "(objectClass=groupOfNames)"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <ou=Groups,dc=home> with scope subtree
# filter: (objectClass=groupOfNames)
# requesting: ALL
#
# SSHAllow, Groups, home
dn: cn=SSHAllow,ou=Groups,dc=home
objectClass: top
objectClass: groupOfNames
objectClass: posixGroup
cn: SSHAllow
gidNumber: 10000
memberUid: test_user
# SMBFull, Groups, home
dn: cn=SMBFull,ou=Groups,dc=home
objectClass: top
objectClass: groupOfNames
objectClass: posixGroup
cn: SMBFull
gidNumber: 10001
memberUid: test_user
# search result
search: 2
result: 0 Success
# numResponses: 3
# numEntries: 2
Webアプリ(on Apache)で使用する
とりあえずCGIを動かして確認してみます。LDAPアクセス用の情報をSetEnvで設定しつつ、デフォルトのcgi-binの設定を書き換えてCGI実行とKerberos認証を埋め込みます。
...
SetEnv LDAP_DN "cn=group_reader,ou=AdminUsers,dc=home"
SetEnv LDAP_PASSWD "********"
...
<Directory "/var/www/cgi-bin">
AllowOverride None
# Options None
Options +ExecCGI
SetHandler cgi-script
AuthType GSSAPI
AuthName "Kerberos Login"
GssapiAllowedMech krb5
GssapiCredStore keytab:/etc/httpd/httpd.keytab
Require valid-user
# Require all granted
</Directory>
構文チェックしてOKならリロード。
$ apachectl configtest
$ systemctl reload httpd
チェック用CGIを置いてアクセスしてみます。
#!/usr/bin/env python3
import os
from ldap3 import Connection, Server, ALL as LDAP_ALL, SIMPLE, Reader
# LDAPサーバーの情報
server_address = 'localhost'
server_port = 636
use_ssl = True
user_dn = os.getenv("LDAP_DN")
password = os.getenv("LDAP_PASSWD")
search_filter = '(objectClass=groupOfNames)'
base_dn = 'cn=SSHAllow,ou=Groups,dc=home' # 拾いたいグループ
attributes = ['*'] # 拾いたいattribute
print("Content-Type: text/plain; charset=utf-8")
print()
server = Server(server_address, port=server_port, use_ssl=use_ssl, get_info=LDAP_ALL)
conn = Connection(server, user=user_dn, password=password, authentication=SIMPLE)
if not conn.bind():
print(f"バインドに失敗しました: {conn.last_error}")
exit()
conn.search(base_dn, search_filter, search_scope='SUBTREE', attributes=attributes)
for entry in conn.entries:
print(f"{entry}")
conn.unbind()
ブラウザでアクセスするとグループの情報が得られるので、REMOTE_USER
とmemberUid等を使ってアクセスを許可するかどうか決定できます。
DN: cn=SSHAllow,ou=Groups,dc=home - STATUS: Read - READ TIME:
cn: SSHAllow
gidNumber: 10000
memberUid: test_user
objectClass: top
groupOfNames
posixGroup
なお仕事では、最後のunbind()を忘れてたり例外で通らないパスがあったりすると、管理部門のひとからお問い合わせ(という名の怒られ)がきます_(:3」∠)_