LayerX エンジニアブログ

LayerX の エンジニアブログです。

開発爆速化を支える経営会議や週次定例の方法論 〜LayerXの透明性への取り組みについて〜

こんにちは。松本(@y_matsuwitter)です。GWはカレーを沢山作っていました。スパイスから作るカレー、思ったよりも手間が掛からないですね。インド料理にハマる波が数年置きにやってきます。

本日は、最近いくつかのイベントでの議論を通じて感じた、開発組織づくりにおいて見られる共通のパターンについて触れつつ、LayerXが意識している取り組みを書いてみようと思います。

各社で見られる最近の組織傾向

ここしばらく、Ubieさんや10Xさん、CADDiさんやAWSさんでのイベントにおいて開発組織の在り方について対話してきました。どの企業も非常に尊敬できる、また学ばせていただいているチームになります。その中から、特に数十人規模までの組織で見られる共通項としていくつかの要素が存在することに気づきました。大雑把に上げてみると以下のような事柄になります。

  • 我々らしい文化とは何か、常に考え言語化し浸透を目指す
  • 透明性に対する狂気的こだわり
  • 文化マッチを重視した採用
  • 責務の分割と過剰とも言える信頼と委譲
  • 事業そのものへエンジニアも取り組む
  • 開発速度へのこだわり

f:id:y_matsuwitter:20210504120626j:plain

これらの中から、特に透明性という部分について取り上げてみようかと思います。

狂気的な透明性の取り組み

直近、透明性に対してNoという会社はスタートアップの中では少なくなってきたと思います。大きな企業の中でもデジタルと向き合う過程でだんだんとその重要性が認識されているのではないでしょうか。

透明性はなぜ重要かと言われれば、端的には自律分散的組織作りのためとなるのかと考えています。それぞれの組織が独立して、しかも組織全体から見て正しい意思決定を自律的に行うためには、同じ情報が共有され、さらにその情報を同じ土台の上で構造化し咀嚼できる必要があります。

同じように情報を取得し、解釈できることで、開発で言えば、迷ったらどう設計するか、どの開発を優先するか、を正しい方向で行うことに繋がります。

開発組織において自分がもっとも重要だと思うこと、それは「何を作り、何を作らないか」という意思決定だと考えています。ソフトウェアアーキテクチャも、エンジニアリングマネジメントも技術選定も、どれもこうした優先度や戦略の元に決まります。

そして速度を優先する組織では、それぞれが独立して意思決定することが多く発生します。正しい意思決定が行われるには、先の透明性を意識した土壌の上で「今我々はこういう方角を向いていて、このような事柄を重視する」という戦略とそれを支える根拠を一人ひとりが理解できることが重要です。背景知識が共通であることで、何を作るか、何を作らないかについて、それぞれのチームや一人ひとりのメンバーが安心して、自信を持って判断できます。

これをもっとシンプルに言うと、会社全体にわたって、一人ひとりが同じ優先度リストを持っていることが、スプリント計画や技術的意思決定、負債との向き合い方に統一性をもたらし開発速度を高めるということになります。爆速化には透明性がセットです。

透明性の要件

爆速化を支える透明性には、情報が公開されていることに加えて3つの要件があります。それは①情報が理解しやすいフォーマットであること ②情報を確認する習慣・場が作られていること ③しつこいまでの情報の能動的なインプット、の3つです。

情報はただ置いてあるだけでは咀嚼できません。理解しやすい形である事が必要です。そのためには、フォーマットが改善され続けていることが大切です。例えば、週次でレポートを出しているのであれば、より一人ひとりが理解しやすい方法とはなにか、そのために必要な補足情報とはなにか考えてみましょう。また、そもそも理解しやすいのかフィードバックを定期的にもらうことも有効です。

さらに、こうした理解しやすい情報も、触れてもらわねばなかなか浸透しません。ですので、時間や場所の工夫が有効です。同じタイミングで発信されたり、いつもこの場所に置いてある、ということが周知されることで情報を必要とする人、伝えておくべき人がその情報を得ることができようになります。週次定例などはそうした仕組みに繋がりますね。

同じ事柄を説明するにあたっても、もし重要な事柄なのであれば一つの文書を用意して終わりとせず、それを発信するための様々な手段を考えてみましょう。情報の伝達においては、そのしつこさも重要です。一つの方法で伝わるとは限らず、例えばそれを口頭で会話する、文書で伝える、外部のメディアでも発信する、1on1で対話する、目標設定の中に盛り込むなど様々な手法を取ることが大切です。徹底してインプットすることでやっと浸透するくらいに考えていきましょう。

ちなみに蛇足になりますが、もしこれをお読みの方が大きな組織(具体的には数百名以上)を運営されている場合は、その伝達経路も意識すると良いかもしれません。情報は信頼されている人から伝達されているほど浸透率が良くなります。組織内の信頼関係のネットワークを意識して、適切な人へ充填的にインプットしていくとより浸透度が高まるのではないかと考えています。

LayerXの経営会議と週次定例の役割

ここまで述べてきた透明性という取り組みにおいて、LayerXでは経営会議や経営合宿をできる限り透明化するということを進めてきました。

経営会議では、経営の意思決定についてセンシティブな項目(特に採用条件など人事的な決定)を除き全てを公開しています。議事録には公開可能な全ての会話を記録しており、過去全てに渡り参照することが可能です。

実際に入社された方がこうした議事録を遡って読んでいるケースもあり、これを通じて会社の変遷を知る、それによって今の取り組みの位置づけを理解するということにもつながっているかもしれません。活用は人それぞれですが、大事なことは知りたい時に知れるということかと思います。

また、経営合宿で決まった大方針は常に「羅針盤」と呼んでいるスライドにまとめ全社に共有されています。この羅針盤を元に、週次の経営会議での意思決定が進み、その過程を全て見れるようにすることで一人ひとりが自分の取り組みの理由を知ることにつながってほしいと考えています。

さらに週次の定例では、こうした経営会議の意思決定された事柄・戦略やそれに関連する学びを福島CEOから20分程度話をし、さらに各事業の責任者から事業のアップデートを共有する時間を設けています。このフォーマットを通じて、全社の優先度リストを最もわかりやすくインプットすることにつながっていると感じています。合計で1時間半を毎週割いており、多少長いと感じる方もいるかもしれません。しかしこれが結果として意思決定を迅速に行える爆速な開発体制を維持することにつながっています。

最近では、この週次定例を経営会議直後に移動し、経営会議の意思決定をフレッシュなうちに全社にSyncする場として活用しようという取り組みをスタートすることになりました。議論内容やその背景を即座に全社に伝達することでより素早い浸透を目指し、会社全体が一つの生き物として動けるような形を目指していきたいところですね。

明日からできる3つの初手

もしこれをお読みの皆さんのチームでここまで書いてきたような取り組みをやってみようとお考えの場合、まず初手として取り組むと良さそうな、透明性を高めるための3つの事柄を書いて終わりにしようと思います。

まず1つが、今の会社の重要事項リストをとりあえず上から3~5つ並べて全員で共有してみましょう。案外と、厳密な優先項目リストを書いてみようとすると曖昧になっている事が多かったりします。特に成長中のスタートアップですと、まだ組織サイズが10名以下だった頃の名残で暗黙的にこれが進んでいることも多い印象です。意図的にこうした全社での優先度リストをまず書き出してみましょう。可能なら、さらにそれをブレークダウンして各チームレベルでも書き出してみると良いかもしれません。

次に、こうした重要事項リストと今のチームでの取り組みがどれだけ沿っているか、開発項目のバックログを洗ってみましょう。開発項目のリストを一列に、優先度順に並べて見た時に今取り組んでいるものがその通りになっているか考えてみてください。

最後に、この会社としての重要項目リストが定期的に更新・共有されるような仕組みを今すぐCEOやProduct Ownerと話して作ってみましょう。月次でも四半期でもOKです。特に、重要項目のリストが変化したときは「なぜそれが変化したのか」を明確に伝える仕組みが大切です。何をやるか決めるとき、その背後にある「なぜやるか」があるかどうかで行動の精度が大きく変わってきます。

最後に:一緒に事業を作りませんか

なぜLayerXは透明性を重視しているのか、それは「すべての経済活動を、デジタル化する」ためにほかなりません。一つの事業・一つのプロダクトで全ての経済が変わるわけではなく、今時点でもLayerXインボイスや三井物産デジタル・アセットマネジメント及びLayerX Labsの3つの方向からアプローチをしています。様々な事業・組織をうまくアラインする、同じ考え方や優先度リストを持った上で推進していくには透明性を高めていかねばなりません。

まだまだ、取り組みは始まったばかりですが、今後も次々新たな取り組みに乗り出そうと考えています。そうした機会を、これをお読みの皆さんと取り組みたいと考えています。新規事業も、今の事業のグロースも爆速で進めていきます。

一緒にこの挑戦へ参加してくださる方、常に募集しています。プロダクトマネージャからテックリード、セールスなど全方位でお待ちしております!

herp.careers

herp.careers

プロダクトをつくる研究開発組織LayerX Labsにおける技術選定の軸

LayerX LabsのOsuke(@zoom_zoomzo)です!今回の記事では、僕自身LayerX創業当初から研究開発をメインに取り組んできたこともあるので、Labsにおける技術選定や設計を考える上での軸やその具体例について紹介したいと思います。

f:id:osuketh:20210430103200p:plain

LayerX Labsにおける技術選定の軸

まずはじめにLayerX Labsについて簡単に紹介すると、「Labs」とついているようにLayerXにおける研究開発部署にあたるチームです。アカデミックに成果を出す基礎研究などを目的としているというよりも、あくまで「プロダクト」の開発を中心に取り組み、数年スパンのより長期的な目線で先端技術にベッドしていくことでこれまで解けなかった業界課題の解決を目指しています。

具体的には、Anonifyというオープンソースの秘匿化モジュールを開発し、個人情報などの機密性の高いデータの保護や秘匿性と透明性を両立するインターネット投票などのプロダクト開発に取り組んでいます。

github.com

このような背景で研究開発としての先端技術への投資とプロダクト開発としてのサービス提供を両立する技術選定・設計をしていることがLabsの特徴です。例えば、今現在の開発生産性・スピードも重要ですが、将来的に強みとなる技術的アセットが積み上がることやさまざまな業務にプロダクトとしてカスタマイズ可能となるような拡張可能性も重要視しています。

また、活用事例が少なく不確実性の高い技術を多く扱うことになるので、十分にテストケースを備え、負荷検証などの検証も早い段階から実施しています。 一方、我々のコアではない技術は積極的に既存のサービス・ツールを利用しています。

Labsにおける技術選定や設計に関して、いくつか具体例をあげてみます。

言語選定: Rust

Anonifyでは、秘匿性と検証可能性をハードウェアレベルで実現するためにIntel SGXと呼ばれるプロセッサのセキュリティ機能(Trusted Execution Environment (TEE))を用いています。

これに対応するメジャーなSDKがそもそもRustかC++に限られます。(最近ではさまざまな言語で開発できるようなLibrary OSタイプのSDKを開発するプロジェクトも増えてきています。)また、言語仕様が多く比較的学習コストは高くなってしまいますが、Intel SGXのセキュアな保護領域で実行されるコードベースでは、メモリ安全性やシステムプログラミングに適しているという観点で非常に適しています。

tech.layerx.co.jp

インフラ選定・設計:Azure・AKS

2021年3月にAzure Kubernetes Service(AKS)上のConfidential ComputingノードがGAしており、Anonifyはこの上でインフラを構築しています。

Intel SGXのようなプロセッサレベルのセキュリティ機能をクラウド文脈で活用することは「Confidential Computing」というワードでよく取り上げられ、2020~2021年にかけて、AWSやGCPを初め多くの主要パブリッククラウドでTEEを利用することが可能になりました。

参考:

その中でも、AzureではIntel SGXやAMD SEV-SNPなどのTEEを活用したサービスを多く展開しています。将来的には、Anonifyのマルチクラウド化もロードマップにありますが、現時点ではIntel SGXを想定して開発を進めており、その相性の良さからAzureを選定しています。

なぜAKSか

AKSは、Azureにおけるマネージドなkubernetesクラスタサービスで、AWSのEKSやGCPのGKEに相当します。Anonifyのサービス的には小規模であるものの以下のようにデプロイメント・コンテナ管理が複雑になります。

  • コンテナごとのデプロイ順序: AnonifyではTEE上で複数種類のコンテナが連携して動きますが、それぞれを適切な順序でデプロイするようにコントロールする必要があります。

  • 自動リカバリ:Anonifyは、アーキテクチャの種類によっては、TEEの隔離保護されたメモリ領域でin-memory databaseのように働くこともあります。この際、コンテナやVMが落ちてしまった場合、自動で復旧し暗号化されたデータの永続化レイヤーから適切にTEEへデータを復元する必要があります。

  • メモリリソース考慮してコンテナをスケジューリング:AnonifyではTEE(物理マシン)の限られたメモリリソースを考慮して適切にコンテナを配置する必要があります。Intel SGXを用いている以上、VM上で動いていようとコンテナ上で動いていようとkubernetesでオーケストレーションされていようと物理マシンを意識して開発することになります。例えば、複数コンテナが同じIntel SGXに対応した物理マシン上で動いているとします。Intel SGXの隔離保護領域のメモリは非常に制限された環境(SGXv1だと100MiB前後)であり、複数コンテナを動かすとこの限られたメモリを共有して使うことになります。(厳密には、暗号化されたメモリページとして100MiB以上使うことが可能ですが数十〜数百倍オーダーのオーバーヘッドがかかります。)このように、複数のコンテナを同一マシン上で動かすことが適していないケースも考えられます。

  • クラウド非依存:Anonifyを活用してさまざまなユースケースでプライバシー保護などのソリューションを提供していることから、将来的にさまざまなクラウド環境にデプロイ可能であることが理想です。クラウドに依存せず抽象化された構成管理ができている必要があります。

このような技術的要件の中で、Intel SGXを扱える環境ということでAKSのConfidnetial Computingノードを選定しています。研究開発組織であるもののプロダクトを開発し本番環境に耐えうるサービスの可用性や信頼性も考慮し、複雑性への対処と技術の再利用性の観点からサービスやチームとして小規模である現段階からAKSを活用することが適切であると判断しています。

ちなみに、AKSのConfidential Computingノードでは、ノードがSGX環境のDCsv2シリーズであり、Intel SGX用のkubernetes device pluginを用いてそれぞれのPodがノードの /dev/sgx/enclave/dev/sgx/provision を認識できるようにしています。これにより、AKS上でハードウェアレベルの機密性に優れたワークロードを実現しています。

プロトコルアーキテクチャ設計

Anonifyにおけるアーキテクチャ設計を考える上で一番の情報源は論文です。例えば、TEE自体セキュリティに関する論文、TEEを応用することで既存システムのプライバシーやセキュリティを向上させる内容の論文、他にも暗号や分散システムなど幅広い要素技術を考慮に入れる必要があります。

アーキテクチャ自体がまずベストプラクティス化されていないので、理論的な側面からAnonifyの文脈に照らし合わせたときの適切なアーキテクチャや要素技術に落とし込んでいきます。ただ理論的には可能であることと、実際に実装可能であることさらに本番環境にプロダクトとしてデプロイ可能であることは、雲泥の差があるので本番環境に耐えうる技術的フィージビリティも考慮に入れる必要があります。 そして、この際、(特にTEE特有の)セキュリティリスクをしっかりと把握・理解しておくことが重要です。

例えば、リスクを理解することで以下のようなアーキテクチャの設計をAnonifyで意識しています。

前述したようにメモリリソースの観点から物理マシンを意識して設計することになりますが、これは、鍵管理の観点からも同様です。Anonifyでは一部の処理でプロセッサに埋め込まれた鍵で暗号化処理をしています。この際、クラウド側のメンテナンスやオペレーションミスなどでノード(VM)と物理マシンの割り当てが解除されてしまうとノード側で取得する鍵が異なり暗号文が復号できなくなってしまうことが起こり得ます。このようなことに対処するために、物理マシンを考慮して冗長化し、万が一割り当てが解除されてしまっても一方のノードで復号可能となるようなアーキテクチャを設計する必要があります。

ちなみに、この幅広い技術分野にチームとして日々理解を深められるように、毎日勉強会を開催しています!(bizも参加・発表しています)

  • 月曜:低レイヤー(OSなど)・TEE
  • 火曜:暗号技術
  • 水曜:サイバーセキュリティ
  • 木曜:分野問わず最新の論文紹介など
  • 金曜:ニュースレター

実装設計

TEEをベースにしたAnonifyの開発では、一般的なweb開発よりもエコシステムがかなり小さいので、活用できるライブラリが少なく多くのコードベースを自分たちで実装する必要があります。また、TEEの保護領域ではシステムコールを呼べないなどの技術的な制約も大きく、SDKなどを活用して特殊な実装をしたりする必要もあります。(詳しくは以下の記事を参考にしてください)

tech.layerx.co.jp

ここでは、現状の課題も含めてAnonify特有の実装設計方針をいくつか紹介します。

Composability

アーキテクチャレベルでサービスごとの疎結合性・カスタマイズ性を保つことも重要ですが、実装レベルでさまざまなコードベースを組み立てられるようなカスタマイズ性もさまざまなユースケースで用いられる前提のAnonifyでは重要です。例えば、あるユースケースではTEEでの認証・認可は必要ないが、他のユースケースではTEEでOauth2.0ベースの認可が必要になったりします。この場合、対応可能な認証・認可オプションを増やしていくことなどが改善ポイントとしてあげられます。

Security

TEEの保護領域で動くコードベースは最低限にすることで、信頼するソフトウェアのコードベースを減らします。例えば、ネットワークI/Oが絡むサーバーやクライアントとしての機能は保護領域の外で実装しています。一方、機密データの暗号化や復号は保護領域の中で実装しています。Anonifyにおいても、この保護領域内のコードベースを小さくするために、依存ライブラリを削減するなどの余地がある部分です。

Performance

TEE上での処理は通常の処理と比較しオーバーヘッドがかかります。これにはいくつかの要因があるのですが、その一つが保護領域内部とその外側で処理をコンテキストスイッチする際のオーバーヘッドです。Queueを使って非同期に処理するなどコンテキストスイッチコストを削減する論文や実装は多く提案されていますが、Anonifyではまだこの部分のパフォーマンス改善はできていない部分になります。

さいごに

研究開発組織だが「プロダクト」を作る組織としてLabsで開発しているAnonifyの技術選定や設計の具体例を中心に紹介しました。

技術的にハードルが高く、開発にある程度時間がかかってしまうことも長い時間軸で見てプラスになる、投資対効果に合うと判断すれば積極的に取り込んでいきます。

LayerXでは引き続き仲間を大募集しています!

herp.careers

スプシを活用してアセットマネジメントの情報開示業務を効率化した話

こんにちは!LayerXから三井物産デジタル・アセットマネジメント(以下、MDM)に出向中の阿部と申します。 本日はGoogleスプレッドシートを活用したアセットマネジメント(以下、アセマネ)の情報開示業務の効率化について書きたいと思います。

経緯

tech.layerx.co.jp

上記は当社丸野による過去の記事ですが、こちらに記載されているとおり、アセットマネジメントの情報開示業務の現況は超非効率なプロセスが存在し、それにより人件費等コストが高くなっています。
非効率なプロセスを効率化することによるコスト削減が実現できれば、その結果投資家へ返せるリターンが増え、魅了的な投資商品になりうると考えております。

データ管理のAs-Is

MDMにおいては、扱う不動産や証券のデータを社内で管理するために、ExcelやGoogleスプレッドシートを使うケースもあれば、社内用の内製Webアプリケーションを使うこともあり、この間を橋渡しする必要がありました。
すぐさま全てを内製やSaaS化する前に、まずは業務を回し少しずつ効率化しながら理想形を見定めていくことが重要であり、その過程ではGoogleスプレッドシートでのマスタ管理は現実的に有用な手段となります。
一つのデータを複数人で共有しやすく、既存のデータ管理業務を変更せずに、Google Apps Scriptを利用して社内の内製Webアプリケーションにデータを取り込む仕組みを導入しやすいためです。
既存のデータ管理業務と社内用の内製Webアプリケーションの橋渡しとして、csvを用いてデータ入稿を行っています。

具体的なデータ入稿業務

証券 × 四半期ごとの分配(配当)額の入稿業務を例に、実際どのように入稿するのかを書いていきたいと思います。
シートの単位はリレーショナルデータベースでいうところのテーブル単位で分けることが多いです。

f:id:es335ab:20210426185321p:plain
スプレッドシート の入力例

ある証券(id:test)の2021/01/01〜2021/03/31における分配額は、このような形式でGoogleスプレッドシートに入力します。
入力したら【csvおじさん】というボタンを押下すると、指定しているGoogle Driveのフォルダにcsvファイルが吐き出されます。

f:id:es335ab:20210426184231p:plain
csvが吐き出されたGoogle Driveのフォルダ

f:id:es335ab:20210426184304p:plain
csvの中身

このボタンを押下した時にGoogle Apps Scriptで書かれた下記のような簡易なスクリプトが動いております。
シートの入力内容を元にcsvを生成し、特定のGoogle Driveのフォルダに保存する、という感じです。

function createCsv() {
  const csv = loadSheetData()
  putToDrive(csv)
}

function loadSheetData() {
  // 現在開いているシートを取得
  const ss = SpreadsheetApp.getActiveSheet()
  // A列の値を全て取得
  const rows = ss.getRange('A:A').getValues()
  // A列の長さを計測
  const rowLength = rows.filter(String).length
  // 4行目の値(吐き出すcsvデータのヘッダー)を取得
  const columns = ss.getRange("4:4").getValues()[0]
  // 4行目の長さを計測
  const colLength = columns.filter(String).length
  // 書き出すデータを取得
  const data = ss.getRange(4, 1, rowLength - 2, colLength).getValues() 
  let csv = ''

  // Webアプリに取り込める形式にコンバート
  data.forEach((item, index) => {
    if(data[index][0] == "") {
      return
    }

    csv += data[index].map((v) => {
      if (v instanceof Date) {
        // 日付の場合goで解釈できるフォーマットに変換
        // 外部ライブラリであるdayjsを使用
        return dayjs.dayjs(v).format('YYYY-MM-DD hh:mm:ss')
      }

      // 数値区切りのカンマが入っていたらカンマを消す
      if (typeof(v) === 'string') {
        return v.replace(/\n|\r|,/g, '')
      }

      return v
    })
    csv += '\r\n'
  })
  return csv
}

function putToDrive(csv) {
  const sheetName = SpreadsheetApp.getActiveSheet().getName()
  const csvFileName = `${sheetName}.csv`
  const drive = DriveApp.getFolderById('folder_id')
  const contentType = 'text/csv'
  const charset = 'utf-8'
  const files = drive.getFilesByName(csvFileName)

  if (files.hasNext()) {
    const csvFile = files.next()
    csvFile.setContent(csv)
  } else {
    const blob = Utilities.newBlob('', contentType, csvFileName).setDataFromString(csv, charset)
    drive.createFile(blob)
  }
}

あとは生成したcsvファイルをMDMの社内で使用するWebアプリケーションにアップロードし、関係者で適切に内容を審査、承認してから投資家に公開、周知していくことになります。
このへんの話は今後の記事でお話できればと考えております。

今回の例では1レコードのみの入稿でしたが、この仕組みを使うことで複数レコードを一気に入稿することも可能です。

この方法による効率化の効果

アセットマネジメントの情報開示業務では入稿、申請、審査、承認が必要なデータ類が多く存在します。
この仕組みを利用することで、社内で使用するWebアプリケーションにはデータの種類ごとに入力フォームを実装することなく、達成したい業務の実現が可能となります。
また、アセットマネジメントの情報開示業務は非常に複雑であるため、まずはこのような簡易な方法で業務運営を行いながらボトルネックとなるプロセスの見極め、改善することでさらなる効率化を目指していければと考えております。

まとめ

今回の記事では、アセマネ会社が非効率なプロセスを効率化することの重要性、Googleスプレッドシートを利用したデータ入稿によるその一例をご紹介させていただきました。
最後まで読んでいただきありがとうございます。

MDMを含むLayerXでは、引き続き業務の効率化を行う仲間を募集しています。
少しでもご興味を持っていただけましたら、ぜひカジュアルにお話しましょう!

herp.careers

RustなしでLayerX Labsの開発は語れない

はじめに

こんにちは。LayerX Labs(以下、Labs)エンジニアのきむ(@jkcomment)です。秘匿化モジュール「Anonify」の開発や大手金融機関や行政等との実証実験に携わっています。

Labsの開発はRustからはじめ、Rustで終わる

tech.layerx.co.jp

先日、恩田(さいぺ)の方からAnonifyとRustについての話をしましたが、Rustは速度・安全性・効率的な並行性を特徴とし、C,C++と同等な性能を発揮しつつ、システムプログラミングに適した言語です。Anonifyはハードウェアレベルの機密性を実現するために Trusted Execution Environment (TEE) の一種であるIntel SGXを活用しています。Intel SGXはIntelのCPUが提供しているメモリ上に「Enclave」と呼ばれるハードウェア的に厳重に保護された領域を生成することで、センシティブデータを保護しつつプログラムを実行する為のCPUの拡張機能です。そのため、システム制御が可能な言語を使う必要があります。また、機密性を担保する技術として高い安全性が求められます。そういう意味でRustはAnonifyにぴったりな言語とも言えるでしょう。また、LabsではAnonifyだけでなく、他の開発にもRustを使っています。大きく3つに分けてみると

  • Anonify
  • 実証実験
  • 関連ツール

となります。Rustをチーム共通言語として使うことで安全で質の高い開発ができるとともにメンバー間異なるプロジェクトを担当していても互いにレビューできたり、鉄火場(繁忙期を意味する、LayerXも社内用語)の場合、すぐにサポートに入れるなど開発業務全般に対する柔軟性が高まる効果もあります。

Anonify

我々の中心プロジェクトであるAnonifyです。詳しい話は割愛しますが、スマートコントラクト以外の部分はほぼRustで開発しています。Anonifyは、単独で動作も可能ですが、モジュールとしても使用可能なところが特徴です。

実証実験

Anonifyを用いた実証実験もRustを利用します。上記で話したようにAnonifyは独自で動かすがことが可能ですが、モジュールとしても利用できるため、実証実験の時はAnonifyをモジュールとしてimportし、Anonify以外の部分、例えばWebサーバ的な部分やユースケースによってはDBを使うことがあったりもするので、そこらへんはRustで実装していく感じです。よく使うライブラリとしてはactix-webやtokio、diesel、rust-sqlxなどがあります。

関連ツール

Anonify本体の開発や実証実験に役立つツール開発も全部Rustで行います。例えば性能試験を行う際、負荷をかけるコマンドツールや集計結果を出力するツールなどを開発したりします。cliツールを作ることが多いため、clapなどのcliライブラリをよく使っています。

毎日使うRust、少し注意を払うことでコードが良くなる&リソースを効率よく使える

上記でお話ししましたようにLayerX LabsではRustを軸にした開発業務を行なっています。Rust SGX SDKやrust-web3など特定の機能に特化した若干癖のあるライブラリを使うことが多いのですが、Rustは基本的にコンパイラがしっかりコードをチェックし、最適化してくれるため、コンパイルさえ通れば大体狙い通りプログラムが動きます。ですが、他の言語を書く時と同じようにコードの読みやすさを維持することまたはリソースをどう使うかはコードを作成するプログラマ次第で、少し注意を払うことでコードが読みやすくなったり、作成したプログラムがリソースを無駄なく効率よく使うことができます(経験上の話になりますが、プログラムの中で無駄なリソースを使うのはコアな処理より大体小さな不注意で書いてしまったコードの方が多かったりしました)。ここではRust SGX SDKやrust-web3などのライブラリの話よりRustそのものにフォーカスしてお話ししたいと思います。

不要なcloneは避ける

ぼーっとしてコードを書く際よくやりがちなのがclone()の使いすぎ問題です(特に私は文字列でよくやりがちでした)。オブジェクトのclone()を使う際は本当に必要かどうかをちゃんと判断する必要がありです。使うことは簡単ですが、変数に対して.clone()を呼び出すと、そのデータのコピーが作成され、コピーの作成にはリソースが必要となります。したがって、clone()はほとんどの場合、パフォーマンスに悪影響を及ぼすので、避けるべきです。多くの場合、clone()を使う必要はなく、同じ変数の参照を異なる関数に渡すことができます。

// clone()を使う場合
fn main() {
    let hoge = Hoge::new();
    foo(hoge.clone());
    foo(hoge);
}
 
// 参照を渡す場合
fn main() {
    let hoge = Hoge::new();
    foo(&hoge);
    foo(&hoge);
}
環境変数の有無によって処理を変えたい

開発を行う際に環境変数を使うことは多いでしょう。例えば、PASSWORDという環境変数が設定されている時は処理Aを、そうでない時は処理Bを実行したい。そういった時には環境変数の値をOptionとして受け取り、処理の呼び出し時に引数として渡し、処理の中で分岐処理を行うようにします。そうすることで環境変数が必要な時だけ用意しておくと済むでしょう。

PASSWORD=hogehoge
fn main() {
    // PASSWORD環境変数が存在なくてもエラーにならない
    let password: Option<String> = env::var("PASSWORD").ok();
    // Option::as_derefはOptionの中の型がDerefトレイトを実装しているときにDeref::Targetの参照へ変換できる
    hoge(password.as_deref());
}
 
fn hoge(password: Option<&str>) {
    // passwordが存在する場合
    if let Some(pw) = password {
        foo();
    // passwordが存在しない場合
    } else {
        bar();
    }
}
_を利用して大きい数値を読みやすくする

実際Anonifyでは、gasの指定など桁数の多い数値を使ったりしているため、数字の間に_を使って理解しやすくしています。

const GAS: u64 = 5_000_000;
unwrapの誘惑に負けるな!

すでにご存知かと思いますが、Rustでコードを書く際無意識でunwrapを使う方いらっしゃいませんか?笑(ここにいます) 結論から言いますとunwrapより?を使いましょう。?はパニックになる代わりにエラーを返すunwrapと想像できます。? は直接エラーを返すのではなく、実際には Err(From::from(err)) を返します(例はFromが実装されている場合です)。つまり、エラーが変換可能であれば、自動的に適切な型に変換されます。

単純にvecを使うのも良いけど、Bytesもありかも

actix-webなどのフレームワークを使わずTCPサーバを作る時、tokioを使うことが多いのではないでしょうか。tokioは軽量で非同期プログラミングに適しており、特にTCPサーバなどで高い性能を発揮します。私たちも要件によってはtokioでTCPサーバを実装したりします。TCPサーバの処理の流れをざっくり話しますと、クライアントから送られてきたデータをbufferに入れて該当する処理を呼び出すという流れになります。データを渡す際、シンプルにバイトデータ(&[u8]やVecなど)をあちらこちらに渡しながらメモリプールを使用することも良いのですが、極端な話、受信したデータが大きい場合(1GBとか)、それをVec<[u8]>にすると大量のメモリを占領してしまうハメになります(本当に極端な例です)。なのでそのかわりにBytesライブラリを活用するのも良い方法の一つでしょう。Bytesは、複数のBytesオブジェクトが同じ基本メモリを指すようにすることで、ゼロコピーのプログラミングを促進します。これは、参照カウント(Reference Counter)を使用し、メモリが不要になり、解放できるようになったときに追跡することで管理されます。

use bytes::{BytesMut, BufMut};
 
let mut buf = BytesMut::with_capacity(1024);
buf.put(&b"hello world"[..]);
buf.put_u16(1234);
 
let a = buf.split();
assert_eq!(a, b"hello world\x04\xD2"[..]);
 
buf.put(&b"goodbye world"[..]);
 
let b = buf.split();
assert_eq!(b, b"goodbye world"[..]);
 
assert_eq!(buf.capacity(), 998);

よく使われるものとしてBytesMutとBytesがあります。BytesMutはbufferを書き込むためのmemory buffer実装です。一般的にbytesMut::with_capacity(1024)のような感じで初期化しますが、メモリ不足時は自動的にメモリを確保します。BytesMutの特徴としては、Deep CopyではなくShallown CopyベースのポインタとReference Counterで実装されているためメモリを効率よく使います。Bytesは読み取り専用のmemory buffer実装です。 実際tokioでTCPサーバを実装する際、channelを利用してやりとりをするケースがよくあります。その場合、1:Nで同じデータを送信することが多いですが、Bytesを使うと、例えば1万個のchannelにデータを送ってもBytesを維持するための小さなスペースを除いては、実際にコピーされるデータはありません(データは全部同じメモリを参照している)

列挙型のサイズは、バリアントのサイズによって決まる

他の言語でも列挙型はありますが、Rustの列挙型は、柔軟性が高く、構造体のような振る舞いもできるためいろんな場面で使われることが多いです。そんな便利な列挙型ですが、効率よく使うためには注意が必要です。列挙型のサイズは、列挙型が持つバリアント(構成子)のデータ型によってサイズが決まるからです。したがって、最適ではないメモリレイアウトにならないように、同じようなサイズのバリアントを列挙することが良いでしょう。また、必要に応じて、大きなバリアントをBox化すると列挙型のサイズを小さくすることができます。

enum Hoge {
    Foo(u8),
    Bar([u8; 100]),
}
 
enum HogeBox {
    Foo(u8),
    Bar(Box<[u8;100]>)
}
 
fn main() {
    let x = Hoge::Foo(0);
    let y = HogeBox::Foo(0);
    println!("Hoge size {:?} bytes",  std::mem::size_of_val(&x));
    println!("HogeBox size {:?} bytes",  std::mem::size_of_val(&y));
 }

結果

Hoge size: 101 bytes
HogeBox size: 16 bytes

RustでSGXプログラミングするならここが最高!

RustでSGXプログラミングしてみたい!でもどうすればいいかわからないまたはどんなライブラリ使えばいいかわからない!そういう方のためのリンク集です。よかったらぜひ参考にしてみてください。

RustによるIntel SGXプログラミングとSDKの内部実装
https://qiita.com/Osuke/items/5e30132a7789955105d6

Rust SGX SDK
https://github.com/apache/incubator-teaclave-sgx-sdk

SGXで使用可能なライブラリ
https://github.com/mesalock-linux

すべての経済活動を、一緒にデジタル化しませんか?

最後に、LayerXでは引き続き、ミッションである「すべての経済活動を、デジタル化する」を一緒に実現する仲間を絶賛募集中です。まずはお話だけでも構いませんので、お気軽にご連絡ください。 herp.careers

herp.careers

秘匿化モジュールAnonifyにおけるRust featuresを活用した開発

こんにちは!LayerX LabsでAnonifyを開発している恩田(さいぺ)です。

Anonifyについて

f:id:cipepser:20210425153143p:plain

Anonifyは「秘匿性」と「透明性」という相反する2つの性質を両立する秘匿化モジュールです。社内ではこの2つの性質を「Confidentiality」と「Execution Integrity」として整理しています。
要素技術としてはTrusted Execution Environment(TEE)を使っており、AnonifyのバックエンドはIntel SGX®︎を採用しています。
Intel SGXがもたらすセキュリティ・プロパティの観点では、ハードウェアレベルでの分離およびメモリ暗号化で「Confidentiality」を、Remote Attestationで「Execution Integrity」を実現します。

SGX環境で開発する難しさ

SGX内で実行されるプログラムはユーザモードでしか動作が許されていません。カーネルモードの権限を持ち合わせていないため、システムコールなどの特権命令を発行することができません。当然、libcも使えず、スレッド、ファイルI/O、ネットワークI/Oを行う際にも大きな制約が課された状態で開発することになります。Anonifyの開発にはRustを採用していますが、標準ライブラリであるstdを使うことができません。
このような制約のため、Anonifyの開発はno_std環境での開発となります。組み込みの開発と似ていますね。幸い、 SDK for Intel® Software Guard Extensions をRust向けにwrapしたRust SGX SDK が存在し、stdに相当するsgx_tstd があるのでこちらを利用して開発を進めています。より詳細を知りたい方はRustによるIntel SGXプログラミングとSDKの内部実装 - Qiitaを参照してください。

sgx_tstdを用いることで、RustによるSGXプログラミングが可能になるわけですが、今度は別の問題が発生します。Anonifyで扱うコードはすべてがSGX内で実行されるわけではないことです。
例えば、Anonifyに対してリクエストを送る場合、クライアントサイドで認証付き公開鍵暗号 でリクエストを暗号化します。暗号化に用いた公開鍵に対応する秘密鍵はSGX内部で生成されたもので、SGXの外に出ることは一切ありません。これによってConfidentialityが達成されるわけですが、暗号化はSGXの外部(クライアントサイドなのでstd環境)で行い、復号はSGXの内部(sgx_tstd環境)で行うという状況が発生します。

Anonifyでは認証付き公開鍵暗号をSodiumで実装している1ため、frame-sodiumというcrateが存在します。frame-sodium crateは、SGXの内部・外部のどちらでも利用したいのですが、カジュアルに実装すると以下のようなduplicate lang itemのエラーに遭遇することになります。

error: duplicate lang item in crate `std` (which `hoge` depends on): `f32_runtime`.
|
= note: the lang item is first defined in crate `sgx_tstd` (which `frame_sodium` depends on)
= note: first definition in `sgx_tstd` loaded from /root/anonify/target/debug/deps/libsgx_tstd-cded739c2546dafd.rmeta
= note: second definition in `std` loaded from /root/.rustup/toolchains/nightly-2020-10-25-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-3010daceac92f8fa.rlib

f32_runtimeのようなprimitiveなitemがstdsgx_tstdの両方で定義されるため、コンパイルエラーとなってしまっています。

featureによる解決

Anonifyではこの課題を解決するためにfeaturesを活用しています。featuresはCargoが提供する条件付きコンパイルと依存選択のメカニズムで、Cargo.tomlのfeaturesセクションにfeatureを定義することができます。

AnonifyではSGX内部・外部どちらでも利用するcrateに対して、以下のようなfeaturesを設定しています。stdがSGX外で利用する場合、sgxがSGX内で利用する場合に有効化するfeatureです。

[features]
default = ["std"]
std = []
sgx = [ "sgx_tstd" ]

lib.rsで、以下のようにfeatureに応じて、stdを利用するのかsgx_tstdを利用するのか切り替えています。

#[cfg(feature = "std")]
use std as localstd;

#[cfg(feature = "sgx")]
#[macro_use]
extern crate sgx_tstd as localstd;

このようにlib.rsで定義しておくことで、実装時に都度切り替える手間を削減しています。例えば、Vecを利用したい場合は以下のようにstdsgx_tstdをまとめて扱えます。

use crate::localstd::vec::Vec;

featureに応じてcrateを利用する

上述のframe-sodiumcrateは、defaultをstdとしているため、SGX外で利用する場合は以下のようにdependenciesを設定します。

[dependencies]
frame-sodium = { path = "../../../frame/sodium" }

逆に、SGX内で利用する場合は、defaultのstdを無効化し、sgxを選択します。

[dependencies]
frame-sodium = { path = "../../../frame/sodium", default-features = false, features = ["sgx"] }

Cargo.tomlでどちらのfeatureを使うか選択するだけで、実際のコードでは特に意識することなく、frame-sodiumを利用することができます。

featuresの実装

利用する側だけでなく、使われる立場であるframe-sodium側の実装を簡単に紹介します。端的には、featureごとにcfgアトリビュートを付与します。例えば、#[cfg(feature = "sgx")を付与したitemはsgx featureが有効な場合にのみコンパイルされます。
この itemに対して有効 というのは非常に強力で、関数の引数や構造体のメンバ変数を特定のfeatureでのみ有効にするといったことも可能です。
cfgアトリビュートが登場する例としては、テスト関数に対して付与することも多いと思います。Rustに触れたことがあれば、テスト関数に対して#[cfg(test)]を付与するコードを目にしたことがある方も多いのではないでしょうか。
crate作成者が開発中にアトリビュートを設定する場合は、特定のfeatureでのみ使う関数に付与することが多いと思います。Anonifyのframe-sodium crateにおいては、復号はSGX内でのみ行えることを保証したいため、decrypt関数に対して以下のようなアトリビュートを付与しています。

#[cfg(any(all(feature = "std", test), feature = "sgx"))]
pub fn decrypt(&self, my_priv_key: &SodiumPrivateKey) -> Result<Vec<u8>> {
    let cbox = CryptoBox::new(&self.ephemeral_public_key.0, &my_priv_key.0);
    let plaintext = cbox
        .decrypt(&self.nonce.0, &self.ciphertext[..])
        .map_err(|e| anyhow!("Failed to decrypt SodiumCiphertext: {:?}", e))?;

    Ok(plaintext)
}

最後に

その他にもfeaturesを利用し、暗号方式をcrate利用側が選択できるようにもしています。 Rustに不慣れだと少し難しいという感じる部分もあるかと思いますが、stdsgxで2回同じ実装をすることなく抽象化でき、それがコンパイラレベルで柔軟にできる点は非常に魅力的です。

最後に、LayerX開発チームではエンジニアブログ同様、4月からPodcastをはじめました。CTO松本がホストになり社内ゲストとトークしています。お時間が有りましたらぜひお聴きいただけると嬉しいです!

tech.layerx.co.jp


  1. 今後はjsなどからも暗号できるよう機能を拡充していき、より広範囲のクライアントに対応する予定です。現状、CIなどではクライアントサイドもRust実装の暗号化を行っています。

新プロダクトでスモールチームを作りつつ、goaからgo-swaggerへの乗り換えていった話

こんにちは。LayerXから三井物産デジタル・アセットマネジメント(以下MDM)に出向中のサルバ(@MasashiSalvador)です。三体 3とSFマガジンの異常論文特集と心待ちにして生きています。

エンジニア集団が「効率的なアセマネ会社」を作ると嬉しみが深い件 において@peroyuki_ が書いているように、MDMでは「OperationとTechを掛け合わせて、どうアセマネ業務をリデザインするか」という問いに挑戦しています。実際に顧客と向き合い、Operationを回さない限り深い課題や広い全体感を得ることは難しいため、OperationとTechの掛け合わせは一筋縄ではいきません。そのような困難さに対処するため、MDMでは極力小さくリリースすることを心がけています。

時には、いち早い立ち上げを目指すため、コードベース自体を切り離し、2,3名ほどの新しい小さなチームを切り出し、開発をクイックに行うこともあります。事業や業務のブラッシュアップの検証を早く行うためです。また、小さな開発の副次的な効果として、ライブラリやアーキテクチャ、SaaS連携などの技術検証の余地が生まれます。

MDMでは、これまでにいくつものSaaSの検証とプロダクトの開発を行ってきました。プロ投資家向けプロダクトである「あさどれ不動産」 もその1つです。あさどれ不動産に関しても、上述のように小さな開発チームを切り出し、短期間でのリリース(企画からβ版公開まで約2ヶ月)を行いました。

本記事は、あさどれ不動産リリース時に、バックエンドのAPIのフレームワークをgoa(v3)からgo-swaggerへの切り替えを行った経緯についてお話します。

goaとは? go-swaggerとは?差は?

goa(v3)とは、下記のような独自DSLを書くことで、httpリクエストの処理(バリデーションなど)やレスポンスを返すコードを自動生成できるフレームワークです。実装者はビジネスロジックの実装に集中すれば良いことになります。v3はGRPCのエンドポイントの自動生成にも対応しており、それなりに便利です。goaのDSLでAPIの定義を書くと、定義に対応した swagger.yaml が生成されます。swagger.yamlを元にAPI定義をswaggerUIなどで共有することもできます。

// https://goa.design/ より引用
var _ = Service("calc", func() {
    Description("The calc service performs operations on numbers.")

    Method("add", func() {
        Payload(func() {
            Field(1, "a", Int, "Left operand")
            Field(2, "b", Int, "Right operand")
            Required("a", "b")
        })

        Result(Int)

        HTTP(func() {
            GET("/add/{a}/{b}")
        })

        GRPC(func() {
        })
    })

    Files("/openapi.json", "./gen/http/openapi.json")
})

go-swagger を利用する場合は独自DSLではなく、openAPI(swagger)でAPIを定義し、swagger.yamlからAPIのリクエスト、レスポンスを返すコードを自動生成します。

実際、現在のMDMのプロダクトでは、swaggerのAPI定義ファイルを下記のように分割し

- definitions
   - index.yaml
   - Foo.yaml
   - Bar.yaml
- paths
   - index.yaml
   - hoges/list.yaml
          /detail.yaml
- root.yaml

下記のようなコマンドでswaggerを結合& validateし、サーバサイドのコードを自動生成しています。

yarn swagger-merger -i docs/swagger_files/root.yaml -o swagger.yaml
swagger validate swagger.yaml
swagger generate server -a mdm -A mdm --exclude-main --strict-additional-properties -t gen -m respmodel -P model.User -f ./swagger.yaml

生成されるrouting周りのコード

// api config.
    api.JSONConsumer = runtime.JSONConsumer()
    api.JSONProducer = runtime.JSONProducer()
    authMiddleware := authMidldeWare{buRepo: buRepo}
    api.AuthorizationAuth = authMiddleware.exec

    // swaggerで定義したエンドポイントのパスとハンドラーを紐付ける
  // ハンドラーのHandle関数の型定義が自動生成される
    api.GetHcHandler = mdmbackoffice.GetHcHandlerFunc(func(params mdmbackoffice.GetHcParams) middleware.Responder {
        return mdmbackoffice.NewGetHcOK().WithPayload(nil)
    })
  
  // swaggerで定義したエンドポイントのパスとハンドラーを紐付ける
  // ハンドラーのHandle関数の型定義が自動生成される
    smplHandler := sampleHandler.Sample{}
    api.GetSampleHandler = mdmbackoffice.GetSampleHandlerFunc(func(params mdmbackoffice.GetSampleParams, bu *model.User) middleware.Responder {
    // 時と場合により、userの権限チェックのコードを挟み込む
        return smplHandler.Handle(params, bu)
    })

レスポンスの型も自動生成されます。

# /project_root/gen/respmodel/
- foo.go # Fooのresponeの型
- bar.go

goaとgo-swaggerはどちらも便利ですが

  • API定義の書きやすさ、柔軟性
  • middlewareの差し込みの柔軟性
  • Cookieの扱いやすさ
  • コード生成の柔軟性
    • goa(v3)の吐き出すswaggerがopenapi 3.0なので、コード生成系が未成熟(バックエンドもフロントエンドも)

などの点を加味すると、われわれのユースケースではgo-swaggerの方が適していました。

MDMのプロダクトでは、バックエンドとフロントエンド間のセッションのやり取りにCookieを用いていますし、特定の秘密保持契約(Confidential Agreement = CA)を結んだ顧客にのみ閲覧させたいリソースのアクセス管理に、Cloudfrontの署名付きCoookieを利用しているため、Cookieの取り扱いを自然に行えないgoaを使い続けるのが苦しくなりました。

Announcing Goa v3.2.0Add ability to design HTTP cookies #1717 に記載されているようにCookieのDSLは存在しますが、柔軟な読み書きはできません。responseのencoderを独自実装し、特定の条件下でのみ、デフォルトのコードがresponseを返す前にresponseを上書きするようなコードを書くワークアラウンドを実装しましたが、encoder内でcontextを受け取れないなど、handlerレイヤとの情報連携がうまくいきません。

チームメンバーがDSLを調査する時間的コストも膨らんできたため、go-swaggerへの以降を決めました。

go-swaggerへの移行によるコンポーネント間及びSaasとの連携

go-swaggerへ移行することで、openapi 2.0のコード生成系が使えるようになりました。これにより、フロンエンドもコード生成の恩恵が受けられるようになりました。

// package.json
"scripts": {
    "gen-webapp-interface": "rm -rf types/response && openapi-generator-cli generate -g typescript-axios -i swagger.yaml -o ./types/response"
}

生成されたAPIクライアントをフロントエンドで利用

methods: {
    async getHogeList (page: number) {
      const api = new DefaultApi()
      const userId = this.currentUserId
          // 自動生成されたapiのクライアントコードを呼び出す。
      const result = await api.getHoges(userId, page).catch((e) => {
        console.error(e)
      })
      if (!result) {
        alert('hoge')
        return
      }
}

自動生成されたAPIクライアントの関数名が長かったり、モデル名Hogeに対して struct Hoge1などとナンバリングされてしまう点に読んでいるとたまに違和感を感じますが、人間が書いているコードではないので、慣れればそういうものだと思えます。

MDMでは各コンポーネントのAPIをswaggerで定義し、APIクライアントの自動生成やレスポンス型の自動生成機構を利用することで、連携にかかる手数を減らしています。また、BoxやDocusignなど公式のGoのSDKが存在しないSaaSとの連携においても、彼らが公開しているswaggerから生成したapiクライアントを利用しています(独自のクライアントを開発せずに済む、OAuthトークンのリフレッシュ機構だけ自前でアレンジが必要)

f:id:masashisalvador:20210423173737p:plain
自動生成クライアントコードに依るコンポーネント間連携

現状の課題・今後の展望

swagger(open api2.0)を用いたコード生成は便利なのですが、swagger側の型とGo側の型の対応付がいまいちなのと、swaggerで生成したモデルとxoで生成したモデル(DBの各テーブルのレコードに対応)、xoで生成したモデルを埋め込み組み合わせたモデル(emmbed model) の間の変換にそれなりに神経を使ってしまっています。

// api/emodel/hoge.go
type Hoge struct {
   Hoge *model.Hoge
   HogeImages *model.HogeImage `gorm:"foreignkey:HogeID;references:id"`
} 

// api/gen/hoge.xo.go # 自動生成
//         hogeimage.xo.go

import (
    "database/sql"
    "encoding/csv"
    "time"

    "github.com/LayerXcom/mdm/api/lib/csvconv"
)

const (
    TableHoge = "hoges"
    // ColumnHoge* represents a column name.
    CHogeID = "id"
        ....

...

api/gen/respmodel/hoge.go

// swagger:model Hoge
type Hoge struct {

    // access description
    // Required: true
    Description *string `json:"description"`

    // address
    // Required: true
    Address *string `json:"address"`
}
...

また、openapi 3.0とコード生成機構の未成熟さ(Goのクライアント生成は、型定義が欠落していてビルドエラーが出るレベル)を鑑みると、今後のメンテナビリティに若干の不安を感じます。そのため、MDMでも、GraphQLによるLayerX インボイス ワークフロー機能のモデル設計 にあるようにGraphQL及びコード生成機構の利用へのシフトを睨んでいます。

おわりに

スモールなチームで素早く開発するのが好きな方も、素早く動きつつも手堅く守りを固めるのが好きな方も、MDMを含むLayerXでは絶賛仲間を募集中です。 100xのその先へ、 TechnologyとOperationの <harmony /> を実現したい方、行きましょう!向こう側へ!

まずはお話だけでも!お待ちしております! herp.careers

herp.careers

意思決定と業務執行をシームレスにした話(DocuSign API実装編)

ご挨拶

毎々お世話になっております、LayerXから三井物産デジタル・アセットマネジメント(以下、MDM)に出向中の片桐(@akinama3と申します。

本記事については下記の後編になりますので、是非ご一読下さいますと幸いです。

tech.layerx.co.jp

前回の記事で、SaaSを活用して意思決定と業務執行をシームレスにした話を取り上げましたが、 今回はそこで利用した電子契約SaaSであるDocuSignについて、DocuSign eSignature APIDocuSign Connectを中心にお話致します。

電子契約とは

電子契約については、COVID-19によるリモートワーク推進等で認知・利用されるようになってきました。 電子契約の詳しいメリットについては、 下記が分かりやすいと思います。 詳しい部分は専門家におまかせし、本記事では技術的な観点にフォーカスしたいと思います。

www.cloudsign.jp www.docusign.jp

とはいえ、DocuSignの機能が膨大であり、用語も特殊なので基本的な部分をさらっと紹介していきます。

DocuSignのアカウント体系

業務の自動化観点でDocuSignを契約する場合、プラン選定の注意点があります。 DocuSignにはWebから申し込めるWebプランと、個別の見積もりによって契約する通常プランがあり、 DocuSign eSignature APIDocuSign Connectは、Webプランでは利用できません。 そのため、上記を利用する場合には見積もりによる通常プランを選択する必要があります。

料金

通常プランには、Business ProEnterprise Pro というプランがあり、 Enterprise Proが全機能を使えるプランになっており、Business ProはDocuSign eSignature APIDocuSign Connectが利用でき、SSOなどの追加機能はオプションとなるプランです。

MDMでは、Business Proの契約で必要十分だったのでBusiness Proを契約しております。

契約によって一通あたりの金額決まる

多くの電子契約SaaSでは、プランごとに月額料金と1通あたりの料金が決められていることが多いかと思います。 DocuSignの通常プランは月額料金等は存在せず、プランとオプションによって1通あたりの金額が決まります。 1年に300通送信する契約であれば、1通あたりの金額 × 300が請求金額となります。(サポート料金等は別途)

DocuSign用語

アカウント

下記のように、アカウントという単位で契約を行います。 アカウント単位で印影の登録などアカウント全体で共有する設定やブランド設定による署名画面のカスタマイズも可能です。

f:id:akinama2:20210422075933p:plain
DocuSign eSignatureの全体像 出典: docusign.com

Enterprise Pro プランでは、複数のアカウントを組織という単位で管理することもできます。 親会社が一括で管理したり、プロジェクト毎にアカウントを分けたりする必要がある場合に有用です。

f:id:akinama2:20210422090653p:plain
DocuSign組織管理 出典: docusign.com

ユーザー

アカウントに複数のユーザを持つことができます。 通常プランであればユーザ数の作成に制限がないため、各メンバーのユーザーに加え、自動化するサービス毎に作成することもできます。

  • ユーザー権限として、Admin / Sender / Viewerの設定ができる
  • ユーザーをグループに所属させることができる(部署の設定等に利用できる)
  • ユーザーのエンベロープを他ユーザに公開するかを設定できる(共有エンベロープ)

エンベロープ

電子契約の送信は エンベロープ という単位で行います。 エンベロープには下記の様な設定が必要になり、作成・送信ともに API 経由で実行できます。

  • どのドキュメントについて電子契約を行うか(ドキュメントという概念)
  • どの部分に署名をしたり、フォーム入力したりしてもらうか(タブという概念)
  • 誰にどの順番で署名・閲覧してもらうか(受信者という概念)

MDMで行った自動化においても、上記をAPI経由で設定し自動的に送信が行われるようにしています。

f:id:akinama2:20210422080345p:plain
エンベロープ概要 出典: docusign.com

DocuSign eSignature API

DocuSign eSignature APIでは、DocuSignのダッシュボードからできることは基本的に何でもできます。 エンベロープの作成だけではなく、アカウントの各種設定、ユーザー管理もAPI経由で実施できるので、各種管理の自動化等も可能になります。

MDMでは、「甲印」「乙印」「丙印」などのアンカー用文字列をドキュメントに忍ばせることによって、署名場所の設定もAPIで自動化しています。

ただ、非常に悔しいのですが、現状やりたくてもできないポイントとして API経由で自動的に署名する があります。 前回お話した、意思決定と業務執行のシームレス化 のシステムに於いて、現状のシステムでは 稟議の承認契約書の署名押印 の二度手間が発生しています。

稟議で承認されていれば、自動的に署名・押印されている状態にするのが理想です。今後のDocuSign eSignature APIの進化に期待したいと思います。

利用するための準備

DocuSign eSignature APIを利用するためには、管理画面からDocuSign eSignature APIの APIインテグレーションアプリを作成する必要があります。 認証についてはOAuth 2.0によって行うことができるので、サービスの特性に合わせて選択して下さい。

MDMでは自動化アプリの都合上、JWT Grantによるサービスインテーグレーションを採用し、 各ユーザから予めエンベロープ送信権限をアプリに委譲してもらっておく事によって、システムから自動的にエンベロープを送信できるようにしています。

今後、自社で稟議ワークフローを構築するなどの内製化が進んだ場合には、 認可コードフローによりアクセストークンを取得して送信するなどできると、よりセキュアかつ運用が楽になるのではと考えています。

DocuSign eSignature API の認証方法

Authentication | DocuSign

DocuSign eSignature API Reference

developers.docusign.com

DocuSign Connect

DocuSign Connectは、DocuSignの自動化に必要なコンポーネントの一つです。 いわゆる Webhook の機能で、締結完了を始めとしたエンベロープの各種イベントに対して発行できます。 開発を開始したタイミングでは、XML形式のWebhookしか対応しておらず若干めんどくさい状態でしたが、 現在はJSONによるWebhookに対応しているので、割と簡単に実装できるはずです。

MDMにてDocuSignを採用した観点として、下記のような観点が挙げられました。

発行できるイベントタイプが豊富

署名だけではなく、受信確認に対してもWebhookが設定できるので受信・開封したかどうかもシステムで管理することができます。

エンベロープ送信者毎にWebhookが設定できる

サービス専用のユーザーを用意し、そのサービスユーザが送信したエンベロープのみをWebhook対象とできるので、 雑多なWebhookが飛び交うことを防ぐことができます。 (ガバナンス・セキュリティ観点で有益)

HMAC署名検証が可能

WebhookのヘッダにHMAC検証用の署名を付与でき、実際にDocuSignから送られたということを検証できます。 HMAC署名用のConnect Keyについては複数登録でき、キーローテーションも可能です。

mTLSに対応可能

契約書や宛先といった情報のやり取りをよりセキュアに実施できます。

Webhook のログ・エラーが詳細に取得できる

管理画面からWebhookのログをリクエスト・レンスポンスを詳細に確認できるのでデバッグしやすくなっています。

DocuSign ConnectのデバッグTips

直接DocuSign Connectとは関係ありませんが、WebhookのHMAC署名検証やリクエストを変更してデバッグするときに、ngrokのinspect機能が役に立ちました。 開発環境でngrok経由でリクエストを受けるようにしておき、Modify Replayにて各種ヘッダを変更したり、 リクエストボディのStatus等を変更したりしながら動作検証をするということでデバッグ効率が向上しました。

f:id:akinama2:20210422104542p:plain
ngrok inspect機能によるデバッグ

MDMでの開発の進め方

ようやく一通り各種コンポーネントについて説明が終わったので、MDMのシステムではどのように実装したのかをお話したいと思います。

DocuSign関連部分の構成概要

f:id:akinama2:20210422104206p:plain
DocuSign関連部分の構成概要

DocuSign Sandboxを利用する

DocuSignは正式契約せずとも、DocuSign Sandboxを利用してシステム構築が可能です。 MDMでも、まずはSandbox 上にてアプリ構築を試して見てから、実際に正式契約するという形を取りました。

注意点としては、DocuSign SandboxはEnterprise Pro準拠で全ての機能が使える状態になっているため、 本契約で利用したい機能をしっかりとリストアップしておくことが重要です。

開発時に困ったポイント

エンベロープ送信権限の委譲のやり方がイマイチわからない

下記を参照することで解決できました。

基本的には各担当者がDocuSignにログインした状態で、 権限委譲甩のスコープを付与したAPIインテグレーションアプリごとのURL生成して展開し、 それ経由で権限移譲をブラウザ上で行ってもらう形になります。

www.docusign.com

エンベロープの送信者、署名者をしっかり整理する

誰の権限で送信し、社内の誰が署名して、社外の誰が署名するかをしっかり整理して置く必要がありました。 この整理により、システムのどの部分で情報を入力しなければいけないかが明確になりました。 (稟議情報入力時に入れるのかどうかなど)

アセットマネジメント業界においては、案件の秘密保持契約書を自社の署名のみで差し入れるパターンが多く、 また、MDMが持っている案件への差入れ依頼も自動化することを考えると、下記のようになります。

f:id:akinama2:20210422111217p:plain
エンベロープ送信者・署名者の整理

本番環境でアプリを使うためには審査がある

これは完全に盲点でした。

DocuSIgn Sandboxで開発をおこない、開発環境にて動作確認を行った後、いざ本番稼働させようとした時に管理画面からAPIインテグレーションアプリ作成の項目が見当たりません。 色々と調べていると、本番環境では Go Live という審査プロセスが存在することがわかり、若干対応コストがかかりました。 Go Live プロセスについては、DocuSign Sandboxアカウントで作成したアプリを本番アカウントに昇格させるプロセスになります。

基本的に、API が正しく実行できているかの確認プロセスを挟むだけなので普通にしっかりと作って問題なく通過すると思います。 開発環境から本番環境への以降は、環境変数等でインテグレーションキーやエンドポイント設定を本番向けに対応するだけで大丈夫です。

Go Live プロセス

あなたの力が必要です

上記の通り、雰囲気で「APIもWebhookもあるからいけるやん」みたいなノリでやると痛い目を見ることが多く、 業務知識、ツール自体の知識、エンジニアリングの総合格闘技感があります。

というわけで前回に引き続き、Coporate Opsで100Xを達成したい方、下記よりお待ちしております

herp.careers