3-1. SSH - 暗号化されたリモートシェル

所要時間: 40-50分(がっつりなら2セッション分) ゴール: SSHの暗号原理を理解し、鍵認証で本番運用に耐える接続を組める。~/.ssh/config、踏み台、ポートフォワーディングまで扱える コミット内容: ~/.ssh/config の設計と、鍵生成〜接続までの手順を ~/learn/linux/day13 にまとめる

この章が終わるとできること

  • ssh-keygen -t ed25519 で鍵ペアを作り、ssh-copy-id で公開鍵をサーバーに登録できる
  • ~/.ssh/config でホストエイリアスを書き、ssh prod の一発接続が組める
  • ProxyJump で踏み台経由の接続が書ける
  • -L (ローカルフォワード) で「ローカルから本番DBに繋ぐ」が書ける
  • ssh -vvv で接続失敗をデバッグできる
  • sshd_config の最低限の堅牢化(PermitRootLogin / PasswordAuthentication)が答えられる

Level 1-2 とのつながり

  • Day 3(パーミッション)で習った ~/.ssh が 700、鍵が 600 がここで本気で効く ─ 緩いと SSH が拒否する
  • Day 11(systemd)の sshd も systemd ユニットとして動いている ─ systemctl reload sshd
  • Day 13(ログ管理)の /var/log/auth.log で SSH 侵入試行を観察する

これができると何が嬉しいか

  • 本番サーバーに 「事故なく安全に入る」 ためのプロの作法が身につく
  • 「VPS買ったけど SSH で詰む」を絶対回避できる
  • Day 16 以降の すべての章 は SSH 経由でサーバーを操る前提 ─ ここが土台

大前提: SSHを知らないバックエンドエンジニアは存在しない

本番サーバーに「触る」唯一の正規ルートが SSH です。

  • 本番Linuxに入ってログ調査 → SSH
  • GitHub に git push する時の認証 → SSH(鍵認証の場合)
  • VS Code Remote Development でリモート開発 → 中身は SSH
  • Ansible / Capistrano / Fabric のような自動化ツール → 中身は SSH
  • Docker / Kubernetes のノード操作(マネージドでない場合)→ SSH

SSH を「なんとなく ssh user@host で繋げる」レベルで止めると、以下のシーンで詰みます:

  1. 鍵をなくしたら本番に入れない: 鍵運用が雑だと事業継続リスク
  2. 総当たり攻撃で乗っ取られる: パスワード認証のままだと数時間でやられる
  3. 踏み台サーバー経由の接続が分からない: AWS の private subnet にいる本番DBに繋げない
  4. ポートフォワーディングを知らない: 「ローカルから本番のRedisにつなぎたい」が一生できない

このレッスンで「SSH の本質 = 暗号化された TCP コネクションで動くシェル」と腹落ちさせ、本番で事故らない運用設計までやります。


セッション①: SSHの本質と公開鍵認証(25-30分)

0. 録画スタート

mkdir -p ~/log ~/learn/linux/day13
cd ~/learn/linux/day13
script ~/log/linux_day13.log

1. SSHとは何か(プロトコルとしての位置付け)

まずは 「SSH とはネットワーク上で何をしている存在か」 を、プロトコルスタックの位置から押さえます。

  • 何のコマンドか: TCP/22 上で動くアプリケーション層プロトコル「Secure Shell」のクライアント実装
  • いつ使うか: リモートサーバーに対話的に入る、ファイル転送、ポート転送、Git over SSH と、用途はとにかく広い。バックエンド業務ではほぼ毎日触る
  • 解決する具体的な問題: 平文通信(telnet/rlogin)の盗聴リスクを暗号化で消し、かつ強力な認証(鍵)を組み合わせて「正しい人だけが入れる」を成立させる
# SSHは TCP/22 で動くアプリケーション層プロトコル
ss -tnl | grep :22 2>/dev/null || netstat -an | grep LISTEN | grep 22

SSH の本質

Secure Shell の略。生のTCPコネクションの上に「暗号化された対話セッション」を作るプロトコル。OSI参照モデルでいうとアプリケーション層(L7)。

ざっくり3層に分かれている:

  1. Transport層: TCP/22 上で暗号化チャネルを張る。サーバー認証もここ(known_hosts)
  2. User Authentication層: 「あなた誰?」を検証。パスワード / 公開鍵 / GSSAPI など
  3. Connection層: 認証済みチャネルの上で複数の論理セッション(シェル / ファイル転送 / ポート転送)を多重化

重要なのは「ただシェルを動かしているのではない」こと。SSHコネクションは多目的トンネルで、shellは数あるサービスの1つ。だからこそ scpsftp、ポート転送、X forwarding が全部同じ仕組みで動く。

歴史: 1995年、フィンランドのTatu Ylönenが、平文のtelnet/rlogin/rshが盗聴される事件をきっかけに開発。元はフリーソフト、商用版を経て、OpenBSDプロジェクトの OpenSSH(1999年-)がデファクトに。今 ssh と打って動いているのは99% OpenSSH。

SSHが解決した問題

  • telnet時代: 平文。LAN上の誰でもパスワードを盗聴可能
  • rsh / rlogin時代: 信頼ベース(.rhosts)。なりすまし放題
  • SSH登場後: 暗号化+強力な認証。これがデファクトになって30年経つが、まだ置き換えられていない

使い所: リモートサーバーに「安全に入る」必要があるすべての場面。クラウドVMもベアメタルも同じ。

2. パスワード認証 vs 公開鍵認証

ざっくり言うと

「合言葉で入る」か「鍵で入る」か。本番では鍵一択です。

まず普通のパターン

ssh user@example.com
# Password: ___

これは「合言葉知ってる?」方式。サーバーは「正しいパスワード」を知ってる人を通します。

クライアント ──「パスワードはこれ」──→ サーバー
                                    └─ 保存済みハッシュと照合

この方式の問題発生フロー

22番ポート公開
   ↓
攻撃ボットがポートスキャンで発見(数分以内)
   ↓
"admin" / "root" / "ubuntu" + よくあるパスワードで総当たり
   ↓
1つでも当たれば侵入完了

イメージ: パスワード = 「世界中の誰でも推測の挑戦権を持っている」

公開鍵認証は「数学的に対になった2つの鍵」を使う

あなたのローカル
  └─ id_ed25519       ← 秘密鍵(絶対に外に出さない)
  └─ id_ed25519.pub   ← 公開鍵(誰に見せてもOK)

サーバー
  └─ ~/.ssh/authorized_keys に公開鍵を登録

接続時のやりとり(チャレンジ・レスポンス):

サーバー「あなたの公開鍵は知ってる。じゃあ乱数Xを送るから、
        秘密鍵で署名して返して」
   ↓
クライアント「(手元の秘密鍵で署名)はいこれ」
   ↓
サーバー「公開鍵で検証 → 一致 → 通します」

イメージ: 公開鍵認証 = 「秘密鍵を持っている本人しか署名できない」

違い

パスワード認証公開鍵認証
保管場所サーバーにハッシュクライアントに秘密鍵
攻撃可能性世界中から推測攻撃秘密鍵がない限り不可能
ネットワークに流れるパスワード(暗号化済)署名だけ。秘密鍵は流れない
2要素化ぎこちないパスフレーズで自然に2要素

一番覚えやすい説明

  • パスワード認証 = 「知っているか」テスト(推測可能)
  • 公開鍵認証 = 「持っているか」テスト(物理的に持ち出さないと突破不能)

パスワード認証を有効のまま 22 番を開けるとどうなるか

/var/log/auth.log(Debian/Ubuntu系の認証ログファイル。SSH ログイン成功/失敗・sudo 実行などが記録される。詳細は 3-2_ユーザー管理 で扱う)を見ると、ロシア・中国・ブラジルから秒間数回ペースで rootadmin のログイン試行が来ている。起動から数時間で誰かが当てる世界。

公開鍵認証なら、秘密鍵ファイルが攻撃者の手元にない限りそもそも推測の概念が存在しない

3. ed25519 を使え(RSA はもう古い)

公開鍵認証を使う最初の一手が鍵ペアの生成です。アルゴリズムに何を選ぶかでセキュリティ強度と扱いやすさが変わります。

  • 何のコマンドか: ssh-keygen は公開鍵/秘密鍵のペアを生成するコマンド。-t で楕円曲線(ed25519)/ RSA / ECDSA などのアルゴリズムを指定
  • いつ使うか: 新しい開発機をセットアップした初日、用途別(GitHub用 / 本番用)に鍵を増やす時、退職者の鍵をローテーションする時
  • 解決する具体的な問題: 「短い鍵長で強いセキュリティ+速度」を満たす ed25519 を使えば、RSA 2048 より安全で軽い鍵が一発で作れる
# 鍵ペアの生成(ed25519推奨)
ssh-keygen -t ed25519 -C "kuroiwa@impv.co.jp"
 
# 質問
# Enter file in which to save the key (~/.ssh/id_ed25519): [Enter で デフォルト]
# Enter passphrase: ***(強めのパスフレーズを設定)
# Enter same passphrase again: ***
 
# 生成された鍵を確認
ls -la ~/.ssh/
# -rw-------  1 takato  staff   411 May 14 10:00 id_ed25519        # 秘密鍵(600)
# -rw-r--r--  1 takato  staff   101 May 14 10:00 id_ed25519.pub    # 公開鍵

なぜ ed25519 か(RSA 2048 との比較)

項目RSA 2048ed25519
鍵長2048 bit(公開鍵 ~400バイト)256 bit(公開鍵 ~80バイト)
セキュリティ強度約112bit相当約128bit相当
署名生成速度遅い高速(数十倍)
量子計算耐性ないない(どちらも将来NIST PQC へ)
実装の罠パラメータ選択ミスで弱くなるパラメータ固定で罠が少ない
OpenSSH対応古くから6.5(2014年)以降

結論: 新規生成は ed25519 一択。RSAは互換性のために残してあるが、新規で -t rsa -b 2048 を選ぶ理由はない。-t rsa -b 4096 でも ed25519 より弱くて遅い。

歴史: ed25519 はDaniel J. Bernstein らが2011年に発表した楕円曲線署名方式。Curve25519という曲線上で動く。「サイドチャネル攻撃耐性」「パラメータが公開済みで透明」など設計が美しい。

パスフレーズ無しの鍵を作るな(特に本番アクセス用)

# NG: パスフレーズ空でEnter連打
ssh-keygen -t ed25519 -N ""

これは「秘密鍵ファイルさえあれば誰でも入れる」状態。ノートPCを盗まれた = 本番アクセス権を渡したのと同じ。

正解:

  • パスフレーズを設定する(強めの12文字以上)
  • ssh-agent でセッション中はキャッシュ → 入力は1日1回
  • macOSなら Keychain 統合で更に楽(後述)

4. 公開鍵をサーバーに登録(authorized_keys)

ざっくり言うと

鍵ペアを作っただけでは何も繋がらない。サーバーに「この公開鍵の人は通していい」と名簿登録する必要がある。それが authorized_keys

まず「鍵だけ作った」状態

あなたのMac
  └─ ~/.ssh/id_ed25519       (秘密鍵)
  └─ ~/.ssh/id_ed25519.pub   (公開鍵)

サーバー
  └─ (あなたの鍵は知らない)

この状態で ssh しても: 「あなた誰?」で拒否される

authorized_keys は「サーバー側の許可リスト」

サーバーの ~/.ssh/authorized_keys
  ssh-ed25519 AAAA...kuroiwa@impv.co.jp     ← 自分の公開鍵
  ssh-ed25519 AAAA...alice@company.com      ← Aliceの公開鍵
  ssh-ed25519 AAAA...bob@company.com        ← Bobの公開鍵

イメージ: authorized_keys = 「会員制クラブの会員リスト」。リストにある人だけ入れる。

登録は ssh-copy-id 一発

ssh-copy-id user@example.com
# 内部的にはこれをやっている:
# 1. 自分の id_ed25519.pub を読む
# 2. サーバーの ~/.ssh/authorized_keys に追記
# 3. パーミッションを 700 / 600 に設定

パーミッションが重要

パス必須 modeなぜ
~/.ssh700自分以外読めない
~/.ssh/authorized_keys600自分以外読み書き不可

これが緩いと SSH デーモンが「他人が書き換えられるリストは信用できない」と判断して認証失敗します。

一番覚えやすい説明

  • 秘密鍵 = 自分の手元にある「鍵」
  • 公開鍵 = サーバーの authorized_keys にある「鍵穴」
  • ペアが噛み合えば開く

authorized_keys は「鍵ごとに細かい制限」も付けられる

command="/usr/bin/rsync --server ...",no-pty,no-port-forwarding ssh-ed25519 AAAA... rsync-bot

→ この鍵で接続したら指定のコマンドしか実行できない(バックアップ専用アカウントなど)

アンチパターン: 全員で同じ鍵を共有

# サーバーの authorized_keys
ssh-ed25519 AAAA... team-shared

チームの全員が同じ秘密鍵ファイルを共有して使い回す運用。

なぜNGか:

  • 誰が入ったか分からない(監査ログが全員 team-shared で同一)
  • 1人辞めたら全員の鍵を変えないといけない(実際は変えない → セキュリティホール放置)
  • 鍵がどこから漏れたか追跡不能

正解: 1人1鍵。authorized_keys には個別の鍵が並ぶ。退職者対応はその行を削除するだけ。

5. known_hosts と中間者攻撃

公開鍵認証は「クライアントがサーバーに自分を証明する」仕組みですが、逆に「このサーバーが本物か」をクライアントが検証する仕組みも必要です。それを担うのが ~/.ssh/known_hosts です。

  • 何のコマンドか: ssh コマンドが初回接続時にサーバーの公開鍵を保存する仕組み。2回目以降は保存済みの鍵と照合する
  • いつ使うか: 新規サーバーに初めて接続する時、サーバーの再構築でホスト鍵が変わった時、The fingerprint has changed 警告が出た時
  • 解決する具体的な問題: 中間者攻撃(MITM)の検知。攻撃者が間に偽サーバーを立てても、ホスト鍵の不一致で警告が出る
# 初回接続時
ssh user@example.com
# The authenticity of host 'example.com (192.0.2.1)' can't be established.
# ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxx
# Are you sure you want to continue connecting (yes/no/[fingerprint])?
 
# yes と答えると ~/.ssh/known_hosts に記録される
cat ~/.ssh/known_hosts | head -2

known_hosts の役割

「このサーバーに以前繋いだ時、サーバー側はこの公開鍵を持っていた」を記録しておくファイル。

2回目以降の接続では:

  1. サーバーが「これが私の公開鍵です」と提示
  2. クライアントが known_hosts の記録と比較
  3. 一致 → 接続続行 / 不一致 → 警告 or 接続拒否

これによって**中間者攻撃(MITM, Man-In-The-Middle)**を検知する。攻撃者が間に入って偽サーバーを立てても、公開鍵が違うので警告が出る。

「The fingerprint has changed」警告を絶対に無視するな

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

これが出たら「サーバーの鍵が変わった」サイン。正規の理由は3つしかない:

  1. サーバーOSを再インストールした(鍵が再生成される)
  2. IP/ドメインの引っ越しで別マシンが同じホスト名に
  3. ホストキーを意図的にローテーション

上記のいずれでもないのに警告が出たら、MITM攻撃の可能性。Wi-Fiが信用ならない場所(カフェ、空港)では特に注意。

正規の更新時の対処: ssh-keygen -R example.com で古いエントリを削除 → 再接続。ただし運用チームに「鍵変えた?」と確認してから消すこと。

アンチパターン: StrictHostKeyChecking=no を常用

ssh -o StrictHostKeyChecking=no user@example.com

または ~/.ssh/config で:

Host *
  StrictHostKeyChecking no

「警告うざい、いつも yes」を避けるための設定だが、これは中間者攻撃を全部許可する設定。

正解: 警告は警告として受け止める。CI/CDなど自動化で必要な場合は、事前にホストキーを known_hosts に焼き付けておく:

ssh-keyscan example.com >> ~/.ssh/known_hosts

こうすれば「予想されたキー」として記録できて、不一致なら警告される。


セッション②: 運用設計 - config、agent、scp/rsync、ポート転送(25-30分)

6. ~/.ssh/config で接続を整理する

接続先が3つを超えたあたりから、ssh -i ~/.ssh/id_xxx -p 22022 -J bastion@... user@host のような呪文を毎回打つのは非現実的になります。それを根本解決するのが ~/.ssh/config です。

  • 何のコマンドか: SSH クライアントの設定ファイル。Host エイリアスごとに接続情報(ホスト名・ユーザー・ポート・鍵・踏み台)を宣言しておく
  • いつ使うか: 管理対象サーバーが2台以上に増えた時、踏み台経由の接続を毎日使う時、チームに「config を配って共通設定する」時
  • 解決する具体的な問題: 接続オプションのコマンドライン入力地獄を消す。ssh prod-web だけで本番に入れるようになる
# ~/.ssh/config を作る(無ければ新規)
cat > ~/.ssh/config <<'EOF'
# デフォルト設定(全Host共通)
Host *
  ServerAliveInterval 60
  ServerAliveCountMax 3
  AddKeysToAgent yes
  UseKeychain yes
 
# 本番Webサーバー
Host prod-web
  HostName 203.0.113.10
  User deploy
  Port 22
  IdentityFile ~/.ssh/id_ed25519_prod
 
# 踏み台経由のDB
Host prod-db
  HostName 10.0.1.50
  User deploy
  IdentityFile ~/.ssh/id_ed25519_prod
  ProxyJump bastion
 
# 踏み台サーバー
Host bastion
  HostName bastion.example.com
  User ops
  IdentityFile ~/.ssh/id_ed25519_prod
 
# GitHub
Host github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_github
EOF
 
chmod 600 ~/.ssh/config
 
# 使い方
ssh prod-web        # → ssh -i ~/.ssh/id_ed25519_prod -p 22 deploy@203.0.113.10
ssh prod-db         # → 自動的に bastion を経由して接続

~/.ssh/config の威力

接続情報をHostエイリアスで抽象化する設定ファイル。これを書かないと毎回:

ssh -i ~/.ssh/id_ed25519_prod -p 22022 -J ops@bastion.example.com deploy@10.0.1.50

こんなのを打つことに。書いた途端 ssh prod-db だけになる。

主要ディレクティブ:

キー意味
HostName実際の接続先(IPやFQDN)
Userログインユーザー
Portポート番号(デフォルト22)
IdentityFile秘密鍵のパス
IdentitiesOnly yesこの鍵だけ使う(複数鍵がある時の事故防止)
ProxyJump踏み台ホスト経由(後述)
ServerAliveIntervalアイドル時のキープアライブ秒数
AddKeysToAgent接続時に自動でagentに鍵を追加
UseKeychainmacOSのKeychainからパスフレーズ取得

Host行の前方一致: Host prod-*prod-web, prod-db などまとめて設定可能。

鍵を用途別に分ける

~/.ssh/id_ed25519_github     # GitHub用
~/.ssh/id_ed25519_personal   # 個人VPS用
~/.ssh/id_ed25519_prod       # 仕事の本番用
~/.ssh/id_ed25519_staging    # 仕事のステージング用

なぜ分けるか:

  • 退職時、仕事用だけ無効化できる
  • GitHubに登録した鍵が漏れても、本番には使えない
  • コンプライアンス要件(個人と業務の鍵を分離する組織が多い)

7. ssh-agent と ~/.ssh/config の役割の違い

ざっくり言うと

  • ~/.ssh/config = どこに繋ぐかの設定の宣言
  • ssh-agent = パスフレーズを覚えてくれるメモリ常駐デーモン

役割がまったく違うので、両方使う。

普通のパターン(両方なし)

ssh -i ~/.ssh/id_ed25519_prod -p 22022 deploy@203.0.113.10
# パスフレーズ入力 ___
# 5分後にもう一回繋ぐ
ssh -i ~/.ssh/id_ed25519_prod -p 22022 deploy@203.0.113.10
# パスフレーズ入力 ___ ←また聞かれる

呪文みたいなコマンドを毎回打って、パスフレーズも毎回入力。

~/.ssh/config は「接続情報の名前付け」

~/.ssh/config:
  Host prod-web
    HostName 203.0.113.10
    User deploy
    Port 22022
    IdentityFile ~/.ssh/id_ed25519_prod
ssh prod-web   # ← 上の長い呪文と同じ意味

イメージ: ~/.ssh/config = 電話帳。番号を覚える代わりに名前で呼ぶ。

ssh-agent は「パスフレーズの保管庫」

1. ssh-add ~/.ssh/id_ed25519_prod
   → パスフレーズ入力(最初の1回だけ)
   → agent がメモリに鍵を保持

2. ssh prod-web
   → agent が代理で署名 → パスフレーズ再入力なし

3. ssh prod-db
   → agent が代理で署名 → 再入力なし

イメージ: ssh-agent = 執事。「これ署名しといて」と頼むだけで、パスフレーズは執事が知っている。

違い

~/.ssh/configssh-agent
何を解決する接続情報の入力地獄パスフレーズ入力地獄
ファイル?プロセス?テキストファイル常駐デーモン
いつ読まれるssh コマンド実行時鍵を使う時
再起動後永続消える(再ssh-add or Keychain)

一番覚えやすい説明

  • ~/.ssh/config = 「どこに繋ぐか」を覚えてくれる
  • ssh-agent = 「パスフレーズ」を覚えてくれる

両方使うと: ssh prod-web の1コマンドで、パスフレーズ入力なしで本番にログインできる状態になります。

  • 何のコマンドか: ssh-agent は秘密鍵をメモリ上に保持するデーモン、ssh-add はそこに鍵を登録するコマンド
  • いつ使うか: 強いパスフレーズ付きの鍵を毎日使う時、macOS で再起動後も鍵を覚えていてほしい時、CI/CD のジョブ内で短時間だけ鍵を有効化したい時
  • 解決する具体的な問題: 「セキュリティのためパスフレーズは長くしたい、でも毎回入力したくない」のトレードオフを agent でほぼ消す
# agentが動いているか確認
ssh-add -l
# → 鍵が登録されていれば一覧、なければ "The agent has no identities."
 
# 鍵を agent に追加(パスフレーズを聞かれる)
ssh-add ~/.ssh/id_ed25519_prod
 
# macOSのKeychainに保存(次回起動後も覚えている)
ssh-add --apple-use-keychain ~/.ssh/id_ed25519_prod
 
# 一覧
ssh-add -l
 
# 全部削除
ssh-add -D

ssh-agent の本質

パスフレーズ付き秘密鍵をメモリ上にキャッシュするデーモン。一度パスフレーズを入れたら、agentが生きている間は再入力不要。

仕組み:

  1. SSH接続時、クライアントは秘密鍵を直接使わずagentに「これで署名して」と頼む
  2. agentがメモリ上の秘密鍵で署名 → クライアントに返す
  3. 秘密鍵自体はagentの外に出ない(プロセス境界での保護)

macOSなら UseKeychain yesKeychainからパスフレーズを取り出す仕組み。つまり「再起動後も鍵が使える」が「Mac本体のログイン認証を突破しないとKeychainが開かない」というバランス。

agent forwarding は便利だが危険

ssh -A user@bastion
# 踏み台に入った後、そこからまた別サーバーへ ssh できる(鍵が転送されているから)

-A(または config の ForwardAgent yes)で、ローカルのagentを踏み台でも使えるようにする機能。便利だが…

リスク: 踏み台が乗っ取られていると、攻撃者があなたのagent経由でその先のサーバーに入れる。秘密鍵は流出しないが、「あなたとして」操作される

対策:

  • ForwardAgent は信頼できるサーバーだけに(共有踏み台では避ける)
  • ProxyJump(-J)を使う → こちらは agent forwarding 不要で同等のことが可能
# ForwardAgent ではなく ProxyJump 推奨
ssh -J bastion target-server

8. scp と rsync の使い分け(rsync が圧倒的に有利)

SSH コネクションの上でファイルを送る手段は複数あります。代表は scprsync。実務で本当に使うのは後者です。

  • 何のコマンドか: scp は SSH 越しの単純コピー、rsync は差分転送 + 中断再開 + 進捗表示などを備えた多機能同期ツール
  • いつ使うか: サーバーへログをまるごと取りに行く時、デプロイで current ディレクトリを同期する時、巨大ファイルを途中で切れても再開したい時
  • 解決する具体的な問題: scp -r で1万ファイルを送って数十分待つ事故を避ける。rsync なら差分だけ送って数倍速い
# scp: シンプルなファイル転送
scp local_file.txt user@example.com:/home/user/
scp -r local_dir/ user@example.com:/home/user/
scp user@example.com:/var/log/app.log ./
 
# rsync: 差分転送 + 多機能
rsync -avzP local_dir/ user@example.com:/home/user/dest/
#       -a: archive(権限・タイムスタンプ保持)
#       -v: verbose
#       -z: 圧縮
#       -P: 進捗表示+途中再開

scp と rsync の本質的な違い

項目scprsync
転送方式毎回全部送る差分のみ送る
中断時最初からやり直し続きから再開(-P
大量小ファイル遅い圧倒的に速い
進捗表示なし(古いscp)あり
フィルタなし--exclude で除外可能
プロトコルSSHSSH or rsync専用デーモン
内部のアルゴリズム単純コピーrolling checksum で差分検出

OpenSSH 9.0 (2022) から scp は内部的にSFTPを使うようになって挙動が変わり、さらに「scpはもう非推奨、rsyncかsftpを使え」とOpenSSHプロジェクトが明言している。

結論: 単発の小さな転送は scp でもOK、それ以外は全部 rsync

rsync が天才な理由: rolling checksum

巨大ファイルをコピーする時、変わった部分だけ送れば最速。でも「どこが変わったか」をどう知る?rsync はローリングチェックサムでこれを解決する。

  1. ファイルを固定サイズのブロックに分割
  2. 各ブロックのチェックサムを計算(軽量なもの+強いもの)
  3. 受信側のチェックサムリストと送信側で比較
  4. 違うブロックだけ送信

Andrew Tridgell(Samba作者)の博士論文がベース。1996年から20年以上、地味に重要な技術として動き続けている。

アンチパターン: 本番でディレクトリごと scp -r

# NG: 1万ファイルあるディレクトリを scp -r
scp -r app/ user@example.com:/var/www/

1万回ファイルが小さくopen/closeされてSSH接続のオーバーヘッドが地獄。30分かかる転送が rsync なら3分で終わる。

# OK
rsync -avz --delete app/ user@example.com:/var/www/app/
# --delete: 送信元にないファイルを送信先からも削除(ミラー)

9. ポートフォワーディング(トンネル)

SSH は「シェル接続」だけでなく、任意の TCP コネクションを暗号化トンネルに通すことができます。これがポートフォワーディング(トンネル)。本番の隠しサービスにアクセスする時の必須技です。

  • 何のコマンドか: ssh -L / -R / -D で、ローカルとリモートの間に暗号化されたポートトンネルを張る
  • いつ使うか: 本番の private subnet にいる RDS に開発機から繋ぎたい時、社内向け管理画面に外部から一時的に届かせたい時、出先で SOCKS プロキシ経由で安全に通信したい時
  • 解決する具体的な問題: 「直接アクセスできない内部サービス」に、SSH 越しの暗号化トンネルで安全に届く経路を作る
# ローカル転送: 自分の3306 → 本番DBサーバーの3306
ssh -L 3306:localhost:3306 user@db-server.com
# → ローカルの mysql -h 127.0.0.1 で本番DBにアクセス
 
# ローカル転送(踏み台経由で内部DBへ)
ssh -L 6379:internal-redis:6379 user@bastion.example.com
# → ローカルの redis-cli -h 127.0.0.1 で内部Redisに接続
 
# リモート転送: 本番サーバーの 8080 → 自分のlocalhost:3000
ssh -R 8080:localhost:3000 user@example.com
# → 本番サーバー上で curl localhost:8080 すると自分のローカル3000にアクセス
 
# ダイナミック転送(SOCKS proxy)
ssh -D 1080 user@example.com
# → ブラウザで SOCKS proxy localhost:1080 を設定すると、全通信がSSH経由

ポートフォワーディングの3種類

種類フラグ意味典型ユースケース
ローカル転送-Lローカルポート → リモート経由で目的地内部DBに開発機から接続
リモート転送-Rリモートポート → ローカル経由で目的地ファイアウォール越しのデモ
ダイナミック-DSOCKSプロキシ出先からの安全なネット利用

一番使うのは -L(ローカル転送)。「内部ネットワークにいるサービスに、SSH越しに繋ぐ」用途。

実例: 本番DBに踏み台経由で繋ぐ

構成:

[開発者PC]
    │ SSH
    ▼
[踏み台 (bastion)]
    │ Private Network
    ▼
[本番DB (port 5432)]

~/.ssh/config:

Host db-tunnel
  HostName bastion.example.com
  User deploy
  LocalForward 5432 prod-db.internal:5432
# トンネルを張る(バックグラウンドで)
ssh -f -N db-tunnel
#   -f: バックグラウンド、-N: コマンド実行しない(トンネル専用)
 
# ローカルからアクセス(実は踏み台経由でprod-dbを叩いてる)
psql -h 127.0.0.1 -p 5432 -U app_user appdb

これで「本番DBに直接アクセスできないが、踏み台経由ならOK」というよくある構成に対応できる。

ポートフォワーディングのセキュリティ

  • -R でサーバー側にローカルアプリを露出する時、デフォルトでは127.0.0.1にしかバインドされない。外部公開したいなら GatewayPorts yes をサーバー側で設定(注意して使う)
  • トンネル張ったまま放置: ローカルのポートが開いてる = LANの他端末から踏み台経由で本番DBにアクセス可能になる。netstat で監視
  • マルチユーザーマシンでは絶対NG: 共用サーバーで -D 1080 すると、そのマシンの他ユーザーも SOCKS を使える

10. sshd 側のセキュリティ設定(運用者向け)

ここまでは「クライアント側」の話でした。最後に サーバー側sshd_config)で、攻撃面を最小化する設定を入れます。本番サーバーを世界に公開する以上、ここが最終防衛線です。

  • 何のコマンドか: /etc/ssh/sshd_config を編集して、root ログイン禁止・パスワード認証無効・接続元制限などを宣言する
  • いつ使うか: 新規 VPS の初期セットアップ時、社内のセキュリティ監査対応で sshd 設定を見直す時、攻撃ログ(/var/log/auth.log)が異常に多いと気付いた時
  • 解決する具体的な問題: 「クライアント側で頑張っても、サーバーが緩いと意味がない」を解消する。サーバー側で root login と password 認証を物理的に塞ぐ
# サーバー側 /etc/ssh/sshd_config の重要項目
sudo vi /etc/ssh/sshd_config
# 推奨設定
Port 22                          # 変更するかは議論(後述)
PermitRootLogin no               # rootで直接入らせない
PasswordAuthentication no        # パスワード認証無効
PubkeyAuthentication yes         # 鍵認証のみ
ChallengeResponseAuthentication no
AllowUsers deploy ops            # 接続できるユーザーを制限
AllowGroups sshusers             # またはグループ
MaxAuthTries 3                   # 認証失敗の許容回数
LoginGraceTime 30                # ログイン待機時間(秒)
ClientAliveInterval 300          # アイドルチェック
ClientAliveCountMax 2
 
# X11やAgentForwardingが不要なら無効化
X11Forwarding no
AllowAgentForwarding no
# 設定変更後は構文チェック→reload
sudo sshd -t                     # 構文OKか確認
sudo systemctl reload sshd       # 再読み込み(既存セッションは切れない)

sshd 設定変更で締め出される事故を防ぐ

本番で sshd_config を編集して reload した瞬間、自分も入れなくなる事故が定期的に発生する。

対策:

  1. 必ず別セッションで作業: 既存のSSHセッションを残したまま、新しいセッションで ssh user@host を試す
  2. sshd -t で構文チェック必須: タイポで sshd が起動しなくなる
  3. コンソールアクセスを確保: AWSならEC2のシリアルコンソール、VPSならコンパネのコンソール機能の使い方を事前に確認
  4. 最初に PermitRootLogin no する前に、一般ユーザーで鍵ログインできることを必ず確認

SSH ポートを 22 から変えるべきか

賛成派の主張:

  • 22のままだと自動化されたボットの攻撃が毎秒来る
  • 変えるだけで /var/log/auth.log のノイズが激減する(fail2ban = 認証失敗ログを監視して自動で IP をブロックする仕組み。詳しくは 3-4_ファイアウォール で扱う、の負荷軽減)

反対派の主張:

  • “security through obscurity”(隠匿によるセキュリティ)に過ぎず、本質的な保護にならない
  • 鍵認証 + fail2ban + 強いパスフレーズで十分
  • ポート変更でツールの設定が増えて運用負荷が上がる

現実的な結論:

  • 個人VPS: 変えてOK(ノイズが減って気持ち良い)。例: 2222, 22022
  • 企業の本番: 変えない(パスワード認証無効+fail2ban+IPアクセス制限の方が本質的)
  • 絶対NG: ポートだけ変えて他は何もしない(攻撃者にとってポートスキャンは数秒)

練習課題

# 1. ed25519 鍵を新規作成(パスフレーズ付き)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_practice -C "practice-key"
 
# 2. fingerprint を表示
ssh-keygen -lf ~/.ssh/id_ed25519_practice.pub
 
# 3. 公開鍵の中身を確認
cat ~/.ssh/id_ed25519_practice.pub
 
# 4. 自分のローカルに「自分への鍵認証SSH」をセットアップ
# (sshdが動くMacの場合のみ)
cat ~/.ssh/id_ed25519_practice.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
ssh -i ~/.ssh/id_ed25519_practice $(whoami)@localhost  # 失敗するなら System Settings > Sharing > Remote Login をON
 
# 5. ~/.ssh/config に Host エイリアスを書く
mkdir -p ~/learn/linux/day13
cat > ~/learn/linux/day13/sample_ssh_config <<'EOF'
Host myvps
  HostName 192.0.2.10
  User deploy
  Port 22
  IdentityFile ~/.ssh/id_ed25519
  ServerAliveInterval 60
 
Host myvps-tunnel
  HostName 192.0.2.10
  User deploy
  IdentityFile ~/.ssh/id_ed25519
  LocalForward 5432 localhost:5432
EOF

締め: git で証跡を残す

exit   # script から抜ける
 
cd ~/learn/linux/day13
git init
git add .
git commit -m "feat(linux): SSH 鍵生成・config 設計・ポート転送を整理"

チェックリスト

  • ed25519 で鍵ペアを作り、ls -l ~/.ssh/ でパーミッションを確認した
  • ~/.ssh/config に最低1つの Host エイリアスを定義した
  • ssh-add -l で agent に鍵が登録されているか確認した
  • known_hosts のエントリを ssh-keygen -R で削除する手順を試した
  • -L(ローカルフォワード)でポート転送を試した
  • rsync で差分転送ができることを --dry-run で確認した
  • パスワード認証無効化、root login 無効化の設定をsshd_configで指摘できる

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

やりたいことコマンド
鍵を作るssh-keygen -t ed25519 -C "comment"
公開鍵をサーバー登録ssh-copy-id user@host
fingerprintを見るssh-keygen -lf <鍵ファイル>
agentに鍵登録ssh-add ~/.ssh/id_ed25519
Keychain連携(Mac)ssh-add --apple-use-keychain ~/.ssh/id_ed25519
設定で接続ssh エイリアス名~/.ssh/config 参照)
踏み台経由ssh -J bastion target
ローカルフォワードssh -L 3306:db:3306 user@bastion
バックグラウンドでトンネルssh -fN -L ...
古い known_hosts エントリ削除ssh-keygen -R example.com
詳細ログssh -vvv user@host
rsync 差分転送rsync -avzP src/ user@host:/dst/
sshd 構文チェックsudo sshd -t
sshd 再読み込みsudo systemctl reload sshd

「実務OK」基準

  • ~/.ssh/config を書ける: 5つ以上のサーバーをエイリアスで管理できる
  • 踏み台経由の接続を ProxyJump で設計できる: 「本番DBに直接届かない」構成で詰まらない
  • 鍵を用途別に分ける運用ができる: GitHub用 / 本番用 / 個人用を別ファイルで管理
  • ssh -vvv でデバッグできる: 接続できない時、どの段階で失敗してるか読める
  • sshd_config の最低限の堅牢化ができる: PermitRootLogin no、PasswordAuthentication no を即答できる
  • rm 前のような慎重さで sshd を扱える: 自分を締め出さない、構文チェック必須、別セッションでテスト

さらに深掘るなら

  • OpenSSH 公式: man ssh, man ssh_config, man sshd_config(必読、各種ディレクティブの本家ドキュメント)
  • 書籍: 『SSH, The Secure Shell: The Definitive Guide』(O’Reilly) - 古いがプロトコル理解は今でも通用
  • OpenSSH ソースコード: https://github.com/openssh/openssh-portable - C言語でプロトコル実装を読みたい人向け
  • mosh: モバイル環境でSSHが切れる問題を解決する派生プロトコル
  • AWS Session Manager / Google IAP: 鍵を持たずに公開鍵認証を回避する近年の流れ。クラウドネイティブな本番運用ではこちらに移行する組織が増えている

アンチパターン / 初心者やらかし事例

NG 1: 秘密鍵を間違って公開リポジトリに git push

git add .   # ~/.ssh/id_ed25519 を含めてしまう

→ 対策: .gitignore.ssh/ を必ず追加。git-secrets ツール導入。万が一公開したら 即鍵失効 + ssh-keygen で再生成。

NG 2: PermitRootLogin yes のまま放置

/etc/ssh/sshd_config:
PermitRootLogin yes

→ 攻撃者が root を狙い撃ち。対策: PermitRootLogin no に変更、一般ユーザー + sudo で運用。

NG 3: パスワード認証を残したまま

PasswordAuthentication yes   # 総当たり攻撃の餌食

→ 対策: 公開鍵が動くことを確認してから PasswordAuthentication no に変更。sudo sshd -t で構文チェック → 別セッションで 確認 → 再起動。

NG 4: sshd 再起動で自分を締め出す

sudo systemctl restart sshd
# あれ、繋がらない… しかも今のセッションも切れる前提

→ 対策: 必ず別セッションを残したまま sshd を再起動。設定変更時は sudo sshd -t で構文チェック必須。


自己評価チェックリスト

  • ssh-keygen -t ed25519 で鍵を生成した
  • ~/.ssh/ のパーミッション(700/600)を確認した
  • ~/.ssh/config に Host エイリアスを書いて接続した
  • ssh -J bastion target の踏み台経由接続を試した
  • ssh -L 3306:db:3306 user@bastion のフォワーディングを試した
  • ssh -vvv で接続トレースを読んだ
  • PermitRootLogin no PasswordAuthentication no の意義を答えられる

次のレッスン: Day 16 - ユーザー管理

明日は ユーザー管理 ─ Day 3(パーミッション)で見た「ユーザー」「グループ」の正体に深く踏み込む。

/etc/passwd /etc/shadow /etc/group の構造、useradd sudoers最小権限の原則、root を直接使わない運用、監査ログまで。今日学んだ SSH の鍵認証と組み合わせて、「専用デプロイユーザー」を作る基礎が固まる。

Day 16: ユーザー管理