FUSEを使ってコマンドをファイルでラップする
少し前からFUSEに興味が湧いて色々作りたいものを考えていました。年末年始にある程度動くものができたので一旦まとめてみます。
ちなみに数日前に同僚のharasouさんが cat するたびに内容が変わるファイル?を作った という記事を上げていて、内容がかなり被っているので二番煎じ感がすごいです。
何をするものか
大体GitHubのREADMEに書いていますが、以下のような設定ファイルをyamlで定義してnanafshiを使ってマウントすると、読み込んだり書き込んだりした時に内部でコマンドを実行するファイルが作成されます。
shell: /bin/bash services: - name: dir files: - name: example read: command: curl http://www.example.com/ - name: writable read: command: | touch /tmp/file cat /tmp/file write: async: true command: echo $FUSE_STDIN >> /tmp/file
このyamlを使ってマウントしてやると、設定ファイルの構造に従ってディレクトリとファイルが作成されます。
$ nanafshi -c config.yml /mnt/nanafshi $ tree /mnt/nanafshi /mnt/nanafshi └── dir ├── example └── writable
以下のように作成されたファイルをcatなどで読み込むと、内部で実行したコマンドの標準出力が表示されます。また、書き込みの場合はコマンドを実行するだけですが、環境変数として入力されたデータを渡せるようになっています。
$ cat /mnt/nanafshi/dir/example # curl http://www.example.com/ の結果 <!doctype html> <html> <head> <title>Example Domain</title> ...
内部コマンドで使用できる環境変数についてはGitHubの方でまとめていますが、ファイルを開いたプロセスのPIDやUIDなどもあり、必要に応じて増やしていくつもりです。
また、Macの場合は標準では使用できませんが、FUSE for macOS をインストールすればLinuxと同様に使えます。Windowsは未検証です。
何に使うのか
設定ファイルの動的生成
はじめに紹介した記事でharasouさんも言及していますが、アプリケーションの設定ファイルのパラメータをAPIやデータベースなどから取ってくるといったことができそうです。
外部ログ基盤との連携
readとwrite両方に対応していますので、例えば bash_history
のようなログファイルを以下の設定で用意してやれば、外部のログ管理基盤と直接やりとりしてログを保存/取得し、複数ホスト間で共有するといったこともできます。
services: - name: logs files: - name: bash_history read: # read時はGETリクエストで全ログを取得 command: curl https://store.example.com/tokibi/bash_history write: # write時はPUTリクエストで保存 command: curl -X PUT -H "Text:$FUSE_STDIN" https://store.example.com/tokibi/bash_history
このようなファイルはコンテナ上のログのような揮発性の高いデータの保存に対して有用になりそうです。ただ、パフォーマンス面は全く検証できていないので、本番環境で使えるような信頼性を担保するところが目標です。
コマンド単位の権限移譲
ファイル内部で実行されるコマンドは、nanafshiのプロセスからforkされて実行されるようになっています。なので、rootユーザがnanafshiを起動してマウントした場合は、ファイルアクセス時に実行されるコマンドもrootユーザの権限で実行されます。
また、ファイルアクセス時にどのようなコマンドが実行されるかは、該当のファイルをどのように参照しても(多分)見えないので、設定ファイルの所有権を厳密に設定すればファイルアクセスを行うユーザから見て内部で何が起きているか調べる手段がないという状態になります。
これらの特性を利用すれば、設定ファイルに記載したコマンドのみに限定して、その内容を隠蔽しつつ他のユーザに実行権限を委譲するといったことが可能です。例えばコンテナプロセスに対してバインドマウントでrootユーザで起動したnanafshiのファイルを提供すれば、Linuxのcapabilityなどを無視して色々なコマンドの実行権限を委譲できてしまいます。ただの脆弱性にもなりかねないので、基本的には専用のユーザなどを用意して起動することをおすすめします。
システム全体の複雑性がとても高くなるので、安易に色々やると痛い目にあいそうですね。
今後の予定
- テスト, ログ周りちゃんとする
- ディレクトリ, ファイルごとに所有権を設定できるようにする
- bash組み込みのreadコマンドのような対話的なコマンドを差し込めるようにしたい
- ファイルを開く時にMFAとか入れたい
- read時のcache実装
- write時のFUSE_STDIN環境変数あたりでインジェクションできそうなので対策する
まとめ
設定ファイルにコマンドを書けばいいだけなので、FUSEを使って色々と簡単に試せると思います。面白そうな使い方があったら教えてください。
あと、nanafshiという名前の由来について会社でよく聞かれるので書いておくと、コマンドをファイル(Node)に擬態させる的なイメージから、枝葉に擬態する生き物を探してナナフシが見つかったのでそのまま採用しました(枝に擬態するのでちょっと違いますが)。あとfilesystemなのでnanafushiのuを削ってfsにしています。