皆さん、こんにちは。ネイサン・クラークです。IDEXX Laboratoriesに勤めており、JMPを使い始めて22年になります。今回は、共同プレゼンターのセージ・ダーリングと共に、シークレット管理についてプレゼンテーションさせていただきます。
こんにちは。セージです。IDEXXでネイサンと一緒に働いています。こちらではシニアアナリティクスエンジニアとして働いています。今日はシークレットについてお話します。
素晴らしいですね。では、シークレットとは何かについて少しお話ししましょう。一般的に、少なくともコードの観点から言えば、シークレットとは、コードが動作して様々なものにアクセスするために必要な認証情報の一種ですが、同時に、他の人に知られるべきではありません。コンピューターにログインするときに使用するパスワードと非常に似ています。頻繁に使用する必要がありますが、他の人に教えるべきではありません。
シークレットとは、様々なプラットフォームが開発した、認証情報を外部から見えない形で保持するための方法です。そのため、バックグラウンドタスクでの使用ははるかに安全になります。JMPにはこの機能は最初から組み込まれていません。しかし、JSLでコードやバックグラウンド処理、あるいは舞台裏の作業を行う機会が増えるにつれ、私たちが行っていることを他の人と共有し、誤って誰かに王国への鍵を渡してしまうことなく、様々なものにアクセスできるようにするために、この機能が必要なのです。
しばらく前に JMP サポートと話し合っていたときに、Evan McCorkle が書いたこの小さなコードを入手しました。彼は、秘密を隠したり、ここで見ているような形で存在できるようにするためにこれをまとめましたが、その後、スクリプトを暗号化して使用することができます。
基本的に、これは秘密情報を含む一つの関数を使用しますが、その関数には二次的な秘密情報、つまりアクセスフレーズのようなものが必要です。このアクセスフレーズは、この暗号化されたコード内のすべての関数に含まれますが、他の誰にもその内容は分かりません。たとえ「use secret A」が呼び出したい関数だと分かっていても、このフレーズを知らない限り、これらの秘密情報に直接アクセスすることはできません。これを暗号化すれば、秘密スクリプトとして組み込むことができ、ユーザーに知られずにこれらの情報にアクセスできるようになります。これまでJMPサポートに問い合わせたところ、テスト中に秘密情報を公開する方法は見つかっていませんでした。私が取り組んでいるものでも、まだ見つけられていません。秘密情報を安全に保存しつつ、ユーザーがそれらを利用して様々なコード操作を実行できるようにするという目標達成のための、非常に優れたフレームワークのように思えます。
今日は2つの例を挙げます。Snowflakeにこれをどのように実装したかの例についてお話します。ここにあるのは、すみません、Snowflakeの接続テンプレートです。これは基本的に私が書いた関数ですが、良い部分はすべて隠されています。
ここを少し大きくしてみましょう。ご覧の通り、シークレットが格納されている場所を示す get_secrets 関数があります。今回のケースでは、現在の設定ではSnowflakeにアクセスするために必要なのはユーザー名とパスワードだけです。これは比較的簡単に扱えるパスワード配列です。
しかし、それらを活用するための他のサポート関数もあります。最初の関数は、データベースへの実際の接続を作成し、新しいデータコネクタを返します。これは、まとめる文字列です。これはデータコネクタがあり、会社固有のすべての部分に接続します。必要なのは、Snowflakeドライバがインストールされていることです。これは、SnowflakeのWebサイトからコンピュータにインストールしたときのSnowflakeドライバの名前です。この方法でODBC接続を行うことができます。ただし、データベースは会社固有のものになります。ただし、会社のサーバーはSnowflakeなどに接続されます。次に、パスワードは、ここで取得した別の配列から取得する場所です。それを入力すると、接続できる他の部分がここにあります。
最後に紹介する関数は、これをさらに一歩進めたものです。これは、実行したいクエリの文字列を受け取り、ここですべての接続を行い、この関数内でクエリを実行します。そして最終的に、そのテーブルから取得されたすべてのテーブルスクリプトを削除します。これがなぜ役立つのかについては、例を挙げて説明するときに説明します。基本的には、認証情報が誤って共有されたり漏洩したりしないようにするための、もう1つの保護レイヤーです。
それで、ここに非常にシンプルな例があります。最初の例では、基本的には生のクエリを実行しています。カスタムSQLを使用して、クエリの文字列バージョンを挿入しているだけです。これを実行すると、Snowflakeからテーブルが取得されます。Big Classに非常によく似ていますが、実際はBig Classです。このテーブルのコピーをSnowflakeにアップロードしました。これで、まさに希望通りのテーブルが作成されます。ここにはいくつかのソース情報が含まれています。これは便利です。これを行うと、ユーザー名とパスワードがマスクされることに気づきました。これは予想通りです。しかし、様々な方法でこの処理を試していたところ、偶然かどうかは分かりませんが、パスワードは漏洩していないのにユーザーIDが漏洩している例がいくつかありました。これらの認証情報をどの程度厳密に監視する必要があるかに応じて、これらの情報を削除する追加関数を作成しました。
このセクションをコメントアウトして実行すると、別の関数を使って新しいテーブルが作成され、クエリとして入力すると、同じものが作成されます。ただし、テーブルにはテーブルスクリプトが一切含まれていない状態です。これは便利ですね。これがどのように動作するかをさらに詳しく説明すると、例えば、これを削除してこの星型グラフのすべての列を選択すると、これですべて取り込まれるはずです。フィルタリング機能を追加したい場合も同様です。ここで実行できます。これらのクエリを実行する際の標準的なSQL言語と同じように動作します。文字列を渡すだけで、スクラブデータテーブルが出力されます。これは比較的単純な例です。これを関数の階層化と組み合わせる方法について、より複雑なバージョンを持つSageに渡したいと思います。それではSageにバトンタッチしましょう。
Fox Driveは、情報を保存できるオンラインフォルダの保存場所です。私たちはこれを様々な用途に使用しています。そのため、まず最初に着手したいのは、Boxからローカルのコンピュータにファイルを取得するために、Boxで認証を行う方法でした。そのためには、Box Developersを通じて認証可能なアプリを開発する必要がありました。そのアプリはJWT(JSON Web Token)と呼ばれるものを提供する必要があり、このJSON Web Tokenはパイプライン的にアクセストークンの取得に使用され、Boxに実際にアクセスできるようになります。私の状況は少し複雑でした。なぜなら、私はこれまでJSONやJSON Web Token、JWTを扱ったことがなかったため、最終的にここで実装することになったからです。
Nathan が説明していたように、右側に私の secrets.jsl の例を示します。これはまだ暗号化されていないので、中身が確認できます。私の secrets.jsl は、get secrets で始まり、get secrets 関数も持っています。ここに JWT 情報がすべて格納されます。JWT には、クライアント ID、クライアントシークレットなど、アプリケーション固有の情報が含まれています。これらの情報をこの連想配列に格納することで、取得するシークレット情報として利用しています。そして、これらの各要素にアクセスするために、クライアント ID から始まる連想配列内の要素にアクセスする別の関数を用意しています。
全部見ていくと連想配列の要素がすべて見えてしまい、おそらく退屈になってしまうので、ここでは1つだけ紹介します。これはクライアントIDを取得する例です。この関数内では、secretsを使ってsecrets(Boxアクセスキー)を取得し、そこからクライアントIDを取得しています。繰り返しますが、この関数は暗号化されているため、すべての情報が1つの関数から取得されるため、すべてが秘密に保たれます。
少し異なる点として、get box JWTトークンに関する部分を追加する必要があります。JWTは基本的に新しいアクセストークンを取得するためにアサーションを必要とするため、署名コンポーネントを必要とするため、少し異なる処理が必要です。署名コンポーネントは機密情報とみなされるため、実際には機密情報と見なされます。secrets.jslには、Boxからその情報を取得するためにもう少し処理を進める関数も含まれています。この場合、JWT関数は… 分かりやすくするためにラップします。JWT関数はBoxとの間でアサーションを作成し、アクセストークンを取得するための送信処理を実行します。そのためには、上記のシークレット情報と同様に、get boxエンタープライズID、get boxクライアントID、box公開鍵、秘密鍵、そしてアプリ固有のパラフレーズを取得する必要があります。
そこから、キーが欠落している場合や本当に重要なものなど、トラブルシューティングのためのコードをいくつか用意しました。そして、JWTアサーションを実行するために、実際にはPythonを使う必要がありました。これはまた、secrets.jsl内で別の言語を使用する例も含まれており、これも出力できるようにするために工夫する必要がありました。そのため、暗号化された情報はJWT情報を使用するスクリプト内ではなく、secrets.jslに格納されています。これは、JSL内にPythonが統合されているのを見るまたとない機会でもあり、非常に興味深いです。
次に、その情報をPythonスクリプトに送信します。スクリプトはJWTの特定の署名を取得します。そして、そこから認証情報を作成し、JWTアサーションを返します。これを後ほど使用できるようにします。JWTアサーションが取得できたら、アクセストークンを取得できます。そこから、基本的にはBox、つまり先ほど説明したBox JWTトークンを取得します。次に、client_IDとclient_secretを使用してトークンを作成します。JWTアサーション、client_ID、クライアントシークレットを取得し、HTTPリクエストを使用してBoxに送信します。これで新しいアクセストークンを取得できます。
さらに詳しく調べたい人のために、ここにはエアキャッチもいくつかありますが、実際にアクセス トークンを取得したかどうかに関する情報が送信され、その後、アクセス トークンがユーザーに返されます。
これはどこで使われるのでしょうか?どのように使われるのでしょうか?アクセストークン自体について、さらに興味深い議論が展開される可能性があるため、例を挙げて説明します。これを統合するために、例として汎用フォルダを用意しました。ただし、実際のコードでは、いくつかの入力値のみを示します。
私の場合は、Box接続を暗号化して含めています。これは、シークレットファイルの使用後に実行したい操作だからです。Boxには、場所、またはフォルダを管理する方法があり、IDコードが割り当てられています。これにより、ファイルを検索するフォルダを一意に識別できます。ここに、固有のフォルダIDを入力します。ここでは、JMPファイルのみを取得しています。これは、目的に応じてはるかに複雑な手順になる可能性もありますが、私の例ではJMPファイルのみを使用しました。
再帰の深さを確認できるパラメータもここに追加しました。Box が興味深いのは、オブジェクト指向で動作する点です。フォルダ ID があり、そのフォルダ ID 内にさらに ID が存在するため、フォルダを再帰的に検索する必要があります。スクリプトは必要なだけ階層を処理できるようにしています。この場合は 5 階層です。次に、ファイルをダウンロードする場所と、ファイルの名前を付ける方法を指定します。この例では、実際にどのように動作するか、そしてセキュリティ上の理由からアクセストークンを使用する際の複雑な点について説明した後、Box から JMP ファイルをいくつか取得する例を示します。
ここにはJSLを再帰的に検索する関数がいくつかあります。それらやPythonのダウンロードについてはここでは説明しませんが、メインのBox接続についてお話しします。この場合、これはセキュリティの観点から少し興味深い話になるかもしれません。各企業/各個人にとって、これをどこに配置するのが最適かという点です。配置場所はそれぞれ異なりますし、セキュリティの観点からも、どの場所について話しているかによって異なりますが、私の場合はこれで終わりにしました。このメイン関数では、まずBoxから情報を取得する必要があります。そのためには、アクセストークンを取得する必要があります。アクセストークンの取得は、先ほど暗号化されたsecrets.jslの一部です。この場合、この関数はアクセストークンを発行します。これはこのJSL内に存在する変数です。
このJSLでは、ここでアクセストークンを取得しているので、例えば583行目にprint文を追加すれば、アクセストークンを確認できます。Boxの興味深い点、あるいは優れた点は、これらのアクセストークンには有効期限があることです。後から誰かが使用できるリスクは全くありません。リスクは小さくなりますが、このアクセストークンを印刷したり、スクリプトを取得して実行し、その場所で印刷したりすると、セキュリティリスクが発生する可能性があります。
理想的には、あるいは可能性として、Boxアクセストークンをここに配置した私のやり方の代わりに、認証部分(テストと検索の両方)をsecret.jslに移動して暗号化するという方法があります。そうすれば、アクセスできなくなり、他の人はそれを見ることができなくなります。これは少し異なるユースケースですが、配置方法はご自身で自由に決めてください。最も安全な方法は、Boxアクセストークンをsecrets.jsl内に配置し、誰にも見られないように暗号化することです。しかし、今回のケースでは、これは良い議論のポイントです。私はこのように残しました。つまり、このように見えるということです。そこから話を進めていきましょう。
次にBoxのアクセストークンを取得し、そこからBoxのアクセスファイルを検索できるようになります。これは、自分が誰であるかを証明する認証情報を取得できたため、アクセスを許可してもらえるからです。私の場合はデモを用意していて、そのデモではBoxドライブにあるファイルからデータを取得する例を示しています。ファイル領域はこのようになっています。Secrets_JMP Discovery、File Examples_Secretsです。この場所から取得して連結するJMPテーブルがいくつかあるので、これを実行します。スクリプトを実行すると、時々このような状態になるので、この「待機音」が鳴ります。1秒ほどかかることもあります。
認証、私が誰なのか、あなたが誰なのか教えてください。さあ、始めましょう。これが私が入手したファイルです。ソーステーブルが表示されていますね。これはBoxに保存していたJMPテーブルの名前です。そして、これらは連結されたテーブルです。ちょっとしたジョークです。
BoxアクセストークンとJWTの統合については以上です。Nathanに報告します。
Sageが示し、私が示したこれらの機能を使って、実際にどのように実装し、私とSageだけでなく他のメンバーも作業できるようにするにはどうすれば良いでしょうか?私たちはこれをGitHubにアップロードしました。GitHubはこちらです。リポジトリはこちらです。プレゼンテーションにもリンクされており、サンプルもここにあります。
これは、どのように整理したいかによって、様々なオプションがあります。しかし、ここでまとめた主なものは、シークレットテンプレートです。これは先ほど基本的な値で示したテンプレートです。そして、ここに様々な例があります。これらの例には、基本的に3つのメインファイルがあります。ここには、すべての情報を含むテンプレートがあります。ここには、個人情報や会社情報などを入力することができます。そして、これが暗号化に必要な情報です。これを安全にするために、このテンプレートをリポジトリまたはクローンに保存し、template を raw に置き換えることをお勧めします。こうすることで、本当に重要な情報はすべてそこに隠されます。gitignore で raw.jsl を無視するように設定しました。こうすることで、作業に必要な情報はローカルに保存されます。認証情報は保持されます。次に、生のファイルを暗号化し、そのファイルの暗号化バージョンとしてアップロードします。
GitHub には、実際に使える暗号化バージョンだけを保存しておく必要があります。各フォルダには、それぞれの用途に特化したドキュメントが入っています。メインの README やドキュメントでどの程度情報を共有するかは、この情報をどのように活用したいかによって大きく左右されます。例えば、Sage と私が昨年発表した Project Jab のようなアドインを開発し、個々のリポジトリからファイルをプルできる仕組みがあります。これは、必要なシークレットファイルだけをプルして、必要な作業を実行できる仕組みです。これは、アクセス性を維持しながら、様々なレベルの機密性を維持できる優れた方法です。
Sage が示唆したように、コードが少し複雑になっている状況では、そのレベルの機密性を維持するために、暗号化されたファイルにさらに多くの情報を入れることになる状況に陥る可能性があります。
Sageさんがテンプレートで話していたように、彼女は多くの作業を行う関数をここにすべて用意していますが、例を説明した際に、非常に特殊なアクセストークンについて言及していました。関数の正確な位置は覚えていませんが、メインの例ではそれを公開したくない場合は、その関数をテンプレートまたはテンプレート化されたrawファイルに追加して暗号化すれば、すべて非表示になります。どのレイヤーを経由する必要があるかは、ユースケースや目的によって異なると思います。Snowflakeでは、接続を直接使用したり、テーブルをスクラブするだけで済むため、比較的簡単でした。そのため、APIなど、接続先によって結果は異なる可能性があります。
しかし、これは比較的理解しやすく、秘密を調べて、クリーンで安全な状態を保つための強固なサイバーセキュリティの実践を維持しながら、バックグラウンド コーディングを改善するための多くのレイヤーを提供できる方法であると思います。
本日お話ししたかったことはこれで全てです。この機会を与えてくださったJMPの皆様、そしてこのイベントをご覧いただいた視聴者の皆様に心より感謝申し上げます。
全く同感です。秘密はしっかり守っていただければ幸いです。もしよろしければ、今年のJMP Discoveryにぜひお越しください。私たちも参加しますので。
はい、そうします。ありがとうございます。