AnsibleでOSSECを構築

## 環境
- WEBサーバ：Amazon Linux 2（プライベートサブネット）
- WAFサーバ：Amazon Linux 2（パブリックサブネット）
- Ansible実行サーバ：CentOS7(VirtualBox7)

## 構成
![ストラクチャー図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581035/027cf1dd-5d0b-51fb-a7d5-0d06e482a7da.png)

## 概要
OSSであるOSSECを使用して簡単なWAF環境を構築してみました。
ちなみに今回はシンプルな構成にしたかったので、OSSECをスタンドアロンで運用しています。
構築にはAnsibleを使用し、ローカル内の仮想マシンからAWSのEC2に対して遠隔で構築を行っています。
また、SSL証明書はLet's Encryptにて発行させています。

前提として以下の点は事前に準備ができているものとしています。
- 多段SSH
[ローカルマシン--(パブリックサブネット内EC2インスタンス経由)-->プライベートサブネット内EC2インスタンス]
- ドメインの取得
- パブリックサブネット内インスタンスの外部との通信設定（NATの設定など）
※Apacheインストールが必要になるため

## Ansible roles

```
.
|-- ansible.cfg
|-- ansible.ini
|-- main.yml
`-- roles
    |-- crt
    |   `-- tasks
    |       `-- main.yml
    |-- ossec
    |   |-- tasks
    |   |   `-- main.yml
    |   |-- templates
    |   |   |-- add_rules.txt
    |   |   |-- edit_decoder.txt
    |   |   `-- preloaded-vars.conf
    |   `-- vars
    |       `-- main.yml
    |-- reverse_proxy
    |   |-- tasks
    |   |   `-- main.yml
    |   |-- templates
    |   |   `-- proxy.conf
    |   `-- vars
    |-- timeset
    |   |-- handlers
    |   |   `-- main.yml
    |   |-- tasks
    |   |   `-- main.yml
    |   `-- templates
    |       `-- chrony.conf
    `-- web
        |-- tasks
        |   `-- main.yml
        `-- templates
            `-- httpd.conf
```
今回作成したroleの詳細は以下にあります。

https://github.com/Yoshiya-H/OSSEC-in-reverse-proxy/tree/masterproxy

### 各タスクの説明

※ansible.cfgやansible.iniは設定事項を記載しているだけなので割愛します。

```./main.yml
- hosts: all,localhost

  tasks:
    - name: Set timezone to Asia/Tokyo
      timezone:
        name: Asia/Tokyo

- hosts: localhost
  become : yes

  roles:
    - timeset

- hosts: WEB
  become: yes

  roles:
    - web

- hosts: WAF
  become: yes

  roles:
    - reverse_proxy
    - ossec
    - crt
```

大まかな流れは以下です。

1. タイムゾーンの設定・時刻合わせ(これはやらなくても支障なし)
2. WEBサーバの構築
2. WAFサーバの構築
    1. リバースプロキシの設定
    1. OSSECの設定
    1. 証明書の設定

#### WEBサーバの構築

```./roles/web/tasks/main.yml
---
- name: Install Apache
  yum:
    name: httpd
    state: latest

- name: Copy httpd.conf
  template:
    src: httpd.conf
    dest: /etc/httpd/conf/httpd.conf

- name: start Apache
  service:
    name: httpd
    state: restarted
    enabled: yes
```
設定ファイルをコピーしてApacheを起動しているだけです。

#### WAFサーバの構築（リバースプロキシの設定）

```./roles/reverse_proxy/tasks/main.yml
---
- name: Enable to install nginx.
  shell: "amazon-linux-extras enable nginx1"

- name: Install nginx
  yum:
    name: nginx
    state: latest

- name: Copy proxy.conf
  template:
    src: proxy.conf
    dest: /etc/nginx/conf.d/proxy.conf

- name: start nginx
  service:
    name: nginx
    state: restarted
    enabled: yes
```

まずはリバプロとして機能するよう設定します。
このplaybookではnginxをインストールして設定ファイルを/etc/nginx/conf.d配下に設置しています。
この設定ファイルにリバプロとして機能させるための設定が記述されています。

```./roles/reverse_proxy/templates/proxy.conf
server{
    server_name    example.com;

    location / {
        proxy_pass    http://{{web_server}}/;

        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Referer $http_referer;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Cookie $http_cookie;

    }

}
```
とりあえず送信元からの情報をオリジンに転送するようにしています。
ここの設定はもう少し精査したいと考えていますが、いったんこれで動きはしました。

ちなみにここの設定は後程行う証明書の取得・設置時にcertbotによってさらに一部追記されます。
それが嫌な方は特定のオプションを使用することで、certbotによる追記を回避することができます。ただし、証明書の発行後に設定ファイルに自分で追記をする必要が出てきます。
この点は後述します。

#### WAFサーバの構築（OSSECの設定）

```./roles/ossec/tasks/main.yml
---
- name: Add Yum necessary packages
  yum:
    name: "{{ packages }}"
  vars:
    packages:
    - "zlib-devel"
    - "pcre2-devel"
    - "make"
    - "gcc"
    - "sqlite-devel"
    - "openssl-devel"
    - "libevent-devel"
    - "systemd-devel"

- name: Check the file exists
  stat:
    path: /tmp/ossec-hids-{{ version }}
  register: ossec_hids

- name: Get and unarchive ossec-hids file
  unarchive:
    src: https://github.com/ossec/ossec-hids/archive/{{ version }}.tar.gz
    dest: /tmp
    remote_src: yes
  when: ossec_hids.stat.exists == true

- name: Copy preloaded-vars.conf
  template:
    src: preloaded-vars.conf
    dest: /tmp/ossec-hids-{{ version }}/etc/preloaded-vars.conf

- name: Execute Installer Script
  command: "bash /tmp/ossec-hids-{{ version }}/install.sh"

- name: Edit ossec.conf
  lineinfile:
    path: /var/ossec/etc/ossec.conf
    regexp: '^\s+<logall>'
    insertafter: '</email_notification>$'
    line: "    <logall>yes</logall>"

- name: Copy add_rules.txt
  template:
    src: add_rules.txt
    dest: /tmp/add_rules.txt

- name: Register var "add_rules"
  command: cat /tmp/add_rules.txt
  register: add_rules

- name: Custom local_rules.xml
  blockinfile:
    path: /var/ossec/rules/local_rules.xml
    marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
    insertbefore: "^</group> <!-- SYSLOG,LOCAL -->$"
    block: |
      - "{{ add_rules.stdout }}"

- name: modify local_rules.xml 1
  replace:
    path: /var/ossec/rules/local_rules.xml
    regexp: '^- \"'
    replace: ''

- name: modify local_rules.xml 2
  replace:
    path: /var/ossec/rules/local_rules.xml
    regexp: '^ \"$'
    replace: ''

- name: Copy edit_decoder.txt
  template:
    src: edit_decoder.txt
    dest: /tmp/edit_decoder.txt

- name: Register var "edit_decoder"
  command: cat /tmp/edit_decoder.txt
  register: edit_decoder

- name: Custom decoder.xml
  blockinfile:
    path: /var/ossec/etc/decoder.xml
    marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
    insertbefore: "^<decoder name=\"web-accesslog\">$"
    block: |
      - "{{ edit_decoder.stdout }}"

- name: modify decoder.xml 1
  replace:
    path: /var/ossec/etc/decoder.xml
    regexp: '^- \"'
    replace: ''

- name: modify decoder.xml 2
  replace:
    path: /var/ossec/etc/decoder.xml
    regexp: '^ \"$'
    replace: ''

- name: Start OSSEC
  shell: /var/ossec/bin/ossec-control restart
  become: yes
```

OSSECのインストール、設定変更後、ルールの追加とでログデコーダの追加を行っています。

ポイントは以下です。
- preloaded-vars.conf
preloaded-vars.confをOSSECの起動時に読み込ませることで、設定を事前に定義させることが可能です。
`USER_INSTALL_TYPE="local"`を指定することでスタンドアロンでの
- ossec.conf
ossec.confに`<logall>yes</logall>`を追記することで、archives.logが出力されるようになります。
- local_rules.xml,decoder.xml
local_rules.xmlに追加のルール（動作チェック用ルール）を追加しています。また、ログのフォーマットを変更している場合などはdecoder.xmlにログデコーダを追記します。
Ansible実行側のサーバに用意したテキストファイルを`cat`で開いて、その出力を変数として保存し、対象のファイルに追記する方法をとっています。

#### WAFサーバの構築（証明書の設定）

```./roles/crt/tasks/main.yml
---
- name: Check the file exists
  stat:
    path: /etc/yum.repos.d/snapd-amzn2.repo
  register: repo

- name: Get ossec-hids archive file
  get_url:
    url: https://people.canonical.com/~mvo/snapd/amazon-linux2/snapd-amzn2.repo
    dest: /etc/yum.repos.d/
  when: repo.stat.exists == false

- name: Install snapd from snapd-amzn2.repo
  yum:
    name: snapd
    state: present

- name: enable service snapd.socket
  systemd:
    name: snapd.socket
    enabled: yes

- name: Check if the symbolic sym_snap exists
  stat:
    path: /snap
  register: sym_snap

- name: Create a symbolic link for snap
  file:
    src: /var/lib/snapd/snap
    dest: /snap
    owner: root
    group: root
    state: link
    mode: '777'
  when: sym_snap.stat.islnk is not defined

- name: Remove snap certbo if it exist
  yum:
    name: certbo
    state: absent

- name: restart service snapd.socket
  systemd:
    name: snapd.socket
    state: restarted

- name: Install snap core
  snap:
    name: core

- name: Refresh snap core
  command: "snap refresh core"

- name: Install certbot with option --classic
  snap:
    name: certbot
    classic: yes

- name: Check if the symbolic sym_certbot exists
  stat:
    path: /usr/bin/certbot
  register: sym_certbot

- name: Create a symbolic link for certbot
  file:
    src: /snap/bin/certbot
    dest: /usr/bin/certbot
    owner: root
    group: root
    state: link
    mode: '0777'
  when: sym_certbot.stat.islnk is not defined

- name: obtains cert keys
  command: certbot --nginx --non-interactive --email test@example.co.jp --keep-until-expiring --agree-tos --domain example.com

- name: start nginx
  service:
    name: nginx
    state: restarted
    enabled: yes
```

基本的には公式の手順どおりに進めます。
Let's Encryptを使用するためにCertbotを使用する必要があり、Certbotをインストールするためにsnapdをインストールする必要があり遠回りが多いですが順に実施していきます。
注意点としては、CertbotのAmazon Linux2へのインストールは公式でサポートされていません。そのため、(アンオフィシャルの)カスタムリポジトリを別途インストールしてあげる必要があります。

また、`certbot`コマンドで証明書を発行していくことになりますが、`--non-interactive`オプションで対話形式ではなくコマンドラインでの証明書発行が可能となります。

**WAFサーバの構築（リバースプロキシの設定）** で触れていたnginxの設定ファイルについてですが、certbotコマンドによって以下の内容が追記されていました。

```/etc/nginx/conf.d/proxy.conf
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server{
    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    server_name    example.com;
    listen 80;
    return 404; # managed by Certbot


}
```

これが気になる場合やもっと自分で詳細に設定したい場合は、`--webroot`オプションを追加し、代わりに`--nginx`オプションを削ることで、certbotによる設定ファイルへの追記を回避することが可能です。その場合、自分で証明書や秘密鍵ファイル・リダイレクトの設定を行う必要がありますのでご注意ください。

## 結果
この内容でAnsibleを実行することで掲題の構築を実施することができました。
個人的には条件分岐をもっと設けて無駄なタスクを実行せずに済むようにしたいところですが、とりあえず動くということでひとまず完了としました。

## 参考
[OSSEC公式ドキュメント](https://www.ossec.net/docs/docs/manual/installation/install-source.html)
[Let's Encrypt公式ドキュメント](https://letsencrypt.org/ja/getting-started/)
[Certbot公式HP](https://certbot.eff.org/)
[AmazonLinux2 に snapd を入れて certbot による証明書自動更新生活を満喫する](https://donbulinux.hatenablog.jp/entry/2021/02/25/005721)