[インデックス 10957] ファイルの概要
このコミットは、Goプロジェクトのビルドダッシュボードシステムに新しい/keyハンドラを追加し、既存の認証ロジックをリファクタリングするものです。これにより、ビルドシステムが認証キーをより柔軟に取得・検証できるようになります。
コミット
- コミットハッシュ:
03805054e30436f445faf3492d66952e3e297c24 - 作者: Andrew Gerrand adg@golang.org
- コミット日時: 2011年12月22日 木曜日 09:38:57 +1100
- コミットメッセージ:
dashboard: add /key handler
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/03805054e30436f445faf3492d66952e3e297c24
元コミット内容
dashboard: add /key handler
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5504066
変更の背景
この変更は、Goプロジェクトの継続的インテグレーション(CI)およびビルドシステムの一部であるダッシュボードアプリケーションの認証メカニズムを改善するために行われました。以前の認証ロジックは、特定のPOSTリクエストに対してハードコードされたsecretKeyとの比較、またはHMAC-MD5ハッシュの検証を行っていました。
しかし、このアプローチでは、外部のビルドエージェントや自動化されたスクリプトがダッシュボードと安全に連携するための柔軟な方法が不足していました。特に、各ビルダ(builder)が自身の識別子に基づいて動的に認証キーを取得する必要がある場合、既存のメカニズムでは対応が困難でした。
このコミットの主な目的は、以下の点を達成することです。
- 認証キーの動的取得: 新しい
/keyエンドポイントを導入することで、ビルダが自身の識別子(builder名)に基づいて、ダッシュボードとの通信に必要な認証キーを安全に取得できるようにします。これにより、キーの管理がより柔軟になります。 - 認証ロジックのリファクタリング: 既存の
AuthHandler内の認証ロジックをvalidKeyという独立した関数に抽出し、コードの可読性、保守性、およびテスト容易性を向上させます。 - 開発環境での利便性向上: 開発環境(App Engineの開発サーバー)では認証をスキップするロジックを導入し、開発プロセスを簡素化します。
これにより、Goプロジェクトのビルドインフラストラクチャがより堅牢で、自動化されたプロセスとの連携が容易になることが期待されます。
前提知識の解説
このコミットの変更内容を理解するためには、以下の技術的な概念について基本的な知識があると役立ちます。
-
Go言語:
- HTTPハンドラ: Goの
net/httpパッケージは、Webサーバーを構築するための基本的な機能を提供します。http.HandlerFuncはHTTPリクエストを処理する関数の型であり、http.HandleFuncは特定のURLパスにハンドラ関数を登録するために使用されます。 http.Requestとhttp.ResponseWriter: HTTPハンドラ関数は、それぞれ受信したリクエストと、クライアントに送信するレスポンスを操作するためのオブジェクトを受け取ります。r.FormValue("param")は、リクエストのクエリパラメータやフォームデータから指定された名前の値を取得するために使用されます。fmtパッケージ:fmt.Fprintは、指定されたio.Writer(この場合はhttp.ResponseWriter)にフォーマットされた文字列を書き込むために使用されます。osパッケージ:os.NewErrorは、新しいエラーオブジェクトを作成するために使用されます。logパッケージ:logErr関数内で使用されているappengine.NewContext(r).Errorfは、Google App Engineのコンテキストにエラーログを記録するためのものです。
- HTTPハンドラ: Goの
-
Google App Engine (GAE):
appengine.IsDevAppServer(): この関数は、アプリケーションがGoogle App Engineの開発サーバー上で実行されているかどうかを判定します。開発環境では、本番環境とは異なる動作(例: 認証のスキップ)をさせたい場合によく使用されます。app.yaml: Google App Engineアプリケーションの構成ファイルです。URLパスとそれに対応するハンドラ(スクリプト)のマッピング、認証設定(例:login: admin)などを定義します。login: adminは、そのURLパスへのアクセスに管理者ログインが必要であることを示します。
-
HMAC (Hash-based Message Authentication Code):
- HMACの目的: HMACは、メッセージの完全性と認証を同時に保証するためのメカニズムです。共有された秘密鍵(
secretKey)とハッシュ関数(この場合はMD5)を使用して、メッセージからMAC(Message Authentication Code)を生成します。受信者は同じ秘密鍵とハッシュ関数を使ってMACを再計算し、受信したMACと比較することで、メッセージが改ざんされていないこと、および送信者が秘密鍵を知っている正当なエンティティであることを確認できます。 crypto/hmacパッケージ: Goの標準ライブラリに含まれるHMACの実装を提供します。hmac.NewMD5([]byte(secretKey))は、指定された秘密鍵とMD5ハッシュ関数を使用して新しいHMACハッシュオブジェクトを作成します。hash.Hashインターフェース:h.Write([]byte(builder))は、HMACハッシュオブジェクトにデータを書き込みます。h.Sum()は、書き込まれたデータに基づいて最終的なHMAC値を計算し、バイトスライスとして返します。fmt.Sprintf("%x", h.Sum())は、バイトスライスを16進数文字列に変換します。
- HMACの目的: HMACは、メッセージの完全性と認証を同時に保証するためのメカニズムです。共有された秘密鍵(
-
ビルドダッシュボードシステム:
- Goプロジェクトのビルドダッシュボードは、Go言語の公式ビルドシステムの一部であり、様々なプラットフォームでのビルド、テスト、およびリリースプロセスの状態を監視するために使用されます。このシステムは、自動化されたビルドエージェント(「ビルダ」)からの情報を受け取り、その状態を可視化します。認証メカニズムは、これらのビルダがダッシュボードに安全にデータを送信できるようにするために不可欠です。
技術的詳細
このコミットは、Goビルドダッシュボードの認証システムに以下の重要な変更を導入しています。
-
/keyハンドラの追加:- 新しいHTTPエンドポイント
/keyが追加されました。このエンドポイントは、misc/dashboard/app/app.yamlで管理者ログインが必要なパスとして設定されています。 keyHandler関数がこのエンドポイントを処理します。keyHandlerは、クエリパラメータbuilderを受け取ります。このbuilderは、認証キーを要求しているビルドエージェントの識別子を指します。builderパラメータが提供されない場合、エラーがログに記録され、クライアントにはエラーレスポンスが返されます。- 有効な
builderが提供された場合、builderKey(builder)関数を呼び出して、そのbuilderに対応する認証キーを生成し、そのキーをHTTPレスポンスとしてクライアントに返します。
- 新しいHTTPエンドポイント
-
認証ロジックのリファクタリングと
validKey関数の導入:- 既存の
AuthHandler関数内の認証ロジックが、validKey(key, builder string) boolという新しいヘルパー関数に抽出されました。 AuthHandlerは、POSTリクエストの場合にvalidKey関数を呼び出して、提供されたkeyとbuilderが有効であるかを検証します。検証に失敗した場合、"invalid key"エラーを返します。validKey関数は以下のロジックでキーを検証します。- 開発環境のスキップ:
appengine.IsDevAppServer()がtrueの場合(つまり、アプリケーションがローカルの開発サーバーで実行されている場合)、認証は常に成功とみなされ、trueが返されます。これにより、開発中のデバッグが容易になります。 - 秘密鍵による直接認証: 提供された
keyがグローバルなsecretKeyと完全に一致する場合、認証は成功とみなされます。これは、特定の特権的な操作や、ビルダが共通の秘密鍵を使用する場合に利用される可能性があります。 - ビルダ固有のキー認証: 上記の条件に当てはまらない場合、
validKeyはbuilderKey(builder)を呼び出して、提供されたbuilder名から期待されるキーを計算し、その計算されたキーと提供されたkeyが一致するかどうかを比較します。これにより、各ビルダが自身の識別子に基づいたHMACキーを使用して認証できるようになります。
- 開発環境のスキップ:
- 既存の
-
builderKey関数の導入:builderKey(builder string) stringという新しいヘルパー関数が導入されました。- この関数は、
secretKeyを秘密鍵として、提供されたbuilder文字列のHMAC-MD5ハッシュを計算します。 - 計算されたHMAC値は16進数文字列としてフォーマットされ、返されます。
- この関数は、
/keyハンドラでビルダ固有のキーを生成するため、およびvalidKey関数でビルダ固有のキーを検証するために使用されます。
これらの変更により、ダッシュボードの認証システムはよりモジュール化され、ビルドエージェントが安全かつ動的に認証キーを取得・使用できる柔軟なメカニズムが提供されます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
misc/dashboard/app/app.yaml:--- a/misc/dashboard/app/app.yaml +++ b/misc/dashboard/app/app.yaml @@ -10,6 +10,6 @@ handlers: script: _go_app - url: /(|commit|packages|result|tag|todo) script: _go_app -- url: /(init|buildtest|_ah/queue/go/delay) +- url: /(init|buildtest|key|_ah/queue/go/delay) script: _go_app login: adminurlパターンに|keyが追加され、/keyパスが管理者ログインを必要とするハンドラとして登録されました。
-
misc/dashboard/app/build/handler.go:-
AuthHandlerの変更:--- a/misc/dashboard/app/build/handler.go +++ b/misc/dashboard/app/build/handler.go @@ -321,12 +321,9 @@ func AuthHandler(h dashHandler) http.HandlerFunc { // Validate key query parameter for POST requests only. key := r.FormValue("key") - if r.Method == "POST" && key != secretKey && !appengine.IsDevAppServer() { - h := hmac.NewMD5([]byte(secretKey)) - h.Write([]byte(r.FormValue("builder"))) - if key != fmt.Sprintf("%x", h.Sum()) { - err = os.NewError("invalid key: " + key) - } + builder := r.FormValue("builder") + if r.Method == "POST" && !validKey(key, builder) { + err = os.NewError("invalid key: " + key) } // Call the original HandlerFunc and return the response.- インラインで行われていたキー検証ロジックが削除され、新しく導入された
validKey関数を呼び出すように変更されました。builderパラメータも取得されるようになりました。
- インラインで行われていたキー検証ロジックが削除され、新しく導入された
-
keyHandler関数の追加:func keyHandler(w http.ResponseWriter, r *http.Request) { builder := r.FormValue("builder") if builder == "" { logErr(w, r, os.NewError("must supply builder in query string")) return } fmt.Fprint(w, builderKey(builder)) }/keyエンドポイントのハンドラとして、builderクエリパラメータを受け取り、builderKeyを生成して返す関数が追加されました。
-
init関数でのハンドラ登録:--- a/misc/dashboard/app/build/handler.go +++ b/misc/dashboard/app/build/handler.go @@ -365,9 +362,19 @@ func initHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "OK") } +func keyHandler(w http.ResponseWriter, r *http.Request) { + builder := r.FormValue("builder") + if builder == "" { + logErr(w, r, os.NewError("must supply builder in query string")) + return + } + fmt.Fprint(w, builderKey(builder)) +} + func init() { // admin handlers http.HandleFunc("/init", initHandler) + http.HandleFunc("/key", keyHandler) // authenticated handlers http.HandleFunc("/commit", AuthHandler(commitHandler))http.HandleFunc("/key", keyHandler)が追加され、/keyパスにkeyHandlerが登録されました。
-
validKey関数の追加:func validKey(key, builder string) bool { if appengine.IsDevAppServer() { return true } if key == secretKey { return true } return key == builderKey(builder) }- 認証キーの検証ロジックをカプセル化した関数が追加されました。
-
builderKey関数の追加:func builderKey(builder string) string { h := hmac.NewMD5([]byte(secretKey)) h.Write([]byte(builder)) return fmt.Sprintf("%x", h.Sum()) }- ビルダ名からHMAC-MD5ハッシュを生成する関数が追加されました。
-
コアとなるコードの解説
このコミットの核心は、Goビルドダッシュボードの認証フローをより柔軟かつ安全にするための新しい関数とロジックの導入にあります。
-
keyHandler(w http.ResponseWriter, r *http.Request):- この関数は、HTTP GETリクエスト(またはPOSTリクエスト)で
/keyエンドポイントにアクセスされたときに実行されます。 - 主な目的は、特定のビルダ(
builder)に対応する認証キーを生成し、それをリクエスト元に返すことです。 builder := r.FormValue("builder")で、リクエストのクエリパラメータからbuilder名を取得します。このbuilder名は、キーを要求しているビルドエージェントの識別子です。if builder == "" { ... }は、builderパラメータが提供されているかどうかの基本的なバリデーションです。提供されていない場合はエラーをログに記録し、処理を終了します。fmt.Fprint(w, builderKey(builder))がこのハンドラの主要な処理です。builderKey(builder)関数を呼び出して、builder名に基づいた認証キーを生成し、そのキーをHTTPレスポンスボディとしてクライアントに書き込みます。
- この関数は、HTTP GETリクエスト(またはPOSTリクエスト)で
-
validKey(key, builder string) bool:- この関数は、
AuthHandlerから呼び出され、受信したリクエストに含まれる認証キー(key)とビルダ名(builder)が有効であるかを判定します。 if appengine.IsDevAppServer() { return true }: アプリケーションがGoogle App Engineの開発サーバーで実行されている場合、認証は常に成功とみなされます。これは、開発中のテストやデバッグを容易にするための一般的なプラクティスです。if key == secretKey { return true }: 提供されたkeyが、アプリケーション全体で共有されている秘密鍵secretKeyと完全に一致する場合、認証は成功とみなされます。これは、特定の特権的な操作や、共通の秘密鍵を使用するビルダのためのフォールバックまたは主要な認証方法として機能します。return key == builderKey(builder): 上記のいずれの条件も満たさない場合、この行が実行されます。これは、提供されたkeyが、builderKey(builder)関数によって計算された、そのbuilder名に固有のHMACキーと一致するかどうかをチェックします。これにより、各ビルダは自身の識別子に基づいて生成されたキーを使用して認証を行うことができます。
- この関数は、
-
builderKey(builder string) string:- この関数は、与えられた
builder文字列から、対応するHMAC-MD5認証キーを生成します。 h := hmac.NewMD5([]byte(secretKey)):secretKeyを秘密鍵として、MD5ハッシュ関数を使用する新しいHMACハッシュオブジェクトを初期化します。secretKeyは、キー生成と検証の両方で一貫して使用される共有秘密です。h.Write([]byte(builder)):builder文字列のバイト表現をHMACハッシュオブジェクトに書き込みます。HMACは、このデータに対してハッシュ計算を行います。return fmt.Sprintf("%x", h.Sum()):h.Sum()は、これまでに書き込まれたデータ(この場合はbuilder)と秘密鍵に基づいてHMAC値を計算し、バイトスライスとして返します。fmt.Sprintf("%x", ...)は、このバイトスライスを16進数形式の文字列に変換します。この16進数文字列が、特定のbuilderに対応する認証キーとなります。
- この関数は、与えられた
これらの関数が連携することで、ダッシュボードは、開発環境での認証スキップ、共通の秘密鍵による認証、そして最も重要な、各ビルダに固有のHMACベースのキーによる認証という、多層的で柔軟な認証メカニズムを提供します。/keyエンドポイントは、ビルダがこのHMACキーを安全に取得するための手段を提供し、自動化されたビルドプロセスとの統合を容易にします。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/5504066
参考にした情報源リンク
- Go言語
net/httpパッケージ: https://pkg.go.dev/net/http - Go言語
crypto/hmacパッケージ: https://pkg.go.dev/crypto/hmac - Google App Engine Go Standard Environment: https://cloud.google.com/appengine/docs/standard/go/
- HMAC (Hash-based Message Authentication Code) - Wikipedia: https://ja.wikipedia.org/wiki/HMAC
- Goプロジェクトのビルドシステムに関する情報 (一般的なGoのドキュメントやブログ記事から推測される情報)