[インデックス 15301] ファイルの概要
このコミットは、Go言語の実験的なcookiejar
パッケージにおいて、PublicSuffixList
がnil
の場合のクッキーストアの挙動を改善するものです。具体的には、クッキーを保存する際のキーとして、従来のトップレベルドメイン(TLD)ではなく、TLD+1(Top-Level Domain + 1)を使用するように変更されました。これにより、クッキーストアの効率性が向上し、セキュリティ上の潜在的な問題(あるドメインが別のドメインのクッキーを設定できてしまう可能性)が軽減されます。
コミット
commit 6ab113531b49621394dbd274c44bb583ded1dc45
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date: Tue Feb 19 19:12:36 2013 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6ab113531b49621394dbd274c44bb583ded1dc45
元コミット内容
exp/cookiejar: store cookies under TLD+1 on nil public suffix list
The current implementation would store all cookies received from
any .com domain under "com" in the entries map if a nil public
suffix list is used in constructing the Jar. This is inefficient.
This CL uses the TLD+1 of the domain if the public suffix list
is nil which has two advantages:
- It uses the entries map efficiently.
- It prevents a host foo.com to set cookies for bar.com.
(It may set the cookie, but it won't be returned to bar.com.)
A domain like www.british-library.uk may still set a domain
cookie for .british-library.uk in this case.
The behavior for a non-nil public suffix list is unchanged, cookies
are stored under eTLD+1 in this case.
R=nigeltao
CC=golang-dev
https://golang.org/cl/7312105
変更の背景
この変更の背景には、net/http/cookiejar
パッケージ(当時はexp/cookiejar
)がクッキーを保存する際の効率性とセキュリティに関する課題がありました。
従来のcookiejar
の実装では、PublicSuffixList
がnil
(つまり、パブリックサフィックスリストが提供されていない)の場合、クッキーはドメインのトップレベルドメイン(TLD)の下に保存されていました。例えば、example.com
やfoo.com
から受け取ったクッキーは、すべて「com」というキーの下に保存されていました。
この挙動には以下の問題がありました。
- 非効率性: すべての
.com
ドメインのクッキーが単一の「com」というキーの下に集約されるため、entries
マップ(クッキーを保存する内部データ構造)が肥大化し、クッキーの検索や管理が非効率になっていました。特に、多くの異なる.com
ドメインとやり取りするアプリケーションでは、パフォーマンスの低下を招く可能性がありました。 - セキュリティ上の懸念:
foo.com
のようなドメインがbar.com
のクッキーを設定できてしまうという潜在的なセキュリティリスクがありました。これは、PublicSuffixList
がnil
の場合、jarKey
関数がドメインのTLDのみを考慮するため、異なるセカンドレベルドメイン(SLD)を持つドメイン間でのクッキーの干渉が発生しうるためです。コミットメッセージの例では、「foo.comがbar.comのクッキーを設定できる」とありますが、これは正確には「foo.comがbar.comのクッキーを上書きしたり、意図しないクッキーをbar.comに送信させたりする可能性がある」という意味合いが強いです。これにより、セッションハイジャックや情報漏洩のリスクが生じる可能性があります。
このコミットは、これらの問題を解決するために、PublicSuffixList
がnil
の場合にクッキーを保存するキーとして、TLD+1(Top-Level Domain + 1)を使用するように変更しました。これにより、クッキーはより具体的なドメイン(例: example.com
、bar.com
)の下に保存されるようになり、効率性とセキュリティが向上します。
前提知識の解説
このコミットを理解するためには、以下の概念を把握しておく必要があります。
1. クッキー (HTTP Cookie)
HTTPクッキーは、ウェブサイトがユーザーのウェブブラウザに送信する小さなデータのことです。ブラウザはこれらのクッキーを保存し、同じウェブサイトに再度アクセスする際に、そのクッキーをウェブサイトに送り返します。これにより、ウェブサイトはユーザーの状態を記憶したり、ユーザーを識別したりすることができます。例えば、ログイン状態の維持、ショッピングカートの内容、ユーザー設定の保存などに利用されます。
2. クッキージャー (Cookie Jar)
クッキージャーは、HTTPクライアント(ウェブブラウザやGoのnet/http
パッケージなど)が受信したクッキーを保存・管理するためのメカニズムです。クッキーはドメインごとに整理され、適切なリクエストに対してのみ送信されるように制御されます。Goのnet/http/cookiejar
パッケージは、このクッキージャーの機能を提供します。
3. ドメイン名 (Domain Name)
インターネット上のリソースを識別するための階層的な名前付けシステムです。例えば、www.example.com
というドメイン名では、各部分が以下のように呼ばれます。
.com
: トップレベルドメイン (TLD)example
: セカンドレベルドメイン (SLD)www
: サードレベルドメイン (またはサブドメイン)
4. トップレベルドメイン (TLD: Top-Level Domain)
ドメイン名の一番右側の部分です。例えば、.com
, .org
, .net
, .jp
, .uk
などがあります。
5. TLD+1 (Top-Level Domain + 1)
TLD+1は、トップレベルドメインの直前のドメイン部分を指します。例えば、example.com
の場合、TLDは.com
で、TLD+1はexample.com
全体です。www.example.com
の場合も、TLD+1はexample.com
です。これは、クッキーのスコープを決定する上で重要な概念です。
6. パブリックサフィックスリスト (Public Suffix List: PSL)
パブリックサフィックスリストは、インターネット上で「パブリックサフィックス」として機能するドメインのリストです。パブリックサフィックスとは、個人や組織が自由にドメイン名を登録できるドメインの部分を指します。例えば、.com
や.co.uk
などがこれに該当します。
なぜ重要か?
クッキーのセキュリティにおいて、パブリックサフィックスリストは非常に重要です。クッキーは通常、そのクッキーが設定されたドメインとそのサブドメインにのみ送信されます。しかし、もしexample.co.uk
のようなドメインが、co.uk
全体に対してクッキーを設定できてしまうと、同じco.uk
の下にある他のドメイン(例: another.co.uk
)もそのクッキーを受け取ってしまう可能性があります。これはセキュリティ上の大きな問題です。
パブリックサフィックスリストは、ブラウザやクッキージャーの実装が、co.uk
のようなパブリックサフィックスに対して直接クッキーを設定することを防ぐために使用されます。これにより、クッキーのスコープが適切に制限され、異なる組織が所有するドメイン間でのクッキーの干渉を防ぎます。
7. eTLD+1 (effective Top-Level Domain + 1)
eTLD+1は「effective Top-Level Domain + 1」の略で、パブリックサフィックスリストを考慮した上でのTLD+1を指します。例えば、www.example.co.uk
の場合、パブリックサフィックスリストに.co.uk
が登録されているため、eTLDは.co.uk
となり、eTLD+1はexample.co.uk
となります。
PublicSuffixList
が提供されている場合、クッキーはeTLD+1に基づいて保存されます。これにより、クッキーのセキュリティと効率性が確保されます。
技術的詳細
このコミットの核心は、src/pkg/exp/cookiejar/jar.go
ファイル内のjarKey
関数の変更にあります。この関数は、クッキーを内部のentries
マップに保存する際のキーを生成する役割を担っています。
変更前のjarKey
関数の挙動 (psl == nil
の場合)
変更前は、PublicSuffixList
(psl
) がnil
の場合、jarKey
関数はホスト名の最後のドット以降の部分、つまりトップレベルドメイン (TLD) をキーとして使用していました。
// 変更前の関連コード (簡略化)
func jarKey(host string, psl PublicSuffixList) string {
if psl == nil {
// Key cookies under TLD of host.
return host[1+strings.LastIndex(host, "."):] // 例: "example.com" -> "com"
}
// ... psl != nil の場合のロジック
}
この挙動により、example.com
、foo.com
、bar.com
といった異なるセカンドレベルドメインを持つすべての.com
ドメインのクッキーが、"com"
という単一のキーの下に保存されていました。これは、前述の通り、効率性とセキュリティの両面で問題がありました。
変更後のjarKey
関数の挙動 (psl == nil
の場合)
このコミットでは、psl == nil
の場合のロジックが大幅に変更されました。新しい実装では、TLDではなく、TLD+1をキーとして使用するように修正されています。
// 変更後の関連コード (簡略化)
func jarKey(host string, psl PublicSuffixList) string {
if isIP(host) {
return host
}
var i int
if psl == nil {
// TLD+1 を取得するロジック
i = strings.LastIndex(host, ".")
if i == -1 {
return host // ドットがない場合はホスト名全体
}
} else {
// ... psl != nil の場合の既存ロジック (eTLD+1)
suffix := psl.PublicSuffix(host)
if suffix == host {
return host
}
i = len(host) - len(suffix)
if i <= 0 || host[i-1] != '.' {
return host
}
}
prevDot := strings.LastIndex(host[:i-1], ".")
return host[prevDot+1:] // TLD+1 を返す
}
新しいロジックでは、psl == nil
の場合、まずホスト名内の最後のドットの位置をi
に格納します。そして、そのi
を使って、最後のドットの前のドット(prevDot
)を見つけ、その次の文字から最後までをキーとして返します。これにより、example.com
からはexample.com
、www.example.com
からもexample.com
がキーとして生成されるようになります。
例:
www.example.com
の場合:psl == nil
なので、i
は最後のドット(.com
の.
)の位置。host[:i-1]
はwww.example
。strings.LastIndex(host[:i-1], ".")
はwww.example
の.
の位置。- 結果として
example.com
が返される。
bbc.co.uk
の場合:psl == nil
なので、i
は最後のドット(.uk
の.
)の位置。host[:i-1]
はbbc.co
。strings.LastIndex(host[:i-1], ".")
はbbc.co
の.
の位置。- 結果として
co.uk
が返される。
この変更により、PublicSuffixList
がnil
の場合でも、クッキーはより具体的なドメイン(TLD+1)の下に保存されるようになり、クッキーストアの効率性とセキュリティが大幅に向上しました。
psl != nil
の場合の挙動
PublicSuffixList
がnil
でない場合(つまり、パブリックサフィックスリストが提供されている場合)のjarKey
関数の挙動は変更されていません。この場合、クッキーはeTLD+1に基づいて保存されます。これは、パブリックサフィックスリストの情報を利用して、より正確な「有効なトップレベルドメイン」を特定し、その直前のドメイン部分をキーとして使用するためです。この挙動は元々セキュリティと効率性を考慮して設計されており、変更の必要がありませんでした。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。
src/pkg/exp/cookiejar/jar.go
: クッキージャーの主要なロジックが実装されているファイルです。jarKey
関数の実装が変更されました。src/pkg/exp/cookiejar/jar_test.go
:cookiejar
パッケージのテストファイルです。jarKey
関数の新しい挙動を検証するためのテストケースが追加・修正されました。
src/pkg/exp/cookiejar/jar.go
の変更点
--- a/src/pkg/exp/cookiejar/jar.go
+++ b/src/pkg/exp/cookiejar/jar.go
@@ -48,8 +48,8 @@ type Options struct {
// an HTTP server can set a cookie for a domain.
//
// A nil value is valid and may be useful for testing but it is not
- // secure: it means that the HTTP server for foo.com can set a cookie
- // for bar.com.
+ // secure: it means that the HTTP server for foo.co.uk can set a cookie
+ // for bar.co.uk.
PublicSuffixList PublicSuffixList
}
@@ -333,19 +333,24 @@ func jarKey(host string, psl PublicSuffixList) string {
if isIP(host) {
return host
}
+
+ var i int
if psl == nil {
- // Key cookies under TLD of host.
- return host[1+strings.LastIndex(host, "."):]
- }
- suffix := psl.PublicSuffix(host)
- if suffix == host {
- return host
- }
- i := len(host) - len(suffix)
- if i <= 0 || host[i-1] != '.' {
- // The provided public suffix list psl is broken.
- // Storing cookies under host is a safe stopgap.
- return host
+ i = strings.LastIndex(host, ".")
+ if i == -1 {
+ return host
+ }
+ } else {
+ suffix := psl.PublicSuffix(host)
+ if suffix == host {
+ return host
+ }
+ i = len(host) - len(suffix)
+ if i <= 0 || host[i-1] != '.' {
+ // The provided public suffix list psl is broken.
+ // Storing cookies under host is a safe stopgap.
+ return host
+ }
}
prevDot := strings.LastIndex(host[:i-1], ".")
return host[prevDot+1:]
Options
構造体のコメントが修正され、foo.com
からbar.com
へのクッキー設定の例が、より具体的なfoo.co.uk
からbar.co.uk
に変更されています。これは、TLD+1の概念をより明確にするためです。jarKey
関数内で、psl == nil
の場合のロジックが完全に書き換えられました。- 以前は
host[1+strings.LastIndex(host, "."):]
でTLDを直接返していましたが、新しいコードではi
という変数を導入し、strings.LastIndex(host, ".")
で最後のドットの位置を取得しています。 - その後、
prevDot := strings.LastIndex(host[:i-1], ".")
で、最後のドットの前のドットの位置を探し、最終的にhost[prevDot+1:]
でTLD+1を返しています。 psl != nil
の場合の既存のロジックは、else
ブロック内に移動され、変更はありません。
- 以前は
src/pkg/exp/cookiejar/jar_test.go
の変更点
--- a/src/pkg/exp/cookiejar/jar_test.go
+++ b/src/pkg/exp/cookiejar/jar_test.go
@@ -99,10 +99,25 @@ func TestJarKey(t *testing.T) {
t.Errorf("%q: got %q, want %q", host, got, want)
}
}\n+}\n \n- for _, host := range []string{"www.example.com", "example.com", "com"} {
- if got := jarKey(host, nil); got != "com" {
- t.Errorf(`%q: got %q, want "com"`, host, got)
+var jarKeyNilPSLTests = map[string]string{
+ "foo.www.example.com": "example.com",
+ "www.example.com": "example.com",
+ "example.com": "example.com",
+ "com": "com",
+ "foo.www.bbc.co.uk": "co.uk",
+ "www.bbc.co.uk": "co.uk",
+ "bbc.co.uk": "co.uk",
+ "co.uk": "co.uk",
+ "uk": "uk",
+ "192.168.0.5": "192.168.0.5",
+}
+
+func TestJarKeyNilPSL(t *testing.T) {
+ for host, want := range jarKeyNilPSLTests {
+ if got := jarKey(host, nil); got != want {
+ t.Errorf("%q: got %q, want %q", host, got, want)
}
}
}
TestJarKey
関数内のpsl == nil
に関するテストケースが削除されました。- 新たに
TestJarKeyNilPSL
というテスト関数が追加されました。 jarKeyNilPSLTests
というマップが定義され、様々なドメイン名と、psl == nil
の場合に期待されるjarKey
の戻り値(TLD+1)がペアで格納されています。- この新しいテスト関数は、
jarKeyNilPSLTests
マップをイテレートし、各ホスト名に対してjarKey(host, nil)
を呼び出し、期待される結果と比較することで、新しいjarKey
の挙動が正しいことを検証しています。特に、co.uk
のような複数部分からなるTLD+1のケースもカバーしている点が重要です。
コアとなるコードの解説
このコミットのコアとなる変更は、jar.go
ファイル内のjarKey
関数におけるPublicSuffixList
がnil
の場合のロジックです。
変更前は、psl == nil
の場合、jarKey
はドメインのトップレベルドメイン(TLD)を返していました。例えば、www.example.com
やsub.domain.com
のようなドメインは、すべてcom
というキーにマッピングされていました。これは、クッキージャーの内部マップで多くのクッキーが同じキーの下に集約されることを意味し、効率性の低下と、異なるセカンドレベルドメインを持つサイト間でのクッキーの意図しない共有(セキュリティリスク)を引き起こしていました。
変更後、psl == nil
の場合のjarKey
関数は、ドメインのTLD+1を返すように修正されました。
var i int
if psl == nil {
i = strings.LastIndex(host, ".")
if i == -1 {
return host
}
} else {
// ... (psl != nil の場合の既存ロジック)
}
prevDot := strings.LastIndex(host[:i-1], ".")
return host[prevDot+1:]
このコードスニペットの動作を詳細に見てみましょう。
if psl == nil
:PublicSuffixList
が提供されていない場合(つまり、パブリックサフィックスリストを使用しない設定の場合)の処理に入ります。i = strings.LastIndex(host, ".")
: ホスト名の中で最後のドット(.
)の位置を探し、そのインデックスをi
に格納します。- 例:
www.example.com
->i
は.com
の.
のインデックス - 例:
bbc.co.uk
->i
は.uk
の.
のインデックス
- 例:
if i == -1
: もしホスト名にドットが含まれていない場合(例:localhost
やIPアドレス)、ホスト名自体をキーとして返します。これは、TLD+1の概念が適用できない特殊なケースです。prevDot := strings.LastIndex(host[:i-1], ".")
: ここが最も重要な変更点です。host[:i-1]
は、最後のドットの直前までの文字列を抽出します。- 例:
www.example.com
(i
は.com
の.
のインデックス) ->www.example
- 例:
bbc.co.uk
(i
は.uk
の.
のインデックス) ->bbc.co
- 例:
- この抽出された文字列の中で、さらに最後のドットの位置を探し、そのインデックスを
prevDot
に格納します。- 例:
www.example
->prevDot
はwww.example
の.
のインデックス - 例:
bbc.co
->prevDot
はbbc.co
の.
のインデックス
- 例:
return host[prevDot+1:]
:prevDot
の次の文字からホスト名の最後までをキーとして返します。これがTLD+1に相当します。- 例:
www.example.com
の場合、prevDot
はwww.example
の.
のインデックスなので、host[prevDot+1:]
はexample.com
を返します。 - 例:
bbc.co.uk
の場合、prevDot
はbbc.co
の.
のインデックスなので、host[prevDot+1:]
はco.uk
を返します。
- 例:
この新しいロジックにより、PublicSuffixList
がnil
の場合でも、クッキーはexample.com
やco.uk
といった、より粒度の高いドメイン(TLD+1)の下に保存されるようになります。これにより、クッキージャーの内部マップが効率的に利用され、異なるセカンドレベルドメインを持つサイト間でのクッキーの意図しない干渉が防止され、セキュリティが向上します。
関連リンク
- Go CL 7312105: https://golang.org/cl/7312105
参考にした情報源リンク
- Public Suffix List: https://publicsuffix.org/
- HTTP cookies - MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies
- Effective Top-Level Domain (eTLD) - MDN Web Docs: https://developer.mozilla.org/en-US/docs/Glossary/Effective_Top-Level_Domain
- Go
net/http/cookiejar
package documentation: https://pkg.go.dev/net/http/cookiejar (このコミットが適用された後のドキュメントを参照) - Top-level domain - Wikipedia: https://en.wikipedia.org/wiki/Top-level_domain
- Second-level domain - Wikipedia: https://en.wikipedia.org/wiki/Second-level_domain