[インデックス 12990] ファイルの概要
このコミットは、Go言語のダッシュボードツールにおける2つの異なる問題を修正しています。一つは、コードレビューの変更リスト(CL)の最終更新時刻を表示する際に、時間単位の計算がGoのmap
のイテレーション順序に依存していた問題を解決し、もう一つは、/gc
エンドポイントへのアクセスに管理者認証を要求するようにapp.yaml
設定を修正しています。
コミット
commit 24cce5c60c1cfc66567bf11203671e004c028c8d
Author: David Symonds <dsymonds@golang.org>
Date: Sat Apr 28 09:47:15 2012 +1000
misc/dashboard/codereview: don't depend on map iteration order for unit calculation.
Fix auth requirements for /gc endpoint too.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6133049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/24cce5c60c1cfc66567bf11203671e004c028c8d
元コミット内容
misc/dashboard/codereview
: 単位計算のためにマップのイテレーション順序に依存しないようにする。
/gc
エンドポイントの認証要件も修正する。
変更の背景
このコミットは、Go言語のコードレビューダッシュボードの安定性とセキュリティを向上させるために行われました。
-
map
のイテレーション順序への依存性: Go言語のmap
(ハッシュマップ)は、その設計上、要素のイテレーション順序が保証されません。これは意図的なものであり、ランダム化することで、開発者がイテレーション順序に誤って依存してしまうことを防ぎ、また、特定の順序に依存するバグを早期に発見できるようにするためです。しかし、このダッシュボードのModifiedAgo()
関数では、変更リストが最後に更新されてからの経過時間を「d」(日)、「h」(時間)、「m」(分)、「s」(秒)といった単位で表示する際に、map
に定義された時間単位のイテレーション順序に依存していました。このため、環境やGoのバージョンによっては、期待しない順序で単位が評価され、例えば「1時間前」と表示されるべきところが「60分前」と表示されるなど、不正確な表示になる可能性がありました。このコミットは、この非決定的な挙動を修正し、常に期待される順序(日→時間→分→秒)で単位が評価されるようにします。 -
/gc
エンドポイントの認証要件: Go言語のダッシュボードには、おそらくガベージコレクション(GC)関連の操作や、その他の管理タスクを行うための/gc
という内部エンドポイントが存在していたと考えられます。しかし、このエンドポイントへのアクセスに適切な認証(管理者ログイン)が設定されていなかったため、セキュリティ上の脆弱性となる可能性がありました。このコミットは、/update-cl
エンドポイントと同様に、/gc
エンドポイントにも管理者ログインを必須とすることで、不正なアクセスを防ぎ、システムのセキュリティを強化します。
前提知識の解説
Go言語のmap
のイテレーション順序
Go言語のmap
は、キーと値のペアを格納するハッシュテーブルの実装です。Goの仕様では、map
をイテレートする際の順序は保証されません。これは、map
の実装が内部的にハッシュ関数を使用しており、要素の追加や削除によってメモリ上の配置が変化するためです。また、Goランタイムは意図的にイテレーション順序をランダム化することがあり、これにより開発者が非決定的な順序に依存するコードを書くことを防ぎます。したがって、順序が重要な場合は、map
ではなくスライス(配列)を使用するか、map
のキーをソートしてスライスに格納し、そのスライスをイテレートするといった工夫が必要です。
Google App Engine (GAE) のapp.yaml
app.yaml
は、Google App Engineアプリケーションのデプロイメント記述子です。このファイルは、アプリケーションのランタイム環境、ハンドラ(URLルーティング)、スケーリング設定、環境変数、認証要件などを定義します。特にhandlers
セクションでは、特定のURLパスに対するリクエストをどのスクリプトやサービスが処理するか、また、そのURLへのアクセスにどのような認証が必要か(例: login: admin
で管理者ログインを要求)を設定できます。
Go言語のtime.Duration
型
time.Duration
は、Go言語のtime
パッケージで定義されている型で、時間の長さをナノ秒単位で表す整数型です。例えば、time.Hour
は1時間のtime.Duration
を表し、time.Minute
は1分のtime.Duration
を表します。時間の計算や比較に便利です。
Go言語のfmt.Sprintf
関数
fmt.Sprintf
は、Go言語のfmt
パッケージで提供される関数で、フォーマット指定子(例: %d
で整数、%s
で文字列)を使用して、指定された値を整形し、新しい文字列として返します。C言語のsprintf
に似ています。
技術的詳細
cl.go
における時間単位計算の修正
元のコードでは、ModifiedAgo()
関数内で時間の単位(日、時間、分、秒)をmap[string]time.Duration
として定義していました。
units := map[string]time.Duration{
"d": 24 * time.Hour,
"h": time.Hour,
"m": time.Minute,
"s": time.Second,
}
そして、このmap
をfor...range
ループでイテレートし、現在の経過時間d
がどの単位u
よりも大きいかをチェックしていました。
for suffix, u := range units {
if d > u {
return fmt.Sprintf("%d%s", d/u, suffix)
}
}
しかし、前述の通りGoのmap
のイテレーション順序は保証されないため、例えば"h"
(時間)が"d"
(日)よりも先に評価される可能性がありました。これにより、d
が1日以上であっても、d > time.Hour
が先に真となり、「24h」と表示されるべきところが「1d」と表示されない、といった問題が発生し得ました。
このコミットでは、この問題を解決するためにmap
の代わりに**匿名構造体のスライス(配列)**を使用するように変更しました。
units := [...]struct {
suffix string
unit time.Duration
}{
{"d", 24 * time.Hour},
{"h", time.Hour},
{"m", time.Minute},
{"s", time.Second},
}
スライスは要素の順序が保証されるデータ構造であるため、この変更により、for...range
ループは常に定義された順序(日→時間→分→秒)でイテレートされるようになります。これにより、最も大きな適切な単位が常に最初に評価され、正確な「Modified Ago」の表示が保証されます。
イテレーションも、for _, u := range units
のように、構造体全体をイテレートするように変更され、u.unit
とu.suffix
でそれぞれの値にアクセスしています。
app.yaml
における認証要件の修正
元のapp.yaml
では、/update-cl
というURLパスに対してのみ管理者ログインを要求していました。
- url: /update-cl
script: _go_app
login: admin
このコミットでは、/gc
エンドポイントも同様に管理者ログインを要求するように変更されました。これは、正規表現のOR演算子|
を使用して、単一のURLパターンで複数のパスをマッチさせることで実現されています。
- url: /(gc|update-cl)
script: _go_app
login: admin
この変更により、/gc
または/update-cl
のいずれかのURLにアクセスしようとすると、Google App Engineの認証システムによって管理者ログインが要求されるようになります。これにより、これらの管理エンドポイントへの不正なアクセスが防止され、アプリケーションのセキュリティが向上します。
コアとなるコードの変更箇所
misc/dashboard/codereview/app.yaml
--- a/misc/dashboard/codereview/app.yaml
+++ b/misc/dashboard/codereview/app.yaml
@@ -16,7 +16,7 @@ handlers:
- url: /_ah/queue/go/delay
script: _go_app
login: admin
-- url: /update-cl
+- url: /(gc|update-cl)
script: _go_app
login: admin
- url: /.*
misc/dashboard/codereview/dashboard/cl.go
--- a/misc/dashboard/codereview/dashboard/cl.go
+++ b/misc/dashboard/codereview/dashboard/cl.go
@@ -80,16 +80,19 @@ func (cl *CL) LGTMHTML() template.HTML {
func (cl *CL) ModifiedAgo() string {
// Just the first non-zero unit.
-\tunits := map[string]time.Duration{\n-\t\t\"d\": 24 * time.Hour,\n-\t\t\"h\": time.Hour,\n-\t\t\"m\": time.Minute,\n-\t\t\"s\": time.Second,\n+\tunits := [...]struct {\n+\t\tsuffix string\n+\t\tunit time.Duration\n+\t}{\n+\t\t{\"d\", 24 * time.Hour},\n+\t\t{\"h\", time.Hour},\n+\t\t{\"m\", time.Minute},\n+\t\t{\"s\", time.Second},\n \t}\n \td := time.Now().Sub(cl.Modified)\n-\tfor suffix, u := range units {\n-\t\tif d > u {\n-\t\t\treturn fmt.Sprintf(\"%d%s\", d/u, suffix)\n+\tfor _, u := range units {\n+\t\tif d > u.unit {\n+\t\t\treturn fmt.Sprintf(\"%d%s\", d/u.unit, u.suffix)\n \t\t}\n \t}\n \treturn \"just now\"\n```
## コアとなるコードの解説
### `app.yaml`の変更解説
変更前:
```yaml
- url: /update-cl
script: _go_app
login: admin
この設定は、/update-cl
という特定のURLパスへのリクエストが来た場合に、_go_app
というスクリプトで処理され、かつアクセスにはadmin
(管理者)権限でのログインが必要であることを示しています。
変更後:
- url: /(gc|update-cl)
script: _go_app
login: admin
変更後の設定では、url
のパターンが正規表現の(gc|update-cl)
に変更されています。これは、/gc
または/update-cl
のいずれかのURLパスにマッチすることを意味します。これにより、両方のエンドポイントに対して同じscript: _go_app
とlogin: admin
の認証要件が適用されるようになります。これは、/gc
エンドポイントが管理者のみがアクセスできるべき重要な機能であると判断されたため、セキュリティを強化するための修正です。
cl.go
の変更解説
変更前:
func (cl *CL) ModifiedAgo() string {
// Just the first non-zero unit.
units := map[string]time.Duration{
"d": 24 * time.Hour,
"h": time.Hour,
"m": time.Minute,
"s": time.Second,
}
d := time.Now().Sub(cl.Modified)
for suffix, u := range units {
if d > u {
return fmt.Sprintf("%d%s", d/u, suffix)
}
}
return "just now"
}
この関数は、変更リスト(CL)が最後に変更されてからの経過時間を人間が読みやすい形式で返します。units
というmap
に、時間単位(日、時間、分、秒)とそのtime.Duration
値が格納されています。time.Now().Sub(cl.Modified)
で経過時間d
を計算し、map
をイテレートして、d
がどの単位u
よりも大きいかをチェックし、最初に条件を満たした単位でフォーマットされた文字列を返していました。しかし、Goのmap
のイテレーション順序は保証されないため、例えば"h"
(時間)が"d"
(日)よりも先に評価される可能性があり、その場合、1日以上の経過時間でも「24h」のように表示されてしまう可能性がありました。
変更後:
func (cl *CL) ModifiedAgo() string {
// Just the first non-zero unit.
units := [...]struct {
suffix string
unit time.Duration
}{
{"d", 24 * time.Hour},
{"h", time.Hour},
{"m", time.Minute},
{"s", time.Second},
}
d := time.Now().Sub(cl.Modified)
for _, u := range units {
if d > u.unit {
return fmt.Sprintf("%d%s", d/u.unit, u.suffix)
}
}
return "just now"
}
この変更では、units
がmap
から**匿名構造体のスライス(配列)**に変更されました。
[...]struct { suffix string; unit time.Duration }
は、suffix
(単位の接尾辞、例: "d", "h")とunit
(time.Duration
値)を持つ構造体の配列を宣言しています。スライスは要素の順序が保証されるため、{"d", 24 * time.Hour}
が常に最初に、{"h", time.Hour}
が次に、というように定義された順序でイテレートされます。
for _, u := range units
ループでは、スライスの各要素(匿名構造体)がu
に代入されます。そして、d > u.unit
で経過時間と現在の単位の値を比較し、fmt.Sprintf("%d%s", d/u.unit, u.suffix)
でフォーマットされた文字列を返します。この修正により、常に「日」→「時間」→「分」→「秒」の順で評価が行われるため、最も大きな適切な単位で経過時間が表示されるようになり、表示の正確性と一貫性が保証されます。
関連リンク
- Go Code Review: https://golang.org/cl/6133049
参考にした情報源リンク
- The Go Programming Language Specification - Map types: https://go.dev/ref/spec#Map_types
- Go by Example: Maps: https://gobyexample.com/maps
- Google Cloud - App Engine
app.yaml
reference: https://cloud.google.com/appengine/docs/standard/go/config/appref - GoDoc -
time
package: https://pkg.go.dev/time - GoDoc -
fmt
package: https://pkg.go.dev/fmt - Go Slices: usage and internals: https://go.dev/blog/slices-usage-and-internals# [インデックス 12990] ファイルの概要
このコミットは、Go言語のダッシュボードツールにおける2つの異なる問題を修正しています。一つは、コードレビューの変更リスト(CL)の最終更新時刻を表示する際に、時間単位の計算がGoのmap
のイテレーション順序に依存していた問題を解決し、もう一つは、/gc
エンドポイントへのアクセスに管理者認証を要求するようにapp.yaml
設定を修正しています。
コミット
commit 24cce5c60c1cfc66567bf11203671e004c028c8d
Author: David Symonds <dsymonds@golang.org>
Date: Sat Apr 28 09:47:15 2012 +1000
misc/dashboard/codereview: don't depend on map iteration order for unit calculation.
Fix auth requirements for /gc endpoint too.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6133049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/24cce5c60c1cfc66567bf11203671e004c028c8d
元コミット内容
misc/dashboard/codereview
: 単位計算のためにマップのイテレーション順序に依存しないようにする。
/gc
エンドポイントの認証要件も修正する。
変更の背景
このコミットは、Go言語のコードレビューダッシュボードの安定性とセキュリティを向上させるために行われました。
-
map
のイテレーション順序への依存性: Go言語のmap
(ハッシュマップ)は、その設計上、要素のイテレーション順序が保証されません。これは意図的なものであり、ランダム化することで、開発者がイテレーション順序に誤って依存してしまうことを防ぎ、また、特定の順序に依存するバグを早期に発見できるようにするためです。しかし、このダッシュボードのModifiedAgo()
関数では、変更リストが最後に更新されてからの経過時間を「d」(日)、「h」(時間)、「m」(分)、「s」(秒)といった単位で表示する際に、map
に定義された時間単位のイテレーション順序に依存していました。このため、環境やGoのバージョンによっては、期待しない順序で単位が評価され、例えば「1時間前」と表示されるべきところが「60分前」と表示されるなど、不正確な表示になる可能性がありました。このコミットは、この非決定的な挙動を修正し、常に期待される順序(日→時間→分→秒)で単位が評価されるようにします。 -
/gc
エンドポイントの認証要件: Go言語のダッシュボードには、おそらくガベージコレクション(GC)関連の操作や、その他の管理タスクを行うための/gc
という内部エンドポイントが存在していたと考えられます。しかし、このエンドポイントへのアクセスに適切な認証(管理者ログイン)が設定されていなかったため、セキュリティ上の脆弱性となる可能性がありました。このコミットは、/update-cl
エンドポイントと同様に、/gc
エンドポイントにも管理者ログインを必須とすることで、不正なアクセスを防ぎ、システムのセキュリティを強化します。
前提知識の解説
Go言語のmap
のイテレーション順序
Go言語のmap
は、キーと値のペアを格納するハッシュテーブルの実装です。Goの仕様では、map
をイテレートする際の順序は保証されません。これは、map
の実装が内部的にハッシュ関数を使用しており、要素の追加や削除によってメモリ上の配置が変化するためです。また、Goランタイムは意図的にイテレーション順序をランダム化することがあり、これにより開発者が非決定的な順序に依存するコードを書くことを防ぎます。したがって、順序が重要な場合は、map
ではなくスライス(配列)を使用するか、map
のキーをソートしてスライスに格納し、そのスライスをイテレートするといった工夫が必要です。
Google App Engine (GAE) のapp.yaml
app.yaml
は、Google App Engineアプリケーションのデプロイメント記述子です。このファイルは、アプリケーションのランタイム環境、ハンドラ(URLルーティング)、スケーリング設定、環境変数、認証要件などを定義します。特にhandlers
セクションでは、特定のURLパスに対するリクエストをどのスクリプトやサービスが処理するか、また、そのURLへのアクセスにどのような認証が必要か(例: login: admin
で管理者ログインを要求)を設定できます。
Go言語のtime.Duration
型
time.Duration
は、Go言語のtime
パッケージで定義されている型で、時間の長さをナノ秒単位で表す整数型です。例えば、time.Hour
は1時間のtime.Duration
を表し、time.Minute
は1分のtime.Duration
を表します。時間の計算や比較に便利です。
Go言語のfmt.Sprintf
関数
fmt.Sprintf
は、Go言語のfmt
パッケージで提供される関数で、フォーマット指定子(例: %d
で整数、%s
で文字列)を使用して、指定された値を整形し、新しい文字列として返します。C言語のsprintf
に似ています。
技術的詳細
cl.go
における時間単位計算の修正
元のコードでは、ModifiedAgo()
関数内で時間の単位(日、時間、分、秒)をmap[string]time.Duration
として定義していました。
units := map[string]time.Duration{
"d": 24 * time.Hour,
"h": time.Hour,
"m": time.Minute,
"s": time.Second,
}
そして、このmap
をfor...range
ループでイテレートし、現在の経過時間d
がどの単位u
よりも大きいかをチェックしていました。
for suffix, u := range units {
if d > u {
return fmt.Sprintf("%d%s", d/u, suffix)
}
}
しかし、前述の通りGoのmap
のイテレーション順序は保証されないため、例えば"h"
(時間)が"d"
(日)よりも先に評価される可能性がありました。これにより、d
が1日以上であっても、d > time.Hour
が先に真となり、「24h」と表示されるべきところが「1d」と表示されない、といった問題が発生し得ました。
このコミットでは、この問題を解決するためにmap
の代わりに**匿名構造体のスライス(配列)**を使用するように変更しました。
units := [...]struct {
suffix string
unit time.Duration
}{
{"d", 24 * time.Hour},
{"h", time.Hour},
{"m", time.Minute},
{"s", time.Second},
}
スライスは要素の順序が保証されるデータ構造であるため、この変更により、for...range
ループは常に定義された順序(日→時間→分→秒)でイテレートされるようになります。これにより、最も大きな適切な単位が常に最初に評価され、正確な「Modified Ago」の表示が保証されます。
イテレーションも、for _, u := range units
のように、構造体全体をイテレートするように変更され、u.unit
とu.suffix
でそれぞれの値にアクセスしています。
app.yaml
における認証要件の修正
元のapp.yaml
では、/update-cl
というURLパスに対してのみ管理者ログインを要求していました。
- url: /update-cl
script: _go_app
login: admin
このコミットでは、/gc
エンドポイントも同様に管理者ログインを要求するように変更されました。これは、正規表現のOR演算子|
を使用して、単一のURLパターンで複数のパスをマッチさせることで実現されています。
- url: /(gc|update-cl)
script: _go_app
login: admin
この変更により、/gc
または/update-cl
のいずれかのURLにアクセスしようとすると、Google App Engineの認証システムによって管理者ログインが要求されるようになります。これにより、これらの管理エンドポイントへの不正なアクセスが防止され、アプリケーションのセキュリティが向上します。
コアとなるコードの変更箇所
misc/dashboard/codereview/app.yaml
--- a/misc/dashboard/codereview/app.yaml
+++ b/misc/dashboard/codereview/app.yaml
@@ -16,7 +16,7 @@ handlers:
- url: /_ah/queue/go/delay
script: _go_app
login: admin
-- url: /update-cl
+- url: /(gc|update-cl)
script: _go_app
login: admin
- url: /.*
misc/dashboard/codereview/dashboard/cl.go
--- a/misc/dashboard/codereview/dashboard/cl.go
+++ b/misc/dashboard/codereview/dashboard/cl.go
@@ -80,16 +80,19 @@ func (cl *CL) LGTMHTML() template.HTML {
func (cl *CL) ModifiedAgo() string {
// Just the first non-zero unit.
-\tunits := map[string]time.Duration{\n-\t\t\"d\": 24 * time.Hour,\n-\t\t\"h\": time.Hour,\n-\t\t\"m\": time.Minute,\n-\t\t\"s\": time.Second,\n+\tunits := [...]struct {\n+\t\tsuffix string\n+\t\tunit time.Duration\n+\t}{\n+\t\t{\"d\", 24 * time.Hour},\n+\t\t{\"h\", time.Hour},\n+\t\t{\"m\", time.Minute},\n+\t\t{\"s\", time.Second},\n \t}\n \td := time.Now().Sub(cl.Modified)\n-\tfor suffix, u := range units {\n-\t\tif d > u {\n-\t\t\treturn fmt.Sprintf(\"%d%s\", d/u, suffix)\n+\tfor _, u := range units {\n+\t\tif d > u.unit {\n+\t\t\treturn fmt.Sprintf(\"%d%s\", d/u.unit, u.suffix)\n \t\t}\n \t}\n \treturn \"just now\"\n```
## コアとなるコードの解説
### `app.yaml`の変更解説
変更前:
```yaml
- url: /update-cl
script: _go_app
login: admin
この設定は、/update-cl
という特定のURLパスへのリクエストが来た場合に、_go_app
というスクリプトで処理され、かつアクセスにはadmin
(管理者)権限でのログインが必要であることを示しています。
変更後:
- url: /(gc|update-cl)
script: _go_app
login: admin
変更後の設定では、url
のパターンが正規表現の(gc|update-cl)
に変更されています。これは、/gc
または/update-cl
のいずれかのURLパスにマッチすることを意味します。これにより、両方のエンドポイントに対して同じscript: _go_app
とlogin: admin
の認証要件が適用されるようになります。これは、/gc
エンドポイントが管理者のみがアクセスできるべき重要な機能であると判断されたため、セキュリティを強化するための修正です。
cl.go
の変更解説
変更前:
func (cl *CL) ModifiedAgo() string {
// Just the first non-zero unit.
units := map[string]time.Duration{
"d": 24 * time.Hour,
"h": time.Hour,
"m": time.Minute,
"s": time.Second,
}
d := time.Now().Sub(cl.Modified)
for suffix, u := range units {
if d > u {
return fmt.Sprintf("%d%s", d/u, suffix)
}
}
return "just now"
}
この関数は、変更リスト(CL)が最後に変更されてからの経過時間を人間が読みやすい形式で返します。units
というmap
に、時間単位(日、時間、分、秒)とそのtime.Duration
値が格納されています。time.Now().Sub(cl.Modified)
で経過時間d
を計算し、map
をイテレートして、d
がどの単位u
よりも大きいかをチェックし、最初に条件を満たした単位でフォーマットされた文字列を返していました。しかし、Goのmap
のイテレーション順序は保証されないため、例えば"h"
(時間)が"d"
(日)よりも先に評価される可能性があり、その場合、1日以上の経過時間でも「24h」のように表示されてしまう可能性がありました。
変更後:
func (cl *CL) ModifiedAgo() string {
// Just the first non-zero unit.
units := [...]struct {
suffix string
unit time.Duration
}{
{"d", 24 * time.Hour},
{"h", time.Hour},
{"m", time.Minute},
{"s", time.Second},
}
d := time.Now().Sub(cl.Modified)
for _, u := range units {
if d > u.unit {
return fmt.Sprintf("%d%s", d/u.unit, u.suffix)
}
}
return "just now"
}
この変更では、units
がmap
から**匿名構造体のスライス(配列)**に変更されました。
[...]struct { suffix string; unit time.Duration }
は、suffix
(単位の接尾辞、例: "d", "h")とunit
(time.Duration
値)を持つ構造体の配列を宣言しています。スライスは要素の順序が保証されるため、{"d", 24 * time.Hour}
が常に最初に、{"h", time.Hour}
が次に、というように定義された順序でイテレートされます。
for _, u := range units
ループでは、スライスの各要素(匿名構造体)がu
に代入されます。そして、d > u.unit
で経過時間と現在の単位の値を比較し、fmt.Sprintf("%d%s", d/u.unit, u.suffix)
でフォーマットされた文字列を返します。この修正により、常に「日」→「時間」→「分」→「秒」の順で評価が行われるため、最も大きな適切な単位で経過時間が表示されるようになり、表示の正確性と一貫性が保証されます。
関連リンク
- Go Code Review: https://golang.org/cl/6133049
参考にした情報源リンク
- The Go Programming Language Specification - Map types: https://go.dev/ref/spec#Map_types
- Go by Example: Maps: https://gobyexample.com/maps
- Google Cloud - App Engine
app.yaml
reference: https://cloud.google.com/appengine/docs/standard/go/config/appref - GoDoc -
time
package: https://pkg.go.dev/time - GoDoc -
fmt
package: https://pkg.go.dev/fmt - Go Slices: usage and internals: https://go.dev/blog/slices-usage-and-internals