本文へスキップ
← Posts

Oracle Cloud E2.MicroとA1のセキュリティ対策

2026-06-03

E2.MicroにMFA・改ざん検知を導入し、A1をパブリックIPなし・Cloudflare Tunnel経由

Oracle Cloud E2.MicroとA1のセキュリティ対策:踏み台構成とCloudflare Tunnel

この日やろうとしたこと

A1インスタンスをようやく手に入れた翌日。 せっかく取れたのに、セキュリティが甘いままでは意味がない。

今日のゴールはこの構成を完成させることだ。

インターネット

Cloudflare Tunnel(Webアクセス)

SSH

Mac

E2.Micro(MFA・踏み台)SSH_PORT

A1(パブリックIPなし・プライベートIPのみ)SSH_PORT

A1はパブリックIPを持たず、E2.Micro経由のSSHとCloudflare Tunnel経由のWebアクセスのみを受け付ける。外から直接触れないサーバーにする。

Note

本記事では、実際のSSHポート番号やドメイン名などの環境固有値はすべてプレースホルダ(SSH_PORT など)に置き換えている。

Note

無料枠でここまでやる必要があるのかと思う人もいるかもしれない。でも学習も兼ねて妥協せずやってみた。


E2.Microのセキュリティ強化

SSHポートの変更

デフォルトの22番ポートは攻撃の標的になりやすい。予測困難な番号に変更する。本記事ではこの番号を SSH_PORT と表記する。

先にOCIのセキュリティリストで新しいポートを開ける。この順番を守らないと閉め出される。

ネットワーキング仮想クラウド・ネットワーク → セキュリティリスト → イングレス・ルールの追加

  • ソースCIDR: 0.0.0.0/0
  • プロトコル: TCP
  • 宛先ポート: SSH_PORT(予測困難な任意の番号)

sshd_configを編集する。

sudo vim /etc/ssh/sshd_config
Port SSH_PORT
PermitRootLogin no
PasswordAuthentication no

iptablesにも新しいポートを追加する。

sudo iptables -I INPUT 5 -p tcp --dport SSH_PORT -j ACCEPT
sudo apt install iptables-persistent -y
sudo netfilter-persistent save

古い22番を削除する。

sudo iptables -D INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT
sudo netfilter-persistent save

別のターミナルで新しいポートへの接続を確認してからsshdを再起動すること。

sudo systemctl restart sshd

Note

ポートを変えてsshd再起動した後、別ターミナルで繋がるかを確認する。これ大事。

Google Authenticator MFAの導入

秘密鍵だけでは鍵が漏れた場合にリスクがある。ワンタイムパスワードを追加する。

sudo apt install libpam-google-authenticator -y
google-authenticator

設定時の選択肢はすべてyで問題ない(時刻ズレの許容範囲はnを推奨)。

QRコードが表示されたらスマホの認証アプリでスキャンする。

PAMの設定を編集する。

sudo vim /etc/pam.d/sshd

先頭に追加する。

auth required pam_google_authenticator.so

@include common-auth をコメントアウトする(パスワード入力を求めないようにするため)。

#@include common-auth

sshd_configに以下を追加する。

sudo vim /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

sshdを再起動する。

sudo systemctl restart sshd

Note

MFAは入れて少し安心できるが、毎回のログインが苦痛

AIDE(ファイル改ざん検知)の導入

sudo apt install aide -y
sudo aideinit
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

初期化にかなり時間がかかる。待つしかない。

cronで毎日チェックを実行する。

crontab -e
# 以下を追加
0 3 * * * sudo aide --check >> ~/aide_check.log 2>&1

lynis(セキュリティ監査)の導入

sudo apt install lynis -y

cronで毎月1日に監査を実行する。

crontab -e
# 以下を追加
0 4 1 * * sudo lynis audit system >> ~/lynis_audit.log 2>&1

タイムゾーンをJSTに変更

sudo timedatectl set-timezone Asia/Tokyo
timedatectl

cronのスケジュールが日本時間で動くようになる。設定後に確認を忘れずに。

IPアドレスの固定化

パブリックIPが変わるとAutonomous DatabaseのIP制限が機能しなくなる。予約済みIPに変換する。

コンピュートインスタンス → E2をクリック → アタッチされたVNICIP管理 → エフェメラルIPの・・・メニュー → 予約済みパブリックIPに変換

Note

OCIではインスタンスにアタッチされている間は予約済みIPも無料だ。これはOCIの数少ない太っ腹なポイントだと思う。


A1のセキュリティ強化

E2.Microを踏み台とした鍵ペアの設定

A1専用の鍵ペアをE2上で生成する。これによりA1の秘密鍵はE2の中にのみ存在する状態になる。

# E2上で実行
ssh-keygen -t ed25519 -f ~/.ssh/a1_key -N ""
cat ~/.ssh/a1_key.pub

表示された公開鍵をA1のauthorized_keysに追加する。

# A1上で実行
echo "E2の公開鍵" >> ~/.ssh/authorized_keys

E2から接続できることを確認する。

# E2上で実行
ssh -i ~/.ssh/a1_key -p SSH_PORT ubuntu@<A1のプライベートIP>

E2の~/.bashrcにエイリアスを追加しておくと便利だ。A1のプライベートIPと接続情報をE2側に閉じ込めることで、Mac側にはA1への直接的な到達情報を一切残さない。

echo 'alias ssh-a1="ssh -i ~/.ssh/a1_key -p SSH_PORT ubuntu@<A1のプライベートIP>"' >> ~/.bashrc
source ~/.bashrc

Note

少し過剰かもしれないが、こういう設計の積み重ねが大事だと思っている。そしていつか漏洩

SSHポートの変更

E2と同様にデフォルトから変更する。

OCIのセキュリティリストはE2と共通のため追加不要だ。iptablesに追加する。

sudo iptables -I INPUT 5 -p tcp --dport SSH_PORT -j ACCEPT
sudo apt install iptables-persistent -y
sudo netfilter-persistent save

iptablesでE2のIPのみSSH許可

sudo iptables -A INPUT -p tcp --dport SSH_PORT -s <E2のIP> -j ACCEPT
sudo iptables -A INPUT -p tcp --dport SSH_PORT -j DROP
sudo netfilter-persistent save

これでA1へのSSHはE2のIPからのみアクセス可能になる。

Cloudflare Tunnelの設定

A1のパブリックIPを削除する前にTunnelを設定する。順序を間違えると入れなくなる。

cloudflaredをインストールする。A1はARMアーキテクチャのためarm64版を使う。

curl -L --output cloudflared.deb \
  https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
sudo dpkg -i cloudflared.deb

Cloudflareにログインする。

cloudflared tunnel login

表示されたURLをブラウザで開いてドメインを選択する。

Tunnelを作成する。

cloudflared tunnel create my-tunnel

設定ファイルを作成する。

mkdir -p ~/.cloudflared
vim ~/.cloudflared/config.yml
tunnel: <TunnelのID>
credentials-file: /home/ubuntu/.cloudflared/<TunnelのID>.json

ingress:
  - hostname: app.example.com
    service: http://localhost:80
  - service: http_status:404

DNSレコードを追加する。

cloudflared tunnel route dns my-tunnel app.example.com

サービスとして登録して常駐させる。

sudo cloudflared --config /home/ubuntu/.cloudflared/config.yml service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared

Active: active (running) になれば成功だ。

A1のパブリックIPを削除

Cloudflare Tunnelが正常に動作していることを確認してから削除する。

コンピュートインスタンス → A1をクリック → アタッチされたVNICIP管理 → パブリックIPの削除

Note

パブリックIPを削除した瞬間、A1との接続が切れた。オワタ


ネットワーク・セキュリティ・グループ(NSG)について

E2とA1が同じVCN・サブネットにいる場合、セキュリティリストは共有される。 NSGを使えばインスタンスごとに異なるルールを適用できるが、同一サブネット内では期待通りに動作しないケースがある。

細かいIP制限はNSGではなくiptablesで行う方が確実だ。

Note

NSGを設定してセキュリティリストからSSHポートを削除したら、E2にもA1にも入れなくなった。セッションが生きていたから復旧できたが、正直かなり焦った


OCI Vaultで機密情報を管理する

スクリプトに平文でOCIDやパスワードを書くのはリスクがある。OCI Vaultを使って暗号化して管理する。

アイデンティティとセキュリティボールトボールトの作成

  • タイプ: デフォルト(仮想プライベートは有料)

マスター暗号化キーを作成する。

  • アルゴリズム: AES
  • キー長: 256
  • 保護モード: HSM

シークレットにJSON形式で機密情報をまとめて登録する。

{
  "compartment_id": "<コンパートメントOCID>",
  "subnet_id": "<サブネットOCID>",
  "image_id": "<イメージOCID>",
  "availability_domain": "<AVAILABILITY_DOMAIN>",
  "ssh_public_key": "ssh-ed25519 AAAA..."
}

Note

Always Free枠でマスター暗号化キーは20個まで無料。JSONで複数の値をまとめれば1シークレットで複数の情報を管理もいける


この日を終えて

インターネット

Cloudflare Tunnel

A1(パブリックIPなし・プライベートサブネット)
├── Nginx(80番・リバースプロキシ)
├── PostgreSQL
├── AIDE(毎日改ざん検知)
├── lynis(毎月セキュリティ監査)
└── cloudflared(常駐サービス)

SSH

Mac → E2.Micro(MFA)→ A1(プライベートIPのみ)

全部設定し終えた後、ブラウザでサブドメインにアクセスしてNginxのデフォルトページが表示された時、初めてこの構成が「本物」になった気がした。

Note

無料枠でここまでできるとは正直思っていなかった。OracleはA1を取るまでが異常に大変だが、取れた後の自由度は高い。


参考リンク