🔒

ssh のセキュリティ対策をまとめてみた

2024/05/03に公開

いくつかのサイトを巡って ssh のセキュリティ対策をまとめてみました。

/etc/ssh/sshd_config で対応可能な対策

詳細は sshd_config(5) の man ページの参照をお勧めします。

方針

  • 公開鍵認証方式だけ有効にし、それ以外の認証方式は無効
  • ユーザー単位、接続元 IP 単位で接続を許可する
  • グループ単位で接続を許可する
  • ポートフォワーディングは禁止
  • ログは詳細めに取得

施策

  • プロトコルを Version 2 に限定する
    Protocol 2
  • 空のパスワードを使用するユーザーをブロックする
    PermitEmptyPasswords no
  • root によるログインを禁止する
    PermitRootLogin no
  • パスワード認証によるログインを禁止する
    PasswordAuthentication no
  • チャレンジ / レスポンス認証を無効にする
    ChallengeResponseAuthentication no
  • キーボードインターフェースによる認証を無効にする
    KbdInteractiveAuthentication no
  • GSSAPI(Generic Security Service API)ベースのユーザ認証を無効にする
    GSSAPIAuthentication no
  • Kerberos 認証を無効にする
    KerberosAuthentication no
  • SSH 鍵認証を使用する
    PubkeyAuthentication yes
  • 暗号強度の強い鍵に制限する
    HostKey /etc/ssh/ssh_host_ed25519_key
  • IP アドレスによるアクセス制限をする
    AllowUsers @131.113.0.0/16
    AllowUsers @192.168.1. @10.0.0. !
    @192.168.1.2
  • 特定のユーザーだけにアクセスを制限する
    AllowUsers alice bob
  • 特定のグループだけにアクセスを制限する
    AllowGroups example-group
  • 同時接続数を制限する
    MaxStartups 10:30:100
  • ポートフォワーディングを禁止する
    AllowTcpForwarding no
    AllowStreamLocalForwarding no
    GatewayPorts no
    PermitTunnel no
  • 認証サービスのログを出力する
    SyslogFacility AUTHPRIV
  • 詳細なログを取得する
    LogLevel VERBOSE

設定ルール

  • /etc/ssh/sshd_config ファイルは編集しない(デフォルト状態のまま)
  • /etc/ssh/sshd_config.d/ ディレクトリー内に以下の命名規則に則ったファイルを作成し、変更内容を記述する
    • /etc/ssh/sshd_config.d/50-redhat.conf の前に読み込ませる場合

      49 以下の 2 桁の数字 + 名前 + .conf
      例 49-crypto-policy-override.conf

  • /etc/ssh/sshd_config.d/ ディレクトリー内のファイルは辞書式順序で読み込まれる[1]

構文チェック

sudo /usr/sbin/sshd -t

現在の設定値の確認

sudo /usr/sbin/sshd -T

変更後に sshd デーモンを再読み込みして設定を反映

sudo systemctl reload sshd.service

他のシステム等との組み合わせが必要な対策

  • デフォルトのポート番号を変更する
    Port 22 → Port xxxxx
    未使用ポートをアサインする必要あり
    ファイアウォールに新しいポートの穴開け、22 番ポートの穴を閉じる
  • 暗号化ポリシーをより強いものに変更する
    DEFAULT → FUTURE
    /etc/crypto-policies/config
    update-crypto-policies --set FUTURE
    要 reboot
  • 安全ではないプロトコルの使用禁止
    telnet, rsh, rlogoin, ftp 等
  • 踏み台サーバー(ジャンプホスト)経由で接続
  • fail2ban をインストールする
  • 2 要素認証を使用する ⇒ トークン(Google Authenticator 等)との組み合わせ
    libpam-google-authenticator

Ansible を使用した設定

Alma Linux 9.3 のノードに対し、Ansible を使用して以下の範囲でセキュリティ対策を実施します。

  • /etc/ssh/sshd_config で対応可能な対策 ※AllowGroups は除く
  • 暗号化ポリシーをより強いものに変更する

現状確認

/etc/ssh/sshd_config を上書きする設定を確認します。

[opadmin@node1 ~]$ sudo ls -l /etc/ssh/sshd_config.d/
total 4
-rw-------. 1 root root 719 Sep 27  2023 50-redhat.conf
[opadmin@node1 ~]$

50-redhat.conf が存在します。記載内容を確認します。

[opadmin@node1 ~]$ sudo cat /etc/ssh/sshd_config.d/50-redhat.conf
# This system is following system-wide crypto policy. The changes to
# crypto properties (Ciphers, MACs, ...) will not have any effect in
# this or following included files. To override some configuration option,
# write it before this block or include it before this file.
# Please, see manual pages for update-crypto-policies(8) and sshd_config(5).
Include /etc/crypto-policies/back-ends/opensshserver.config

SyslogFacility AUTHPRIV

ChallengeResponseAuthentication no

GSSAPIAuthentication yes
GSSAPICleanupCredentials no

UsePAM yes

X11Forwarding yes
 
# It is recommended to use pam_motd in /etc/pam.d/sshd instead of PrintMotd,
# as it is more configurable and versatile than the built-in version.
PrintMotd no

[opadmin@node1 ~]$

今回の作業で関係する部分です。

パラメーター 設定されている値 変更後の値 備考
SyslogFacility AUTHPRIV - 変更しない
ChallengeResponseAuthentication no - 変更しない
GSSAPIAuthentication yes no

play

/etc/ssh/sshd_config.d/49-crypto-policy-override.conf を作成し、その中に変更する設定を記述します。一部は /etc/ssh/sshd_config.d/50-redhat.conf を変更しています。 update-crypto-policies コマンドの実行以外は ansible.builtin.lineinfile モジュールで一つずつ設定します。

ssh_security.yml
---
- name: SSH security measures with Ansible
  hosts: node1
  gather_facts: false

  vars:
    FilePath: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf
    FilePath2: /etc/ssh/sshd_config.d/50-redhat.conf

  tasks:
    - name: Limit protocol to Version 2
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^Protocol'
        line: 'Protocol 2'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Block users with empty passwords
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^PermitEmptyPasswords'
        line: 'PermitEmptyPasswords no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Disallow login by root
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^PermitRootLogin'
        line: 'PermitRootLogin no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Prohibit login by password authentication
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^PasswordAuthentication'
        line: 'PasswordAuthentication no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Disable challenge/response authentication
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^ChallengeResponseAuthentication'
        line: 'ChallengeResponseAuthentication no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Disable authentication using the keyboard interface
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^KbdInteractiveAuthentication'
        line: 'KbdInteractiveAuthentication no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Disable GSSAPI-based user authentication
      ansible.builtin.lineinfile:
        path: "{{ FilePath2 }}"
        create: true
        state: present
        regexp: '^GSSAPIAuthentication'
        line: 'GSSAPIAuthentication no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Disable Kerberos authentication
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^KerberosAuthentication'
        line: 'KerberosAuthentication no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Using SSH Key Authentication
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^PubkeyAuthentication'
        line: 'PubkeyAuthentication yes'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Restrict access by IP address
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^AllowUsers'
        line: 'AllowUsers *@192.168.0.0/24'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Restrict to keys with strong cryptographic strength
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^HostHostKey'
        line: 'HostKey /etc/ssh/ssh_host_ed25519_key'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Limit the number of simultaneous connections
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^MaxStartups'
        line: 'MaxStartups 3:30:10'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Disable port forwarding
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^AllowTcpForwarding'
        line: 'AllowTcpForwarding no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Disallow local forwarding of streams
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^AllowStreamLocalForwarding'
        line: 'AllowStreamLocalForwarding no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Do not allow port relay
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^GatewayPorts'
        line: 'GatewayPorts no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Tunnels not allowed
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^PermitTunnel'
        line: 'PermitTunnel no'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Output logs for authentication services
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^SyslogFacility'
        line: 'SyslogFacility AUTHPRIV'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Obtain detailed logs
      ansible.builtin.lineinfile:
        path: "{{ FilePath }}"
        create: true
        state: present
        regexp: '^LogLevel'
        line: 'LogLevel VERBOSE'
        owner: root
        group: root
        mode: 0600
      become: true

    - name: Change encryption policy to a stronger one
      ansible.builtin.command:
        cmd: 'update-crypto-policies --set FUTURE'
      become: true

    - name: Restart the node
      ansible.builtin.reboot:
      become: true

play の実行結果

各モジュールの実行前後の状態がわかるよう --diff オプションを付けて実行しました。

y_mrok@ctrl:~/ex$ ansible-playbook ssh/ssh_security.yml --diff

PLAY [SSH security measures with Ansible] *********************************************************************************************

TASK [Limit protocol to Version 2] ****************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -0,0 +1 @@
+Protocol 2

--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (file attributes)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (file attributes)
@@ -1 +1 @@
-mode: '0644'
+mode: '0600'

changed: [node1]

TASK [Block users with empty passwords] ***********************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -1 +1,2 @@
 Protocol 2
+PermitEmptyPasswords no

changed: [node1]

TASK [Disallow login by root] *********************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -1,2 +1,3 @@
 Protocol 2
 PermitEmptyPasswords no
+PermitRootLogin no

changed: [node1]

TASK [Prohibit login by password authentication] **************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -1,3 +1,4 @@
 Protocol 2
 PermitEmptyPasswords no
 PermitRootLogin no
+PasswordAuthentication no

changed: [node1]

TASK [Disable challenge/response authentication] **************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -2,3 +2,4 @@
 PermitEmptyPasswords no
 PermitRootLogin no
 PasswordAuthentication no
+ChallengeResponseAuthentication no

changed: [node1]

TASK [Disable authentication using the keyboard interface] ****************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -3,3 +3,4 @@
 PermitRootLogin no
 PasswordAuthentication no
 ChallengeResponseAuthentication no
+KbdInteractiveAuthentication no

changed: [node1]

TASK [Disable GSSAPI-based user authentication] ***************************************************************************************
--- before: /etc/ssh/sshd_config.d/50-redhat.conf (content)
+++ after: /etc/ssh/sshd_config.d/50-redhat.conf (content)
@@ -9,7 +9,7 @@
 
 ChallengeResponseAuthentication no
 
-GSSAPIAuthentication yes
+GSSAPIAuthentication no
 GSSAPICleanupCredentials no
 
 UsePAM yes

changed: [node1]

TASK [Disable Kerberos authentication] ************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -4,3 +4,4 @@
 PasswordAuthentication no
 ChallengeResponseAuthentication no
 KbdInteractiveAuthentication no
+KerberosAuthentication no

changed: [node1]

TASK [Using SSH Key Authentication] ***************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -5,3 +5,4 @@
 ChallengeResponseAuthentication no
 KbdInteractiveAuthentication no
 KerberosAuthentication no
+PubkeyAuthentication yes

changed: [node1]

TASK [Restrict access by IP address] **************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -6,3 +6,4 @@
 KbdInteractiveAuthentication no
 KerberosAuthentication no
 PubkeyAuthentication yes
+AllowUsers *@192.168.0.0/24

changed: [node1]

TASK [Restrict to keys with strong cryptographic strength] ****************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -7,3 +7,4 @@
 KerberosAuthentication no
 PubkeyAuthentication yes
 AllowUsers *@192.168.0.0/24
+HostKey /etc/ssh/ssh_host_ed25519_key

changed: [node1]

TASK [Limit the number of simultaneous connections] ***********************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -8,3 +8,4 @@
 PubkeyAuthentication yes
 AllowUsers *@192.168.0.0/24
 HostKey /etc/ssh/ssh_host_ed25519_key
+MaxStartups 3:30:10

changed: [node1]

TASK [Disable port forwarding] ********************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -9,3 +9,4 @@
 AllowUsers *@192.168.0.0/24
 HostKey /etc/ssh/ssh_host_ed25519_key
 MaxStartups 3:30:10
+AllowTcpForwarding no

changed: [node1]

TASK [Disallow local forwarding of streams] *******************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -10,3 +10,4 @@
 HostKey /etc/ssh/ssh_host_ed25519_key
 MaxStartups 3:30:10
 AllowTcpForwarding no
+AllowStreamLocalForwarding no

changed: [node1]

TASK [Do not allow port relay] ********************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -11,3 +11,4 @@
 MaxStartups 3:30:10
 AllowTcpForwarding no
 AllowStreamLocalForwarding no
+GatewayPorts no

changed: [node1]

TASK [Tunnels not allowed] ************************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -12,3 +12,4 @@
 AllowTcpForwarding no
 AllowStreamLocalForwarding no
 GatewayPorts no
+PermitTunnel no

changed: [node1]

TASK [Output logs for authentication services] ****************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -13,3 +13,4 @@
 AllowStreamLocalForwarding no
 GatewayPorts no
 PermitTunnel no
+SyslogFacility AUTHPRIV

changed: [node1]

TASK [Obtain detailed logs] ***********************************************************************************************************
--- before: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
+++ after: /etc/ssh/sshd_config.d/49-crypto-policy-override.conf (content)
@@ -14,3 +14,4 @@
 GatewayPorts no
 PermitTunnel no
 SyslogFacility AUTHPRIV
+LogLevel VERBOSE

changed: [node1]

TASK [Change encryption policy to a stronger one] *************************************************************************************
changed: [node1]

TASK [Restart the node] ***************************************************************************************************************
changed: [node1]

PLAY RECAP ****************************************************************************************************************************
node1                      : ok=20   changed=20   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

y_mrok@ctrl:~/ex$ 

実行前後の比較

play の実行前後の設定状態の比較結果です。左側が実行前、右側が実行後です。パラメーター名でソート済みです。/etc/ssh/sshd_config.d/49-crypto-policy-override.conf の設定内容や
/etc/ssh/sshd_config.d/50-redhat.conf の変更内容が反映されていることを確認できます。

暗号化ポリシーは FUTURE になっています。

[opadmin@node1 ~]$ sudo cat /etc/crypto-policies/config
FUTURE
[opadmin@node1 ~]$
脚注
  1. https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/9/html/securing_networks/making-openssh-more-secure_assembly_using-secure-communications-between-two-systems-with-openssh ↩︎

Discussion