はじめに
OpenLDAPはLDAPを実装したオープンソースソフトウェアです。LDAPはディレクトリサービスを提供するプロトコルで、いろいろなソフトウェアで認証・認可の基盤として利用できます。会社だとMicrosoft Active Directoryが使われているケースが多いですが、それと似たようなことができます。
LDAPは人・モノに関する情報を木構造で管理します。この情報は何らかのデータベースに保存しますが、OpenLDAPではデータベース部分が変更できるような設計になっています。
そこで今回はflowブロックチェーンをデータベースとして利用する実験をしてみます。
なぜ LDAP * ブロックチェーン?
ブロックチェーンの応用分野としてDID (Decentralized IDentity)がホットです。例えば、MiscoroftはBitcoinをバックエンドとするIONというDIDを開発しています(ホワイトペーパーはこちら)。
Bitcoinを使用する場合、データサイズを極限まで削るための工夫が欠かせません。つまり、相当数の生データのハッシュをまとめたマークルツリーのルートをBitcoinに書き込むことになります(プライバシーの観点からも生データは書き込めません)。また、アプリケーションで認証・認可に使用するためにはその生データをどこに保存し、どうやって受け渡すかと言った取り決めが必要です。IONは理想的なDIDではありますが、普及させるには技術的な観点以外にも様々な障壁があると考えられます。
これに対し、LDAPはすでに世の中で広く使われている認証・認可(にもつかえる)技術です。ここにDID的な観点を入れられたら面白いんじゃないか?というのが動機です。また、プライバシーの観点を無視すればflowを使うことでデータをそのままオンチェーンで管理する事もできます。
バックエンド
OpenLDAPがサポートするデータベースにはShell backendと呼ばれるものがあります。これは、ldapadd
, ldapsearch
のようなコマンドの解析結果をそのまま外部のプログラムに渡します。つまり、flowにアクセスする外部プログラムを作成すればOpenLDAPのデータベースとして使うことができるということです。
毎回プロセスを起動するのでパフォーマンスはよくないです。が、OpenLDAPがC言語 v.s. flow SDKがGo言語 なのでOpenLDAPに直接組み込むのは辛いということで、今回はShellで実装しました。
詳細
flowで管理するもの
ブロックチェーンを使用する醍醐味は「分散環境で自分のデータは自分で管理する」です。なので、LDAPエントリそのものをflowのオンチェーンで管理することにしました。今回作成したコントラクトはこちらです。
LDAPエントリを表すリソースの定義が以下です。各アカウントが自身のストレージに保存し、Reader
インターフェースをCapabilityとして公開します。
pub resource Entry: Reader {
pub var attributes: {String: [String]}
init() {
self.attributes = {}
}
pub fun setAttributes(attributes: {String: [String]}) {
self.attributes = attributes
}
}
Capabilityはコントラクト本体のentries
にDicionaryとして保存します。
pub contract LdapDb {
pub let EntryStoragePath: StoragePath
pub let EntryPublicPath: PublicPath
pub let entries: {Address: Capability<&Entry{Reader}>}
...
pub fun setCapability(address: Address, capability: Capability<&Entry{Reader}>) {
self.entries[address] = capability
}
...
Go言語で開発するbackendはentries
を参照することで現在保存されているエントリをすべて参照することができます。
また、flow/CadenceはCapabilityをいつでも削除できるという素晴らしい特徴があります。なので、エントリを作ってCapabilityをentries
に格納しても、あとでCapabilityを削除すれば参照されなくなります。なんともDID的じゃないですか!
バックエンド
ここに実装があります。基本的にはOpenLDAPから標準入力で渡されたコマンドを解析して実行します。ただし、今回の実験の要件として、エントリの作成・管理は各個人が自身の秘密鍵でトランザクションを署名する必要があります。もし、OpenLDAPのバックエンドとしてエントリの作成を行うとこれが実現できません。(バックエンドがエントリを作ったら自分で管理できないですからね)
そこで、エントリの作成・管理は別アプリケーションとして実装します。バックエンドはLDAPクライアントからの検索クエリ(ldapsearch
)のみに応答するよう実装しました。ここを見ると、entries
にアクセスするためのCadence scriptが確認できます。
エントリの作成
ここに実装があります。アカウントの作成、エントリの作成、Capabilityの登録を行っています。
実験
Thunderbirdの住所録、Apache2のBasic認証、sshのログイン認証を試しました。まるっとDockerで環境を作ります。
ディレクトリ構造
イメージとしては複数の会社が参加するディレクトリです。rootの下に複数の会社(org1, org2)がぶら下がり、その下に従業員(taro.yamada, jiro.sato)が所属しています。
Thunderbird
アドレス帳でLDAPを検索できます。以下のように設定すると org1 の従業員を検索できます。
taro
で検索すると表示されました。
jiro.sato
さんは org2 の従業員なので、検索しても引っかかりません。
Apache2
authnz_ldap_moduleモジュールを読み込み、Basic認証でLDAPを使用できるようにします。
/internal
の.htaccess
に以下のように設定しています。これにより、Apacheはou=org1,dc=root
以下を検索し、cn=taro.yamada
がアクセスできます。
AuthName "Internal site authentication"
AuthType Basic
AuthBasicProvider ldap
AuthLDAPUrl "ldap://openldap-shell/ou=org1,dc=root?uid"
Require ldap-attribute cn=taro.yamada
参考資料:
https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html
結果
localhost:8081/internal
にアクセスすると認証を要求されます。
正しく入力すると表示できます
ちなみに、jiro.sato さんは権限を持っていないためアクセスできません。
SSH
SSHログインにLDAPを使用するには、sssdを利用します。今回の設定はこちらです。sssdについてはこちらの記事で詳細に解説されています。
以下の設定により、sssdはorg2を参照します。
ldap_search_base = ou=employee,ou=org2,dc=root
結果
SSHに jiro.sato さんでアクセスすると
$ ssh -p 8022 jiro.sato@localhost
以下のようにログインできます。
ちなみに、taro.yamada さんは権限を持っていないためアクセスできません。
参考資料:
https://linux.die.net/man/5/sssd-ldap
考察
バックエンドをブロックチェーンにしても正しく動くことがわかりました。また、ブロックチェーンを使用することで「自分の情報は自分で管理する」というDID的世界観に一歩近づきました。
今回は簡単な検証でしたが、以下のようにするとより良いと考えています。
- 従業員のエントリは
ou=person,dc=root
以下に置き、org1, org2内部ではエイリアスとしてこれを参照 - org1, org2等、各社を頂点とするツリー内部にエントリを追加できるのは、各社の(flow)アドレスに限定するようにコントラクトを記述
現状では任意のdnを持つエントリを作成できます。これでは自分が所属していない会社の従業員として勝手に登録できてしまうため、少々具合が悪いです。そこで、コントラクトがエントリを作成したアドレスを管理し、そのエントリの子エントリを作成できるのはそのアドレスに限定する処理を行う必要があります。ただし、ou=person,dc=root
だけは例外とし、誰でも自分のエントリを作成できるようにします。
つまり、各個人はou=person,dc=root
以下に自身のエントリを作成・管理し、org1に就職したらorg1の担当者が自社のツリーにその個人エントリへのエイリアスを作成します。
本当は今回の検証でエイリアスも試したかったのですが、検索フィルタを適当に実装したために動きませんでした。また、「エントリを作成したアドレスを管理」部分も、現在のコントラクトでは一アドレス一エントリしか保持できないので改良の必要があります。
ちなみに、Thunderbirdのアドレス帳はエイリアスを参照した検索をしてくれないのでちょっと悩ましいです。
参考資料:
https://docs.oracle.com/cd/E24001_01/oid.1111/b55919/oid_alias_entries.htm