Ansible でSSHのセキュリティ設定

Linux
Linux
スポンサーリンク

UNIX系のサーバーでは、クライアントのリモート通信に SSH を使うことが標準となっています。すなわち、公開サーバーで SSHサーバー(sshd) が動作していることは容易に推測できるため、攻撃の格好の的になることはどうしても避けられません。そこで今回は、 SSHのセキュリティの3点セット「パスワード認証の無効化」「rootユーザでのログイン禁止」「sshdの待受ポートの変更」を、Ansible で設定してみたいと思います。

 
この記事では、Ansible で実行するタスクは、ロール(Role)に書いて、そのロールを Playbook から呼び出すといった手順で設定しています。ロールについては「Ansible Roles の使い方(Playbookの分割と再利用)」の記事をご参照ください。

SELinux 無効化ロールの作成

sshd 等、各種ミドルウェアの設定を変更するには、SELinux の設定も変更する必要があります。賛否はあるかと思いますが、今回は SELinux を無効にすることにします。

ロールディレクトリの作成(ロール名は「disable_selinux」になります)

mkdir /etc/ansible/roles/disable_selinux

タスクディレクトリの作成

mkdir /etc/ansible/roles/disable_selinux/tasks

SELinux 無効化タスクの作成
vi /etc/ansible/roles/disable_selinux/tasks/main.yml

---
- name: SELinuxを無効化
  selinux: state=disabled

---
 YAMLフォーマットのファイルは、慣習的に「---」(ハイフン3つ)から始めます。

- name:
 タスクの簡単な説明を書きます。(これはどのタスクでも共通です)

selinux:
 SELinuxモジュールを使って、SELinuxを無効にします。「state=disabled」を指定した場合は、「setenforce 0」コマンドの実行と、SELinuxの設定ファイル(/etc/selinux/config)の「SELINUX=enforcing」を「SELINUX=disabled」に書換えてくれます。

SSHD セキュリティ設定ロールの作成

本題の sshd のセキュリティ設定を行うロールの作成です。

ロールディレクトリの作成(ロール名は「sshd」になります)

mkdir /etc/ansible/roles/sshd

タスクディレクトリの作成

mkdir /etc/ansible/roles/sshd/tasks

パスワード認証の無効化

パスワード認証の無効化に加えて、チャレンジ・レスポンス認証(ChallengeResponseAuthentication)が有効の場合、事実上パスワード認証も有効になってしまいますので、こちらも合わせて no にしておきます。

vi /etc/ansible/roles/sshd/tasks/main.yml

---
- name: パスワード認証の無効化
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^PasswordAuthentication"
    insertafter: "^#PasswordAuthentication"
    line: "PasswordAuthentication no"

- name: チャレンジ・レスポンス認証の無効化
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^ChallengeResponseAuthentication"
    insertafter: "^#ChallengeResponseAuthentication"
    line: "ChallengeResponseAuthentication no"

lineinfile:
 lineinfileモジュールを使って、sshdの設定ファイルを編集します。

dest:
 編集するファイルのパスを指定

regexp:
 ここに指定した正規表現にマッチした行を、lineで指定した文字列に置き換える。(ただし lineで指定した文字列と、まったく同じ場合はなにもしない)

insertafter:
 regexp にマッチする行が無かった場合、ここに指定した正規表現にマッチした次の行に、lineで指定した文字列を挿入する。マッチしない場合はファイル末尾に、lineで指定した文字列が挿入されます。

line:
 置換または挿入する文字列を指定します。

lineinfileモジュールの動作サンプル

上の説明だと regexp、insertafter、line の動作が少々分かりづらいですね(^^;) 以下にこのタスクを実行した時の置換/挿入例をあげておきます。

regexp にマッチするので lineで指定した文字列に置き換える。

PasswordAuthentication yes
 ↓
PasswordAuthentication no

regexp にマッチするが lineで指定した文字列と同じなのでなにもしない。

PasswordAuthentication no ←(なにもしない。そのまま)

regexp にマッチしないので、insertafter にマッチした次の行に、 lineで指定した文字列を挿入する。

#PasswordAuthentication yes
 ↓
#PasswordAuthentication yes
PasswordAuthentication no

regexp にも insertafter にもマッチしない場合は、 lineで指定した文字列がファイル末尾に挿入されます。

PasswordAuthentication no ←(ファイル末尾に挿入)

root ユーザでのログイン禁止

同様に、root ユーザでのログイン禁止設定もlineinfileモジュールを使います。

vi /etc/ansible/roles/sshd/tasks/main.yml(続き)

- name: rootユーザのログイン禁止
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^PermitRootLogin"
    insertafter: "^#PermitRootLogin"
    line: "PermitRootLogin no"

sshd の待受ポート変更

タスクを作成する前に、設定するsshdの待受ポートを変数として定義しておきます。

ロールの変数の定義は、変数の優先度が高い「vars」ディレクトリまたは、優先度が一番低い「defaults」ディレクトリで定義します。Playbook などから変数を上書き出来るように、今回は defaults ディレクトリで定義することにします。

Ansibleの変数の優先度については下記をご参照ください。
Variable Precedence: Where Should I Put A Variable? | Ansible Documentation

変数定義ディレクトリの作成

mkdir /etc/ansible/roles/sshd/defaults

変数定義ファイルの作成
vi /etc/ansible/roles/sshd/defaults/main.yml

sshd_port: 50022

 
ロールで定義する変数は、先頭にロール名(上の場合は sshd_ )をプレフィックスとして付けておくのが良いそうです。ポートは、動的/プライベートポート(49513~65535)の中からお好みでご選択ください。

sshdの待受ポート変更するタスクの作成です。
vi /etc/ansible/roles/sshd/tasks/main.yml(続き)

- name: "待受ポートを {{ sshd_port }} に変更"
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^Port"
    insertafter: "^#Port"
    line: "Port {{ sshd_port }}"

 
先ほど登録した変数 sshd_port を {{}} で囲んで呼び出します。「Port {{ sshd_port }}」は「Port 50022」に展開されます。また、変数を値に使う場合は、「"」もしくは「'」でクォートしないとエラーになりますので注意です。

sshd の再起動

このタスクが実行されたタイミングで、sshdの待受ポートが 22番 から 50022番 に変更されますが、AnsibleのSSH接続はポート22番のまま維持されますので、この後のタスクも問題無く実行できます。

vi /etc/ansible/roles/sshd/tasks/main.yml(続き)

- name: 再起動
  service:
    name: sshd
    state: restarted

 
service:
 serviceモジュールを使って、sshdサービスを再起動します。

name:
 対象のサービス名を指定します(今回は sshd)

state:
 行う操作を指定します。サービスを再起動する場合は「restarted」を指定します。

firewalld ポートオープンとクローズ

firewalldで、変更したsshdポートをオープンし、標準のsshdポート(22番)をクローズします。

firewalld で変更したsshdポートをオープン

vi /etc/ansible/roles/sshd/tasks/main.yml(続き)

- name: "firewalld ポート {{ sshd_port }}/tcp のオープン"
  firewalld:
    port: "{{ sshd_port }}/tcp"
    permanent: true
    state: enabled
    immediate: true

 
firewalld:
 firewalldモジュールを使って、firewalld設定を変更します。

port:
 変更したsshdポートと、IPプロトコルを指定します。sshdの場合はIPプロトコルはTCPになりますので、「{{ sshd_port }}/tcp」のように指定します。

permanent:
 OS再起動後もこの設定を有効にする場合は「true」を指定します。

state:
 ポートをオープンにする場合は「enabled」、クローズする場合は「disabled」を指定します。

immediate:
 permanent: true としている場合は、必ず「true」を指定しておきましょう「firewall-cmd --reload」を実行してくれます。permanent: true の時に、immediate: true を指定しないと、firewalldが再起動されるまで設定が有効にならないので注意です。

firewalld で標準のsshdポート(22番)をクローズ

最後に、firewalldで標準のsshdポートをクローズします。タスクの「service: ssh」と「port: 22/tcp」は、同じ意味です。CentOS7では、サービス指定(ssh)で標準のsshdポート(22番)がオープンされていますので、「service: ssh」だけでも構いませんが、念のため「port: 22/tcp」もクローズするように設定しています。

vi /etc/ansible/roles/sshd/tasks/main.yml(続き)

- name: "firewalld サービス ssh のクローズ"
  firewalld:
    service: ssh
    permanent: true
    state: disabled
    immediate: true

- name: "firewalld ポート 22/tcp のクローズ"
  firewalld:
    port: 22/tcp
    permanent: true
    state: disabled
    immediate: true

 

完成したSSHD セキュリティ設定ロール

以上でSSHDセキュリティ設定ロールの完成です。まとめると下記のようになります。

タスクファイル

vi /etc/ansible/roles/sshd/tasks/main.yml

---
- name: パスワード認証の無効化
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^PasswordAuthentication"
    insertafter: "^#PasswordAuthentication"
    line: "PasswordAuthentication no"

- name: チャレンジ・レスポンス認証の無効化
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^ChallengeResponseAuthentication"
    insertafter: "^#ChallengeResponseAuthentication"
    line: "ChallengeResponseAuthentication no"

- name: rootユーザのログイン禁止
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^PermitRootLogin"
    insertafter: "^#PermitRootLogin"
    line: "PermitRootLogin no"

- name: "待受ポートを {{ sshd_port }} に変更"
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "^Port"
    insertafter: "^#Port"
    line: "Port {{ sshd_port }}"

- name: 再起動
  service:
    name: sshd
    state: restarted

- name: "firewalld ポート {{ sshd_port }}/tcp のオープン"
  firewalld:
    port: "{{ sshd_port }}/tcp"
    permanent: true
    state: enabled
    immediate: true

- name: "firewalld サービス ssh のクローズ"
  firewalld:
    service: ssh
    permanent: true
    state: disabled
    immediate: true

- name: "firewalld ポート 22/tcp のクローズ"
  firewalld:
    port: 22/tcp
    permanent: true
    state: disabled
    immediate: true

 

変数定義ファイル

vi /etc/ansible/roles/sshd/defaults/main.yml

sshd_port: 50022

 

呼出し元の Playbook の作成

呼出し元の Playbook では、対象ホストとAnsible実行ユーザを指定します。

vi /etc/ansible/site.yml

---
- hosts: 172.16.1.111
  remote_user: ansible
  become: true
  roles:
    - disable_selinux
    - sshd

 
- hosts:
 処理対象のホストを指定します。ホストのIPアドレスまたはFQDNが、インベントリファイル「/etc/ansible/hosts」に、記載されている必要があります。

remote_user:
 Ansible実行ユーザを指定します。ユーザの作成方法は「Ansible でユーザの作成」をご参照ください。

become:
 タスクを root 権限で事項する場合は、「true」を指定します。サーバーの各種設定は、ほとんどの場合 root 権限が必要になりますので、remote_user が root 以外の場合は、必須の設定になるかと思います。
Become (Privilege Escalation) | Ansible Documentation

roles:
 この下に、処理を実行するロールを書いていきます。ロールの指定方法には色々オプションがあるようです、詳細は公式マニュアルをご参照ください。
Playbook Roles and Include Statements | Ansible Documentation

Playbook(ロール)の実行

構文チェック

Playbook を「--syntax-check」オプションを指定して実行します。なにもエラーが表示されなければOKです。

cd /etc/ansible/
ansible-playbook site.yml --syntax-check
 
playbook: site.yml

実行

Playbook(ロール)を実行します。

cd /etc/ansible/
ansible-playbook site.yml

以下のような表示であれば成功です。

PLAY [172.16.1.111] ************************************************************

TASK [setup] *******************************************************************
Enter passphrase for key '/home/foo/.ssh/id_rsa': 
ok: [172.16.1.111]

TASK [disable_selinux : SELinuxを無効化] *******************************************
changed: [172.16.1.111]

TASK [sshd : パスワード認証の無効化] ******************************************************
changed: [172.16.1.111]

TASK [sshd : チャレンジ・レスポンス認証の無効化] ************************************************
ok: [172.16.1.111]

TASK [sshd : rootユーザのログイン禁止] ***************************************************
changed: [172.16.1.111]

TASK [sshd : 待受ポートを 50022 に変更] *************************************************
changed: [172.16.1.111]

TASK [sshd : 再起動] **************************************************************
changed: [172.16.1.111]

TASK [sshd : firewalld ポート 50022/tcp のオープン] ************************************
changed: [172.16.1.111]

TASK [sshd : firewalld サービス ssh のクローズ] *****************************************
changed: [172.16.1.111]

TASK [sshd : firewalld ポート 22/tcp のクローズ] ***************************************
ok: [172.16.1.111]

PLAY RECAP *********************************************************************
172.16.1.111               : ok=10   changed=7    unreachable=0    failed=0    

 

SSH標準ポート(22番)で、接続できないことや、パスワード認証ができないことを確認できるかと思います。

ssh ansible@172.16.1.111
ssh: connect to host 172.16.1.111 port 22: No route to host
 
ssh -p50022 ansible@172.16.1.111 -o PreferredAuthentications=password
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

終わりに

sshdの待受ポートの変更と、firewalldの設定変更を間違ってしまい、サーバーにまったく接続できなくなった苦い思いは、皆さんも一度は経験があるのではないでしょうか(^^;)

手作業で設定している以上、どうしてもミスは付き物です。Ansibleで設定を自動化してしまえば、このような苦い思いをすることも少なくできるかもしれませんね。

コメント

タイトルとURLをコピーしました