Skip to content

セキュリティ設定

Spine v0.5.1は、安全な通信と制限付きランタイムをデフォルトで使用します。デプロイ前にApp.Validateで設定を検査でき、App.Runもネットワーク接続を開く前に同じ検証を自動的に実行します。

go
if err := app.Validate(options); err != nil {
    log.Fatal(err)
}
if err := app.Run(options); err != nil {
    log.Fatal(err)
}

検証エラーは*boot.ConfigErrorです。Issues内の各ConfigIssueにはPathCodeMessageHintが含まれます。自動化では安定したCodeを使用し、人向けのメッセージを解析しないでください。Broker URLの認証情報はエラーに含まれません。

安全なデフォルト

領域デフォルト明示的な開発・互換設定
Kafka有効なreader/writerにTLS 1.2以上を適用カスタムTLS/Dialer/Transport、またはローカル専用のAllowInsecureTransport: true
RabbitMQamqps://が必須amqp://AllowInsecureTransport: true
RabbitMQ publishpersistent、mandatory routing、publisher confirm、有限の再試行PublisherRetryを調整
RabbitMQ handler失敗再キューせずrejectFailurePolicy: boot.RabbitMqFailureRequeue
RabbitMQ prefetchconsumerごとに未確認メッセージ1件正のPrefetchCount
Consumer通信失敗指数バックオフとjitterでreaderを再生成ConsumerRetryを調整
WebSocket Originschemeとhostが一致する同一Originのみ許可正確なAllowedOriginsを指定
WebSocket容量接続および待機中handshakeを合わせて1024件正の上限、またはboot.UnlimitedWebSocketConnections
WebSocket認証任意のhandshake interceptorがupgrade前に実行認証が必要なrouteでWebSocketHandshakeInterceptorを実装
グローバルinterceptorHTTPとWebSocketの両方に適用InterceptorForで範囲を限定
credentials付きCORS明示的なOriginが必須*とcredentialsの組み合わせは無効
Cookie応答を書き込む前に不正なフィールドを拒否任意値はhttpx.EncodeCookieValueでエンコード

Kafka

通信設定を省略すると、consumerとpublisherの両方にTLS 1.2以上が適用されます。

go
Kafka: &boot.KafkaOptions{
    Brokers: []string{"kafka.example:9093"},
    Read:  &boot.KafkaReadOptions{GroupID: "orders"},
    Write: &boot.KafkaWriteOptions{},
}

カスタムCA、mTLS、SASLが必要な場合に限りTLSDialerTransportを上書きしてください。明示的なTLSとAllowInsecureTransportを同時に設定するとKAFKA_TLS_INSECURE_CONFLICTになります。平文接続は隔離されたローカル開発環境でのみ明示的に許可してください。

go
Kafka: &boot.KafkaOptions{
    Brokers:                []string{"localhost:9092"},
    AllowInsecureTransport: true,
    Read:                   &boot.KafkaReadOptions{GroupID: "local"},
}

readerエラーまたはACK/NACK失敗の後は、ConsumerRetryに従って現在のreaderを閉じ、新しいreaderを作成します。デフォルトは初回100ms、最大5秒、倍率2、jitter 20%、無制限の再試行です。Kafka handlerは再配信を前提に冪等に実装してください。ランタイムは恒久的に失敗するメッセージを自動的にskipしたりDLQへ送ったりしません。

RabbitMQ

本番URLにはamqps://を使用します。デフォルトの失敗ポリシーは、再キューなしのrejectです。

go
RabbitMQ: &boot.RabbitMqOptions{
    URL: "amqps://user:pass@rabbit.example/vhost",
    PublisherRetry: boot.PublisherRetryOptions{
        InitialDelay:   200 * time.Millisecond,
        MaxDelay:       5 * time.Second,
        MaxAttempts:    5,
        ConfirmTimeout: 5 * time.Second,
    },
    Read: &boot.RabbitMqReadOptions{
        Exchange:      "events",
        PrefetchCount: 16,
        FailurePolicy: boot.RabbitMqFailureReject,
        DeadLetter: &boot.RabbitMqDeadLetterOptions{
            Exchange:   "events.dlx",
            RoutingKey: "events.failed",
        },
    },
    Write: &boot.RabbitMqWriteOptions{Exchange: "events"},
}

DLX exchangeは運用側で先に作成する必要があります。既存queueの引数が異なるとRabbitMQはqueue preconditionエラーを返すことがあります。RabbitMqFailureRequeueはpoison messageを無期限に再配信できるため、新しい設定ではrejectとDLXを使用してください。RequeueOnErrorは非推奨であり、新しいコードではFailurePolicyを使用します。dispatch keyはAMQPのTypeではなくqueue bindingのRoutingKeyです。

Publisherはpersistentメッセージ、mandatory=true、publisher confirmを使用します。routing不能、negative/missing confirmは成功ではありません。confirm消失後の再試行は重複publishを起こし得るため、consumerは冪等にしてください。

WebSocket

正確なOriginと接続上限を指定することを推奨します。

go
HTTP: &boot.HTTPOptions{
    WebSocket: boot.WebSocketOptions{
        AllowedOrigins: []string{"https://app.example.com"},
        MaxConnections: 4096,
    },
},

AllowedOriginsが空の場合、ブラウザ要求はschemeとhostの両方が一致する必要があります。TLS終端proxyの背後ではTrustedProxyCIDRsを指定できます。ForwardedX-Forwarded-Protoは接続相手がこの一覧に含まれる場合のみ信頼されます。proxyは外部から届いたforwarded headerを削除または上書きしてください。

接続上限のデフォルトはboot.DefaultWebSocketMaxConnections(1024)です。認証待ちのhandshakeも上限に含まれます。超過した要求はupgradeされず、HTTP 503、Retry-AfterWEBSOCKET_CAPACITY_EXCEEDED JSONを受け取ります。

認証interceptorはcore.WebSocketHandshakeInterceptorも実装してください。接続slotはPreHandshakeより先に予約され、PreHandshakeはHTTP upgradeより前に実行されます。

go
func (i *AuthInterceptor) PreHandshake(
    ctx core.WebSocketHandshakeContext,
    meta core.HandlerMeta,
) error {
    token := ctx.Header("Authorization")
    if token == "" {
        return httperr.Unauthorized("missing credentials")
    }
    return i.verify(token)
}

handshakeとmessageのcontextは、header、query、cookie、remote address、host、request URIの不変snapshotを提供します。

CORSとCookie

AllowOrigins: []string{"*"}AllowCredentials: trueの組み合わせは無効です。起動時に検査するにはcors.NewValidatedを使用してください。従来のcors.Newも最初の要求で応答headerを書き込む前に構造化エラーを返します。

go
corsInterceptor, err := cors.NewValidated(cors.Config{
    AllowOrigins:     []string{"https://app.example.com"},
    AllowCredentials: true,
})

Cookieの名前、値、path、domain、SameSite、Priorityが不正な場合、JSON、文字列、binary、redirect handlerはheaderやbodyを書き込む前に失敗します。Unicodeや区切り文字を往復させる必要がある場合のみhttpx.EncodeCookieValuehttpx.DecodeCookieValueを使用してください。

運用チェック

  • WEBSOCKET_CAPACITY_EXCEEDED、consumer再接続の枯渇、RabbitMQのType/RoutingKey不一致を監視します。
  • Kafka/RabbitMQ consumerを冪等にし、Kafkaのpoison messageとRabbitMQ DLXの運用手順を準備します。
  • 実際のsocket writeやbroker publishはDB commitと原子的には結合できません。原子性が必要ならtransactional outboxと冪等性キーを使用します。
  • 対応バージョンは現在のリリースで確認してください。