AnsibleでLinuxサーバーのハードニングを自動化:一貫したセキュリティ設定とポリシー遵守の確保

Security tutorial - IT technology blog
Security tutorial - IT technology blog

背景とサーバーハードニングの自動化が必要な理由

IT分野において、セキュリティは常に最重要事項です。しかし、私も最初はかなり油断していました。ある運命的な夜、午前2時頃、電話が鳴り止まなくなりました。ログを開くと、本番サーバーがSSHブルートフォース攻撃を集中して受けていることに気づき、肝を冷やしました。心臓がドキドキする中、私は急いでIPブロック、ポート変更、設定リセットのコマンドを打ち込みました。その夜は徹夜で、もっと早く防げたはずのインシデントに対処するだけでした。

その忘れられない経験の後、私は各サーバーを手動でセキュリティ設定することには多くのリスクが潜んでいることに気づきました。些細なミス、たとえば手順を忘れたり、コマンドを誤入力したり、あるいは単にサーバー間のわずかな違いであっても、深刻な結果を招く可能性があります。数十、あるいは数百ものサーバーからなるシステムでは、一貫性を維持し、すべてが適切にハードニング(強化)されることを保証することは、ほとんど不可能です。

その時、私がAnsibleに目を向け始めたのです。このツールは、迅速な設定展開を助けるだけでなく、すべてのサーバー(新品のものから稼働中のものまで)が同じセキュリティ設定を持つことを保証してくれます。このプロセスは自動的かつ繰り返し実行可能(冪等性)であるため、以前のように徹夜することなく、基本的な脆弱性から強固に保護されたシステムに対してより安心して取り組めるようになりました。

Ansibleのインストール:自動化の旅を始めよう

始めるには、Ansible Controller(制御マシン)として機能するコンピューターと、Managed Nodes(ハードニングが必要なサーバー)が必要です。Ansibleの特別な点は、Managed Nodesにエージェントのインストールが不要なことです。代わりにSSHを使用して接続し、コマンドを実行するため、非常に便利で安全です。

1. Ansible Controllerのインストール

制御マシンでのAnsibleのインストールは非常に簡単です。Pythonとpipをインストールし、その後pipを使用してAnsibleをインストールするだけです。Ubuntuでの例として、以下のコマンドを実行します。


sudo apt update
sudo apt install python3 python3-pip -y
pip3 install ansible

Ansibleが正常にインストールされたことを確認します:


ansible --version

2. Managed Nodesの準備

ハードニングが必要なサーバーには、Python(通常は利用可能)とSSHサーバーが稼働していることだけが必要です。最も重要なのは、ControllerからManaged NodesへのSSHキーベース認証を設定することです。これにより、Ansibleは手動でパスワードを入力することなく接続できます。


ssh-keygen -t rsa -b 4096
ssh-copy-id user@your_server_ip

3. Inventoryファイルの作成

ファイルinventory(またはhosts)は、Ansibleが管理するサーバーを定義する場所です。管理と設定適用を容易にするために、サーバーをグループに編成することをお勧めします。


[web_servers]
web1.example.com
web2.example.com

[db_servers]
db1.example.com

[all:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_user=your_ssh_username

ここで、your_ssh_usernameは、サーバーへのSSH接続に使用するユーザー名です。

詳細設定:ステップごとのハードニングプレイブック

ここが最も重要な部分です。ハードニングプロセスを自動化するためのプレイブックを作成します。通常、私はhardening.ymlファイルを作成し、その中で特定のロールやタスクを呼び出します。

1. SSHセキュリティ:最初のゲートウェイ

あの夜のインシデント以来、SSHセキュリティは私の最優先事項となりました。以下のプレイブックは、基本的なSSH設定を構成します。

  • rootアカウントでのログインを無効化します。
  • パスワード認証を無効にし、SSHキーのみを許可します。
  • デフォルトのSSHポートを変更します(任意ですが推奨)。

# roles/ssh_hardening/tasks/main.yml
- name: SSHサーバーの構成
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^(PermitRootLogin|PasswordAuthentication|Port)'
    line: '{{ item.line }}'
    state: present
    validate: '/usr/sbin/sshd -t'
  loop:
    - { regexp: '^PermitRootLogin', line: 'PermitRootLogin no' } # rootログインを許可しない
    - { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' } # パスワード認証を許可しない
    - { regexp: '^Port', line: 'Port 2222' } # 2222を任意のポート番号に変更してください
  notify: Restart sshd

- name: 許可されたユーザーのみがSSHできることを確認する
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^AllowUsers'
    line: 'AllowUsers your_ssh_username another_user'
    state: present
    validate: '/usr/sbin/sshd -t'
  notify: Restart sshd

# handlers/main.yml (ssh_hardeningロール用)
- name: sshdを再起動
  ansible.builtin.systemd:
    name: sshd
    state: restarted
    enabled: yes

重要な注意点:このプレイブックを実行する前に、必ず新しいポートでログインするための有効なSSHキーとユーザーを持っていることを確認してください。そうしないと、サーバーからロックアウトされる可能性があります!

2. UFWを使用したファイアウォール設定

ファイアウォールは最初の防衛線です。Ubuntuでは、その使いやすさからUFW(Uncomplicated Firewall)をよく使用します。このプレイブックはUFWを有効にし、必要なポートを許可し、不要なトラフィックをすべてブロックします。


# roles/firewall/tasks/main.yml
- name: UFWをインストール
  ansible.builtin.apt:
    name: ufw
    state: present

- name: カスタムポート経由のSSHを許可する (例: 2222)
  community.general.ufw:
    rule: allow
    port: '2222'
    proto: tcp

- name: HTTP (ポート80) を許可する
  community.general.ufw:
    rule: allow
    port: '80'
    proto: tcp

- name: HTTPS (ポート443) を許可する
  community.general.ufw:
    rule: allow
    port: '443'
    proto: tcp

- name: デフォルトポリシーをすべてブロックするに設定する (着信拒否)
  community.general.ufw:
    state: enabled
    policy: deny
    direction: incoming

- name: UFWを有効にする
  community.general.ufw:
    state: enabled

3. 更新とソフトウェアパッケージの管理

オペレーティングシステムとソフトウェアパッケージを常に最新の状態に保つことは、基本的ながら極めて重要なハードニングステップです。これにより、修正済みのセキュリティ脆弱性が攻撃対象となることを防ぎます。


# roles/system_updates/tasks/main.yml
- name: ソフトウェアパッケージリストを更新
  ansible.builtin.apt:
    update_cache: yes

- name: すべてのソフトウェアパッケージを最新バージョンにアップグレード
  ansible.builtin.apt:
    upgrade: dist
    autoclean: yes
    autoremove: yes

- name: 自動更新 (unattended-upgrades) をインストールし設定する
  ansible.builtin.apt:
    name: unattended-upgrades
    state: present

- name: 自動更新を有効にする
  ansible.builtin.lineinfile:
    path: /etc/apt/apt.conf.d/20auto-upgrades
    regexp: 'APT::Periodic::Unattended-Upgrade "1"'
    line: 'APT::Periodic::Unattended-Upgrade "1";'
    state: present

4. ユーザーと権限の管理

最小権限の原則(Principle of Least Privilege)がここでは指針となります。私は必要なアカウントのみを作成し、sudo権限は慎重に付与します。


# roles/user_management/tasks/main.yml
- name: ユーザーが存在しない場合は新規作成
  ansible.builtin.user:
    name: mynewadmin
    comment: '管理ユーザー'
    group: sudo
    shell: /bin/bash
    generate_ssh_key: yes
    ssh_key_type: rsa
    password: '{{ "my_strong_password" | password_hash("sha512") }}'

- name: 新しいユーザーにSSH公開鍵を追加
  ansible.builtin.authorized_key:
    user: mynewadmin
    state: present
    key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

- name: 不要な古いユーザーが削除されていることを確認する (the_old_user_to_removeを置き換える)
  ansible.builtin.user:
    name: the_old_user_to_remove
    state: absent
    remove: yes
  ignore_errors: yes # ユーザーが存在しない場合でもエラーを無視する

- name: sudoグループのsudoers設定 (sudo使用時にパスワード不要)
  ansible.builtin.lineinfile:
    path: /etc/sudoers.d/90-nopasswd-sudo
    line: '%sudo ALL=(ALL) NOPASSWD:ALL'
    state: present
    create: yes
    validate: '/usr/sbin/visudo -cf %s'

注意: mynewadminmy_strong_passwordthe_old_user_to_removeは、あなたの具体的な情報に置き換えてください。password_hashを使用すると、プレイブック内でパスワードをより安全に保存できます。

5. メインプレイブックの集約 (main.yml)

最後に、これらのロールを呼び出すメインのhardening.ymlファイルを作成します。


# hardening.yml
---
- name: Linuxサーバーのハードニングを実行
  hosts: web_servers, db_servers # または hosts: all
  become: yes # sudo権限でコマンドを実行する
  roles:
    - ssh_hardening
    - firewall
    - system_updates
    - user_management

テストと監視:常に制御下に置く

デプロイが完了したからといって、作業が終わったわけではありません。すべてが期待通りに機能していることを確認するために、常にテストと監視を行う必要があります。

1. デプロイ前のテスト (ドライラン)

実際にプレイブックを実行する前に、常に--checkフラグを使ってテスト実行し、Ansibleがサーバーに適用する変更内容を確認します。これは、多くの予期せぬ問題から私を救ってくれました。


ansible-playbook -i inventory hardening.yml --check

出力を見てすべてが問題ないと判断した後、実際に実行します:


ansible-playbook -i inventory hardening.yml

2. デプロイ後のテスト

プレイブックの実行が完了した後、SSHでサーバーに接続し、変更内容を再確認します。例:

  • SSH設定の確認: sudo cat /etc/ssh/sshd_config | grep -E 'PermitRootLogin|PasswordAuthentication|Port|AllowUsers'
  • UFWステータスの確認: sudo ufw status verbose
  • ユーザーの確認: id mynewadmin

サーバーが稼働したら、新しいユーザーとポートでSSH接続を試すことができます。古いユーザー(またはroot)がログインできないことを確認してください。私は以前、SSHデーモンのポートを変更する前にファイアウォールで新しいSSHポートを開くのを忘れてしまい、その結果コンソールを使用して問題を解決しなければなりませんでした。

3. 定期的な監視と監査

ハードニングは一度行えば終わりという作業ではありません。セキュリティ状況は常に変化するため、設定が引き続き標準に準拠していることを確認する必要があります。私は通常、ハードニングプレイブックを定期的に(例えば毎月)再実行するようスケジュールし、一貫性を維持しています。Ansibleは冪等性があるため、再実行してもエラーは発生せず、変更がある場合にのみ適用されます。

さらに、私はログ監視ツール(ELKスタックやPrometheusとGrafanaなど)を統合してセキュリティイベントを追跡しています。これらのイベントには、ログイン失敗、異常なアクセス、またはsudoアクションなどが含まれます。これにより、攻撃の兆候や不審な活動を早期に検出できます。また、定期的にシステムをLynisで監査することも組み合わせており、レポートの追跡を容易にするために、Ansibleを介してLynisの実行を自動化することもあります。


# Lynis実行タスクの例 (system_auditingロールがあれば追加)
- name: Lynisをインストール
  ansible.builtin.apt:
    name: lynis
    state: present

- name: Lynis監査を実行し、結果を保存する
  ansible.builtin.command: lynis audit system --quick --report-file /var/log/lynis_report.txt
  args:
    creates: /var/log/lynis_report.txt # レポートファイルが存在しない場合、または再実行したい場合にのみ実行

AnsibleによるLinuxサーバーのハードニング自動化は、私の仕事のやり方を変え、大きな安心感をもたらしてくれました。サーバー攻撃による眠れない夜から、今では自分のシステムが一貫して効果的に保護されていると自信を持てるようになりました。あなたもこの自動化の旅をできるだけ早く始めるべきです!

Share: