Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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言語のコードレビューダッシュボードの安定性とセキュリティを向上させるために行われました。

  1. mapのイテレーション順序への依存性: Go言語のmap(ハッシュマップ)は、その設計上、要素のイテレーション順序が保証されません。これは意図的なものであり、ランダム化することで、開発者がイテレーション順序に誤って依存してしまうことを防ぎ、また、特定の順序に依存するバグを早期に発見できるようにするためです。しかし、このダッシュボードのModifiedAgo()関数では、変更リストが最後に更新されてからの経過時間を「d」(日)、「h」(時間)、「m」(分)、「s」(秒)といった単位で表示する際に、mapに定義された時間単位のイテレーション順序に依存していました。このため、環境やGoのバージョンによっては、期待しない順序で単位が評価され、例えば「1時間前」と表示されるべきところが「60分前」と表示されるなど、不正確な表示になる可能性がありました。このコミットは、この非決定的な挙動を修正し、常に期待される順序(日→時間→分→秒)で単位が評価されるようにします。

  2. /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,
}

そして、このmapfor...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.unitu.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_applogin: 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"
}

この変更では、unitsmapから**匿名構造体のスライス(配列)**に変更されました。 [...]struct { suffix string; unit time.Duration }は、suffix(単位の接尾辞、例: "d", "h")とunittime.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言語のダッシュボードツールにおける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言語のコードレビューダッシュボードの安定性とセキュリティを向上させるために行われました。

  1. mapのイテレーション順序への依存性: Go言語のmap(ハッシュマップ)は、その設計上、要素のイテレーション順序が保証されません。これは意図的なものであり、ランダム化することで、開発者がイテレーション順序に誤って依存してしまうことを防ぎ、また、特定の順序に依存するバグを早期に発見できるようにするためです。しかし、このダッシュボードのModifiedAgo()関数では、変更リストが最後に更新されてからの経過時間を「d」(日)、「h」(時間)、「m」(分)、「s」(秒)といった単位で表示する際に、mapに定義された時間単位のイテレーション順序に依存していました。このため、環境やGoのバージョンによっては、期待しない順序で単位が評価され、例えば「1時間前」と表示されるべきところが「60分前」と表示されるなど、不正確な表示になる可能性がありました。このコミットは、この非決定的な挙動を修正し、常に期待される順序(日→時間→分→秒)で単位が評価されるようにします。

  2. /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,
}

そして、このmapfor...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.unitu.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_applogin: 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"
}

この変更では、unitsmapから**匿名構造体のスライス(配列)**に変更されました。 [...]struct { suffix string; unit time.Duration }は、suffix(単位の接尾辞、例: "d", "h")とunittime.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)でフォーマットされた文字列を返します。この修正により、常に「日」→「時間」→「分」→「秒」の順で評価が行われるため、最も大きな適切な単位で経過時間が表示されるようになり、表示の正確性と一貫性が保証されます。

関連リンク

参考にした情報源リンク