3-1. サーバーハードニング - 新規VPSを本番運用に耐える状態にする

所要時間: 40-60分 ゴール: 新規VPSを30分で「攻撃に耐えうる」状態に持ち上げる手順を身につける コミット内容: ハードニングスクリプト harden.sh と SSH 設定ファイルを ~/learn/security/day31/ にコミット


前章とのつながり

このレッスンの位置づけ

Level 1 / Level 2 は アプリ層 の話だった。Level 3 は インフラ層・運用層。最初の本章はその土台、サーバー自体を堅牢化する技術。

  • 2-6 認可設計 までで「アプリのコードレベル」を守った
  • 本章: アプリが乗る OS とネットワーク を守る
  • 次章 3-2 依存スキャン: アプリの 依存パッケージ を守る(同じ「外側を守る」テーマ)
  • 関連: インフラ 3-1 VPS構築 とセットで読むと最強

実害シーン: ハードニング不足の代償

  • 新規VPS立てて1週間放置: SSH ブルートフォースで侵入 → 仮想通貨マイニング配備 → 電気代爆発 + 踏み台にされて他人を攻撃
  • AWS のセキュリティグループを 0.0.0.0/0 で開放: 数分後には毎秒何十回のスキャン、数日で侵入事例多数
  • MongoDB / Redis をパスワード無しでネット公開: Shodan で検索され、ランサムウェアで全データ暗号化 → 「データを返してほしければ BTC を送れ」

「個人サーバーだから狙われない」は 完全な誤解。bot は IPv4 空間を全スキャンしている。全員が常時、自動で狙われている


大前提: なぜサーバーハードニングを学ぶか

クラウド上で apt install nginx してパブリックIPを開けた瞬間から、サーバーは24時間365日攻撃され続ける。これは比喩ではない。新規VPSを立てて何もしないと、数分以内に SSH ポートに対するブルートフォース、HTTP に対するパス探索(/wp-admin/.env/phpmyadmin)が始まる。

実際に AWS、GCP、DigitalOcean などで何もしないインスタンスを立てて tail -f /var/log/auth.log を眺めると、毎秒何十件もの SSH 試行ログが流れる。これは bot ネットワークが IPv4 空間を全スキャンしているため。狙われているのではなく、全員が常時狙われている

ハードニング(hardening)とは、サーバーを「守るために硬く(hard)する」作業のこと。具体的には:

  1. 入り口を絞る(SSH、ファイアウォール)
  2. 権限を絞る(root禁止、最小権限)
  3. 動いているものを減らす(不要サービス停止)
  4. 見張りを置く(fail2ban、監査ログ)
  5. 常に新しくする(自動セキュリティ更新)

これを怠ると、1週間以内に踏み台にされる。仮想通貨マイニング、SPAM配信、DDoS加担、もっと深刻なら個人情報・顧客データの漏洩。全部「あなたの責任」になる(クラウド事業者は「セキュリティの共有責任モデル」でユーザー側責任にしている)。

このレッスンは インフラ Level3-1: VPS構築 とセットで読むと最強。VPS の物理面・課金面が向こう、攻撃面の防御がこっち。


セッション①: SSH と最小権限(25-30分)

1. root 直接ログインの禁止 - 最初の一手

VPS プロバイダーから「root のパスワード」がメールで送られてくる時代がまだある。これを放置するのが最悪のスタート

# まず一般ユーザーを作成
adduser deploy
# パスワードを設定(強力なものを)
 
# sudo グループに追加(Debian/Ubuntu系)
usermod -aG sudo deploy
 
# RHEL/CentOS系なら wheel
# usermod -aG wheel deploy
 
# 動作確認: 一旦ログアウトして deploy で再ログインし、sudo が効くか
exit
ssh deploy@<server>
sudo whoami  # → root と表示されればOK

なぜ root ログインを禁止するか

root は 全権限を持つ唯一のアカウント。これでログインを許す = ブルートフォースで当てられた瞬間に全敗。

一般ユーザー + sudo の構成にすると:

  1. 攻撃面が「2段階」になる: まず一般ユーザーのパスワードを破り、次に sudo のパスワードも要る
  2. 誰が何をしたか追跡できる: auth.log に「deploy が sudo で何を実行したか」が残る。root 共有だと「誰の操作か」が消える
  3. 権限の最小化: 普段の作業は一般ユーザー、危ない時だけ sudo。手が滑って rm -rf / しても被害が限定されることがある

設計思想: Unix の権限モデルは1970年代に「マルチユーザーで使う前提」で作られた。root は「神」、一般ユーザーは「凡人」。神を日常使いしない、という思想。

実務ユースケース

  • 新規 VPS の初期セットアップ: root メールが来た → 一般ユーザー作成 → root を封印、までを30分以内に
  • チーム開発のサーバー: 各メンバーごとにアカウント。auth.log で誰がいつ来たか追跡可能
  • 退職者の管理: 個別アカウントなら userdel 1発で締め出せる。root 共有だとパスワード変更後の周知地獄

アンチパターン: root のまま運用

# NG: root でログインし続ける
ssh root@<server>
# 全ての作業を root で実行
apt install nginx
vim /etc/nginx/nginx.conf
systemctl restart nginx

なぜNGか:

  • SSH パスワードが破られた瞬間にサーバー乗っ取り完了
  • 操作ログが全部 root 名義。インシデント発生時、誰の操作か特定不能
  • 手が滑った時の被害が無制限(rm -rf /var を root でやると本当に消える)
  • 多くのクラウドプロバイダのコンプライアンス監査で即指摘される

2. SSH を公開鍵オンリーにする

パスワード認証は廃止する。これがハードニングの第二歩。

# ローカルマシンで鍵を生成(まだなければ)
ssh-keygen -t ed25519 -C "deploy@my-vps" -f ~/.ssh/id_ed25519_deploy
 
# 公開鍵をサーバーへ転送
ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub deploy@<server>
 
# サーバー側で確認
ssh deploy@<server>
ls -la ~/.ssh/
# -rw------- 1 deploy deploy  ... authorized_keys  ← 600 になっているはず

サーバー側で /etc/ssh/sshd_config を編集:

sudo vim /etc/ssh/sshd_config

最低限の設定:

# パスワード認証を完全に禁止
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
KbdInteractiveAuthentication no
 
# 公開鍵認証のみ
PubkeyAuthentication yes
 
# root 直接ログイン禁止
PermitRootLogin no
 
# 空パスワード禁止(念のため)
PermitEmptyPasswords no
 
# X11転送など不要機能オフ
X11Forwarding no
AllowAgentForwarding no
 
# プロトコル2のみ(古いSSHv1は脆弱)
Protocol 2
 
# ログレベル
LogLevel VERBOSE
 
# ログイン試行制限
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30

設定変更後、必ず別ターミナルで先に動作確認してから既存セッションを閉じる:

# サービス再起動
sudo systemctl restart sshd
 
# 別ターミナルで接続テスト(既存セッションは閉じない)
ssh deploy@<server>
# 入れたらOK

公開鍵認証の本質

パスワード認証 = 「秘密の合言葉」を毎回送る → 通信路で盗聴される可能性、ブルートフォースで総当たり可能。

公開鍵認証 = サーバーが「ランダムな数値」を送り、クライアントが秘密鍵で署名して返す → 秘密鍵自体は通信路を流れない。署名検証は公開鍵で行う。

強度の桁違い: 12文字のパスワードは数兆通り → 強いGPUで数日で総当たり可能。ED25519 鍵は 2^256 通り → 宇宙が終わるまで総当たり不可能。

追加の利点:

  • パスワード入力不要(鍵がディスクにあるだけ)
  • 鍵を持ってない人は絶対に入れない(パスワードと違って「推測」できない)
  • 1つの鍵で複数サーバーへログイン可能

鍵アルゴリズムの選び方

  • ED25519 推奨: 新しい、速い、強い。ssh-keygen -t ed25519
  • RSA 4096: 古いシステム互換性が必要な時。ssh-keygen -t rsa -b 4096
  • DSA / RSA 1024: 使うな(脆弱)
  • ECDSA: NIST曲線への信頼問題があり ED25519 が好まれる

なぜ ED25519 がいいか: 鍵が短い(68文字)、署名検証が高速、サイドチャネル攻撃に強い。Daniel J. Bernstein らによる設計で、設計の各ステップが論文で公開されている透明性。

鍵管理の落とし穴

  • 秘密鍵をリポジトリにコミットする事故: GitHub に SSH 秘密鍵を pushしてしまう。爆弾。git secrets などのフックで防ぐ
  • 鍵にパスフレーズを付けない: ノートPCを盗まれた瞬間に全サーバー突破される。ssh-keygen 時に必ずパスフレーズを設定。日常運用は ssh-agent でキャッシュ
  • authorized_keys の権限: ファイルが他人に読み書き可能だと SSH が認証を拒否する。chmod 600 ~/.ssh/authorized_keyschmod 700 ~/.ssh
  • 古い鍵の野放し: 退職者の公開鍵が authorized_keys に残っていると永遠に入れる。退職時の棚卸し必須

アンチパターン: パスワード認証放置

# NG: デフォルトのまま
# PasswordAuthentication yes

なぜNGか: 新規VPSを立てて1時間放置すると sshd: Failed password for root from 116.x.x.x のようなログが毎秒数十件流れる。これは中国・ロシア・ベトナムなどのbotネットからの SSH ブルートフォース。パスワードが弱ければ数時間〜数日で破られる。実際に「Password123!」のような一見強そうなパスワードも辞書攻撃で1秒で破られる。

3. SSH ポート変更の議論 - 22 のままでいいか

「SSHポートを 22 → 2222 などに変更すべきか」は宗教論争レベルでよく揉める。

# ポート変更例
Port 2222

ポート変更の効果と限界

効果(あるもの):

  • ログ量が激減する: 22 番に来る無差別 bot ブルートフォースが消える(auth.log を読みやすくなる)
  • しょぼい bot を弾ける: ポート 22 だけスキャンする雑な攻撃ツールは届かない

効果がないもの(重要):

  • 本気の攻撃者には無意味: nmap -p- <ip> で全ポートスキャンされれば SSH ポートは数秒でバレる
  • 「Security through obscurity」: 隠すこと自体は防御にならないという原則

結論: 「やる意味はあるが、これだけに頼るな」。鍵認証 + fail2ban + ufw が本丸で、ポート変更は補助。

落とし穴: ポート変更すると ufw allow 22 のような既存ルール、CI/CD の SSH 設定、git の remote URL、.ssh/config など全部を変更する必要がある。手間と効果のバランス次第。

~/.ssh/config に書いておくと楽

Host my-vps
    HostName 198.51.100.10
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_deploy
    IdentitiesOnly yes

これで ssh my-vps だけで繋がる。チームで共有する場合は .ssh/config.d/ に分ける。


セッション②: 自動防御とファイアウォール(25-30分)

4. fail2ban - ログ監視で自動BAN

公開鍵オンリーでも、攻撃者は試し続ける(無駄に CPU と帯域を食う)。fail2ban は ログを監視して、攻撃パターンを検知したら IP を自動で iptables/ufw に banルール追加する古典かつ強力なツール。

# インストール
sudo apt install fail2ban
 
# 設定ファイル(.local が編集用、.conf は触らない)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo vim /etc/fail2ban/jail.local

最低限の設定例:

[DEFAULT]
# Ban期間(秒)。1h = 1時間、-1 = 永久
bantime = 1h
# 検知期間(この時間内に maxretry 回失敗で ban)
findtime = 10m
# 失敗回数の閾値
maxretry = 5
# 自分の IP は ban しない
ignoreip = 127.0.0.1/8 ::1 203.0.113.5
 
[sshd]
enabled = true
port    = 22
filter  = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime  = 24h
 
[nginx-http-auth]
enabled = true
 
[nginx-noscript]
enabled = true

起動と確認:

sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban
 
# 状況確認
sudo fail2ban-client status
sudo fail2ban-client status sshd
 
# 現在 ban されている IP
sudo fail2ban-client status sshd | grep "Banned IP"
 
# 手動で ban / unban
sudo fail2ban-client set sshd banip 198.51.100.100
sudo fail2ban-client set sshd unbanip 198.51.100.100

fail2ban の本質

ログファイルを tail -f のように監視し、正規表現にマッチした行(=攻撃の兆候)をカウント。閾値超えたら iptables にDROPルールを追加する仕組み。

構成要素:

  • Filter: ログをマッチする正規表現(/etc/fail2ban/filter.d/sshd.conf など)
  • Jail: 監視対象とアクションの組み合わせ(どのログをどの条件で見て、超えたらどうするか)
  • Action: ban方法(iptables/ufw/firewalld どれを使うか)

守備範囲:

  • SSH ブルートフォース
  • nginx/apache の認証失敗連発
  • WordPress 管理画面への攻撃
  • 自前アプリのログにも対応可能(filter を書けば)

実務ユースケース

  • 新規 VPS 起動直後: SSH ブルートフォースを1日数千〜数万件受ける状況を fail2ban で自動さばき
  • WAF 持ってない小規模サイト: nginx-noscript で /wp-admin /phpmyadmin を叩く bot を ban
  • API レート制限の前段: 雑な攻撃を fail2ban で削ぎ落とし、アプリ層は本質的な処理に集中
  • メール/FTP サーバー: postfix, dovecot, vsftpd など、ログイン処理がある全てに適用可能

fail2ban の落とし穴

  • 自分の IP を ban する事故: パスワード忘れて何回も打ち間違えると自分が ban される。ignoreip に自分の IP(または会社の固定 IP)を追加。VPS のレスキューコンソールで救出可能だが面倒
  • ログフォーマット変更で動かなくなる: nginx などアップグレードでログ形式が変わると filter の正規表現にマッチせず無防備に。fail2ban-regex でテスト可能
  • CloudFlare などプロキシ経由: 全てのリクエストが CDN の IP から来るので fail2ban が CDN を ban する事故。real_ip 設定 + X-Forwarded-For から本物のIPを取り出す
  • 過信は禁物: fail2ban は「雑な攻撃」を弾くだけ。分散攻撃(DDoS)や1回でやられる0day には無力

5. ufw でファイアウォール最小開放

「不要なポートを閉じる」=「攻撃面(attack surface)を最小化する」。ufw(uncomplicated firewall)は iptables のフロントエンドで、覚えやすい。

# 状態確認
sudo ufw status verbose
 
# 初期化(既存ルール全部消す。本番では慎重に)
sudo ufw --force reset
 
# デフォルトポリシー: 受信は全部拒否、送信は全部許可
sudo ufw default deny incoming
sudo ufw default allow outgoing
 
# SSH を許可(ポート変更してたら 2222)
sudo ufw allow 22/tcp
 
# Web サーバー
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
 
# 特定IPからのみアクセス許可(管理画面とか)
sudo ufw allow from 203.0.113.5 to any port 3306
 
# 有効化
sudo ufw enable
 
# 確認
sudo ufw status numbered

ファイアウォールの本質

ファイアウォール = OSのネットワークスタックの入り口で、パケットを通すか落とすか判定する仕組み。Linux カーネルの netfilter という機能を iptables/nftables 経由で操作する。ufw はその上のラッパー。

「デフォルト拒否」が鉄則: 「許可リスト」で運用する。「拒否リスト」だと「予想外のサービス」を漏らす。

例: いつの間にか Redis が外向きに :6379 で listen していて、認証なしだったら丸見え(実際にあった事故が無数にある)。デフォルト拒否なら ufw が遮断してくれる。

実務ユースケース

  • 本番 Web サーバー: 22 + 80 + 443 のみ開ける。それ以外は全部拒否
  • データベースサーバー: アプリサーバーの IP からのみ 3306/5432 を許可、それ以外拒否
  • 管理画面: VPN 経由 or オフィスの固定 IP からのみ許可
  • 本番デプロイ後の検証: nmap -p 1-65535 <server> で外から見えるポートを確認

アンチパターン: 全ポート開放

# NG: 「とりあえず全部開けて動かす」
sudo ufw disable

なぜNGか:

  • 開発中に立てた一時的なサービス(Redis, MongoDB の認証なし起動など)が外から丸見え
  • Shodan という検索エンジンで「世界中の開放ポート」が検索可能 → 1日以内に侵入される
  • Redis の認証なし起動を放置 → マイニングスクリプトを RCE で仕込まれる事例は毎週起きている
  • 「あとで閉じる」は来ない。最初から閉めておく

ufw の落とし穴

  • enable する前に SSH を許可していないと、自分が締め出される: ufw allow 22先に実行
  • Docker との相性: Docker は iptables を直接いじるため ufw のルールを無視することがある。/etc/docker/daemon.jsoniptables: false にして手動管理 or DOCKER-USER チェーンを使う
  • ルール順序: 上から評価される。allow from 1.2.3.4 の前に deny all があると意味なし
  • IPv6 を忘れる: /etc/default/ufwIPV6=yes を確認。IPv6 が無防備だと意味がない

6. 不要サービスの停止

サーバーで動いている全プロセスが攻撃面。「使ってないなら止める」。

# 動いているサービス一覧
systemctl list-units --type=service --state=running
 
# 自動起動しているサービス
systemctl list-unit-files --state=enabled
 
# 不要そうなものの例
sudo systemctl disable --now cups          # プリンタ
sudo systemctl disable --now bluetooth     # ブルートゥース
sudo systemctl disable --now avahi-daemon  # mDNS
sudo systemctl disable --now ModemManager  # モデム
 
# 残すべき例: sshd, systemd-*, networkd, cron, fail2ban

なぜ「動いてるものを減らす」のが防御になるか

全てのサービスは潜在的な脆弱性。CUPS(プリンタ印刷サービス)に過去 RCE 脆弱性が見つかった例もある(CVE-2024-47176 など)。「使ってないのに動いている」サービスから侵入される事例は無数。

守るべきは「動かしているものだけ」。動かしてないものは攻撃者にも届かない。最強の防御は「存在しないこと」。

ポート listening の確認

sudo ss -tlnp     # listening ポート一覧(PID付き)
sudo lsof -i -P -n | grep LISTEN  # 同様(lsof 使い)

「あれ、これ何の listen?」を全部潰す。説明できないものは止める。

7. 自動セキュリティ更新

「最新にしておく」のは最も基本的かつ最も効く防御。

# Debian/Ubuntu
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
 
# 設定確認
cat /etc/apt/apt.conf.d/50unattended-upgrades
 
# 有効化確認
sudo systemctl status unattended-upgrades

/etc/apt/apt.conf.d/50unattended-upgrades の主要項目:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
};
 
Unattended-Upgrade::Automatic-Reboot "false";    // 自動再起動するか
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
Unattended-Upgrade::Mail "alerts@example.com";

自動更新の効果と注意

効果:

  • CVE 公開当日にパッチが当たる事例も。手動だと「気づくのに数週間」も普通
  • 多くのインシデントは「既知の脆弱性で、パッチが既にあった」もの。Equifax 漏洩(2017、Apache Struts 未パッチ)はその典型

注意:

  • 再起動が必要なカーネル更新 → 自動再起動を ON にする勇気、OFF にする保守性、トレードオフ
  • 本番で稀にパッケージ更新で破綻: 動作影響あるサービスは「セキュリティのみ自動、機能更新は手動」設定
  • 大規模環境では Ansible 等で計画的に: 数百台のサーバーが一斉に再起動するとサービス停止

アンチパターン: 更新放置

# NG: 「動いているから触らない」
# 1年前にセットアップしたまま、apt update を一度も走らせていない

なぜNGか: 公開済み脆弱性が累積する。Heartbleed、Shellshock、Log4Shell など、未パッチのまま放置するとほぼ確実に被害に遭う。「動いているから触らない」は短期的安定と長期的破綻のトレードオフで、長期側に倒れている


セッション③: 監査ログとファイル権限(10-15分)

8. 監査ログを読む癖

# 認証関連
sudo tail -f /var/log/auth.log
 
# SSH 関連だけ
sudo journalctl -u sshd -f
 
# 最近の全ログイン履歴
last
lastlog       # 各ユーザーの最終ログイン
 
# 失敗したログイン
sudo lastb
 
# 今ログインしているユーザー
who
w

ログを「読む癖」がセキュリティの基本

サーバーで何が起きているかはログにしか書いていない。週1で auth.log を眺めるだけで:

  • 知らない IP からの SSH 試行が増えていないか
  • sudo 実行が普段と違わないか
  • rsyslog が止まっていないか(ログ自体が消されている可能性)

見るべきポイント:

  • Failed password の連発 → fail2ban が機能しているか
  • Accepted publickey の IP が見覚えあるか
  • sudo の実行時刻が業務時間外でないか

怪しいログの見方

May 14 03:14:22 vps sshd[12345]: Failed password for invalid user admin from 116.x.x.x port 54321 ssh2
May 14 03:14:25 vps sshd[12346]: Failed password for invalid user root from 116.x.x.x port 54323 ssh2
May 14 03:14:28 vps sshd[12347]: Failed password for invalid user postgres from 116.x.x.x port 54325 ssh2

同じIPから3秒間隔で別ユーザー名で総当たり = bot。fail2ban が動いていれば次の5回目あたりで ban される。

9. ファイルシステム権限の最小化

# 重要ファイルの権限確認
ls -la /etc/shadow      # 600 (root のみ読み書き)
ls -la /etc/passwd      # 644 (誰でも読める。これは正常)
ls -la /etc/sudoers     # 440
ls -la ~/.ssh/          # 700
ls -la ~/.ssh/authorized_keys  # 600
ls -la ~/.ssh/id_ed25519       # 600 (秘密鍵)
# 修正例
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub

なぜ SSH 鍵は 600 でないと動かないか

SSH デーモンは「秘密鍵が他人に読まれる状況」を検知すると認証自体を拒否する。

Permissions 0644 for '/home/deploy/.ssh/id_ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.

これは「Group/Other に1ビットでも見せたら鍵を信頼しない」という設計。ユーザーが間違えた時にも安全側に倒すためのガード。優秀な防御例。

よくある権限事故

  • /etc/shadow を 644 にしてしまう: パスワードハッシュが世界中から見える。クラックに数日
  • ~/.ssh/authorized_keys の権限ゆるすぎ: 他人が公開鍵を追加できる = いつでも入れる
  • rsync で -p 忘れ: コピー先の権限がデフォルトになり、機密ファイルが緩い権限に
  • umask がゆるい: 新規作成ファイルが 666 で作られる。/etc/profileumask 077umask 022 を確認

10. AppArmor / SELinux の入口

「プログラムごとに、できることを縛る」仕組み。

# AppArmor(Ubuntu系)の状態確認
sudo aa-status
 
# SELinux(RHEL系)の状態
getenforce      # Enforcing / Permissive / Disabled
sestatus

AppArmor / SELinux の本質

通常の Unix 権限は「ユーザーごと」の制御。AppArmor / SELinux は「プロセスごと」の制御で、より細かい。

例: nginx プロセスが「/var/www/ 以外を読めない」「外向き 80/443 以外で listen できない」と縛る。仮にRCEで乗っ取られても、nginx の権限内でしか何もできない

AppArmor: パス指定で書く(書きやすい)。Ubuntu/Debian 採用。 SELinux: ラベル指定(学習コスト高い)。RHEL/Fedora/CentOS 採用。

初心者は「とりあえず Enforcing にしておく」だけでも防御層が1つ増える。深掘りは インフラ Level3-3 で。


練習課題: 新規 VPS を 30 分でハードニング

仮想の新規 VPS を立てた前提で、以下の harden.sh を組み立てる。コピペで動くだけでなく、各行で何をしているか説明できる状態にする。

#!/usr/bin/env bash
# harden.sh - 新規 Ubuntu VPS の初期ハードニング
set -euo pipefail
 
NEW_USER="deploy"
SSH_PORT=22  # 変えるなら2222など
 
# 1. パッケージ最新化
apt update && apt upgrade -y
 
# 2. 必要なツール導入
apt install -y ufw fail2ban unattended-upgrades
 
# 3. 一般ユーザー作成(既存ならスキップ)
if ! id "$NEW_USER" &>/dev/null; then
    adduser --disabled-password --gecos "" "$NEW_USER"
    usermod -aG sudo "$NEW_USER"
fi
 
# 4. SSH 鍵を root から deploy にコピー(root で鍵認証している前提)
mkdir -p /home/$NEW_USER/.ssh
cp /root/.ssh/authorized_keys /home/$NEW_USER/.ssh/
chown -R $NEW_USER:$NEW_USER /home/$NEW_USER/.ssh
chmod 700 /home/$NEW_USER/.ssh
chmod 600 /home/$NEW_USER/.ssh/authorized_keys
 
# 5. SSH 設定強化
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#\?Port .*/Port '$SSH_PORT'/' /etc/ssh/sshd_config
 
# 6. ファイアウォール
ufw default deny incoming
ufw default allow outgoing
ufw allow $SSH_PORT/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
 
# 7. fail2ban 起動
systemctl enable --now fail2ban
 
# 8. 自動セキュリティ更新
dpkg-reconfigure -plow unattended-upgrades
 
# 9. SSH 再起動(最後に)
systemctl restart sshd
 
echo "===== 完了 ====="
echo "別ターミナルで以下を試して、入れることを確認してから既存セッションを閉じる:"
echo "  ssh -p $SSH_PORT $NEW_USER@<server>"

チェック作業:

  1. 別のターミナルで ssh -p 22 deploy@<server> が通るか確認
  2. ssh -p 22 root@<server>拒否されることを確認
  3. sudo ufw status で 22/80/443 のみ開いていることを確認
  4. sudo fail2ban-client status sshd で sshd jail が active か確認
  5. tail -f /var/log/auth.log を流しっぱなしで5分眺める → 攻撃の生々しさを体験

締め: git で証跡を残す

cd ~/learn/security/day31
git add harden.sh sshd_config_diff.md
git commit -m "feat(security): VPS初期ハードニングスクリプト"

チェックリスト

  • root ログインを禁止し、一般ユーザー + sudo の構成にした
  • SSH を公開鍵認証オンリーにし、PasswordAuthentication no を設定
  • ~/.ssh/authorized_keys が 600、~/.ssh/ が 700 になっている
  • ufw でデフォルト deny incoming、必要ポートのみ allow
  • fail2ban を起動し、sshd jail が active
  • unattended-upgrades を有効化
  • 不要サービスを systemctl disable で停止
  • auth.log を1度は本気で読んだ
  • AppArmor または SELinux の存在を理解した

詰まった時のチートシート

やりたいことコマンド
開いてるポート確認sudo ss -tlnp
動いているサービスsystemctl list-units --type=service --state=running
認証ログ監視sudo tail -f /var/log/auth.log
sshd ログだけsudo journalctl -u sshd -f
fail2ban 状態sudo fail2ban-client status sshd
手動 bansudo fail2ban-client set sshd banip <IP>
ufw ルールsudo ufw status numbered
ログイン履歴last
失敗ログインsudo lastb
今ログイン中の人w
SSH 設定テストsudo sshd -t
SSH 再起動sudo systemctl restart sshd

「実務OK」基準

  • 新規 VPS を立てて30分で本番運用相当のハードニングが出来る
  • auth.log を眺めて「何が起きているか」を即座に把握できる
  • fail2ban / ufw / SSH 設定を他人に説明できる
  • AppArmor / SELinux の存在意義を1分で語れる
  • 「rootで運用してるけど何が悪いの?」に対して3つ以上のリスクを即答できる

さらに深掘りするなら


メタ認知: 自分の理解度を診断する

30秒セルフテスト

  1. 新規VPSで 最初に変える3つ の SSH 設定
  2. ufw で 22, 80, 443 だけ開ける1コマンド
  3. fail2ban が「やってくれること」と「やってくれないこと」
  4. unattended-upgrades のリスクと利点
  5. AppArmor / SELinux の 役割 を1文で

詰まったら → セッション①の SSH 最小権限、セッション②の fail2ban / ufw を再読。

次のレッスン

3-2 依存スキャン で「依存パッケージの脆弱性スキャン」を扱う。サーバーがどれだけ堅くても、コード側で npm install した1つのパッケージに RCE があれば全部終わる。サプライチェーン攻撃の世界へ。

間隔反復ポイント

ハードニングは 新規サーバー立てるたび に手順を踏む。3ヶ月後にこのページのチートシートを眺める習慣で、即時に「30分ハードニング」が出る体になる。