netarmor

module
v1.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: MIT

README

🛡️ Net Armor

CI Language Go Version GitHub tag (latest by date) Go Reference

💡 概要 (About)— 鉄壁のネットワーク防御と回復力を提供する高信頼性ユーティリティ

Net Armor は、Go言語アプリケーションの外部通信における「安定性」と「安全性」を強化するためのネットワークユーティリティキットです。

一時的なネットワークエラーに対する指数バックオフリトライ機能と、SSRF (Server-Side Request Forgery) や DNS Rebinding 攻撃からインフラを保護するセキュリティ機能を提供します。

✨ 特徴

  • 堅牢なリトライ (retry): backoff/v5 をベースに、Context キャンセル・最大試行回数・ジッタ・観測フック・サーバ指定の待機時間 (Retry-After) を直感的に扱えるインターフェースを提供。
  • 強力な防御 (securenet): HTTP クライアントの Transport 層で接続直前に IP アドレスを検証し、検証済み IP に対して直接接続します。DNS Rebinding 等の TOCTOU 攻撃を遮断します。
  • 型付きエラー: すべての失敗理由を errors.Is / errors.As で分類できます。エラーメッセージの文字列比較は不要です。
  • テスト容易性: リゾルバを差し替えられるため、実 DNS に依存しないユニットテストが書けます。
  • クラウド対応: HTTP/HTTPS だけでなく、gs:// (GCS) や s3:// (S3) といったクラウドストレージ用スキームの検証にも対応。
  • モジュール性: 各パッケージは独立しており、必要な機能のみをインポートして利用可能です。

📦 パッケージ構成 (Package Structure)

パッケージ 説明 主な提供機能
securenet ネットワークセキュリティ。SSRF 対策や、サービス URL の妥当性判定を行います。 NewSafeHTTPClient / NewSafeTransport / ValidateURL / IsSecureServiceURL
retry 耐障害性向上。一時的なエラーが発生した際に、指数バックオフを用いて再試行します。 Run / RunValue / With* オプション / DelayHinter

🚀 クイックスタート

1. 安全な HTTP リクエスト (securenet)

DNS Rebinding 攻撃を防ぐため、接続を確立する直前に名前解決を行い、解決された IP アドレスが制限対象の範囲(プライベート / ループバック / リンクローカル等)でないか検証した上で、その IP に直接接続します。 安全な HTTP クライアントは環境変数の HTTP_PROXY / HTTPS_PROXY を使用しません。

import (
    "time"
    "github.com/shouni/netarmor/securenet"
)

// 接続直前のIP検証機能を持つ安全なクライアントを生成
client := securenet.NewSafeHTTPClient(10 * time.Second)

// 安全なURL(例:パブリックなAPI)へのアクセス
resp, err := client.Get("https://api.example.com/data")

// 安全ではないURL(例:内部ネットワークへの攻撃試行)は、DialContext層で遮断されます
_, err = client.Get("http://169.254.169.254/latest/meta-data/")
// errors.Is(err, securenet.ErrRestrictedIP) == true

独自の *http.Client を組み立てたい場合は Transport だけを取得できます。

client := &http.Client{
    Transport: securenet.NewSafeTransport(10 * time.Second),
    Jar:       jar,
}
2. URL の静的検証

ユーザー入力を受け付けた時点で早期に弾きたい場合は ValidateURL を使用します。

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

if err := securenet.ValidateURL(ctx, rawURL); err != nil {
    switch {
    case errors.Is(err, securenet.ErrRestrictedIP):
        // 制限されたネットワークへのアクセス
    case errors.Is(err, securenet.ErrDisallowedScheme):
        // 許可されていないスキーム
    case errors.Is(err, securenet.ErrInvalidURL):
        // URL のパース失敗
    }
}

⚠️ 静的検証だけでは DNS Rebinding を防げません。 検証後・接続前に DNS 応答が差し替えられる攻撃に対する本体の防御は NewSafeHTTPClient / NewSafeTransport の側にあります。必ず併用してください。

3. ポリシーの調整

既定のポリシーは Option で緩めたり厳しくしたりできます。

// テストでローカルサーバに接続する
client := securenet.NewSafeHTTPClient(2*time.Second, securenet.WithAllowLoopback())

// 社内ネットワークの特定範囲だけを許可する
client := securenet.NewSafeHTTPClient(10*time.Second,
    securenet.WithAllowedCIDRs("10.1.0.0/16"))

// 社内プロキシ経由にする(プロキシ自身のアドレスの許可も必要)
client := securenet.NewSafeHTTPClient(10*time.Second,
    securenet.WithProxyFromEnvironment(),
    securenet.WithAllowedCIDRs("10.0.0.0/8"))

// リゾルバを差し替えて実 DNS に依存しないテストを書く
client := securenet.NewSafeHTTPClient(2*time.Second,
    securenet.WithResolver(myFakeResolver))
Option 用途
WithResolver 名前解決の差し替え(テスト用)
WithProxy / WithProxyFromEnvironment プロキシの明示的な有効化
WithAllowedCIDRs / WithAllowedPrefixes 特定ネットワークの許可(最優先)
WithBlockedCIDRs 追加のブロック範囲
WithAllowLoopback / WithAllowPrivate / WithAllowLinkLocal ポリシーの緩和
WithBaseTransport / WithDialer Transport / Dialer の持ち込み
WithMaxRedirects / WithAllowRedirectDowngrade リダイレクトポリシー
4. 指数バックオフリトライ (retry)

一時的な接続エラーに対し、適切な待機時間を挟みながら自動的にリトライを行います。

import (
    "context"
    "log/slog"
    "time"

    "github.com/shouni/netarmor/retry"
)

err := retry.Run(ctx, func() error {
    return callRemoteResource()
},
    retry.WithName("ExternalAPI"),
    retry.WithMaxRetries(5),
    retry.WithShouldRetry(isTransient),
    retry.WithNotify(func(err error, attempt uint, next time.Duration) {
        slog.Warn("retrying", "attempt", attempt, "next", next, "err", err)
    }),
)

戻り値を伴う処理には RunValue を使用します(ジェネリクス対応)。

body, err := retry.RunValue(ctx, func() ([]byte, error) {
    return fetch(ctx, url)
}, retry.WithMaxRetries(3))

サーバが待機時間を指定してくる場合(HTTP 429/503 の Retry-After など)は、エラー型に RetryAfter() time.Duration を実装してください(retry.DelayHinter)。正の値を返すと、次の待機時間は指数バックオフの算出値の代わりにその値になり、以降のバックオフ計算はリセットされます。

type rateLimitError struct{ retryAfter time.Duration }

func (e *rateLimitError) Error() string             { return "rate limited" }
func (e *rateLimitError) RetryAfter() time.Duration { return e.retryAfter }

失敗理由は型で判別できます。

switch {
case errors.Is(err, retry.ErrExhausted):     // 最大試行回数に到達
case errors.Is(err, retry.ErrPermanent):     // リトライ不能と判定
case errors.Is(err, context.DeadlineExceeded): // タイムアウト
}

if re, ok := errors.AsType[*retry.Error](err); ok {
    slog.Error("failed", "op", re.Op, "attempts", re.Attempts, "cause", re.Err)
}
Option 既定値 用途
WithMaxRetries 3 リトライ回数(0 でリトライなし
WithMaxAttempts 4 初回を含む総試行回数
WithInitialInterval 5s 初回待機時間
WithMaxInterval 30s 待機時間の上限
WithMultiplier 1.5 増加倍率
WithRandomizationFactor 0.5 ジッタ(0 で無効)
WithMaxElapsedTime 無効 経過時間による打ち切り
WithShouldRetry 全てリトライ リトライ可否の判定
WithNotify なし リトライ直前のフック
WithName なし エラーメッセージ用の操作名

🛡️ セキュリティポリシー

securenet パッケージは、デフォルトで以下のアクセスを「制限されたネットワーク」として検知し、ブロックします。

  • プライベート IP アドレス範囲 (RFC 1918、IPv6 ULA を含む)
  • ループバックアドレス (localhost, 127.0.0.1, ::1)
  • リンクローカルアドレス (169.254.0.0/16 等)
  • 未指定アドレス (0.0.0.0, ::)
  • Carrier-grade NAT 範囲 (100.64.0.0/10)
  • ベンチマーク用ネットワーク (198.18.0.0/15)
  • ドキュメント用 / TEST-NET 範囲 (192.0.2.0/24、198.51.100.0/24、203.0.113.0/24、2001:db8::/32、3fff::/20)
  • IPv4 を埋め込める変換範囲 — NAT64 (64:ff9b::/96、64:ff9b:1::/48)、6to4 (2002::/16、192.88.99.0/24)、Teredo (2001::/32)
  • 廃止されたサイトローカル (fec0::/10)、SRv6 SID (5f00::/16)
  • マルチキャスト、予約済みアドレス範囲

::ffff:127.0.0.1 のような IPv4-mapped IPv6 表記は正規化した上で判定するため、表記による回避はできません。NAT64 などの変換範囲は、アドレス内に埋め込んだ内部 IPv4(例: 64:ff9b::a9fe:a9fe はメタデータエンドポイント)への到達経路になりうるため範囲全体をブロックします。NAT64 環境で正当に必要な場合は WithAllowedCIDRs で明示的に許可してください。

ホスト名が複数の IP に解決される場合、1 つでも制限対象があれば全体を拒否します (fail-closed)。

IsSecureServiceURL は HTTPS URL またはローカル開発用 HTTP URL を許可しますが、ホスト名が空の URL は拒否します。これは設定値の妥当性チェックであり、名前解決を行いません。

脅威モデルの詳細と脆弱性の報告方法は SECURITY.md を参照してください。


🔄 v1.1.0 からの移行 (Migration)

v1.2.0 で非推奨 API を削除しました。以下の対応が必要です。

retry
// Before (v1.1.0)
cfg := retry.DefaultConfig()
cfg.MaxRetries = 5
err := retry.Do(ctx, cfg, "ExternalAPI", op, shouldRetry)

// After (v1.2.0)
err := retry.Run(ctx, op,
    retry.WithName("ExternalAPI"),
    retry.WithMaxRetries(5),
    retry.WithShouldRetry(shouldRetry),
)
削除された API 移行先
retry.Do retry.Run
retry.Config retry.With* オプション
retry.DefaultConfig 既定値は Run に内蔵(指定不要)

注意: Config.MaxRetries は 0 を「未設定」として既定値 3 に補完していましたが、WithMaxRetries(0) は「リトライしない」を意味します。0 を渡していた箇所は意図を確認してください。

securenet
// Before (v1.1.0)
if ok, err := securenet.IsSafeURL(rawURL); !ok {
    return err
}

// After (v1.2.0)
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := securenet.ValidateURL(ctx, rawURL); err != nil {
    return err
}
削除された API 移行先
securenet.IsSafeURL securenet.ValidateURL(タイムアウトは ctx で指定)
securenet.IsSafeURLContext securenet.ValidateURL

エラーメッセージは日本語から英語+型付きエラーに変更されました。文字列比較を行っていた箇所は errors.Is / errors.As に置き換えてください。


📜 ライセンス (License)

このプロジェクトは MIT License の下で公開されています。

Directories

Path Synopsis
Package retry は、指数バックオフとカスタムエラー判定を用いて 操作をリトライするためのユーティリティを提供します。
Package retry は、指数バックオフとカスタムエラー判定を用いて 操作をリトライするためのユーティリティを提供します。
Package securenet は、SSRF (Server-Side Request Forgery) 対策として、 URLのスキーム検証やDNS Rebinding対策済みのHTTPクライアント生成を行うユーティリティを提供します。
Package securenet は、SSRF (Server-Side Request Forgery) 対策として、 URLのスキーム検証やDNS Rebinding対策済みのHTTPクライアント生成を行うユーティリティを提供します。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL