[インデックス 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: admin
url
パターンに|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のドキュメントやブログ記事から推測される情報)