代表的な面接トピック

Go 1.26 面接:net/url によるホストのコロン検証の厳格化に伴い、どのように移行しますか?

一般難しい
Offer.cc 編集チーム公開日 更新日

質問

ある Go サービスを 1.26 にアップグレードしたところ、一部の内部 URL がパースできなくなりました。net/url がホスト内の余分なコロンを拒絶する理由を説明し、有効な IPv6 と不正な入力を区別した上で、テスト、カナリアリリース、および一時的なロールバックを設計してください。

問題とコンテキスト

ある Go HTTP サービスを 1.25 から 1.26 にアップグレードしたところ、従来の値である http://localhost:80:80/http://::1/ はエラーになる一方、http://[::1]/ は引き続き成功することが判明しました。net/url.Parse の変更内容、プロキシや SSRF 防御への影響、そして恒久的な互換性スイッチに依存しない移行計画を説明してください。

Go 1.26 では urlstrictcolons のデフォルトが 1 になり、host:port として解釈できないホストサブコンポーネント内の余分なコロンが拒絶されます。この動作は Go 1.25.2 および 1.24.8 にもバックポートされました。RFC 3986 ではホストを IP-literal、IPv4 アドレス、または登録名と定義し、ポートは単一のコロンで区切られるため、テキスト形式の IPv6 は角括弧内に含める必要があります。

面接官がテストしていること

  • URI 構文の観点から、余分なコロンが曖昧または無効である理由を説明できるか?
  • 角括弧のない IPv6、有効な [IPv6]:port、ホスト名、および無効なポートを区別できるか?
  • 検証を無効化するのではなく、構成の移行、サードパーティ URL、プロキシの書き換え、およびログを適切に処理できるか?
  • GODEBUG=urlstrictcolons=0 は一時的な互換性維持のためのツールであり、入力検証ポリシーではないことを説明できるか?
  • アップグレード後のリグレッションテストによって、パース、接続、リダイレクト、および SSRF の挙動を検証・証明できるか?

まず確認すべき質問

  • エラーが発生している URL の出所は、手動の構成設定、データベース、ユーザー入力、サードパーティのコールバックのどれですか?信頼レベルが異なります。
  • アプリケーションは url.Parseurl.Hostnameurl.Port を組み合わせていますか?使用するパース API によって挙動が異なります。
  • 内部 IPv6 のサポートは必須ですか、ポートは必須ですか、またプロキシが Host を書き換えることはありますか?
  • 古い値はオンラインで修正できますか、それともリリース前のバッチ処理で検証・拒絶できますか?
  • サービスはアクセス制御、テナントルーティング、または SSRF 防御のために URL パースを使用していますか?

30秒の回答

「エラーを発生元とホストの形式ごとに分類します。Go 1.26 では urlstrictcolons がデフォルトで有効になるため、角括弧のない IPv6 やホスト内に複数のコロンを含む文字列は拒絶されます。明示的な形式は [::1] であり、ポートは [::1]:8080 のように記述します。構成の境界で不正な値を修復・拒絶し、パースとネットワークのリグレッションテストを追加した上で、カナリアリリースでエラー率を監視します。GODEBUG=urlstrictcolons=0 は短期間のロールバックおよびクリーンアップ用ツールにすぎず、信頼できない入力に対する恒久的なポリシーとしては使用しません。」

ステップごとの詳細解説

  1. ベースラインの確立。 旧バージョンと Go 1.26 で失敗するサンプルを収集し、パースエラー、元の値、発生元、および最終的な用途を記録します。「パースできる」ことは「接続しても安全」であることを意味しません。スキーム、ホスト名、ポート、およびリダイレクトも検査してください。
  1. URI 構文による分類。 http://[::1]/ は角括弧付きの IP-literal です。http://[::1]:8080/ は角括弧の後にポートを配置します。http://::1/ はコロンをホストデータとポート区切り文字の両方として使用しており、http://localhost:80:80/ は複数のポート区切り文字を含んでいます。これらはいずれも修正すべき値です。
  1. データ境界の優先修正。 構成ファイル、データベースマイグレーション、および管理者 API に検証を追加します。IPv6 を角括弧で正規化し、ポートを範囲内の整数としてパースし、解釈不能なホストを拒絶します。一括クリーンアップ中は元の値、修正後の値、および所有元を保持し、自動判別できないレコードは隔離します。
  1. セキュリティパスの見直し。 アクセス制御には、パースされた Hostname()、ポート、および IP の結果を使用し、DNS、リダイレクト、およびプロキシの書き換えに対して明示的な境界を設ける必要があります。文字列のプレフィックスや単一の Parse の結果から内部宛先と公開宛先を分類してはなりません。アップグレード後に SSRF、プロキシ、およびリダイレクトのケースを再テストしてください。
  1. 一時的なロールバックの設計。 GODEBUG=urlstrictcolons=0 は制御された環境で従来の動作を復元できますが、有効期限、メトリクス、およびアラートが必要であり、新しいユーザー入力が検証をバイパスできるようにしてはなりません。より安全な選択肢は、分離されたプロセス内でインベントリ化された既存のレガシー構成セットに対してのみ有効にし、クリーンアップ完了後に無効化することです。
  1. カナリアリリースと検証。 シャドウトラフィックと小規模なインスタンスセットを実行し、パースエラー、接続エラー、プロキシの一致、およびリダイレクトの結果を比較します。ゲート条件には、有効な IPv4、角括弧付き IPv6、不正な IPv6、余分なコロン、空のポート、および悪意のあるリダイレクトを含める必要があります。レガシーデータのクリーンアップが完了した後にのみ展開を拡大します。
go
func validateEndpoint(raw string) (*url.URL, error) {
    u, err := url.Parse(raw)
    if err != nil {
        return nil, err
    }
    if u.Scheme != "http" && u.Scheme != "https" {
        return nil, fmt.Errorf("unsupported scheme")
    }
    host := u.Hostname()
    if host == "" {
        return nil, fmt.Errorf("missing host")
    }
    if p := u.Port(); p != "" {
        n, err := strconv.Atoi(p)
        if err != nil || n < 1 || n > 65535 {
            return nil, fmt.Errorf("invalid port")
        }
    }
    return u, nil
}

模範回答

まず各不正な値の発生元を特定し、構文エラーと接続障害を分離します。Go 1.26 では urlstrictcolons=1 がデフォルトとなり、コロンが有効な IPv6 または単一のポートとして一意に解釈できないため、http://::1/ および http://localhost:80:80/ を拒絶します。http://[::1]:8080/ は明示的な形式です。

構成および管理の境界において、不正な形式の値を拒絶し、IPv6 であることが確認されたレコードを自動修復し、曖昧なレコードは所有者の確認用に隔離します。ルーティング、プロキシ、または SSRF 防御のために URL を使用するすべてのパスで、ホスト名、ポート、DNS、リダイレクト、およびプロキシ書き換えを再テストする必要があります。GODEBUG=urlstrictcolons=0 はメトリクスとアラートを備えた期限付きの互換性ロールバック手段であり、信頼できない入力に新しいチェックを回避させるための手段ではありません。ロールバックを解除する前に、カナリア環境でパースエラー、接続エラー、およびセキュリティケースを比較検証します。

よくある間違い

  • 症状: 角括弧のないすべての IPv6 文字列を有効として扱う → 失敗する理由: ホストとポートの境界が曖昧になる → 修正策: [IPv6] を使用し、ポートは ] の後ろに配置する。
  • 症状: GODEBUG=urlstrictcolons=0 を恒久的に設定する → 失敗する理由: 不正な入力とレガシーな曖昧さが残ったままになる → 修正策: 有効期限を設定し、まずデータと境界を修正する。
  • 症状: url.Parse がエラーを返すかどうかのみをテストする → 失敗する理由: ホスト名、ポート、DNS、リダイレクト、およびプロキシによってセキュリティ結果が変わる可能性がある → 修正策: 完全なネットワークパスをテストする。
  • 症状: 生の文字列からプライベートアドレスを判定する → 失敗する理由: エンコーディング、パース処理、DNS、およびリダイレクトによって文字列ベースのルールがバイパスされる可能性がある → 修正策: 一貫したパースを行い、IP を解決し、リダイレクトのたびにポリシーを再適用する。

フォローアップ質問と回答

レガシーな構成を本日中に復旧させる必要があります。リスクをどのように制御しますか?

分離されたプロセス、または明示的にインベントリ化されたレガシーセットに対してのみ urlstrictcolons=0 を有効化し、有効期限、送信元および送信先の制限、そしてヒットごとのメトリクスを設定します。新しい値には引き続き厳格な検証を適用し、クリーンアップ完了後にロールバックを解除します。

[::1] が有効であるのに対し、::1 が推奨されないのはなぜですか?

URI の authority コンポーネントは、ホストとポートを区切るためにコロンを使用します。角括弧を使用することで、IPv6 の IP-literal とポートの間の境界が明示的になります。hostport 内に直接 ::1 を配置すると曖昧になるため、Go 1.26 では拒絶されます。

プロキシはどの段階で再検証を行う必要がありますか?

Ingress でのパースのみでは不十分です。Host の書き換え、リダイレクトの追跡、または DNS の再解決を行った後、新しい宛先に対してスキーム、ホスト名、IP、ポート、およびネットワークポリシーを再適用する必要があります。ホップを越えて最初の URL に対する判定結果を引き継いではいけません。

アップグレードによって拒絶範囲が不当に広がっていないことをどのように証明しますか?

有効な IPv4、角括弧付き IPv6、ポート付き URL、余分なコロンを含む不正なホスト、空のホスト、無効なポート、およびリダイレクトからなる固定コーパスを維持します。これを Go 1.24.8、1.25.2、および 1.26 で実行し、カナリアリリース時の実際の構成エラー率と比較します。

参考資料

  • Go 1.26 リリースノート
  • Go、後方互換性、および GODEBUG
  • RFC 3986 Uniform Resource Identifier (URI): 汎用構文
  • Go net/url ソースコード

面接チェックリスト

まずホスト、IPv6 の角括弧、およびポートの境界について説明してください。次に、データのクリーンアップ、厳格な境界検証、SSRF パスの再テスト、および期限付きの GODEBUG ロールバックについて説明します。パースの互換性とアクセスのセキュリティは明確に区別して扱ってください。

1行のまとめ

Go 1.26 では、安全な移行のために、サービスが曖昧な URL を明示的な URI に変換し、テスト、カナリアリリース、および期限付きロールバックを活用することが求められます。

公開情報ソース

関連する質問