はじめに
Corner Shop の注文アプリは、非公開の PostgreSQL データベースからレコードを読み取り、作成します。ページは動いていますが、Visitor もデータベースに直接接続できます。アプリが動くことだけでは、データベースの境界が安全とはいえません。
実際の接続を比較し、準備済みの Gateway で転送を制限しながら、注文と公開ステータスサービスを維持します。基本的なターミナル操作だけが必要です。人物とレコードはすべて合成です。3 つのターミナルノードは別々のネットワーク名前空間を持つコンテナで、1 つのインスタンスのカーネルを共有します。独立した VM ではありません。
注文とネットワーク経路を追う
このステップでは、実際の注文を作成し、データベースへの 2 つの経路を比較します。アプリは顧客向けのプログラムで、データベースは記録の保存先です。Order Page を開き、seed: Green tea を確認して、Order reference に practice-1 を入力し、Create tea order を押します。新しい行は表示されるだけでなく、PostgreSQL に保存されます。
ネットワーク図の IP アドレスは通信先のノードを識別します。ポートはそのアドレス上のサービスを識別します。PostgreSQL は 172.28.20.10:5432、別の公開ステータスサービスは 8001 を使います。App Client(172.28.10.20)はデータベースを必要としますが、Visitor(172.28.10.30)に必要なのは公開ステータスだけです。
App Client に切り替えます。準備済みの tcp-probe は実際の TCP 接続を開き、最大 3 秒待ちます。2 つの引数は宛先アドレスとポートです。
tcp-probe 172.28.20.10 5432
TCP OPEN が表示されます。これは到達性を示し、読み取り権限を示すものではありません。psql は PostgreSQL のクエリクライアントです。次のコマンドは今回の実行だけに練習用パスワードを渡します。引用符付きの接続文字列は、サーバー、ポート、ユーザー、データベース、3 秒の接続タイムアウトをまとめます。-c は引用符内の SQL を実行し、SELECT は orders の指定列を読み、ORDER BY は結果を並べ替えます。セミコロンは SQL 文の終わりです。
PGPASSWORD=training-only psql "host=172.28.20.10 port=5432 user=shopapp dbname=shop connect_timeout=3" -c "SELECT id, item FROM orders ORDER BY id;"
結果で practice-1 と Green tea を確認します。意図的に公開した練習用認証情報は変更せず、ネットワークだけの変化を観察します。
Visitor に切り替え、両方のコマンドを繰り返します。
tcp-probe 172.28.20.10 5432
PGPASSWORD=training-only psql "host=172.28.20.10 port=5432 user=shopapp dbname=shop connect_timeout=3" -c "SELECT id, item FROM orders ORDER BY id;"
両方とも成功します。不要なクライアントが接続し、合成注文を読めています。パスワードが無効なら、PostgreSQL に到達した後で認証エラーになります。ネットワークポートは依然として公開されたままです。
ルートはパケットの送り先をノードに伝えます。Visitor からデータベースへの準備済みルートを調べます。
ip route get 172.28.20.10
注目するのは via 172.28.10.254(Gateway)と src 172.28.10.30(Visitor)です。App Client も同じゲートウェイを別の送信元で使います。データベースの戻りルートは Gateway のもう一方のインターフェース 172.28.20.254 を指します。両クライアントにデータベース網への直結インターフェースはありません。ルートは準備済みの基盤なので変更しません。このステップは実際の注文で確認し、レポートは提出しません。
アプリのデータベース経路だけを許可する
このステップでは Gateway を通るパケットを制御します。Gateway に切り替えます。iptables はカーネルのパケットフィルタールールを編集します。チェーンは順序付きリストで、最初に一致した判断が適用されます。FORWARD はノードを通過する通信を、INPUT はノード自身が宛先の通信を扱います。環境では FORWARD を実習専用の LAB_FORWARD に接続しています。初期の広い許可ルールが公開状態を生んでいます。
まずルールを表示します。-n はアドレスを数値で表示し、-v はパケット数を含め、-L は指定チェーンを一覧表示します。
iptables -nvL LAB_FORWARD
このターミナルの LAB_FORWARD だけを変更してください。ホストのルール、ルート、認証情報、サービスは変更しません。ルールは即時に反映され、この実習セッション中は有効です。広い許可を 4 つの判断に置き換えます。一致しない通信は準備済みの親チェーンが拒否します。
-F は指定チェーンを空にします。追加中は転送サービスが一時的に止まりますが、ターミナルと Order Page の入口にはアクセスできます。入力を誤ったら、この消去から再開し、下の 4 ルールをすべて入力し直します。
iptables -F LAB_FORWARD
応答も戻る必要があります。たとえばデータベースから App Client への返信では、宛先はもうデータベースポートではありません。-A は末尾に追加、-m conntrack は接続追跡を使用し、--ctstate ESTABLISHED,RELATED は許可済み接続の返信や関連通信を識別します。-j ACCEPT は一致するパケットを許可します。
iptables -A LAB_FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
次にアプリの新規接続を許可します。-s は送信元、-d は宛先、-p tcp は TCP、--dport は宛先ポートに一致させます。単独のアドレスはそのノードだけを表します。
iptables -A LAB_FORWARD -s 172.28.10.20 -d 172.28.20.10 -p tcp --dport 5432 -j ACCEPT
アプリ用例外の後ろに一般的なデータベース拒否を置きます。-s がないため、ほかの送信元を対象にします。REJECT --reject-with tcp-reset は、無応答でタイムアウトを待たせず、TCP 接続を明示的に拒否します。
iptables -A LAB_FORWARD -d 172.28.20.10 -p tcp --dport 5432 -j REJECT --reject-with tcp-reset
最後に両クライアントの公開ステータスポートを維持します。これを許可してもデータベースポートは公開されません。
iptables -A LAB_FORWARD -d 172.28.20.10 -p tcp --dport 8001 -j ACCEPT
結果を表示します。
iptables -nvL LAB_FORWARD
順序は、応答、アプリのデータベース接続、一般的なデータベース拒否、公開ステータスです。アプリ用例外より前に拒否を置くと、店舗も止まります。カウンターは一致する通信が実際に Gateway を通ると増えます。ルールの存在だけでは証明になりません。Order Page を更新し、App Client は OPEN、Visitor は BLOCKED、公開ステータスは PUBLIC STATUS OK、注文は引き続き表示されることを確認します。
安全性と店舗を再テストする
このステップでは接続表を確認し、もう 1 件の実際の注文を作成します。App Client で新しい接続を開きます。
tcp-probe 172.28.20.10 5432
TCP OPEN を期待します。同じ認証情報で再度読み取ります。
PGPASSWORD=training-only psql "host=172.28.20.10 port=5432 user=shopapp dbname=shop connect_timeout=3" -c "SELECT id, item FROM orders ORDER BY id;"
Visitor に切り替えます。
tcp-probe 172.28.20.10 5432
TCP BLOCKED と接続拒否の説明を期待します。このコマンドは意図的に失敗状態を返します。黙って破棄する場合はタイムアウトになります。どちらも新規接続を防ぎますが、タイムアウトだけでは原因を特定できません。App Client も失敗する場合、Gateway で順序と応答処理を確認します。
curl は Web リソースを取得します。--max-time 4 は要求全体を 4 秒に制限し、-f は HTTP エラーを失敗として扱い、-s は進捗を隠し、-S はエラーを表示します。Visitor から別の公開サービスをテストします。
curl --max-time 4 -fsS http://172.28.20.10:8001/status.txt
PUBLIC STATUS OK が返ります。転送パケットをすべて止める方法は、データベース経路を閉じてもこの要件を満たしません。
Order Page で practice-2 を作成し、練習用の 2 件の注文を確認します。これは修復後に実際のアプリのデータベース接続を使います。もう一度更新しても Visitor は遮断されたままです。Gateway のカウンターは調査に役立ちますが、成功した注文と許可・遮断のテストが実際の証拠です。PostgreSQL の停止やパスワード変更で、訪問者のクエリ失敗を安全な修復に見せかけてはいけません。
まとめ
注文が PostgreSQL に入る流れを追い、経路制御された 2 つのクライアントを比較し、順序付き転送ルールを適用して安全性と可用性を確認しました。パスワードエラーと TCP 接続の遮断は異なります。公開経路を閉じるときも、正規アプリと別のサービスを維持しましょう。



