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

[インデックス 15301] ファイルの概要

このコミットは、Go言語の実験的なcookiejarパッケージにおいて、PublicSuffixListnilの場合のクッキーストアの挙動を改善するものです。具体的には、クッキーを保存する際のキーとして、従来のトップレベルドメイン(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の実装では、PublicSuffixListnil(つまり、パブリックサフィックスリストが提供されていない)の場合、クッキーはドメインのトップレベルドメイン(TLD)の下に保存されていました。例えば、example.comfoo.comから受け取ったクッキーは、すべて「com」というキーの下に保存されていました。

この挙動には以下の問題がありました。

  1. 非効率性: すべての.comドメインのクッキーが単一の「com」というキーの下に集約されるため、entriesマップ(クッキーを保存する内部データ構造)が肥大化し、クッキーの検索や管理が非効率になっていました。特に、多くの異なる.comドメインとやり取りするアプリケーションでは、パフォーマンスの低下を招く可能性がありました。
  2. セキュリティ上の懸念: foo.comのようなドメインがbar.comのクッキーを設定できてしまうという潜在的なセキュリティリスクがありました。これは、PublicSuffixListnilの場合、jarKey関数がドメインのTLDのみを考慮するため、異なるセカンドレベルドメイン(SLD)を持つドメイン間でのクッキーの干渉が発生しうるためです。コミットメッセージの例では、「foo.comがbar.comのクッキーを設定できる」とありますが、これは正確には「foo.comがbar.comのクッキーを上書きしたり、意図しないクッキーをbar.comに送信させたりする可能性がある」という意味合いが強いです。これにより、セッションハイジャックや情報漏洩のリスクが生じる可能性があります。

このコミットは、これらの問題を解決するために、PublicSuffixListnilの場合にクッキーを保存するキーとして、TLD+1(Top-Level Domain + 1)を使用するように変更しました。これにより、クッキーはより具体的なドメイン(例: example.combar.com)の下に保存されるようになり、効率性とセキュリティが向上します。

前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

HTTPクッキーは、ウェブサイトがユーザーのウェブブラウザに送信する小さなデータのことです。ブラウザはこれらのクッキーを保存し、同じウェブサイトに再度アクセスする際に、そのクッキーをウェブサイトに送り返します。これにより、ウェブサイトはユーザーの状態を記憶したり、ユーザーを識別したりすることができます。例えば、ログイン状態の維持、ショッピングカートの内容、ユーザー設定の保存などに利用されます。

クッキージャーは、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.comfoo.combar.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.comwww.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が返される。

この変更により、PublicSuffixListnilの場合でも、クッキーはより具体的なドメイン(TLD+1)の下に保存されるようになり、クッキーストアの効率性とセキュリティが大幅に向上しました。

psl != nilの場合の挙動

PublicSuffixListnilでない場合(つまり、パブリックサフィックスリストが提供されている場合)のjarKey関数の挙動は変更されていません。この場合、クッキーはeTLD+1に基づいて保存されます。これは、パブリックサフィックスリストの情報を利用して、より正確な「有効なトップレベルドメイン」を特定し、その直前のドメイン部分をキーとして使用するためです。この挙動は元々セキュリティと効率性を考慮して設計されており、変更の必要がありませんでした。

コアとなるコードの変更箇所

このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。

  1. src/pkg/exp/cookiejar/jar.go: クッキージャーの主要なロジックが実装されているファイルです。jarKey関数の実装が変更されました。
  2. 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関数におけるPublicSuffixListnilの場合のロジックです。

変更前は、psl == nilの場合、jarKeyはドメインのトップレベルドメイン(TLD)を返していました。例えば、www.example.comsub.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:]

このコードスニペットの動作を詳細に見てみましょう。

  1. if psl == nil: PublicSuffixListが提供されていない場合(つまり、パブリックサフィックスリストを使用しない設定の場合)の処理に入ります。
  2. i = strings.LastIndex(host, "."): ホスト名の中で最後のドット(.)の位置を探し、そのインデックスをiに格納します。
    • 例: www.example.com -> i.com.のインデックス
    • 例: bbc.co.uk -> i.uk.のインデックス
  3. if i == -1: もしホスト名にドットが含まれていない場合(例: localhostやIPアドレス)、ホスト名自体をキーとして返します。これは、TLD+1の概念が適用できない特殊なケースです。
  4. 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 -> prevDotwww.example.のインデックス
      • 例: bbc.co -> prevDotbbc.co.のインデックス
  5. return host[prevDot+1:]: prevDotの次の文字からホスト名の最後までをキーとして返します。これがTLD+1に相当します。
    • 例: www.example.comの場合、prevDotwww.example.のインデックスなので、host[prevDot+1:]example.comを返します。
    • 例: bbc.co.ukの場合、prevDotbbc.co.のインデックスなので、host[prevDot+1:]co.ukを返します。

この新しいロジックにより、PublicSuffixListnilの場合でも、クッキーはexample.comco.ukといった、より粒度の高いドメイン(TLD+1)の下に保存されるようになります。これにより、クッキージャーの内部マップが効率的に利用され、異なるセカンドレベルドメインを持つサイト間でのクッキーの意図しない干渉が防止され、セキュリティが向上します。

関連リンク

参考にした情報源リンク