2020.4.23
はじめに
脆弱性には速やかな対応が求められる
脆弱性には速やかな対応が求められます。 ベンダによって脆弱性情報が公開された後、攻撃者は脆弱性を分析し、攻撃コードを作成。その後、攻撃を開始します。 防御側はこの攻撃開始までに脆弱性への対応を行わなければなりません。 攻撃開始までの期間は年々短くなっており、脆弱性への対応は以前と比較してより短期間での実施が求められています。
速やかな対応を阻む要因
では、脆弱性への速やかな対応を阻む要因にはどのようなものがあるでしょうか? そのひとつに、各サーバやサービスで利用しているシステム構成情報の把握があります。 各サーバへ ssh 経由でログインし、パッケージ情報を取得するコマンドを実行してまわることも可能ですが、サーバの台数の増加に伴い工数が増加します。 また各サーバの管理者にメールや電話で聞き取り調査を行い、その結果を元に Excel で構成管理を行うことは、工数のみならず速度の面からも現実的ではなくなりつつあります。 脆弱性への速やかな対応には自動化が必要です。
Ansible を利用したシステム構成情報取得の自動化
このエントリでは、Linux サーバ (CentOS/Ubuntu) にインストール済みのパッケージを取得する Ansible Playbook をご紹介します。
- 想定する環境
- システム構成情報の自動取得
- 取得したシステム構成情報の活用 (1) - Git によるパッケージ情報の変更管理
- 取得したシステム構成情報の活用 (2) - 脆弱性の影響の特定
- Ansible Playbook の解説
- まとめ
Ansible とは、オープンソースの構成管理ツールの一種です。 エージェントレスのため、管理対象ノードに追加でインストールするソフトウェアは一部を除き不要です。(ssh 経由での接続は必要)
このエントリで扱う内容は、下図に示された脆弱性対策のフローの中で、「対象ソフトウェアの把握」フェーズに含まれるものです。
想定する環境
想定する環境を下図に示します。
Ansible コントロールノードとして ctrl.example.com が構築されており、 このコントロールノードは 「host1.example.com (CentOS 7 minimal)」 と 「host2.example.com (Ubuntu Server 18 LTS)」 を管理しているとします。 これら管理対象ノードへは SSH 経由で接続可能であり、SSH 接続でのユーザは sudo によるコマンド実行が許可されているとします。
Ansible のインストールや管理対象ノードの設定については、以下のリンク先をご参照ください。
システム構成情報の自動取得
以下の手順で、管理対象ノードのインストール済みパッケージを取得します。
インストール済みパッケージの自動取得
-
Ansible コントロールノード ctrl.example.com にログインします。
-
作業用のディレクトリを作成し、そのディレクトリへ移動します。
$ mkdir get_packages
$ cd get_packages
-
パッケージ情報を格納するためのホスト名のディレクトリを作成します。
$ mkdir host1 host2
-
インベントリファイルを作成します。
$ vi hosts
[test_servers]
host1.example.com
host2.example.com
-
パッケージ情報を取得するための Ansible Playbook を作成します。
$ vi get_packages.yml
- name: Get packages from hosts
hosts:
- test_servers
tasks:
- name: Get packages
package_facts:
manager: auto
become: true
- name: Output packages
template:
src: ./package_list.j2
dest: "./{{ inventory_hostname_short }}/packages"
mode: '0644'
delegate_to: localhost
-
パッケージ情報を出力するためのテンプレートを作成します。
$ vi package_list.j2
# {{ inventory_hostname_short }}
{% if ansible_facts.os_family == 'RedHat' %}
{% for package_name in ansible_facts.packages.keys()|sort %}
{% for package in ansible_facts.packages[package_name] %}
{{ package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}
{% endfor %}
{% endfor %}
{% elif ansible_facts.os_family == 'Debian' %}
{% for package_name in ansible_facts.packages.keys()|sort %}
{% for package in ansible_facts.packages[package_name] %}
{{ [package['name'], package['version']] | join('_') }}
{% endfor %}
{% endfor %}
{% endif %}
-
Playbook を実行します。
$ ansible-playbook -i hosts get_packages.yml
-
実行結果を確認します。
まず host1.example.com (CentOS 7 minimal) を見てみましょう。$ cat host1/packages
# host1
acl-2.2.51-14.el7.x86_64
aic94xx-firmware-30-6.el7.noarch
alsa-firmware-1.0.28-2.el7.noarch
alsa-lib-1.1.6-2.el7.x86_64
alsa-tools-firmware-1.1.0-1.el7.x86_64
audit-2.8.4-4.el7.x86_64
audit-libs-2.8.4-4.el7.x86_64
authconfig-6.2.8-30.el7.x86_64
basesystem-10.0-7.el7.centos.noarch
bash-4.2.46-31.el7.x86_64
(以下省略)
$ cat host2/packages
# host2
accountsservice_0.6.45-1ubuntu1
acl_2.2.52-3build1
acpid_1:2.0.28-1ubuntu1
adduser_3.116ubuntu1
amd64-microcode_3.20180524.1~ubuntu0.18.042
apparmor_2.12-4ubuntu5.1
apport_2.20.9-0ubuntu7.7
apport-symptoms_0.20
apt_1.6.11
apt-utils_1.6.11
(以下省略)
トラブルシューティング
何らかのエラーが発生する場合は「Ansible をインストールする - トラブルシューティング」を参照してください。
取得したシステム構成情報の活用 (1) - Git によるパッケージ情報の変更管理
Git は分散型のバージョン管理システムですが、単純にローカル環境にあるテキストファイルの履歴を管理するツールとしても利用することができます。 Git を利用して、Ansible Playbook で取得したパッケージ情報の変更履歴を管理してみましょう。
Git のインストールと初期設定
-
Ansible コントロールノード ctrl.example.com にログインします。
-
Git をインストールします。
user@ctrl:~$ sudo apt-get install git -y
-
作業用のディレクトリに移動します。
user@ctrl:~$ cd get_packages
-
リポジトリを作成します。
user@ctrl:~$ git init
-
ユーザの設定を行います。
user@ctrl:~$ git config --global user.email "user@example.com"
user@ctrl:~$ git config --global user.name "test user"
Playbook の実行とパッケージ情報の登録
-
Playbook を実行します。
$ ansible-playbook -i hosts get_packages.yml
-
パッケージ情報をリポジトリに登録します。
$ git add */packages
$ git commit
パッケージの更新と変更管理
-
管理対象ホスト host2.example.com にログインします。
-
vim パッケージを最新版に更新します。
user@host2:~$ sudo apt-get install vim -y
-
Ansible コントロールノード ctrl.example.com にログインします。
-
Ansible Playbook を実行し、パッケージ情報を取得します。
user@ctrl:~$ ansible-playbook -i hosts get_packages.yml
-
パッケージ更新前後の差分を表示します。
user@ctrl:~$ git diff
diff --git a/host2/packages b/host2/packages
index abcdefg..hijkelm 100644
--- a/host2/packages
+++ b/host2/packages
@@ -483,10 +483,10 @@ ureadahead_0.100.0-21
usbutils_1:007-4build1
util-linux_2.31.1-0.4ubuntu3.3
uuid-runtime_2.31.1-0.4ubuntu3.3
-vim_2:8.0.1453-1ubuntu1.1
-vim-common_2:8.0.1453-1ubuntu1.1
-vim-runtime_2:8.0.1453-1ubuntu1.1
-vim-tiny_2:8.0.1453-1ubuntu1.1
+vim_2:8.0.1453-1ubuntu1.3
+vim-common_2:8.0.1453-1ubuntu1.3
+vim-runtime_2:8.0.1453-1ubuntu1.3
+vim-tiny_2:8.0.1453-1ubuntu1.3
wget_1.19.4-1ubuntu2.2
whiptail_0.52.20-1ubuntu1
wireless-regdb_2018.05.09-0ubuntu1~18.04.1
-
最新のパッケージ情報をリポジトリに登録します。
登録する際はコメントに「脆弱性対応」などのようにパッケージの変更理由を記載しておくと変更履歴を参照する際に役に立ちます。$ git add */packages
$ git commit
-
git log
コマンドで変更履歴をgit show
コマンドで各変更の差分を確認できるようになります。$ git log -p */packages
取得したシステム構成情報の活用 (2) - 脆弱性の影響の特定
SIDfm Biz/Group や他の脆弱性情報サービスから受け取った脆弱性情報には、通常、影響を受けるアプリケーションが記載されています。 しかしながら、その脆弱性がどのサーバに影響するのかについては自ら調査しなければなりません。(SIDfm VM は影響まで自動的に判定します。)
先程の Ansible Playbook で取得したパッケージ情報を利用すれば、ある脆弱性が管理下のサーバに影響するかどうかを判定することが可能です。
ここでは例として vim パッケージを取り上げ、脆弱性の影響の特定を行います。
CentOS 7 (host1.example.com) での影響特定
-
脆弱性情報サービスから脆弱性情報を受けとります。
ここでは SIDfm に登録された脆弱性コンテンツの中から「CentOS 7 の vim に任意の OS コマンドを実行される問題(CVE-2019-12735)」を受け取ったとします。 脆弱性が修正されたパッケージのバージョンは「対処方法」に記載されています。
-
host1.example.com の vim のバージョンを確認します。
Ansible コントロールノード ctrl.example.com 上で次のコマンドを実行してください。$ grep "vim" host1/packages
host1/packages:vim-minimal-7.4.160-5.el7.x86_64
-
バージョンの比較を行います。
host1.example.com のバージョンは脆弱性が修正されたバージョンより小さく、脆弱性の影響を受けることがわかります。[脆弱性が修正されたバージョン]
vim-minimal-7.4.160-6.el7_6.x86_64.rpm
[host1 のパッケージバージョン]
vim-minimal-7.4.160-5.el7.x86_64
Ubuntu Server 18 LTS (host2.example.com) での影響特定
-
脆弱性情報サービスから脆弱性情報を受けとります。
ここでは SIDfm に登録された脆弱性コンテンツの中から「Ubuntu の vim に任意の OS コマンドを実行されるなど複数の問題 (CVE-2017-5953, CVE-2019-12735)」を受け取ったとします。 脆弱性が修正されたパッケージのバージョンは「対処方法」に記載されています。
-
host2.example.com の vim のバージョンを確認します。
Ansible コントロールノード ctrl.example.com 上で次のコマンドを実行してください。$ grep "vim" host2/packages
host2/packages:vim_2:8.0.1453-1ubuntu1.1
host2/packages:vim-common_2:8.0.1453-1ubuntu1.1
host2/packages:vim-runtime_2:8.0.1453-1ubuntu1.1
host2/packages:vim-tiny_2:8.0.1453-1ubuntu1.1
-
バージョンの比較を行います。
host2.example.com のバージョンは脆弱性が修正されたバージョンと同じであり、脆弱性の影響を受けないことが判明しました。[脆弱性が修正されたバージョン]
vim 2:8.0.1453-1ubuntu1.1
vim-common 2:8.0.1453-1ubuntu1.1
[host2 のパッケージバージョン]
vim_2:8.0.1453-1ubuntu1.1
vim-common_2:8.0.1453-1ubuntu1.1
Ansible Playbook の解説
Ansible Playbook - get_packages.yml
この Ansible Playbook は次のタスクから成り立っています。
- package_facts モジュールを使用し、各監視対象ノードからパッケージ情報を収集する。
- template モジュールを使用し、パッケージ情報をコントロールノード上にファイルとして出力する。
では詳しく見てみます。
6 〜 9 行目が、各監視対象ノードからパッケージ情報を収集するタスクです。
このタスクで使用する
package_facts
モジュールは、インストール済みのパッケージに関する情報を集め、その結果をansible_facts
に格納します。
get_packages.yml:
1: - name: Get packages from hosts
2: hosts:
3: - test_servers
4:
5: tasks:
6: - name: Get packages
7: package_facts:
8: manager: auto # パッケージマネージャの自動選択 (デフォルト)
9: become: true # 特権で実行
参考までに、ansible_facts
に格納されたパッケージ情報を示します。
host1.example.com (CentOS 7 minimal):
"ansible_facts.packages": {
"GeoIP": [
{
"arch": "x86_64",
"epoch": null,
"name": "GeoIP",
"release": "13.el7",
"source": "rpm",
"version": "1.5.0"
}
],
(中略)
"zlib": [
{
"arch": "x86_64",
"epoch": null,
"name": "zlib",
"release": "18.el7",
"source": "rpm",
"version": "1.2.7"
}
]
}
host2.example.com (Ubuntu Server 18 LTS)
"ansible_facts.packages": {
"accountsservice": [
{
"arch": "amd64",
"category": "gnome",
"name": "accountsservice",
"origin": "Ubuntu",
"source": "apt",
"version": "0.6.45-1ubuntu1"
}
],
(中略)
"zlib1g": [
{
"arch": "amd64",
"category": "libs",
"name": "zlib1g",
"origin": "Ubuntu",
"source": "apt",
"version": "1:1.2.11.dfsg-0ubuntu2"
}
]
}
get_packages.yml の 11 〜 16 行目は、パッケージ情報の出力部分となります。
template
モジュールを使用しansible_facts
に格納されたパッケージ情報をコントロールノード上にファイルとして出力します。
ここで inventory_hostname_short
変数はドメイン部分を除いたホスト名です。
例えば、host1.example.com の inventory_hostname_short
は host1 になります。
get_packages.yml:
11: - name: Output packages
12: template:
13: src: ./package_list.j2 # テンプレートファイルの指定
14: dest: "./{{ inventory_hostname_short }}/packages" # 出力先の指定
15: mode: '0644' # パーミッションの指定
16: delegate_to: localhost # 管理対象ノードでなくコントロールノード上で実行
Jinja2 テンプレートファイル - package_list.j2
Ansible では Jinja2 テンプレートエンジンを利用できます。今回の Playbook もデータの整形に Jinja2 を使用しています。Jinja2 の文法については こちら をご参照ください。それではテンプレートファイルの内容を見てみましょう。
2 行目でos_family
変数によって Red Hat 系と Debian 系で処理を分岐させています。
その他のos_family
の取りうる値は
こちら
を参照してください。
パッケージ情報の内容を出力する部分は 5 行目と 11 行目です。
package_list.j2:
1: # {{ inventory_hostname_short }}
2: {% if ansible_facts.os_family == 'RedHat' %} # Red Hat 系の場合
3: {% for package_name in ansible_facts.packages.keys()|sort %} # パッケージ名でソート
4: {% for package in ansible_facts.packages[package_name] %}
5: {{ package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}
6: {% endfor %}
7: {% endfor %}
8: {% elif ansible_facts.os_family == 'Debian' %} # Debian 系の場合
9: {% for package_name in ansible_facts.packages.keys()|sort %} # パッケージ名でソート
10: {% for package in ansible_facts.packages[package_name] %}
11: {{ [package['name'], package['version']] | join('_') }}
12: {% endfor %}
13: {% endfor %}
14: {% endif %}
host1.example.com (CentOS 7) のパッケージ情報ansible_facts.packages
は次のようになります。
"ansible_facts.packages": {
(中略)
"zlib": [
{
"arch": "x86_64",
"epoch": null,
"name": "zlib",
"release": "18.el7",
"source": "rpm",
"version": "1.2.7"
}
]
}
上記のデータを整形します。パッケージ名やバージョンなどを取り出し、文字列として連結します。
package_list.j2:
5: {{ package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}
このような出力となります。
host1/packages:
zlib-1.2.7-18.el7.x86_64
host2.example.com (Ubuntu Server 18 LTS) も同様の処理を行います。
"ansible_facts.packages": {
(中略)
"zlib1g": [
{
"arch": "amd64",
"category": "libs",
"name": "zlib1g",
"origin": "Ubuntu",
"source": "apt",
"version": "1:1.2.11.dfsg-0ubuntu2"
}
]
}
整形部分。こちらは取り出したデータを join で連結しています。
package_list.j2:
11: {{ [package['name'], package['version']] | join('_') }}
出力結果です。
host2/packages:
zlib1g_1:1.2.11.dfsg-0ubuntu2
まとめ
Linux サーバ (CentOS/Ubuntu) にインストール済みのパッケージを取得する Ansible Playbook をご紹介しました。 この Ansible Plabook を利用することで、脆弱性対策における対象ソフトウェアの把握が比較的容易になります。
脆弱性の調査やパッチ探しは一切不要!
脆弱性対策を自動化できるので工数大幅削減!
さらに自社の脆弱性状況を全て可視化できるので
管理がグッと楽になる!
それらを全て実現するサービスがあります。
脆弱性管理ツール「SIDfm VM」について詳しくはこちらから