ネットワーク設計¶
ひとことで
パケットがサーバーに届くまでには 3 つの関門があります:ルーティング(経路があるか)→ セキュリティ・リスト / NSG(クラウドのファイアウォールが通すか)→ OS のファイアウォール。 設計は「サーバーにパブリック IP を持たせない」から始めます:入口は LB だけ、運用は要塞(Bastion)経由、出口は用途ごとに分けます。
このページは Phase 1、Phase 2、Phase 4、Phase 9 の実験をまとめたものです。OKE 固有のネットワーク(Pod ネットワーク、API エンドポイント)は Kubernetes のページで扱います。
1. まず構成要素を知る¶
OCI が初めてなら、AWS と対応づけて理解するのが近道です。名前は違っても、考え方はほぼ同じです。
| OCI | AWS | ひとことで |
|---|---|---|
| VCN | VPC | 自分専用のプライベート・ネットワーク。最初に大きな CIDR を決める(例:10.4.0.0/16) |
| サブネット(Subnet) | Subnet | VCN を切り分けた範囲。OCI のサブネットはデフォルトでリージョン全体にまたがる(AWS は AZ ごと) |
| パブリック / プライベート・サブネット | 同左 | OCI は prohibit_public_ip_on_vnic で区別:プライベート・サブネットの VM はパブリック IP を持てない |
| ルート表(Route Table) | Route Table | 「どの宛先 → どのゲートウェイに渡すか」。サブネットごとに 1 つ関連付ける |
| インターネット・ゲートウェイ(IGW) | Internet Gateway | Internet と双方向に通信する。パブリック・サブネットだけが使う |
| NAT ゲートウェイ | NAT Gateway | 送信のみ、受信不可:プライベートな VM は外部からダウンロードできるが、外からは接続できない |
| サービス・ゲートウェイ(SGW) | VPC Endpoint | Internet を経由せず、Oracle の内部ネットワーク経由で OCI のサービス(オブジェクト・ストレージ、ADB、dnf リポジトリ…)にアクセスする |
| セキュリティ・リスト(SL) | NACL(ただし OCI はデフォルトで stateful) | サブネット単位のファイアウォール。サブネット内のすべての VM に効く |
| ネットワーク・セキュリティ・グループ(NSG) | Security Group | VNIC(ネットワーク・カード)単位のファイアウォール。その NSG に入れた VM にだけ効く |
| 要塞(Bastion) | SSM Session Manager(近い) | マネージドな踏み台。パブリック IP のない VM に外からログインする |
2. 原理:パケットが通る 3 つの関門¶
flowchart TB
P["パケット"] --> R{"① ルート表<br/>経路はある?"}
R -->|ない| D1["破棄<br/>フロー・ログにも出ない"]
R -->|ある| S{"② セキュリティ・リスト ∪ NSG<br/>どちらかが許可?"}
S -->|どちらも不許可| D2["拒否<br/>フロー・ログ:REJECT"]
S -->|許可| F{"③ OS のファイアウォール<br/>ポートは開いている?"}
F -->|閉じている| D3["拒否"]
F -->|開いている| OK["プロセスに到達"]
① 「届くかどうか」はルーティング、「入れるかどうか」はセキュリティ・ルールが決めます。両者は独立しています。 セキュリティ・ルールをすべて許可しても、経路がなければ通じません。経路があっても、セキュリティ・ルールが許可しなければ通じません。
② セキュリティ・リストと NSG は「和集合」です。 どちらか一方が許可すれば通ります。「両方で許可が必要」ではありません。
- 結果:NSG で細かく絞りたいポートを、SL で広く許可してはいけません。 SL が VCN 全体からの 22 を許可していると、NSG にいくら「要塞だけ許可」と書いても意味がありません
- AWS は逆です:NACL と Security Group は「両方で許可が必要」(積集合)。AWS から来た人が特につまずきやすいポイントです
③ クラウドで許可しても、OS にもう 1 つ関門があります。 Oracle Linux はデフォルトで firewalld が有効なので、NSG で 80 を許可しても、OS 側で開けなければつながりません。
補足のルール 2 つ:
- stateful:OCI のルールはデフォルトで stateful です。入ってきた接続の戻りパケットは自動的に許可され、別にルールを書く必要はありません。ただし「送信 / 受信」はそれぞれのネットワーク・カードの立場で判断されます。例:要塞のプライベート・エンドポイントも VM と同じサブネットにあり、そこから VM:22 への通信はエンドポイントにとって「送信」です。SL の送信ルールでも許可が必要です
- 最長プレフィックス一致:1 つのルート表に
0.0.0.0/0 → NATとOracle サービス → SGWが両方あるとき、Oracle サービス向けの通信はより具体的なほうに一致し、SGW を通ります
3. 出口:3 種類のゲートウェイの選び方¶
| 要件 | 使うもの | 例 |
|---|---|---|
| 外から接続できる必要がある | インターネット・ゲートウェイ(IGW、パブリック・サブネットだけ) | LB を置くサブネット |
| VM が外部からダウンロードしたいが、外から接続されたくない | NAT ゲートウェイ(NAT GW) | pip パッケージのインストール、Docker Hub のイメージ取得 |
| VM が OCI 自身のサービスにだけアクセスする | サービス・ゲートウェイ(SGW) | dnf リポジトリ、ボールト、オブジェクト・ストレージ、ADB、監視エージェント |
# プライベート・サブネットのルート表(Phase 4、簡略化:実際のコードは dynamic ブロックでスイッチに応じて生成):2 つのルールで、それぞれ別のゲートウェイへ
route_rules {
destination = "0.0.0.0/0"
network_entity_id = oci_core_nat_gateway.this[0].id # 一般の Internet → NAT
}
route_rules {
destination = data.oci_core_services.all[0].services[0].cidr_block # "all-kix-services-in-oracle-services-network"
destination_type = "SERVICE_CIDR_BLOCK" # 宛先は「サービス」。IP 範囲は OCI が管理
network_entity_id = oci_core_service_gateway.this[0].id
}
いちばん安全な組み合わせ:サービス・ゲートウェイ(SGW)だけ
Phase 9 のプライベート VM にはサービス・ゲートウェイしかなく、インターネット・ゲートウェイも NAT ゲートウェイもありません。dnf でのインストール、ボールトからのパスワード取得、エージェントの報告はすべて正常ですが、パブリック・インターネットにはまったく出られません。Internet が不要なサーバーには NAT ゲートウェイを付けません。
4. アクセス制御:セキュリティ・リスト(SL)と NSG の役割分担¶
| セキュリティ・リスト | NSG | |
|---|---|---|
| 効く範囲 | サブネット内のすべての VM | その NSG に入れた VM |
| ルールのソース / 宛先 | CIDR だけ | CIDR、または別の NSG |
| 向いているもの | サブネット全体で必要な基本ルール | 「層」(LB 層、app 層)ごとの業務ルール |
このリポジトリのやり方:SL には最低限のものだけを置き、業務ルールはすべて NSG に書き、NSG どうしで参照し合います。
# app 層は「LB 層から」の 80 だけを受け付ける(Phase 4 security.tf)
resource "oci_core_network_security_group_security_rule" "app_http_from_lb" {
network_security_group_id = oci_core_network_security_group.app.id
direction = "INGRESS"
protocol = "6" # TCP
source = oci_core_network_security_group.lb.id # ソースは IP ではなく NSG
source_type = "NETWORK_SECURITY_GROUP"
tcp_options {
destination_port_range {
min = 80
max = 80
}
}
}
ソースを「lb-nsg」にしておけば、LB の IP が変わっても、app VM が増減しても、ルールの修正は不要です。
SL に残すのは次の 2 種類だけです:
- ICMP type 3 code 4(Path MTU Discovery):経路の途中に MTU の小さい区間があるとき、この ICMP で送信元にパケットを小さくするよう知らせます。ブロックすると大きなパケットが黙って捨てられます。OCI のデフォルトの SL にもこのルールがあります
- 送信はすべて許可(送信の制御はルーティングに任せる:NAT を付けなければ外に出られない)
実際にハマった点:SL が VCN 全体からの 22 を許可していた
Phase 4 では当初、SL で「VCN 内 → 22」を許可し、NSG には「22 は要塞からだけ」と書いて、SSH は要塞からしか入れないと考えていました。 和集合なので、実際には VCN 内のどこからでも 22 に接続できました(例:app-1 → app-2)。当時は「要塞からつながる」ことだけを確認し、「ほかからはつながらない」ことを確認しなかったため、気づきませんでした。 現在のコードは SL で 22 を許可せず、SSH は NSG だけで制御するように修正済みです(修正後の再デプロイ検証は未実施)。
5. 運用:パブリック IP のないサーバーにログインするには¶
| 方法 | やり方 | 使った場所 | 評価 |
|---|---|---|---|
| パブリック IP + SSH | SL / NSG でローカル PC の IP からの 22 を許可 | — | 利用中のネットワークが 22 をブロックしていて使えない。そもそも攻撃対象になる |
| Cloud Shell のプライベート・ネットワーク | Cloud Shell を VCN のサブネットに接続し、プライベート IP を得てから直接 SSH | Phase 2、Phase 6 | 手軽。ただし Cloud Shell がサブネットにネットワーク・カードを 1 つ残すので、destroy 前に Public に戻す必要がある |
| OCI 要塞 | Cloud Shell → 要塞(セッション)→ VM:22 | Phase 4、Phase 9 | おすすめ。無料、マネージド(踏み台を自分で保守しない)、セッションに時間制限があり、監査記録も残る |
flowchart TB
CS["Cloud Shell"] -->|"① IAM 権限で<br/>セッション作成<br/>SSH 公開鍵を登録<br/>最長 3 時間"| B["要塞"]
B -->|"② プライベート・<br/>エンドポイント経由<br/>VCN 内のプライベート IP"| VM["VM :22<br/>NSG は<br/>エンドポイントの IP<br/>だけ許可"]
設計のポイント:
- NSG の SSH ソースは IP を直書きせず、要塞の属性を参照します:
source = "${oci_bastion_bastion.this.private_endpoint_ip_address}/32" - セッションは Terraform で管理しません:最長 3 時間で期限切れになるので、管理下に置くと毎回 plan で差分が出ます。使うときに Cloud Shell で作成します
- 要塞の
client_cidr_block_allow_listは0.0.0.0/0にするしかありません:Cloud Shell の送信元 IP が固定されないためです。これは意識したうえでのトレードオフで、セキュリティは 2 つのもので担保します:セッションの作成には IAM 権限が必要、接続にはセッションに登録した秘密鍵が必要
Cloud Shell のパブリック IP を直接許可しないのはなぜ?
Cloud Shell の送信元 IP は Oracle の共用 IP で、しかも変わります。これを許可すると、ほかの OCI ユーザーにも開放することになります。
6. 設計判断の一覧¶
| 判断 | 採用 | 不採用 | 理由 |
|---|---|---|---|
| サーバーの置き場所 | プライベート・サブネット、パブリック IP なし | パブリック・サブネット + パブリック IP | パブリック IP + 開いたポート = 攻撃対象 |
| 外部からの入口 | LB だけ(パブリック・サブネット) | VM を直接公開 | 入口を 1 か所に集約し、VM は増減・入れ替えできる |
| セキュリティ・ルールの書き方 | NSG を層ごとに作り、相互参照 | ルールに IP を書く | IP や台数が変わってもルールの修正が不要 |
| SSH のソース制御 | 要塞の IP を NSG にだけ書く | SL で VCN 全体を許可 | SL と NSG は和集合なので、SL が広いと NSG が無意味になる |
| 運用経路 | OCI 要塞 | 踏み台 VM を自作 | 無料、マネージド、パッチ不要、監査記録あり |
| 出口 | 用途別:NAT + SGW。Internet 不要なら SGW だけ | すべて NAT 経由 | OCI のサービスへは Internet を経由しない。開けるものは最小限に |
| Always Free の ADB へのアクセス | パブリック・エンドポイント + ACL(自分の VCN とローカル PC の IP だけ許可)、VCN 内からは SGW 経由 | プライベート・エンドポイント | Always Free の ADB はプライベート・エンドポイント非対応 |
7. 検証:設計が効いていることをどう証明するか¶
「つながる」ことの確認だけでは足りません。「つながってはいけないものが本当につながらない」ことも実際に試します。
| 証明したいこと | 正しい方法 | 証拠にならないもの |
|---|---|---|
| Internet への送信が NAT 経由 | VM で curl ifconfig.me → NAT ゲートウェイのパブリック IP が表示される |
dnf install の成功(dnf リポジトリは Oracle サービスで、SGW を通る) |
| パブリック・インターネットにまったく出られない | curl -m 5 ifconfig.me → タイムアウト |
フロー・ログに記録があるかどうか(経路のないパケットはフロー・ログに出ない。実験で観察した) |
| ADB へのアクセスが SGW 経由 | ADB で sys_context('USERENV','IP_ADDRESS') を確認 → VM のプライベート IP |
つながったこと(NAT 経由でもつながる) |
| SSH は要塞からしか入れない | 要塞からログインできる + app-1 → app-2:22 がタイムアウトする(当時はこの逆方向のテストをしておらず、問題を見逃した) | 要塞からログインできたことだけ |
| ルールがコードどおり | API / コンソールで実際のルールを見る | plan がエラーにならなかったこと |
8. ハマりどころ¶
| 現象 | 原因 | 対策 |
|---|---|---|
| NSG に「X だけ許可」と書いたのに、ほかからも接続できる | SL と NSG は和集合で、SL の許可が広すぎる | 細かく制御したいポートは SL で許可しない。逆方向のテストをする |
| 要塞のセッションで VM に接続できない | SL が送信 443 しか許可しておらず、エンドポイント → VM:22 はエンドポイントにとって送信 | SL に「サブネット内への 22 の送信」を追加 |
| destroy でサブネットがいつまでも削除されない | Cloud Shell のプライベート・ネットワークのネットワーク・カードがサブネットに残っている | destroy 前に Cloud Shell の Network を Public に戻す。list_private_ips(subnet_id=...) で誰が残っているか調べる |
dnf install が成功したので NAT が通じたと思った |
dnf リポジトリは SGW を通る | curl ifconfig.me で送信元 IP を確認 |
| ローカル PC からの SSH がタイムアウト | 利用中のネットワークが送信の 22 をブロック | Cloud Shell(プライベート・ネットワークまたは要塞)を使う |
9. 確認問題¶
1. VM の SL が 22 を許可し、NSG は 22 を許可していない。VCN 内から SSH できる?
できます。SL と NSG は和集合なので、どちらかが許可すれば通ります。
2. 同じ設定を AWS で(NACL は許可、Security Group は不許可)。接続できる?
できません。AWS の NACL と Security Group は両方で許可が必要です。
3. プライベート・サブネットの VM で dnf install ができた。NAT ゲートウェイが正しく設定されている証拠になる?
なりません。OCI の dnf リポジトリは Oracle サービスなので、SGW があればインストールできます。curl ifconfig.me で送信元 IP が NAT のパブリック IP かどうかを確認します。
4. ボールトからパスワードを取得し、オブジェクト・ストレージにログを書くだけの VM。出口はどう設定する?
SGW だけを付け、NAT と IGW は付けません。OCI のサービスには問題なくアクセスでき、パブリック・インターネットにはまったく出られません。
5. app-nsg の 80 番ポートのソースを、LB の IP ではなく lb-nsg にするのはなぜ?
LB の IP は変わる可能性があり、app VM も増減します。NSG で書けばルールの修正が不要で、ルール自体が「LB 層だけが app 層にアクセスできる」ことを表します。
6. 要塞の client_cidr_block_allow_list が 0.0.0.0/0。安全?
トレードオフです。Cloud Shell の送信元 IP は固定されないため、ソースを絞れません。セッションの作成に IAM 権限が必要、接続にセッションに登録した秘密鍵が必要、セッションは最長 3 時間、という点で安全性を担保しています。クライアントの IP が固定なら絞るべきです。
7. 「SSH は要塞からしか入れない」ことはどう証明する?
両方向を試します:要塞からログインできること(正方向)、VCN 内のほかの VM から 22 への接続がタイムアウトすること(逆方向)。正方向だけでは、SL の許可が広すぎるといった問題に気づけません。
8. ルート表に 0.0.0.0/0 → NAT と Oracle サービス → SGW が両方ある。オブジェクト・ストレージへのアクセスはどちらを通る?
SGW です。ルーティングは最長プレフィックス一致で、Oracle サービスのアドレス範囲のほうが 0.0.0.0/0 より具体的だからです。
コード全文¶
本文には要点だけを抜粋しました。コード全体(Phase 4 の 3 層構成):
04-3tier/modules/network/main.tf(VCN / ゲートウェイ / ルート表 / SL / サブネット)
# ------------------------------------------------------------
# network module:VCN 一式(Phase 4 版)
# VCN ─ インターネット・ゲートウェイ / NAT ゲートウェイ / サービス・ゲートウェイ
# ├─ パブリック・サブネット ─ rt-public (0.0.0.0/0 → IGW)
# └─ プライベート・サブネット ─ rt-private(0.0.0.0/0 → NAT GW、Oracle Services → サービス・ゲートウェイ)
# セキュリティ・リストは全サブネットで 1 つを共用
# 03 との違い:
# - NAT GW / サービス・ゲートウェイを追加(bool のスイッチで、count で作る / 作らないを制御)
# - プライベート・サブネットは VCN のデフォルトのルート表を使わず、自作の rt-private を使う
# ------------------------------------------------------------
locals {
# for_each 用の式:is_public でサブネットを 2 グループに分ける
public_subnets = { for k, s in var.subnets : k => s if s.is_public }
private_subnets = { for k, s in var.subnets : k => s if !s.is_public }
create_nat = var.enable_nat_gateway && length(local.private_subnets) > 0
create_sgw = var.enable_service_gateway && length(local.private_subnets) > 0
}
resource "oci_core_vcn" "this" {
compartment_id = var.compartment_ocid
cidr_blocks = [var.vcn_cidr]
display_name = "${var.name_prefix}-vcn"
dns_label = var.vcn_dns_label
freeform_tags = var.freeform_tags
}
# ---- ゲートウェイ ----
# パブリック・サブネットが 1 つもなければ IGW は不要 → count で 0 個か 1 個かを制御
resource "oci_core_internet_gateway" "this" {
count = length(local.public_subnets) > 0 ? 1 : 0
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.this.id
display_name = "${var.name_prefix}-igw"
enabled = true
freeform_tags = var.freeform_tags
}
# NAT GW:プライベート・サブネットの VM は Internet へ「内から外」にだけ接続できる(外からは接続できない)
# 用途:dnf install、OS の更新など
resource "oci_core_nat_gateway" "this" {
count = local.create_nat ? 1 : 0
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.this.id
display_name = "${var.name_prefix}-natgw"
freeform_tags = var.freeform_tags
}
# サービス・ゲートウェイの接続先:"All <region> Services In Oracle Services Network"
# (もう一方の選択肢はオブジェクト・ストレージだけのもの。ADB にはこの All のほうが必要)
data "oci_core_services" "all" {
count = local.create_sgw ? 1 : 0
filter {
name = "name"
values = ["All .* Services In Oracle Services Network"]
regex = true
}
}
# サービス・ゲートウェイ:Internet を経由せず、Oracle の内部ネットワークで OCI のサービス(ADB、オブジェクト・ストレージなど)に直接アクセス
resource "oci_core_service_gateway" "this" {
count = local.create_sgw ? 1 : 0
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.this.id
display_name = "${var.name_prefix}-sgw"
services {
service_id = data.oci_core_services.all[0].services[0].id
}
freeform_tags = var.freeform_tags
}
# ---- ルート表 ----
resource "oci_core_route_table" "public" {
count = length(local.public_subnets) > 0 ? 1 : 0
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.this.id
display_name = "${var.name_prefix}-rt-public"
route_rules {
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
network_entity_id = oci_core_internet_gateway.this[0].id # count で作ったリソースは [0] で参照
}
freeform_tags = var.freeform_tags
}
# プライベート用のルート表:NAT / サービス・ゲートウェイのうち有効なものの数だけルールを生成
# Oracle Services 向けの通信はより具体的な SGW のルールに一致し、NAT は通らない(最長プレフィックス一致)
resource "oci_core_route_table" "private" {
count = length(local.private_subnets) > 0 ? 1 : 0
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.this.id
display_name = "${var.name_prefix}-rt-private"
dynamic "route_rules" {
for_each = local.create_nat ? [1] : []
content {
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
network_entity_id = oci_core_nat_gateway.this[0].id
}
}
dynamic "route_rules" {
for_each = local.create_sgw ? [1] : []
content {
# 宛先は CIDR ではなく「サービス」(例:all-kix-services-in-oracle-services-network)
destination = data.oci_core_services.all[0].services[0].cidr_block
destination_type = "SERVICE_CIDR_BLOCK"
network_entity_id = oci_core_service_gateway.this[0].id
}
}
freeform_tags = var.freeform_tags
}
# ---- セキュリティ・リスト ----
resource "oci_core_security_list" "this" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.this.id
display_name = "${var.name_prefix}-sl"
egress_security_rules {
destination = "0.0.0.0/0"
protocol = "all"
}
# dynamic ブロック:var.ssh_allowed_cidrs の数だけルールを生成
dynamic "ingress_security_rules" {
for_each = var.ssh_allowed_cidrs
content {
source = ingress_security_rules.value
protocol = "6"
description = "SSH"
tcp_options {
min = 22
max = 22
}
}
}
# ICMP type 3 code 4(Path MTU Discovery)
ingress_security_rules {
source = "0.0.0.0/0"
protocol = "1"
icmp_options {
type = 3
code = 4
}
}
freeform_tags = var.freeform_tags
}
# ---- サブネット ----
# for_each:var.subnets のエントリごとにサブネットを 1 つ作成
# state 上のアドレスは oci_core_subnet.this["public"] のようになる
resource "oci_core_subnet" "this" {
for_each = var.subnets
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.this.id
cidr_block = each.value.cidr
display_name = "${var.name_prefix}-subnet-${each.key}"
dns_label = each.value.dns_label
security_list_ids = [oci_core_security_list.this.id]
route_table_id = each.value.is_public ? oci_core_route_table.public[0].id : oci_core_route_table.private[0].id
prohibit_public_ip_on_vnic = !each.value.is_public
freeform_tags = var.freeform_tags
}
04-3tier/env/security.tf(NSG)
# ------------------------------------------------------------
# ネットワーク・セキュリティ・グループ(NSG):「層」ごとに作成し、同じ層の VM で共用
# lb-nsg :ロード・バランサ(Load Balancer)
# app-nsg:app VM ×2
# ルールの「ソース/宛先」に別の NSG を指定可能(source_type = NETWORK_SECURITY_GROUP)
# → IP を書かずに済み、VM の増減や IP 変更があってもルールの修正は不要
#
# my_ip ──:80──→ [lb-nsg] LB ──:80──→ [app-nsg] VM ×2 ←──:22── 要塞(Bastion)
#
# checkov CKV_OCI_21(ingress ルールに stateless を要求)→ あえて対応せず、各ルールの skip コメントに理由を記載
# stateless = 接続状態を記録しないため、戻りトラフィック用のルールが別途必要(ルール数が倍、設定ミスしやすい)。利点は大量トラフィック時の性能
# ここはトラフィックが小さいため、デフォルトの stateful を使用
# ------------------------------------------------------------
# ---- lb-nsg ----
resource "oci_core_network_security_group" "lb" {
compartment_id = var.compartment_ocid
vcn_id = module.network.vcn_id
display_name = "${local.name_prefix}-lb-nsg"
freeform_tags = local.common_tags
}
# イングレス:ローカル PC の IP から LB の 80 へのアクセスのみ許可
resource "oci_core_network_security_group_security_rule" "lb_http_from_my_ip" {
#checkov:skip=CKV_OCI_21:Low traffic lab; stateful rules are simpler and sufficient
network_security_group_id = oci_core_network_security_group.lb.id
direction = "INGRESS"
protocol = "6"
source = "${var.my_ip}/32"
source_type = "CIDR_BLOCK"
description = "HTTP from my IP"
tcp_options {
destination_port_range {
min = 80
max = 80
}
}
}
# エグレス:LB → app VM の 80(リクエスト転送 + ヘルスチェック)
# サブネット(Subnet)のセキュリティ・リスト(Security List)で全エグレスを許可済みのため、このルールがなくても通信は可能。NSG 単体で「LB は app にのみ送信」と示すために明記
resource "oci_core_network_security_group_security_rule" "lb_http_to_app" {
network_security_group_id = oci_core_network_security_group.lb.id
direction = "EGRESS"
protocol = "6"
destination = oci_core_network_security_group.app.id
destination_type = "NETWORK_SECURITY_GROUP"
description = "HTTP to app"
tcp_options {
destination_port_range {
min = 80
max = 80
}
}
}
# ---- app-nsg ----
resource "oci_core_network_security_group" "app" {
compartment_id = var.compartment_ocid
vcn_id = module.network.vcn_id
display_name = "${local.name_prefix}-app-nsg"
freeform_tags = local.common_tags
}
# SSH は要塞からの接続のみ受け付ける
# 要塞は app サブネット内にプライベート・エンドポイント(Private Endpoint、プライベート IP)を持ち、SSH はこの IP から入ってくる
resource "oci_core_network_security_group_security_rule" "app_ssh_from_bastion" {
#checkov:skip=CKV_OCI_21:Low traffic lab; stateful rules are simpler and sufficient
network_security_group_id = oci_core_network_security_group.app.id
direction = "INGRESS"
protocol = "6"
source = "${oci_bastion_bastion.this.private_endpoint_ip_address}/32"
source_type = "CIDR_BLOCK"
description = "SSH from Bastion"
tcp_options {
destination_port_range {
min = 22
max = 22
}
}
}
# HTTP は LB からの接続のみ受け付ける(ソースは IP ではなく lb-nsg を指定)
# app VM 同士、要塞 → VM の 80 は引き続き通信不可
resource "oci_core_network_security_group_security_rule" "app_http_from_lb" {
#checkov:skip=CKV_OCI_21:Low traffic lab; stateful rules are simpler and sufficient
network_security_group_id = oci_core_network_security_group.app.id
direction = "INGRESS"
protocol = "6"
source = oci_core_network_security_group.lb.id
source_type = "NETWORK_SECURITY_GROUP"
description = "HTTP from LB"
tcp_options {
destination_port_range {
min = 80
max = 80
}
}
}
04-3tier/env/bastion.tf
# ------------------------------------------------------------
# OCI 要塞(Bastion)サービス(Always Free)
# 踏み台は OCI がマネージド。VM の作成やパッチ適用は不要
# Terraform で作るのは要塞本体だけ。セッション(有効期間は最長 3 時間)は使うたびにコンソール / CLI で作成
# → セッションを Terraform で管理すると、期限切れのたびに plan で差分が出る
# ------------------------------------------------------------
resource "oci_bastion_bastion" "this" {
compartment_id = var.compartment_ocid
bastion_type = "STANDARD"
name = "${local.name_prefix}bastion" # 英数字のみ(- は使えない)
target_subnet_id = module.network.subnet_ids["app"]
# 要塞への接続を許可するソース
# 利用中のネットワークが 22 をブロック → 実際は Cloud Shell から接続。Cloud Shell のパブリック IP は固定されない → 0.0.0.0/0 にするしかない
# セキュリティはセッションで担保:セッションの作成には IAM 権限が必要で、セッション作成時に登録した SSH 公開鍵しか受け付けない
client_cidr_block_allow_list = ["0.0.0.0/0"]
max_session_ttl_in_seconds = 10800 # 3 時間(上限)
freeform_tags = local.common_tags
}