1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

OpenLDAPのバックエンドデータベースをブロックチェーンにした話

Last updated at Posted at 2022-04-12

はじめに

OpenLDAPLDAPを実装したオープンソースソフトウェアです。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で環境を作ります。

ディレクトリ構造

directory.png

イメージとしては複数の会社が参加するディレクトリです。rootの下に複数の会社(org1, org2)がぶら下がり、その下に従業員(taro.yamada, jiro.sato)が所属しています。

Thunderbird

アドレス帳でLDAPを検索できます。以下のように設定すると org1 の従業員を検索できます。

thunderbird01.png

taro で検索すると表示されました。

thunderbird02.png

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 にアクセスすると認証を要求されます。

apache01.png

正しく入力すると表示できます

apache02.png

ちなみに、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

以下のようにログインできます。

ssh01.png

ちなみに、taro.yamada さんは権限を持っていないためアクセスできません。

参考資料:
https://linux.die.net/man/5/sssd-ldap

考察

バックエンドをブロックチェーンにしても正しく動くことがわかりました。また、ブロックチェーンを使用することで「自分の情報は自分で管理する」というDID的世界観に一歩近づきました。

今回は簡単な検証でしたが、以下のようにするとより良いと考えています。

  1. 従業員のエントリは ou=person,dc=root以下に置き、org1, org2内部ではエイリアスとしてこれを参照
  2. 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

1
1
0

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?