Linux(Amazon Linux、CentOS、Ubuntuなど)にSSH公開鍵認証でログインできない状況を防ぐ.sshの権限・所有者を修復・復旧するシェルスクリプト(バッチ処理プログラム)、AWSのEC2インスタンスでSSHでログインできなくなった場合の対処法

6月 26, 2017AWS,EC2,Programming,Shell Script

クラウド時代の到来により、AWS、GCP、Azureなどのパブリッククラウドサービスで誰でも手軽にエンタープライズレベルのインフラを構築できるようになりました。しかし、その利便性の裏側で、セキュリティ対策の重要性はかつてないほど高まっています

特にAWS EC2でLinuxインスタンスを運用する際、デフォルトの認証方式として採用されているSSH公開鍵認証は、パスワード認証と比較して格段に高いセキュリティを提供します。秘密鍵を持たない第三者はログインできないため、ブルートフォース攻撃などのリスクを大幅に軽減できます。

SSH公開鍵認証で陥りやすい「ログイン不可」の罠

しかし、公開鍵認証には見落とされがちな重大な落とし穴があります。それは.sshディレクトリおよびその配下のファイルの権限設定と所有者設定です。

⚠️ 注意: SSHは権限設定に非常に厳格です。.sshディレクトリ、ホームディレクトリ、秘密鍵ファイルの権限が不適切だと、セキュリティリスクと判断されログインが拒否されます。

よくある失敗シナリオ

  • ホームディレクトリ全体に対してchmod -R 777などの一括権限変更を実行
  • ホームディレクトリを700にしてしまい、SSHが拒否される(755が必須)
  • 誤ってchownで所有者を変更してしまう
  • バックアップスクリプトが権限を変更してしまう
  • 開発環境から本番環境へのファイル移行時に権限情報が失われる
  • rsyncやscpで--preserve=modeオプションを忘れる

これらの操作により.ssh配下の権限が変更されると、次回ログイン時にSSH接続が拒否され、サーバーへのアクセスが完全に失われるという深刻な事態に陥ります。

自動修復スクリプトで「ログインロックアウト」を防ぐ

この記事では、システム起動時に自動的に.sshの権限・所有者を正しい状態に修復するシェルスクリプトを紹介します。これにより、誤操作によるログイン不可状態を未然に防ぐことができます。

🔧 SSH権限自動修復スクリプト(検証済み最終版 v2.0)

以下のスクリプトは、エンタープライズレベルの堅牢性を備えています:

  • 連想配列による複数ユーザーの一括管理
  • 包括的なエラーハンドリング
  • 詳細なログ出力(成功・警告・エラーを分類)
  • 処理結果のサマリー表示
  • ホームディレクトリの権限も同時に修正

動作確認済み環境:

  • Amazon Linux 2、Amazon Linux 2023
  • CentOS 7、CentOS Stream 8/9
  • Rocky Linux 8/9
  • AlmaLinux 8/9
  • Ubuntu 20.04 LTS、22.04 LTS、24.04 LTS
  • Debian 11、Debian 12
#!/bin/bash
#
# SSH権限自動修復スクリプト(検証済み最終版)
# 用途: .sshディレクトリとホームディレクトリの権限・所有者を適切な状態に自動修復
# 対応OS: Amazon Linux 2/2023, CentOS 7/8/9, Ubuntu 20.04/22.04/24.04, Debian 11/12
# 作成日: 2025-01-14
# 更新日: 2025-01-14
# バージョン: 2.0
# ライセンス: MIT
#

# エラー時にスクリプトを停止
set -euo pipefail

# nullglobを有効化(ワイルドカードマッチなしの場合に空リスト)
shopt -s nullglob

# ログファイルのパス
LOG_FILE="/var/log/ssh_permission_fix.log"

# ログファイルが存在しない場合は作成(権限エラー回避)
if [ ! -f "$LOG_FILE" ]; then
    touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/ssh_permission_fix.log"
    chmod 644 "$LOG_FILE" 2>/dev/null || true
fi

# ログ出力関数
log() {
    local level="${2:-INFO}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $1" | tee -a "$LOG_FILE"
}

# エラーログ関数
log_error() {
    log "$1" "ERROR"
}

# 警告ログ関数
log_warn() {
    log "$1" "WARN"
}

log "========================================="
log "SSH権限修復スクリプト v2.0 を開始します"
log "========================================="

# 連想配列でユーザーディレクトリと所有者情報を管理
declare -A USERS

# 修復対象ユーザーの設定
# キー: ユーザーのホームディレクトリ(先頭のスラッシュなし)
# 値: 所有者:グループ の形式
USERS["root"]="root:root"
USERS["home/ec2-user"]="ec2-user:ec2-user"          # Amazon Linux
USERS["home/ubuntu"]="ubuntu:ubuntu"                # Ubuntu
USERS["home/centos"]="centos:centos"                # CentOS
USERS["home/admin"]="admin:admin"                   # Debian
USERS["home/rocky"]="rocky:rocky"                   # Rocky Linux
USERS["home/alma"]="alma:alma"                      # AlmaLinux

# 処理したユーザー数をカウント
PROCESSED=0
FAILED=0

# 各ユーザーディレクトリを順次処理
for USER_DIR_RELATIVE in "${!USERS[@]}"; do
    HOME_DIR="/${USER_DIR_RELATIVE}"
    SSH_DIR="${HOME_DIR}/.ssh"
    
    # ホームディレクトリが存在しない場合はスキップ
    if [ ! -d "$HOME_DIR" ]; then
        log "スキップ: ${HOME_DIR} が存在しません"
        continue
    fi
    
    # 所有者情報を取得
    OWNER="${USERS[$USER_DIR_RELATIVE]}"
    USER_NAME="${OWNER%%:*}"
    GROUP_NAME="${OWNER##*:}"
    
    log "-----------------------------------------"
    log "処理開始: ${HOME_DIR} (所有者: ${OWNER})"
    log "-----------------------------------------"
    
    # ユーザーが実際に存在するか確認(/etc/passwdで)
    if ! grep -q "^${USER_NAME}:" /etc/passwd 2>/dev/null; then
        log_warn "${USER_NAME} が /etc/passwd に存在しないためスキップします"
        continue
    fi
    
    # 処理開始
    ERROR_OCCURRED=false
    
    # 1. ホームディレクトリの権限を755に設定
    if chmod 755 "$HOME_DIR" 2>/dev/null; then
        log "  ✓ ホームディレクトリ権限を755に設定"
    else
        log_error "  ✗ ホームディレクトリ権限の設定に失敗"
        ERROR_OCCURRED=true
    fi
    
    # 2. ホームディレクトリの所有者を設定
    if chown "$OWNER" "$HOME_DIR" 2>/dev/null; then
        log "  ✓ ホームディレクトリ所有者を ${OWNER} に設定"
    else
        log_error "  ✗ ホームディレクトリ所有者の設定に失敗"
        ERROR_OCCURRED=true
    fi
    
    # 3. .sshディレクトリの処理
    if [ ! -d "$SSH_DIR" ]; then
        log "  情報: ${SSH_DIR} が存在しないため作成します"
        if mkdir -p "$SSH_DIR" 2>/dev/null; then
            log "  ✓ .sshディレクトリを作成"
        else
            log_error "  ✗ .sshディレクトリの作成に失敗"
            ERROR_OCCURRED=true
            ((FAILED++))
            continue
        fi
    fi
    
    log "  処理中: ${SSH_DIR}"
    
    # 4. .sshディレクトリの権限を700に設定
    if chmod 700 "$SSH_DIR" 2>/dev/null; then
        log "    ✓ .sshディレクトリ権限を700に設定"
    else
        log_error "    ✗ .sshディレクトリ権限の設定に失敗"
        ERROR_OCCURRED=true
    fi
    
    # 5. .ssh配下のファイル処理
    FILES_IN_SSH=( "$SSH_DIR"/* )
    if [ ${#FILES_IN_SSH[@]} -gt 0 ]; then
        for file in "${FILES_IN_SSH[@]}"; do
            if [ -f "$file" ]; then
                if chmod 600 "$file" 2>/dev/null; then
                    log "    ✓ ${file##*/} を600に設定"
                else
                    log_warn "    ⚠ ${file##*/} の権限設定に失敗"
                fi
            fi
        done
    else
        log "    情報: .ssh配下にファイルが存在しません"
    fi
    
    # 6. authorized_keysの明示的な処理
    if [ -f "$SSH_DIR/authorized_keys" ]; then
        if chmod 600 "$SSH_DIR/authorized_keys" 2>/dev/null; then
            log "    ✓ authorized_keys を600に設定"
        else
            log_error "    ✗ authorized_keys の権限設定に失敗"
            ERROR_OCCURRED=true
        fi
    else
        log "    情報: authorized_keys が存在しません"
    fi
    
    # 7. 秘密鍵ファイルの処理
    KEY_COUNT=0
    PRIVATE_KEYS=( "$SSH_DIR"/id_* )
    for key_file in "${PRIVATE_KEYS[@]}"; do
        if [ -f "$key_file" ] && [[ "$key_file" != *.pub ]]; then
            if chmod 600 "$key_file" 2>/dev/null; then
                log "    ✓ 秘密鍵 ${key_file##*/} を600に設定"
                ((KEY_COUNT++))
            else
                log_warn "    ⚠ 秘密鍵 ${key_file##*/} の権限設定に失敗"
            fi
        fi
    done
    
    [ $KEY_COUNT -eq 0 ] && log "    情報: 秘密鍵ファイルが存在しません"
    
    # 8. 公開鍵ファイル(.pub)の処理
    PUB_KEYS=( "$SSH_DIR"/*.pub )
    for pub_file in "${PUB_KEYS[@]}"; do
        if [ -f "$pub_file" ]; then
            if chmod 644 "$pub_file" 2>/dev/null; then
                log "    ✓ 公開鍵 ${pub_file##*/} を644に設定"
            else
                log_warn "    ⚠ 公開鍵 ${pub_file##*/} の権限設定に失敗"
            fi
        fi
    done
    
    # 9. known_hostsの処理
    if [ -f "$SSH_DIR/known_hosts" ]; then
        chmod 600 "$SSH_DIR/known_hosts" 2>/dev/null && \
            log "    ✓ known_hosts を600に設定" || \
            log_warn "    ⚠ known_hosts の権限設定に失敗"
    fi
    
    # 10. configの処理
    if [ -f "$SSH_DIR/config" ]; then
        chmod 600 "$SSH_DIR/config" 2>/dev/null && \
            log "    ✓ config を600に設定" || \
            log_warn "    ⚠ config の権限設定に失敗"
    fi
    
    # 11. 所有者とグループを再設定(再帰的)
    if chown -R "$OWNER" "$SSH_DIR" 2>/dev/null; then
        log "    ✓ .ssh配下の所有者を ${OWNER} に設定"
    else
        log_error "    ✗ .ssh所有者の設定に失敗"
        ERROR_OCCURRED=true
    fi
    
    # 処理結果の集計
    if [ "$ERROR_OCCURRED" = true ]; then
        log "  ⚠ 警告: ${SSH_DIR} の処理中にエラーが発生しました"
        ((FAILED++))
    else
        log "  ✓ 完了: ${SSH_DIR} の権限修復が正常に完了しました"
        ((PROCESSED++))
    fi
done

# nullglobを無効化
shopt -u nullglob

# 最終結果のサマリー
log "========================================="
log "処理結果サマリー:"
log "  成功: ${PROCESSED} ユーザー"
log "  失敗: ${FAILED} ユーザー"
log "========================================="

if [ $FAILED -gt 0 ]; then
    log_warn "SSH権限修復スクリプトが警告付きで完了しました"
    exit 1
else
    log "SSH権限修復スクリプトが正常に完了しました"
    exit 0
fi

📝 スクリプトの設置方法

# スクリプトを作成
sudo vim /usr/local/bin/ssh_auth_owner_setting.sh

# 上記のスクリプト内容を貼り付けて保存(ESC → :wq)

# 実行権限を付与
sudo chmod +x /usr/local/bin/ssh_auth_owner_setting.sh

# ログディレクトリの権限確認
sudo touch /var/log/ssh_permission_fix.log
sudo chmod 644 /var/log/ssh_permission_fix.log

# 手動で実行してテスト
sudo /usr/local/bin/ssh_auth_owner_setting.sh

# 終了コードを確認(0=成功、1=警告あり)
echo "終了コード: $?"

# ログを確認
sudo cat /var/log/ssh_permission_fix.log

# 権限が正しく設定されたか確認
ls -ld ~
ls -la ~/.ssh/
stat -c "%a %n" ~ ~/.ssh ~/.ssh/*

⚙️ システム起動時の自動実行設定

方法1: systemdサービスとして登録(推奨)

CentOS 7以降、Amazon Linux 2以降、Ubuntu 16.04以降など、systemdを採用しているシステムではこの方法が推奨されます。

# systemdサービスファイルを作成
sudo vim /etc/systemd/system/ssh-permission-fix.service

以下の内容を記述:

[Unit]
Description=SSH Permission Auto Fix Service
Documentation=https://www.magtranetwork.com/aws/ssh_auth_owner_setting.html
After=local-fs.target remote-fs.target network.target
Before=sshd.service ssh.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ssh_auth_owner_setting.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
TimeoutStartSec=30
Restart=on-failure
RestartSec=5
StartLimitBurst=2

[Install]
WantedBy=multi-user.target
# systemd設定を再読み込み
sudo systemctl daemon-reload

# サービスを有効化(起動時に自動実行)
sudo systemctl enable ssh-permission-fix.service

# 出力例:
# Created symlink /etc/systemd/system/multi-user.target.wants/ssh-permission-fix.service → /etc/systemd/system/ssh-permission-fix.service

# サービス状態を確認
sudo systemctl status ssh-permission-fix.service

# 手動で実行してテスト
sudo systemctl start ssh-permission-fix.service

# 終了コードを確認
sudo systemctl show -p ExecMainStatus ssh-permission-fix.service

# ログを確認
sudo journalctl -u ssh-permission-fix.service -n 50 --no-pager

# 起動時に実行されるか確認
sudo systemctl is-enabled ssh-permission-fix.service
# → enabled と表示されればOK

方法2: rc.local を使用(レガシーシステム)

CentOS 6以前や、systemdを使用していないシステムの場合:

# rc.localに実行権限を付与(CentOS 7以降で必要)
sudo chmod u+x /etc/rc.d/rc.local

# rc.localを編集
sudo vim /etc/rc.d/rc.local

以下の行を追加:

#!/bin/bash
# SSH権限自動修復スクリプトの実行
/usr/local/bin/ssh_auth_owner_setting.sh >> /var/log/ssh_permission_fix.log 2>&1

exit 0

方法3: cron の @reboot を使用

# root の crontab を編集
sudo crontab -e

# 以下の行を追加
@reboot /usr/local/bin/ssh_auth_owner_setting.sh >> /var/log/ssh_permission_fix.log 2>&1

✅ 動作確認手順

# 1. わざと権限を変更してテスト
chmod 777 ~/.ssh
chmod 777 ~/.ssh/authorized_keys

# 2. スクリプトを手動実行
sudo /usr/local/bin/ssh_auth_owner_setting.sh

# 3. 権限が修復されたか確認
ls -la ~/.ssh/
# 期待される出力:
# drwx------  .ssh/           (700)
# -rw-------  authorized_keys (600)

# 4. 新しいSSHセッションでログインテスト
# (現在のセッションは維持したまま、別ターミナルで)
ssh -i your-key.pem ec2-user@localhost

# 5. システム再起動後の自動実行確認
sudo reboot

# 再起動後、ログを確認
sudo cat /var/log/ssh_permission_fix.log
sudo journalctl -u ssh-permission-fix.service

🆘 緊急時の復旧手順: EC2でSSHログインできなくなった場合(完全版)

万が一、権限設定のミスでSSHログインができなくなった場合でも、AWS EC2では復旧が可能です。オンプレミス環境やVMware、VirtualBoxと異なり、EBSボリュームを別インスタンスにマウントすることで修復できます。

💡 Amazon Linux 2023の重要な注意点:

Amazon Linux 2023では、NVMeデバイスにも/dev/xvdaなどの従来型デバイス名がシンボリックリンクとして提供されます。ls -l /dev/xvdaで確認できます。復旧手順は同じですが、デバイス名の表記が異なる場合があります。詳細は手順8で説明します。

復旧手順の全ステップ(実践的詳細版)

  1. 事前準備と状況確認
    • ログイン不可のインスタンスを「インスタンスA」とする
    • 復旧作業用の新規インスタンスを「インスタンスB」とする
    • ⚠️ 重要: インスタンスBはインスタンスAと同じアベイラビリティゾーン(AZ)に作成必須
    • EBSボリュームは同じAZ内のインスタンスにのみアタッチ可能
    • 推奨: 作業前に必ずEBSスナップショットを取得
    # インスタンスAのボリュームIDを確認
    INSTANCE_ID="i-0123456789abcdef0"
    VOLUME_ID=$(aws ec2 describe-instances \
      --instance-ids $INSTANCE_ID \
      --query 'Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId' \
      --output text)
    
    echo "ボリュームID: $VOLUME_ID"
    
    # スナップショット作成
    aws ec2 create-snapshot \
      --volume-id $VOLUME_ID \
      --description "Emergency backup before recovery $(date +%Y%m%d-%H%M%S)" \
      --tag-specifications 'ResourceType=snapshot,Tags=[{Key=Name,Value=EmergencyBackup},{Key=Date,Value='$(date +%Y%m%d)'}]'
    
    # スナップショットIDを記録
    
  2. インスタンスAの停止
    • EC2コンソールで「インスタンスA」を選択
    • 「インスタンスの状態」→「インスタンスを停止」
    • ⚠️ 注意: 「終了」ではなく「停止」を選択
    • ステータスが「stopped」になるまで待機(1〜2分)
  3. ルートボリュームの詳細情報を記録

    以下の情報を必ずメモまたはスクリーンショットで記録してください:

    • 「インスタンスA」の「ストレージ」タブを確認
    • ルートデバイス名(例: /dev/xvda, /dev/sda1)
    • ボリュームID(例: vol-0123456789abcdef0)
    • デバイス(例: /dev/xvda)
    • サイズ、ボリュームタイプ(gp3, gp2等)
    • ボリュームIDをクリックしてボリューム詳細画面へ移動
  4. ルートボリュームのデタッチ
    • ボリューム詳細画面で「アクション」→「ボリュームのデタッチ」
    • 確認ダイアログで「デタッチ」をクリック
    • 状態が「available(使用可能)」になるまで待機(通常30秒〜1分)
  5. 復旧用インスタンスBの作成
    • 「インスタンスを起動」をクリック
    • 同じアベイラビリティゾーンを選択(例: ap-northeast-1a)
    • AMI: インスタンスAと同じOS(Amazon Linux 2、Ubuntu等)を推奨
    • インスタンスタイプ: t3.micro など小さいサイズでOK
    • キーペア: インスタンスAと同じキーペアを使用
    • セキュリティグループ: 自分のIPからSSH(ポート22)を許可
    • ストレージ: デフォルトのまま(8GBなど小さくてOK)
    • 起動して「running」状態になるまで待機
  6. デタッチしたボリュームをインスタンスBにアタッチ
    • デタッチしたボリューム(vol-xxxxx)を選択
    • 「アクション」→「ボリュームのアタッチ」
    • インスタンスBを選択
    • デバイス名は /dev/sdf を指定
    • ⚠️ 注意: ルートボリューム(/dev/xvda)と異なる名前にする
    • 「ボリュームのアタッチ」をクリック
    • 状態が「in-use(使用中)」になることを確認
  7. インスタンスBにSSHログイン
    # Amazon Linux 2/2023の場合
    ssh -i your-key.pem ec2-user@<インスタンスBのパブリックIP>
    
    # Ubuntuの場合
    ssh -i your-key.pem ubuntu@<インスタンスBのパブリックIP>
    
    # CentOS/Rocky/AlmaLinuxの場合
    ssh -i your-key.pem centos@<インスタンスBのパブリックIP>
    
    # Debianの場合
    ssh -i your-key.pem admin@<インスタンスBのパブリックIP>
    
  8. アタッチしたボリュームを確認してマウント
    # デバイス名を確認
    lsblk -f
    
    # 出力例(Nitroインスタンスの場合):
    # NAME          FSTYPE LABEL UUID                                 MOUNTPOINT
    # nvme0n1                                                          
    # └─nvme0n1p1   xfs    /     a1b2c3d4-...                         /
    # nvme1n1                                                          ← これがアタッチしたボリューム
    # └─nvme1n1p1   xfs    /     e5f6g7h8-...                         
    
    # 出力例(Xenインスタンスの場合):
    # NAME    FSTYPE LABEL UUID                                 MOUNTPOINT
    # xvda                                                        
    # └─xvda1 xfs    /     a1b2c3d4-...                         /
    # xvdf                                                        ← これがアタッチしたボリューム
    # └─xvdf1 xfs    /     e5f6g7h8-...                         
    
    # dmesgでアタッチログを確認(デバイス名特定に有効)
    sudo dmesg | tail -20
    
    # ファイルシステムタイプを確認
    sudo file -s /dev/nvme1n1p1  # Nitroの場合
    sudo file -s /dev/xvdf1       # Xenの場合
    
    # マウントポイントを作成
    sudo mkdir -p /mnt/rescue
    
    # ボリュームをマウント
    # Amazon Linux 2/2023(XFS)の場合
    sudo mount -t xfs /dev/nvme1n1p1 /mnt/rescue  # Nitro
    sudo mount -t xfs /dev/xvdf1 /mnt/rescue      # Xen
    
    # Ubuntu(ext4)の場合
    sudo mount -t ext4 /dev/nvme1n1p1 /mnt/rescue  # Nitro
    sudo mount -t ext4 /dev/xvdf1 /mnt/rescue      # Xen
    
    # マウント確認
    df -h | grep rescue
    ls -la /mnt/rescue
    # bin, etc, home, root などのディレクトリが見えればOK
    

    インスタンスタイプ別デバイス名対応表:

    インスタンスタイプ 世代 デバイス名形式
    T3, T3a, M5, M5a, C5, C5a, R5, R5a, I3, I3en, P3など Nitro /dev/nvme[0-9]n1p1 /dev/nvme1n1p1
    T2, M4, M3, C4, C3, R4, R3, I2など Xen /dev/xvd[a-z]1 /dev/xvdf1
    T4g, M6g, M7g, C6g, C7g, R6g, R7gなど Graviton (Nitro) /dev/nvme[0-9]n1p1 /dev/nvme1n1p1

    Amazon Linux 2023の補足: AL2023では、NVMeデバイスに/dev/xvdaなどのシンボリックリンクが作成され、従来の命名規則も使用できます。ls -l /dev/xvdaで確認可能です。

  9. ユーザー名とUID/GIDを確認
    # インスタンスAで使用していたユーザーを確認
    cat /mnt/rescue/etc/passwd | grep -E "/home|/root" | grep -v "nologin\|false"
    
    # 一般ユーザーのみ表示(UID 1000以上)
    awk -F: '$3 >= 1000 {printf "%-15s UID:%-5s GID:%-5s Home:%s\n", $1, $3, $4, $6}' \
      /mnt/rescue/etc/passwd
    
    # .sshディレクトリが存在するユーザーを探す
    sudo find /mnt/rescue/home -maxdepth 2 -name ".ssh" -type d 2>/dev/null
    sudo find /mnt/rescue/root -maxdepth 1 -name ".ssh" -type d 2>/dev/null
    
    # ディストリビューション別のデフォルトユーザー名:
    # - Amazon Linux 2/2023: ec2-user
    # - Ubuntu: ubuntu
    # - CentOS/Rocky/Alma: centos, rocky, alma
    # - Debian: admin
    # - RHEL: ec2-user または cloud-user
    
  10. 権限と所有者の修復(完全版・エラーチェック付き)
    # 修復対象ユーザー名を設定(環境に合わせて変更)
    USER_NAME="ec2-user"
    
    # UID/GIDを取得
    USER_UID=$(grep "^${USER_NAME}:" /mnt/rescue/etc/passwd | cut -d: -f3)
    USER_GID=$(grep "^${USER_NAME}:" /mnt/rescue/etc/passwd | cut -d: -f4)
    
    # エラーチェック
    if [ -z "$USER_UID" ] || [ -z "$USER_GID" ]; then
        echo "❌ エラー: ユーザー ${USER_NAME} が /etc/passwd に存在しません"
        echo "利用可能なユーザー一覧:"
        awk -F: '$3 >= 1000 {print $1}' /mnt/rescue/etc/passwd
        exit 1
    fi
    
    echo "========================================="
    echo "ユーザー情報:"
    echo "  ユーザー名: ${USER_NAME}"
    echo "  UID: ${USER_UID}"
    echo "  GID: ${USER_GID}"
    echo "========================================="
    
    # ホームディレクトリのパス
    HOME_DIR="/mnt/rescue/home/${USER_NAME}"
    SSH_DIR="${HOME_DIR}/.ssh"
    
    # 1. ホームディレクトリの修復(SSHに必須)
    echo "ホームディレクトリを修復中: ${HOME_DIR}"
    sudo chmod 755 "$HOME_DIR" && echo "✓ 権限を755に設定" || echo "✗ 失敗"
    sudo chown ${USER_UID}:${USER_GID} "$HOME_DIR" && echo "✓ 所有者を設定" || echo "✗ 失敗"
    
    # 2. .sshディレクトリの修復
    if [ -d "$SSH_DIR" ]; then
        echo ".sshディレクトリを修復中: ${SSH_DIR}"
        
        # ディレクトリ権限
        sudo chmod 700 "$SSH_DIR" && echo "✓ ディレクトリ権限を700に設定" || echo "✗ 失敗"
        
        # authorized_keys
        if [ -f "$SSH_DIR/authorized_keys" ]; then
            sudo chmod 600 "$SSH_DIR/authorized_keys" && echo "✓ authorized_keys を600に設定" || echo "✗ 失敗"
        else
            echo "⚠ authorized_keys が存在しません"
        fi
        
        # 秘密鍵ファイル
        for key in "$SSH_DIR"/id_*; do
            if [ -f "$key" ] && [[ ! "$key" == *.pub ]]; then
                sudo chmod 600 "$key" && echo "✓ 秘密鍵修復: ${key##*/}" || echo "✗ 失敗: ${key##*/}"
            fi
        done
        
        # 公開鍵ファイル
        for pub in "$SSH_DIR"/*.pub; do
            if [ -f "$pub" ]; then
                sudo chmod 644 "$pub" && echo "✓ 公開鍵修復: ${pub##*/}" || echo "✗ 失敗: ${pub##*/}"
            fi
        done
        
        # known_hosts, config
        [ -f "$SSH_DIR/known_hosts" ] && sudo chmod 600 "$SSH_DIR/known_hosts" && echo "✓ known_hosts修復"
        [ -f "$SSH_DIR/config" ] && sudo chmod 600 "$SSH_DIR/config" && echo "✓ config修復"
        
        # 所有者を一括設定
        sudo chown -R ${USER_UID}:${USER_GID} "$SSH_DIR" && echo "✓ 所有者を一括設定" || echo "✗ 失敗"
        
        echo "✓ .ssh修復完了"
    else
        echo "⚠ 警告: .sshディレクトリが存在しません: ${SSH_DIR}"
    fi
    
    # rootユーザーの修復(存在する場合)
    if [ -d "/mnt/rescue/root/.ssh" ]; then
        echo "rootユーザーの.sshを修復中"
        sudo chmod 755 /mnt/rescue/root
        sudo chmod 700 /mnt/rescue/root/.ssh
        sudo chmod 600 /mnt/rescue/root/.ssh/* 2>/dev/null || true
        sudo chown -R 0:0 /mnt/rescue/root/.ssh
        echo "✓ root .ssh修復完了"
    fi
    
    # 修復結果を確認
    echo "========================================="
    echo "修復結果の確認:"
    echo "========================================="
    ls -ld "$HOME_DIR" | awk '{print "ホームディレクトリ:", $1, $3":"$4, $9}'
    ls -ld "$SSH_DIR" 2>/dev/null | awk '{print ".sshディレクトリ:", $1, $3":"$4, $9}' || echo ".sshディレクトリが存在しません"
    ls -la "$SSH_DIR" 2>/dev/null || echo ".ssh配下のファイルが存在しません"
    
    # 期待される出力:
    # drwxr-xr-x  ec2-user:ec2-user  /mnt/rescue/home/ec2-user  (755)
    # drwx------  ec2-user:ec2-user  /mnt/rescue/home/ec2-user/.ssh  (700)
    # -rw-------  authorized_keys  (600)
    
  11. 自動修復スクリプトの配置(再発防止)
    # スクリプトディレクトリを作成
    sudo mkdir -p /mnt/rescue/usr/local/bin
    
    # スクリプトを配置
    sudo vim /mnt/rescue/usr/local/bin/ssh_auth_owner_setting.sh
    # (上記の検証済み最終版v2.0スクリプトの全内容を貼り付け)
    
    # 実行権限付与
    sudo chmod +x /mnt/rescue/usr/local/bin/ssh_auth_owner_setting.sh
    
    # systemdディレクトリを作成
    sudo mkdir -p /mnt/rescue/etc/systemd/system
    sudo mkdir -p /mnt/rescue/etc/systemd/system/multi-user.target.wants
    
    # systemdサービスファイルを配置
    sudo vim /mnt/rescue/etc/systemd/system/ssh-permission-fix.service
    # (上記のサービスファイル内容を貼り付け)
    
    # サービス有効化(シンボリックリンク作成)
    sudo ln -sf /etc/systemd/system/ssh-permission-fix.service \
      /mnt/rescue/etc/systemd/system/multi-user.target.wants/ssh-permission-fix.service
    
    # 設定確認
    ls -la /mnt/rescue/etc/systemd/system/multi-user.target.wants/ | grep ssh
    
  12. ボリュームのアンマウントとデタッチ
    # ファイルシステムの同期を確保
    sudo sync
    sleep 2
    sudo sync
    
    # カレントディレクトリを移動
    cd ~
    
    # アンマウント
    sudo umount /mnt/rescue
    
    # アンマウント確認
    df -h | grep rescue
    # 何も表示されなければ成功
    
    # インスタンスBからログアウト
    exit
    
    • EC2コンソールでボリュームを選択
    • 「アクション」→「ボリュームのデタッチ」
    • 状態が「available」になるまで待機
  13. 元のインスタンスAにボリュームを再アタッチ
    • デタッチしたボリュームを選択
    • 「アクション」→「ボリュームのアタッチ」
    • インスタンスAを選択
    • 元のデバイス名(手順3でメモした名前)を完全一致で指定
    • ⚠️ 重要: 通常は/dev/xvdaまたは/dev/sda1
    • 「ボリュームのアタッチ」をクリック
    • 状態が「in-use」になることを確認
  14. インスタンスAの起動と接続テスト
    • 「インスタンスA」を選択
    • 「インスタンスの状態」→「インスタンスを開始」
    • ステータスチェックが「2/2 チェックに合格しました」になるまで待機(1〜3分)
    # SSHログイン試行
    ssh -i your-key.pem ec2-user@<インスタンスAの新しいパブリックIP>
    
    # ログイン成功後、スクリプトが実行されたか確認
    sudo cat /var/log/ssh_permission_fix.log
    
    # systemdサービスの状態確認
    sudo systemctl status ssh-permission-fix.service
    
    # 権限が正しく設定されているか最終確認
    ls -ld ~
    ls -la ~/.ssh/
    stat -c "%a %U:%G %n" ~ ~/.ssh ~/.ssh/*
    
    # 期待される出力:
    # 755 ec2-user:ec2-user /home/ec2-user
    # 700 ec2-user:ec2-user /home/ec2-user/.ssh
    # 600 ec2-user:ec2-user /home/ec2-user/.ssh/authorized_keys
    
  15. インスタンスBのクリーンアップ
    • 復旧完了後、「インスタンスB」を選択
    • 「インスタンスの状態」→「インスタンスを終了」
    • 不要なスナップショットがあれば削除

🔍 トラブルシューティング完全版

問題1: スクリプトが終了コード1で失敗する

# ログで詳細を確認
sudo grep ERROR /var/log/ssh_permission_fix.log
sudo grep WARN /var/log/ssh_permission_fix.log

# よくある原因:
# 1. スクリプト内のUSERS配列に存在しないユーザーが定義されている
# 2. rootで実行していない
# 3. ファイルシステムが読み取り専用

# 対処: 実際に存在するユーザーのみを配列に追加
sudo vim /usr/local/bin/ssh_auth_owner_setting.sh
# 不要なユーザーをコメントアウト

# 手動で再実行
sudo /usr/local/bin/ssh_auth_owner_setting.sh
echo "終了コード: $?"

問題2: デバイスが見つからない

# すべてのブロックデバイスを表示
lsblk -f
sudo fdisk -l

# NVMeデバイスの詳細
sudo nvme list

# dmesgでカーネルログを確認
sudo dmesg | tail -30 | grep -i "nvme\|xvd"

問題3: マウント時にエラー

# ファイルシステムタイプを確認
sudo file -s /dev/nvme1n1p1

# XFSの場合
sudo mount -t xfs /dev/nvme1n1p1 /mnt/rescue

# ext4の場合
sudo mount -t ext4 /dev/nvme1n1p1 /mnt/rescue

# エラーがある場合
sudo fsck -n /dev/nvme1n1p1  # 読み取り専用チェック

問題4: アンマウントできない

# 使用中のプロセスを確認
sudo lsof +D /mnt/rescue
sudo fuser -vm /mnt/rescue

# カレントディレクトリを移動
cd ~

# 強制アンマウント(最終手段)
sudo fuser -km /mnt/rescue
sudo umount -l /mnt/rescue

問題5: インスタンスAが起動しない

# システムログを確認
# EC2コンソール → アクション → モニタリングとトラブルシューティング → システムログを取得

# 主な原因:
# 1. ルートボリュームのデバイス名が間違っている
#    → 再度デタッチして正しいデバイス名で再アタッチ
# 2. /etc/fstab の設定ミス
#    → 再マウントして /mnt/rescue/etc/fstab を確認・修正
# 3. ファイルシステムが破損
#    → fsck を実行

🛡️ セキュリティベストプラクティス

適切なSSH権限設定

ファイル/ディレクトリ 推奨権限 数値 説明
ホームディレクトリ
/home/ec2-user
drwxr-xr-x 755 必須: 他ユーザーの実行権限が必要
⚠️ 700だとSSH拒否
~/.ssh/ drwx------ 700 必須: 所有者のみアクセス
~/.ssh/authorized_keys -rw------- 600 必須: 公開鍵リスト
~/.ssh/id_rsa -rw------- 600 必須: 秘密鍵(400も可)
~/.ssh/id_*.pub -rw-r--r-- 644 推奨: 公開鍵
~/.ssh/config -rw------- 600 推奨: SSH設定

SSH設定強化(/etc/ssh/sshd_config)

# バックアップを取得
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup.$(date +%Y%m%d)

# セキュリティ強化設定を追加
sudo vim /etc/ssh/sshd_config

以下の設定を追加/変更:

# 公開鍵認証を有効化
PubkeyAuthentication yes

# パスワード認証を無効化
PasswordAuthentication no

# 空パスワードを禁止
PermitEmptyPasswords no

# rootログインを禁止
PermitRootLogin no

# Challengeレスポンス認証を無効化
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no

# ログレベルを詳細に
LogLevel VERBOSE

# 最大認証試行回数を制限
MaxAuthTries 3

# セッションタイムアウト設定
ClientAliveInterval 300
ClientAliveCountMax 2

# 特定ユーザーのみ許可(オプション)
AllowUsers ec2-user ubuntu
# 設定ファイルの文法チェック
sudo sshd -t

# エラーがなければSSHサービスを再起動
sudo systemctl restart sshd

# ⚠️ 別のSSHセッションを開いておくこと

📊 まとめ: SSH権限管理の完全チェックリスト

📋 導入時の必須チェック項目

☑ 自動修復スクリプト(v2.0)を /usr/local/bin に配置

☑ 実行権限(chmod +x)を付与

☑ systemdサービスファイルを作成

☑ systemctl enable で有効化

☑ 手動実行でテスト

☑ ログファイルを確認

☑ 再起動して自動実行を確認

🔄 日常運用のチェック項目

☑ ホームディレクトリが755であることを確認(ls -ld ~)

☑ .sshディレクトリが700であることを確認

☑ authorized_keysが600であることを確認

☑ 所有者が正しいことを確認

☑ /var/log/ssh_permission_fix.log を定期確認

🛡️ セキュリティ対策のチェック項目

☑ パスワード認証を無効化(PasswordAuthentication no)

☑ rootログインを禁止(PermitRootLogin no)

☑ セキュリティグループでIP制限

☑ 定期的なAMIバックアップ

☑ CloudWatch Logsで監視

☑ 年1回の鍵ローテーション

🆘 緊急時対応のチェック項目

☑ 復旧手順書を作成・共有

☑ インスタンスのAZを記録

☑ ルートボリュームのデバイス名を記録

☑ 定期的に復旧訓練を実施

☑ Systems Manager Session Managerを準備

🎯 結論

SSH公開鍵認証は強力なセキュリティ手段ですが、適切な権限管理が大前提です。この記事で紹介した検証済み最終版v2.0の自動修復スクリプトを導入することで、人的ミスによるログインロックアウトのリスクを大幅に軽減できます。

🔑 特に重要なポイント:

  1. 権限設定: ホームは755、.sshは700、ファイルは600
  2. 自動化: systemdサービスで起動時に自動実行
  3. 復旧手順: EBSデタッチ・アタッチは同一AZ内必須
  4. セキュリティ: パスワード認証無効化+IP制限

クラウド環境では自動化と再現性が重要です。手動修正に頼らず、スクリプトによる自動管理と多層的なバックアップ戦略を組み合わせることで、より安全で運用しやすいシステムを構築しましょう。


Reference: Tech Blog citing related sources