Proxmox VEとcloud-initで監査エージェント(salt-minion)導入済みの仮想マシンを払い出して、自動的に管理対象にする話

## はじめに

社内に閉じた比較的小規模な開発用途で、仮想マシンを作っては壊すような（それこそインターンシップのような）ケースでは、監査を意識した管理は正直面倒です。  
しかし、ふとしたことから「使っているOSやKernelのバージョンはいくつなのか、入っているbashは古くないか、opensshは更新されているか...などを全数チェックせよ」なんてイベントが起きたら、運が悪かったと思って調べることになります。

今回は、払い出す仮想マシンに監査エージェント(salt-minion)を起動時にインストールするようにして、自動的に管理対象にしてみようという話です。  
なお、salt-master/salt-minionの設定は紹介程度になりますので、実際に運用する際はユースケースに合わせて詳細を調査しましょう。

なんか他にもあるでしょ？という声も聞こえてきそうですが、今回はSaltを使うんです。そう決めたんです。  
ZabbixやNagios...（会社の方を見ながら）...とかHinemos、もしくはPrometheusとかDatadogとかCloudWatchとか、そういうのは今回無しです。

## 今回使うモノの紹介

- Proxmox VE 7.x: 仮想マシンの管理プラットフォーム
- Salt: ITインフラストラクチャの自動化と構成管理を行うためのオープンソースのソフトウェア
    - 公式Doc: https://saltproject.io
    - Github: https://github.com/saltstack/salt
    - salt-master: 管理サーバーのこと。今回は手動で構築します。
    - salt-minion: 各ホストに導入するエージェントのこと。cloud-initで自動インストールします。

大体以下のようなイメージです。

```
+---------------+---------------+
| VM            | VM            |
| - salt-master | - salt-minion |
+-------------------------------+
|          Proxmox VE           |
+-------------------------------+
```

## サンプル用salt-masterの準備

適当なUbuntu 20.04なマシンを準備して、salt-masterをインストールします。  
salt-minionが情報を集めてくる、中央の管理サーバーとなります。

```
sudo mkdir /etc/apt/keyrings
sudo curl -fsSL -o /etc/apt/keyrings/salt-archive-keyring.gpg https://repo.saltproject.io/salt/py3/ubuntu/20.04/amd64/latest/salt-archive-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/salt-archive-keyring.gpg arch=amd64] https://repo.saltproject.io/salt/py3/ubuntu/20.04/amd64/latest focal main" | sudo tee /etc/apt/sources.list.d/salt.list
sudo apt-get update 
sudo apt-get install salt-master -y
```

salt-minionを自動的に登録するため、以下の設定を追加してsalt-masterを再起動します。

```
echo "auto_accept: True" | sudo tee /etc/salt/master.d/auto_accept.conf
sudo systemctl restart salt-master
```

上記の設定をしない場合は、以下のように未登録のsalt-minionをsalt-masterで逐一登録する必要があります。

```
$ sudo salt-key -L
Accepted Keys:
Denied Keys:
Unaccepted Keys:
salt-master.example.com
Rejected Keys:
$ sudo salt-key -A
The following keys are going to be accepted:
Unaccepted Keys:
salt-master.example.com
Proceed? [n/Y] Y
Key for minion salt-master.example.com accepted.
$ sudo salt-key -L
Accepted Keys:
salt-master.example.com
Denied Keys:
Unaccepted Keys:
Rejected Keys:
```

## Proxmox VE上にsalt-minionをインストールするcloud-initファイルを準備する

Proxmox VEは元々仮想マシンの一部設定（ユーザー名(ciuser)、IPアドレス(ifconfig0)など）にcloud-initを使用していますが、その書式はProxmox VE固有のものです。  
そこで、Proxmox VEでは独自に作成したcloud-initファイルを `cicustom` というオプションで追加することができるようになっています。  

まずは、cloud-initで仮想マシンの初回起動時にsalt-minionをインストールし、先ほど作ったsalt-masterに登録されるようにcloud-initファイルを作成します。  
cloud-initファイルの置き場所として `snippets` が使える必要があるので、無い場合は設定します。今回は `local` ストレージに設定しました。

```
# pvesm set local --content snippets,iso,backup,vztmpl
# pvesm status --content snippets
Name          Type     Status           Total            Used       Available        %
local          dir     active        98497780         7460732        85987500    7.57%
# cat <<__EOL__ > /var/lib/vz/snippets/ci_salt-minion_ubuntu2204.yml
#cloud-config
salt_minion:
  conf:
    logging: info
    master: 192.168.0.37
__EOL__
```

`192.168.0.37` は先ほど作ったsalt-masterのIPアドレスです。  
現在API経由でsnippetsにファイルを格納することは出来ないため、管理者があらかじめProxmox VE上に準備しておく必要があります。  
（Proxmox VEはDebianベースで作られているので方法自体はあるかと思いますが、Proxmox VEはその方法をAPIとして提供していない、という意味です）

`salt_minion:` にどのような設定が可能なのかは https://cloudinit.readthedocs.io/en/latest/reference/modules.html#salt-minion を参照してください。

Ubuntu 22.04の場合はデフォルトのaptで導入できるので、上記のような記述になりますが、デフォルトのリポジトリでインストール出来ない場合は、リポジトリ登録用の設定も合わせて必要となります。  
以下はRHEL9にsalt-3005を導入する場合のcloud-initファイル記述例です。

```yaml
yum_repos:
  salt-3005-repo:
    # cloud-init get url: baseurl + /repodata/repomd.xml
    baseurl: https://repo.saltproject.io/salt/py3/redhat/9/x86_64/3005
    enabled: true
    failovermethod: priority
    gpgcheck: true
    gpgkey: https://repo.saltproject.io/salt/py3/redhat/9/x86_64/3005/SALTSTACK-GPG-KEY2.pub
    name: Salt Packages for RHEL/CentOS 9
```

## Proxmox VEで仮想マシンに追加のcloud-initを設定する

それでは、Ubuntu 22.04の仮想マシンに、上記のcloud-initファイルを追加します。  
テンプレートは [以前作ったもの](https://qiita.com/ymbk990/items/bd3973d2b858eb86e334) を流用して、クローンして作っていきます。（vCPU/Memory/Disk sizeは適当に）

```
qm clone 9100 1002 --name salt-sample01
qm set 1002 --cores 2 --memory 1024
qm resize 1002 virtio0 20G
qm set 1002 --ipconfig0 ip=192.168.0.12/24,gw=192.168.0.1
qm set 1002 --ciuser sample01 --sshkeys sample01_ed25519.pub
### --cicustom でcloud-initファイルを追加する。今のところ `vendor=...` という形式で記述することに決まっています。
qm set 1002 c vendor=local:snippets/ci_salt-minion_ubuntu2204.yml
qm start 1002
```

今回は以前作った設定を流用したので、クローンした後の仮想マシンにcloud-initファイルを追加していますが、同じ設定の仮想マシンを作るようであればVM Templateの方に `--cicustom` の設定を追加するのが良いと思います。

さて、起動したらsalt-minionが自動的にインストールされて、salt-masterに登録されるはずです。  
どうなっているか確認してみましょう。

## salt-minionの登録確認

仮想マシンが起動して、少し経つとsalt-masterに自動的に登録されました。

```
$ sudo salt-key -L
Accepted Keys:
salt-master.example.com
salt-sample01.example.com
Denied Keys:
Unaccepted Keys:
Rejected Keys:
```

これで、仮想マシンを作っては壊すようなケースでも、自動的に管理対象にしてくれそうです。

## salt-masterでsalt-minionの情報を取得する

それでは、唐突に「使っているOSやKernelのバージョンはいくつなのか、入っているbashは古くないか、opensshは更新されているか...」などを聞かれた場合に対応できそうか、確認してみましょう。  
試しに使ってみるのはこの辺りのモジュールです。

* 基本情報は [salt.modules.grains](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.grains.html)
* パッケージ周りは [salt.modules.pkg](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.pkg.html)
* コマンド実行は [salt.modules.cmdmod](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.cmdmod.html)

ここで紹介するモジュールはほんの一部ですので、他にも必要そうなモジュールがあるかどうかは https://docs.saltproject.io/en/latest/ref/modules/ を探してみてください。

### OSのバージョン取得(salt.modules.grains)

Windows/Linuxの違いなどで値の表現が異なりますが、例えば今回準備したLinuxの場合は、以下のように出力されます。

```yaml
$ sudo salt '*' grains.get os
salt-sample01.example.com:
    Ubuntu
salt-master.example.com:
    Ubuntu
$ sudo salt '*' grains.get osrelease
salt-sample01.example.com:
    22.04
salt-master.example.com:
    20.04
```

今回の手順では準備していませんが、Windowsの場合だと以下のような値が入っています。

```yaml
os:
    Windows
os_family:
    Windows
osfinger:
    Windows-10
osfullname:
    Microsoft Windows 10 Pro
osmanufacturer:
    Microsoft Corporation
osrelease:
    10
osrelease_info:
    - 10
osservicepack:
    None
osversion:
    10.0.19044
```

`salt.modules.grains` は他にもIPアドレスやCPUモデルなども取得することが可能です。  
もし、何を取得するか決まっていない場合は `sudo salt '*' grains.items` を実行することで `salt.modules.grains` で取得可能な情報が全て出力されます。

### OSのバージョン取得(salt.modules.pkg)

次に、パッケージを取得してみます。  
これもインストールパッケージの管理方法によって出力パターンは異なるのですが、今回準備した環境では以下のように表示されます。

bashのバージョンを取得した場合:

```yaml
$ sudo salt '*' pkg.version bash
salt-master.example.com:
    5.0-6ubuntu1.2
salt-sample01.example.com:
    5.1-6ubuntu1
```

絞らずに全件を取得する場合は `sudo salt '*' pkg.list_pkgs` を実行します。

また、今回の手順では準備していませんが、Windowsの場合の取得例は以下の通りです。

```yaml
$ sudo salt "WIN-XXX" pkg.version "Google Chrome"
WIN-XXX:
    111.0.5563.64
$ sudo salt "WIN-XXX" pkg.version "Slack"
WIN-XXX:
    4.29.149
```

Windowsの場合は一部のパッケージが `Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.17` みたいな名前で登録されてしまうので、何も考えずに全件取得しておいた方が簡単な気がします。  
（パッケージ名にバージョン入っちゃってるから...）

### コマンド実行(salt.modules.cmdmod)

最後の例はコマンド実行です。  
salt-master から salt-minion に対して、任意のコマンドを実行することが可能です。

```
$ sudo salt '*' cmd.run 'uname -a'
salt-sample01.example.com:
    Linux salt-sample01 5.15.0-67-generic #74-Ubuntu SMP Wed Feb 22 14:14:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
salt-master.example.com:
    Linux salt-master 5.4.0-96-generic #109-Ubuntu SMP Wed Jan 12 16:49:16 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
```

#### コマンド実行の制限

とはいえ、いくら払い出された環境であっても、管理者が自由にコマンド実行できるというのは心理的な抵抗があるユーザーも多くいると思います。  
あくまでOSやパッケージのバージョンを取得する目的でインストールされているものとして、外部から受け付けるコマンドの制限をsalt-minionの設定に追加するのが良いのではないでしょうか。

```yaml
cmd_whitelist_glob: ["uname -a"]
```

[cmd_whitelist_glob](https://docs.saltproject.io/en/latest/ref/configuration/minion.html#cmd-whitelist-glob) は、一致したコマンド以外の salt または salt-call コマンド実行を拒否します。  
上記例の設定を追加した場合は、以下のように `uname -a` だけが実行可能になります。

```
$ sudo salt '*' cmd.run 'uname -a'
salt-sample01.example.com:
    Linux salt-sample01 5.15.0-67-generic #74-Ubuntu SMP Wed Feb 22 14:14:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
salt-master.example.com:
    Linux salt-master 5.4.0-96-generic #109-Ubuntu SMP Wed Jan 12 16:49:16 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
$ sudo salt '*' cmd.run 'uname'
salt-sample01.example.com:
    ERROR: The shell command "uname" is not permitted
salt-master.example.com:
    Linux
ERROR: Minions returned with non-zero exit code
```

なお、全てのコマンドを拒否するつもりで以下のように空のリストを渡すと、全て許可という意味になってしまうのでご注意ください。

```yaml
cmd_whitelist_glob: []
```

全てのコマンドを拒否したい場合は `[""]` としましょう。

## salt-minionの登録削除

仮想マシンが削除された場合、登録済みのsalt-minionはsalt-masterに残り続け、コマンド実行してもエラーが返ってきてしまいます。

```
$ sudo salt '*' pkg.version bash
salt-master.example.com:
    5.0-6ubuntu1.2
salt-sample01.example.com:
    Minion did not return. [Not connected]
```

不要になった場合は、ダウンしているノードを登録から削除する `salt-run manage.down removekeys=True` を実行することで、salt-masterの管理対象から外せます。

```
$ sudo salt-run manage.down removekeys=True
- salt-sample01.example.com
$ sudo salt-key -L
Accepted Keys:
salt-master.example.com
Denied Keys:
Unaccepted Keys:
Rejected Keys:
```

## おしまい

今回はcloud-initでsalt-minionをインストールすることで、Proxmox VEの仮想マシンを自動的に管理対象に追加する方法について書いてみました。  
Saltを使った方法が良い方法なのかというと、少し大げさというか、それにしては穴だらけと言うか、微妙なところだと思いますが、実現したいことに対するサンプルとしては分かりやすいかなと思います。

今のところ、Proxmox VEに独自のcloud-initファイルを直接設置できるのは管理者だけなので、OpenStackでcloud-initを使用するのとはまた違った使用感になりますが、ある程度利用方法が決まっているような環境では十分活用できるのではないかと思います。