環境
- WEBサーバ:Amazon Linux 2(プライベートサブネット)
- WAFサーバ:Amazon Linux 2(パブリックサブネット)
- Ansible実行サーバ:CentOS7(VirtualBox7)
構成
概要
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の詳細は以下にあります。
各タスクの説明
※ansible.cfgやansible.iniは設定事項を記載しているだけなので割愛します。
- 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
大まかな流れは以下です。
- タイムゾーンの設定・時刻合わせ(これはやらなくても支障なし)
- WEBサーバの構築
- WAFサーバの構築
- リバースプロキシの設定
- OSSECの設定
- 証明書の設定
WEBサーバの構築
---
- 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サーバの構築(リバースプロキシの設定)
---
- 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配下に設置しています。
この設定ファイルにリバプロとして機能させるための設定が記述されています。
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の設定)
---
- 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サーバの構築(証明書の設定)
---
- 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コマンドによって以下の内容が追記されていました。
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公式ドキュメント
Let's Encrypt公式ドキュメント
Certbot公式HP
AmazonLinux2 に snapd を入れて certbot による証明書自動更新生活を満喫する