
⏰ 🔥 ネットワークとシステムの状態をシミュレートし、カオスおよびレジリエンシーテストを実施するためのTCPプロキシ
Toxiproxy はネットワーク状態をシミュレートするためのフレームワークです。テスト、CI、開発環境で動作するよう特別に作られており、接続への決定的な改ざんをサポートしつつ、ランダムなカオスとカスタマイズにも対応しています。Toxiproxy は、アプリケーションに単一障害点が存在しないことをテストで証明するために必要なツールです。 2014年10月以降、Shopify のすべての開発環境およびテスト環境で使用に成功しています。詳細は、回復性に関するブログ記事を参照してください。
Toxiproxy の利用は2つの部分から構成されます。Go で書かれた TCP プロキシ(このリポジトリに含まれるもの)と、HTTP 経由でプロキシと通信するクライアントです。すべてのテスト接続が Toxiproxy を通過するようにアプリケーションを設定し、HTTP 経由でその状態を操作できます。プロジェクトの設定方法については、以下の使用法を参照してください。
たとえば、Ruby クライアントから MySQL の応答に 1000ms の遅延を追加するには:```ruby Toxiproxy[:mysql_master].downstream(:latency, latency: 1000).apply do Shop.first # this takes at least 1s end
すべての Redis インスタンスを停止するには:```ruby
Toxiproxy[/redis/].down do
Shop.first # this will throw an exception
end
このREADMEの例は現在Rubyで書かれていますが、他の言語でクライアントを作成することを妨げるものは何もありません(Clientsを参照)。
既存のツールは、私たちが必要とする統合テストや単体テストのための動的APIを提供してくれませんでした。nc などのLinuxツールはクロスプラットフォームではなく、root権限が必要なため、テスト・開発・CI環境では問題になります。
Railsアプリケーションを使った例を見ていきましょう。ToxiproxyはRubyにまったく依存しているわけではなく、単に最初のユースケースだったというだけです。完全な例は sirupsen/toxiproxy-rails-example で確認できます。すぐに始めたい場合は、使い方 にジャンプしてください。
私たちの人気ブログでは、何らかの理由で投稿のタグをRedisに、投稿自体をMySQLに保存しています。Post クラスには、Redisセット 内のタグを操作するメソッドが含まれているかもしれません。```ruby
class Post < ActiveRecord::Base
def tags TagRedis.smembers(tag_key) end
def add_tag(tag) TagRedis.sadd(tag_key, tag) end
def remove_tag(tag) TagRedis.srem(tag_key, tag) end
def tag_key "post:tags:#{self.id}" end end
タグデータストアへの書き込み(追加/削除)中にエラーが発生しても問題ないと判断しました。ただし、タグデータストアがダウンしている場合は、タグなしで投稿を表示できるようにする必要があります。`tags` メソッド内の `SMEMBERS` Redis 呼び出しを `Redis::CannotConnectError` で rescue するだけで済みます。これをテストするために Toxiproxy を使いましょう。
Toxiproxy はすでにインストール済みで、マシン上で実行されているので、ステップ 2 に進むことができます。ここで、Toxiproxy が Redis タグのマッピングを持っていることを確認する必要があります。`config/boot.rb` に(接続が行われる前に)追加します:```ruby
require 'toxiproxy'
Toxiproxy.populate([
{
name: "toxiproxy_test_redis_tags",
listen: "127.0.0.1:22222",
upstream: "127.0.0.1:6379"
}
])
次に、config/environments/test.rb 内で、TagRedis が Toxiproxy を介して Redis に接続する Redis クライアントになるように、
次の行を追加します。```ruby
TagRedis = Redis.new(port: 22222)
テスト環境内のすべての呼び出しは現在、Toxiproxy を経由します。つまり、障害をシミュレートするユニットテストを追加できるということです。```ruby
test "should return empty array when tag redis is down when listing tags" do
@post.add_tag "mammals"
# Take down all Redises in Toxiproxy
Toxiproxy[/redis/].down do
assert_equal [], @post.tags
end
end
テストは Redis::CannotConnectError で失敗します。完璧です!Toxiproxy はクロージャの間、Redis を正常に停止させました。tags メソッドを耐障害性を持つように修正しましょう:```ruby
def tags
TagRedis.smembers(tag_key)
rescue Redis::CannotConnectError
[]
end
テストはパスしました!これで、Redis がダウンしているときにタグを取得すると、例外を投げる代わりに空の配列を返すことを証明するユニットテストができました。完全なカバレッジのためには、Redis がダウンしているときにブログ投稿ページ全体の取得をラップする統合テストも書くべきです。
完全なサンプルアプリケーションは、
[sirupsen/toxiproxy-rails-example](https://github.com/sirupsen/toxiproxy-rails-example) にあります。
## Usage
Toxiproxy を使用するプロジェクトの設定は、次の3つのステップで構成されます。
1. Toxiproxy のインストール
2. Toxiproxy へのプロキシ登録
3. Toxiproxy の使用
### 1. Toxiproxy のインストール
**Linux**
最新のバイナリとお使いのアーキテクチャ向けシステムパッケージについては、[`Releases`](https://github.com/Shopify/toxiproxy/releases) を参照してください。
**Ubuntu**```bash
$ wget -O toxiproxy-2.1.4.deb https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy_2.1.4_amd64.deb
$ sudo dpkg -i toxiproxy-2.1.4.deb
$ sudo service toxiproxy start
OS X
Homebrew を使用する場合:```bash $ brew tap shopify/shopify $ brew install toxiproxy
または [MacPorts](https://www.macports.org/):```bash
$ port install toxiproxy
Windows
Windows版Toxiproxyは、https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy-server-windows-amd64.exe からダウンロードできます。
Docker
ToxiproxyはGithubコンテナレジストリから入手できます。
旧バージョン(<= 2.1.4)はDocker Hubから入手できます。```bash
$ docker pull ghcr.io/shopify/toxiproxy
$ docker run --rm -it ghcr.io/shopify/toxiproxy
Toxiproxy を他のコンテナではなくホストから使用する場合、`--net=host` でホストネットワーキングを有効にしてください。```shell
$ docker run --rm --entrypoint="/toxiproxy-cli" -it ghcr.io/shopify/toxiproxy list
Goがインストールされている場合、makeファイルを使用してソースからToxiproxyをビルドできます:```bash $ make build $ ./toxiproxy-server
#### Upgrading from Toxiproxy 1.x
#### Toxiproxy 1.x からのアップグレード
Toxiproxy 2.0 では、バージョン 1.x と互換性がなくなる API の変更がいくつか行われました。
Toxiproxy サーバーのバージョン 2.x を使用するには、クライアントライブラリが同じバージョンをサポートしていることを確認する必要があります。実行中の Toxiproxy のバージョンは、`/version` エンドポイントを確認することで確認できます。
具体的なライブラリの変更については、クライアントライブラリのドキュメントを参照してください。Toxiproxy サーバーの詳細な変更は、[CHANGELOG.md](https://github.com/shopify/toxiproxy/blob/HEAD/CHANGELOG.md) に記載されています。
### 2. Populating Toxiproxy
### 2. Toxiproxy への投入
When your application boots, it needs to make sure that Toxiproxy knows which
endpoints to proxy where. The main parameters are: name, address for Toxiproxy
to **listen** on and the address of the upstream.
アプリケーションの起動時には、Toxiproxy がどのエンドポイントをどこにプロキシするかを把握している必要があります。主なパラメータは、名前、Toxiproxy が**リッスン**するアドレス、アップストリームのアドレスです。
Some client libraries have helpers for this task, which is essentially just
making sure each proxy in a list is created. Example from the Ruby client:
一部のクライアントライブラリには、このタスクのためのヘルパーがあり、基本的にはリスト内の各プロキシが確実に作成されるようにするだけです。Ruby クライアントの例:```ruby
# Make sure `shopify_test_redis_master` and `shopify_test_mysql_master` are
# present in Toxiproxy
Toxiproxy.populate([
{
name: "shopify_test_redis_master",
listen: "127.0.0.1:22220",
upstream: "127.0.0.1:6379"
},
{
name: "shopify_test_mysql_master",
listen: "127.0.0.1:24220",
upstream: "127.0.0.1:3306"
}
])
このコードは、Toxiproxy を介した接続を確立するコードよりも前に、起動のできるだけ早い段階で実行する必要があります。ポピュレーションヘルパーに関するドキュメントについては、お使いのクライアントライブラリをご確認ください。
あるいは、CLI を使用してプロキシを作成することもできます。例:```bash toxiproxy-cli create -l localhost:26379 -u localhost:6379 shopify_test_redis_master
上記のような命名を推奨します: `<app>_<env>_<data store>_<shard>`。
これにより、同じToxiproxyを使用するアプリケーション間で
競合が発生しなくなります。
大規模なアプリケーションでは、Toxiproxy設定を別の設定ファイルに保存することを
推奨します。`config/toxiproxy.json` を使用します。このファイルは、
`-config` オプションを使用してサーバーに渡すか、アプリケーションが
`populate` 関数で使用するためにロードできます。
`config/toxiproxy.json` の例:```json
[
{
"name": "web_dev_frontend_1",
"listen": "[::]:https://raw.githubusercontent.com/shopify/toxiproxy/HEAD/18080%22,
"upstream": "webapp.domain:8080",
"enabled": true
},
{
"name": "web_dev_mysql_1",
"listen": "[::]:13306",
"upstream": "database.domain:3306",
"enabled": true
}
]
一時ポート範囲の外側のポートを使用して、ランダムなポート競合を避けてください。
Linuxではデフォルトで32,768から61,000です、参照
/proc/sys/net/ipv4/ip_local_port_range。
Toxiproxyを使用するには、アプリケーションがToxiproxyを経由して接続するように設定する必要があります。ステップ2の例に続いて、RedisクライアントがToxiproxy経由で接続するように設定できます:```ruby
redis = Redis.new(port: 6380)
redis = Redis.new(port: 22220)
これで、Toxiproxy API を介してそれを改ざんできます。Ruby の場合:```ruby
redis = Redis.new(port: 22220)
Toxiproxy[:shopify_test_redis_master].downstream(:latency, latency: 1000).apply do
redis.get("test") # will take 1s
end
またはCLI経由:```bash toxiproxy-cli toxic add -t latency -a latency=1000 shopify_test_redis_master
使用方法については、それぞれのクライアントライブラリを参照してください。
### 4. ロギング
ログレベルは次のとおりです: panic、fatal、error、warn(または warning)、info、debug、trace。
レベルは環境変数 `LOG_LEVEL` で更新できます。
### Toxics
Toxics は、クライアントとアップストリーム間のパイプを操作します。プロキシに対しては、[HTTP API](#http-api) を使って Toxic を追加・削除できます。各 Toxic には、プロキシリンクへの影響を変更するための独自のパラメータがあります。
カスタム Toxic の実装に関するドキュメントは、[CREATING_TOXICS.md](https://github.com/shopify/toxiproxy/blob/HEAD/CREATING_TOXICS.md) を参照してください。
#### latency
プロキシを通過するすべてのデータに遅延を追加します。遅延は `latency` +/- `jitter` に等しくなります。
属性:
- `latency`: ミリ秒単位の時間
- `jitter`: ミリ秒単位の時間
#### down
サービスを停止することは、Toxiproxy の実装では厳密には Toxic ではありません。これは `/proxies/{proxy}` に `POST` し、`enabled` フィールドを `false` に設定することで行います。
#### bandwidth
接続を1秒あたりの最大キロバイト数に制限します。
属性:
- `rate`: KB/秒単位のレート
#### slow_close
`delay` が経過するまで TCP ソケットのクローズを遅延させます。
属性:
- `delay`: ミリ秒単位の時間
#### timeout
すべてのデータの通過を停止し、`timeout` 後に接続を閉じます。`timeout` が 0 の場合、接続は閉じられず、Toxic が削除されるまでデータはドロップされます。
属性:
- `timeout`: ミリ秒単位の時間
#### reset_peer
スタブ入力を即座に、または `timeout` 後に閉じることにより、接続上で TCP RESET(ピアによる接続リセット)をシミュレートします。
属性:
- `timeout`: ミリ秒単位の時間
#### slicer
TCP データを小さな断片にスライスし、必要に応じてスライスされた各「パケット」の間に遅延を追加します。
属性:
- `average_size`: 平均パケットのサイズ(バイト単位)
- `size_variation`: 平均パケットの変動(バイト単位)(average_size より小さい必要があります)
- `delay`: 各パケットを遅延させる時間(マイクロ秒単位)
#### limit_data
送信データが制限を超えたときに接続を閉じます。
- `bytes`: 接続が閉じられるまでに送信するバイト数
#### packet_loss
プロキシを流れるチャンクをランダムにドロップし、不安定な Wi-Fi、モバイル、衛星ネットワーク環境をシミュレートします。
属性:
- `loss_rate`: チャンクがドロップされる確率 [0.0-1.0](デフォルト 0.0)
- `correlation`: 前のチャンクがドロップされた場合の追加のドロップ確率。バースト損失をモデル化します(デフォルト 0.0)
### HTTP API
クライアントから Toxiproxy デーモンへのすべての通信は、ここで説明する HTTP インターフェースを介して行われます。
Toxiproxy は HTTP をポート **8474** で待ち受けます。
#### プロキシのフィールド:
- `name`: プロキシ名(文字列)
- `listen`: リッスンアドレス(文字列)
- `upstream`: プロキシのアップストリームアドレス(文字列)
- `enabled`: true/false(作成時はデフォルトで true)
プロキシ名を変更するには、プロキシを削除して再作成する必要があります。
`listen` または `upstream` フィールドを変更すると、プロキシが再起動され、アクティブな接続はすべて切断されます。
`listen` にポート 0 を指定すると、toxiproxy は一時的なポートを選択します。応答内の `listen` フィールドは、実際のポートで更新されます。
`enabled` を `false` に変更すると、プロキシが停止します。`true` に戻すと再び有効化できます。
#### Toxic のフィールド:
- `name`: Toxic 名(文字列、デフォルトは `<type>_<stream>`)
- `type`: Toxic タイプ(文字列)
- `stream`: 影響を与えるリンクの方向(デフォルトは `downstream`)
- `toxicity`: Toxic がリンクに適用される確率(デフォルトは 1.0、100%)
- `attributes`: Toxic 固有の属性のマップ
Toxic 固有の属性については、[Toxics](#toxics) を参照してください。
`stream` の方向は `upstream` または `downstream` のいずれかでなければなりません。`upstream` は `client -> server` 接続に Toxic を適用し、`downstream` は `server -> client` 接続に Toxic を適用します。これにより、リクエストとレスポンスを別々に変更できます。
#### エンドポイント
すべてのエンドポイントは JSON です。
- **GET /proxies** - 既存のプロキシとその Toxics を一覧表示
- **POST /proxies** - 新しいプロキシを作成
- **POST /populate** - プロキシのリストを作成または置換
- **GET /proxies/{proxy}** - アクティブなすべての Toxics を含むプロキシを表示
- **POST /proxies/{proxy}** - プロキシのフィールドを更新
- **DELETE /proxies/{proxy}** - 既存のプロキシを削除
- **GET /proxies/{proxy}/toxics** - アクティブな Toxics を一覧表示
- **POST /proxies/{proxy}/toxics** - 新しい Toxic を作成
- **GET /proxies/{proxy}/toxics/{toxic}** - アクティブな Toxic のフィールドを取得
- **POST /proxies/{proxy}/toxics/{toxic}** - アクティブな Toxic を更新
- **DELETE /proxies/{proxy}/toxics/{toxic}** - アクティブな Toxic を削除
- **POST /reset** - すべてのプロキシを有効化し、アクティブなすべての Toxics を削除
- **GET /version** - サーバーのバージョン番号を返す
- **GET /metrics** - Prometheus 互換のメトリクスを返す
#### プロキシの一括登録
プロキシは `/populate` エンドポイントを使用して一括で追加・設定できます。これは、プロキシの JSON 配列を toxiproxy に渡すことで行われます。同じ名前のプロキシがすでに存在する場合は、新しいプロキシと比較され、`upstream` と `listen` アドレスが一致しない場合に置き換えられます。
`/populate` 呼び出しは、たとえばアプリケーション起動時に、必要なプロキシがすべて存在することを保証するために含めることができます。この呼び出しを複数回行っても安全です。プロキシのフィールドが新しいデータと一致している限り、プロキシは変更されないためです。
### CLI の例```bash
$ toxiproxy-cli create -l localhost:26379 -u localhost:6379 redis
Created new proxy redis
$ toxiproxy-cli list
Listen Upstream Name Enabled Toxics
======================================================================
127.0.0.1:26379 localhost:6379 redis true None
Hint: inspect toxics with `toxiproxy-client inspect <proxyName>`
(入力コンテンツが提供されていません。翻訳対象のMarkdownテキストを貼り付けてください。)```bash $ redis-cli -p 26379 127.0.0.1:26379> SET omg pandas OK 127.0.0.1:26379> GET omg "pandas"
Please provide the Markdown content to translate.```bash
$ toxiproxy-cli toxic add -t latency -a latency=1000 redis
Added downstream latency toxic 'latency_downstream' on proxy 'redis'
(empty)```bash $ redis-cli -p 26379 127.0.0.1:26379> GET omg "pandas" (1.00s) 127.0.0.1:26379> DEL omg (integer) 1 (1.00s)
[No input text provided to translate.]```bash
$ toxiproxy-cli toxic remove -n latency_downstream redis
Removed toxic 'latency_downstream' on proxy 'redis'
以下の表は、スキャン中に検出された中リスクに分類されるすべてのセキュリティ問題を示しています。これらの問題は、アプリケーションの全体的なセキュリティ体制を改善するために対処する必要があります。
| ID | 重大度 | 検出名 | 影響を受けるURL | 説明 | 推奨事項 |
|---|---|---|---|---|---|
| M-01 | ✅ Medium | セキュリティ設定の不備 | https://example.com/login | X-Frame-Options ヘッダーがないため、ユーザーがクリックジャッキング攻撃に対して脆弱になる可能性があります。 |
攻撃者がログインページを悪意のあるiframeに埋め込み、ユーザーの認証情報を盗む可能性があります。
影響: 認証情報の窃取、セッションハイジャック。
推奨される修正方法:
すべてのレスポンスに以下のヘッダーを設定してください:
Header always set X-Frame-Options "DENY"
設定例:
add_header X-Frame-Options "SAMEORIGIN" always;
以下の表は、スキャン中に検出された高リスクに分類されるすべてのセキュリティ問題を示しています。これらの問題は直ちに対処が必要です。
| ID | 重大度 | 検出名 | 影響を受けるURL | 説明 | 推奨事項 |
|---|---|---|---|---|---|
| H-01 | ✅ Critical |
CORSポリシーが寛容なため、攻撃者がクロスオリジンリクエストをAPIに送信し、機密データを読み取ることができます。
影響: 不正なデータアクセス、データ漏えい。
推奨される修正方法:
許可するオリジンを * ではなく信頼できるリストに制限してください。
設定例:
# Flask example
CORS(app, resources={r"/api/*": {"origins": ["https://trusted-domain.com"]}})
| H-02 | ✅ Critical | SQLインジェクション | https://example.com/products?id=1 | ユーザー入力が適切なサニタイズなしにSQLクエリに直接連結されており、攻撃者がデータベースクエリを操作できる可能性があります。 | SQLインジェクションを防ぐために、パラメータ化クエリまたはプリペアドステートメントを使用してください。 |
攻撃者が id パラメータを操作して、アクセス権限のないデータを取得または改ざんする可能性があります。
影響: 不正なデータアクセス、データの喪失または破損。
推奨される修正方法:
SQLインジェクションを防ぐために、パラメータ化クエリまたはプリペアドステートメントを使用してください。
設定例:
# Python example
cursor.execute("SELECT * FROM products WHERE id = %s", (product_id,))
| H-03 | ✅ Critical | 格納型XSS | https://example.com/search | ユーザー入力が適切なエンコードなしにページに反映されるため、攻撃者がアプリケーションに任意のJavaScriptを注入できる可能性があります。 | すべてのユーザー入力をページにレンダリングする前にサニタイズおよびエンコードしてください。 |
悪意のあるスクリプトがサーバーに保存され、他のユーザーが影響を受けるページを閲覧すると実行される可能性があります。
影響: セッションハイジャック、改ざん、認証情報の窃取。
推奨される修正方法:
すべてのユーザー入力をページにレンダリングする前にサニタイズおよびエンコードしてください。
設定例:
// JavaScript example
const safeHTML = DOMPurify.sanitize(userInput);
element.innerHTML = safeHTML;
| H-04 | ✅ Critical | 認証バイパス | https://example.com/admin | role Cookieを操作することで認証メカニズムがバイパスされ、攻撃者が管理者アクセスを取得できる可能性があります。 | 堅牢なセッション管理を実装し、すべてのリクエストでセッショントークンを検証してください。 |
攻撃者が role Cookieの値を変更して権限を昇格させ、管理パネルにアクセスできる可能性があります。
影響: 権限昇格、不正アクセス、データ漏えい。
推奨される修正方法:
堅牢なセッション管理を実装し、すべてのリクエストでセッショントークンを検証してください。
設定例:
// JavaScript example
if (user.role !== 'admin') {
// redirect to login
}
``````bash
$ redis-cli -p 26379
127.0.0.1:26379> GET omg
(nil)
I don't see any content to translate. The input section is empty. Please provide the actual Markdown content you'd like translated.```bash $ toxiproxy-cli delete redis Deleted proxy redis
翻訳対象のMarkdownコンテンツが見つかりません。内容を提供してください。```bash
$ redis-cli -p 26379
Could not connect to Redis at 127.0.0.1:26379: Connection refused
Toxiproxy は /metrics の HTTP API を介して Prometheus 互換のメトリクスを公開します。 完全な説明は METRICS.md を参照してください。
Toxiproxy はどのくらい速いですか? Toxiproxy の速度は主にハードウェアに依存しますが、
トキシクスが有効でない場合、レイテンシは < 100µs と期待できます。実行する場合、
Macbook Pro で GOMAXPROCS=4 を使用すると、~1000MB/s のスループットを達成し、
高性能なデスクトップでは 2400MB/s にも達しました。基本的に、Toxiproxy はデータを
テスト対象のアプリケーションと少なくとも同じ速さで転送できると期待できます。
Toxiproxy はランダムテストを実行できますか? 利用可能なトキシクスの多くは、ランダム性を持つように設定でき、
たとえば、latency トキシクスの jitter のようなものです。また、グローバルな
toxicity パラメータがあり、トキシクスが影響する接続の割合を指定します。これは
timeout トキシクスのようなものに最も有用で、接続の X% を
タイムアウトさせることができます。
MySQL で Toxiproxy の操作が反映されないのですが。MySQL は一部のクライアントでは、
どのポートを渡しても、ローカル Unix ドメインソケットを優先します。
ホストが localhost に設定されている場合は、MySQL サーバーがソケットを作成しないように設定し、
ホストとして 127.0.0.1 を使用してください。古いソケットを削除することを忘れないでください。
サーバーを再起動した後に。
Toxiproxy は断続的な接続障害を引き起こします。エフェメラルポート範囲の外側のポートを使用してください。
ランダムなポート競合を避けるためです。Linux ではデフォルトで 32,768 から 61,000 で、
/proc/sys/net/ipv4/ip_local_port_range を参照してください。
アプリケーションごとに Toxiproxy を実行すべきですか? いいえ、すべてのアプリケーションで同じ Toxiproxy を
使用することをお勧めします。サービスを区別するために、
プロキシに <app>_<env>_<data store>_<shard> というスキームで名前を付けることをお勧めします。
たとえば、shopify_test_redis_master または shopify_development_mysql_1 です。
make。現在のプラットフォーム用の toxiproxy 開発バイナリをビルドします。make all。すべてのプラットフォーム向けの Toxiproxy バイナリとパッケージをビルドします。
Linux および Darwin (amd64) でクロスコンパイルを有効にした Go と、
$PATH に goreleaser があり、
Linux パッケージのバイナリをビルドできることが必要です。make test。Toxiproxy のテストを実行します。RELEASE.md を参照してください。
| すべてのレスポンスに以下のヘッダーを設定してください: |
| CORS設定の不備 |
https://api.example.com/ |
Access-Control-Allow-Origin ヘッダーが過度に寛容で、任意のオリジンが機密リソースにアクセスできる状態になっています。 |
許可するオリジンを * ではなく信頼できるリストに制限してください。 |