SSHでZabbixの途中経路を暗号化(zabbix2.2系)

SSHでZabbixの途中経路を暗号化(zabbix1.8系)
では、2.2系の{HOST.PORT}が使える場所拡大になったという事で乗り換えもかねて新しくサーバを構築してみました。

Chef11を使っての構築は別の投稿でgithubにリポジトリ作っているので割愛。
(この作業中にリポジトリのRPMを入れるよりもEPELでZabbix1.8が先に入ってしまう問題が発覚したので後で対応の予定)

■試したこと

1.{PROFILE.TAG1}ではなく、{HOST.PORT}をアクションで起動するスクリプトの引数に渡すことはできるのか?

2.複数のインタフェースを持たせた場合に{HOST.PORT}はどれが入ってくるのか?

3.以前はZabbix AgentのUnreachableをトリガーに再起動していたけれど、サーバのSSHdが復帰した時点をトリガーにできないか?

4.SSHエージェントのアイテムでこの機能を実現して、外部ファイルのスクリプトの形ではなくZabbixにデータとして保存できないか?
 → これはすぐに結論が出まして僕がSSHエージェントを誤解していました。対象サーバにログインしてからコマンドを実行するタイプだったので不適切でした。

■1.{PROFILE.TAG1}ではなく、{HOST.PORT}をアクションで起動するスクリプトの引数に渡すことはできるのか?

結論から言うとできました。
Zabbix2.2アクション

上の画像のとおりですが、書き出してみるとこのような形です。

実行内容のタイプ:リモートコマンド
ターゲットリスト:現在のホスト
タイプ:カスタムスクリプト
次で実行:Zabbixサーバー
コマンド:

/opt/zabbix/bin/zabbix_ssh_tunnel.sh \
restart {HOSTNAME} zabbix_agent {HOST.PORT} 12422 \
>> /tmp/zabbix_ssh_tunnel.log 2>&1

※本来は改行なしで入れてます。

以前は/usr/local/zabbix/bin にスクリプトを置いていたのですが、昨今の流行は/opt/[パッケージ名]のようなので移動しました。また、接続用のユーザーを改めて作ったので変更しています。12422はSSHのポートなのですが、どこかで設定した値を持ってくるのはあきらめています。(理由は後述の2で)ですので、アクションはSSHのポートの種類だけ作った形になりました。とはいっても、うちは2種類しか設定していないのでそれほど煩雑ではありません。

ようやくこれで、{PROFILE.TAG1}のような関連の薄い項目を利用して自サーバ内のポートを指定しなくてよくなりましたし、ホストのインタフェースと紐づけられるので同じことを何度も設定しなくてよくなりました。

ただ、{HOSTNAME}は曲者でした。僕はこの仕組みを主にさくらVPSのようなグローバルIPを持っているけどそれほど規模が無く、個別にZabbixサーバを立てられないようなシステムに適用しています。ですので、{HOSTNAME}はDNSをきちんと設定されている事が前提で、それに紐づいたものを入れてあげることでホスト設定と連動して設定ができています。

今回困ったのは、バックアップ用途だからとDNSをきちんと設定していないサーバに対しても監視を施さねばならず、泣く泣く/etc/hostsに勝手に決めたホスト名を入れて監視にしているということです。適当過ぎると本番サーバとの関連が一目で分からないので、苦慮するかと思いまして。Zabbix2.2はホスト名のほかに表示名をつけられるので、そっちで関連付けた名前にしたらよいのですが…まあ、さくらVPSがもともと持っている名前ばかりが並ぶと混乱するので。

■2.複数のインタフェースを持たせた場合に{HOST.PORT}はどれが入ってくるのか?

なぜこれを考えたか?というと、スクリプトに渡す引数に{HOSTNAME}ではなく、グローバルIP、12422のような固定のポートではなく、インタフェースのPORTを渡せないかと思った次第でして。それができるなら、アクションは一つで済みます。ホストの設定で変えられるので手間が減るという事を考えました。

■設定しているインタフェース
・127.0.0.1:10100   ← SSHVPNでサーバ自身に開けるポート。接続先は対象サーバの10050ポート(Zabbix Agentのデフォルト)
・21x.xx.xx.xxx:12422 ← サーバの持っているグローバルIPとSSHのポート

■テストしてみたアクションのスクリプト

/opt/zabbix/bin/zabbix_ssh_tunnel.sh restart \
{HOST.IP2} zabbix_agent {HOST.PORT} {HOST.PORT2} >> /tmp/zabbix_ssh_tunnel.log 2>&1

※わかりやすいように改行入っていますが、実際は1行

■デバッグ出力

start *UNKNOWN* zabbix_agent 10100 *UNKNOWN*      <- 引数を出してみた行
Bad port '*UNKNOWN*'
Bad port '*UNKNOWN

UNKNOWNになってしまいました。

参考にしたZabbix2.2のドキュメントには下記の文言が入っていました。
https://www.zabbix.com/documentation/2.2/manual/appendix/macros/supported_by_location

The numbered macro syntax of {MACRO<1-9>} is used to reference hosts in the order in which they appear in a trigger expression. Thus, macros like {HOST.IP1}, {HOST.IP2}, {HOST.IP3} will expand to the IP of the first, second and third host in the trigger expression, providing the expression contains those hosts.

ざっくりというと、トリガーの中に出てきたホストが複数あった場合、それらを番号つけて展開しますっていう事かと。
もしも、{HOST.IP2}等の値を使いたいと思うならば、複雑なトリガーを作らなくてはダメだと思いました。
それは本末転倒な気がしたので、あっさりとこの{HOST.IP2}は止めました。

■3.以前はZabbix AgentのUnreachableをトリガーに再起動していたけれど、サーバのSSHdが復帰した時点をトリガーにできないか?

シンプルチェック(サーバのインタフェースから直接対象ホストのインタフェースに監視をかける)アイテムを作りました。

■アイテム

名前:SSH接続確認
タイプ:シンプルチェック
キー:net.tcp.service[ssh,,12422] ← ポートの部分は未設定だとSSHのデフォルトポートが入るので、変更したポートを入力
ホストインターフェース:21x.xx.xxx.xx:12422 ← 対象サーバのグローバルIPをエージェントのインタフェースとして登録
データ型:整数

次に、トリガーも作成しました。

■トリガー

名前:SSHポートのシンプルチェック
条件式:{[ホスト名]:net.tcp.service[ssh,,12422].last()}=0

※テストの後でテンプレート化したので、ホスト名は自動的に入ります。

■アクション

最後にアクションですが、通常の実行条件では「トリガーの値=障害」であることが多いところを
「トリガーの値=正常」としています。
「SSHのVPNが切断された=SSHで接続できなくなった」という現象のみにしか対応していませんが。
以前のようにUnreachableでもrestartをかけるとVPNは切断されていないけれど通信できない場合に対応できるかもしれません。

アクションの実行条件:
メンテナンスの状態 期間外 メンテナンス
トリガーの値 = 正常
トリガー名 含まれる SSHポートのシンプルチェック

■スクリプト

#!/bin/bash
KEY=/opt/zabbix/ssh/id_rsa
KNOWN_HOSTS=/opt/zabbix/ssh/known_hosts
 
SSH_OPTIONS="-t -t -i ${KEY} -o UserKnownHostsFile=${KNOWN_HOSTS}"
 
# debug
echo $@
 
# 接続
function start() {
  /usr/bin/ssh ${SSH_OPTIONS} ${AGENT_USER}@${AGENT_HOST} -n -p ${SSH_PORT} -L ${TUNNEL_PORT}:127.0.0.1:10050 &
  /usr/bin/ssh ${SSH_OPTIONS} ${AGENT_USER}@${AGENT_HOST} -n -p ${SSH_PORT} -R 10051:127.0.0.1:10051 &
}
 
# 切断
function stop() {
  pgrep -l -f "${AGENT_USER}@${AGENT_HOST} -n -p ${SSH_PORT} "
  pkill -f "${AGENT_USER}@${AGENT_HOST} -n -p ${SSH_PORT} "
  echo "${AGENT_USER}@${AGENT_HOST} -n -p ${SSH_PORT} "
  echo 'stop ssh for tunnel'
}
 
if [ $# -ne 5 ];then
  echo $"Usage: $0 {start|stop|restart} Destination_server Login_user Localport Destination_ssh_port"
  exit 1
fi
 
AGENT_HOST=${2}
AGENT_USER=${3}
TUNNEL_PORT=${4}
SSH_PORT=${5}
 
case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    stop
    sleep 1
    start
    ;;
  *)
    echo $"Usage: $0 {start|stop|restart}"
    exit 1
esac

■その他の注意点、テンプレートの構成とかchefのrecipeとか

■その他の注意点

このスクリプトを実行すると次のような出力が出ます。

start xxxxxxxx.com zabbix_agent 10100 12422
tcgetattr: Inappropriate ioctl for device
tcgetattr: Inappropriate ioctl for device

SAKURA Internet [Virtual Private Server SERVICE]

[zabbix_agent@xxxxxxxx.com ~]$ Last login: Mon Jul 28 10:29:31 2014 from www99999xx.sakura.ne.jp

SAKURA Internet [Virtual Private Server SERVICE]

[zabbix_agent@xxxxxxxx.com ~]$

1行目はdebug用の出力なので、スクリプトの該当行をコメントアウトしたらでません。
問題の2行目以降はSSHで接続したときの出力になります。tcgetattrの行はssh -tオプションで実現しているところの警告なので無視しています。
http://unix.stackexchange.com/questions/82158/pseudo-terminal-will-not-be-allocated-because-stdin-is-not-a-terminal
これで取り上げられている現象と同じですね。

切断、接続が多くなければ/tmpに出力しているログはそれほど増えないと思います。
追記じゃなくて上書きでもよいのかもしれません。

■テンプレートの構成と適用のやり方

特殊かもしれませんが、管理しているサーバ群はシステムごと…というか顧客ごとに異なるポートでSSHを開けています。
ですので、スクリプトにSSHポートを渡せる仕様にしたのです。しかし、ホストの設定などから取得して設定はできなかったので
アイテム、トリガー、アクションをポートごとに作成しています。

テンプレート内のアイテムやトリガーは無効にしたまま適用し、開けたいポートのアイテムとトリガーだけを有効に。
あと、テンプレートのアイテムであってもインタフェースは変更ができるので、グローバルIPを記入したインタフェースに変更して
ようやく設定が終わります。

■Chefのrecipe

鍵ファイルを作るだけと、/var/lib/zabbix/.ssh このディレクトリを作っています。
zabbixユーザーがスクリプトを起動してSSH接続を開始するときにログインディレクトリのチェックをするので。

※attributeは一部を抜粋

default[:zabbix][:optdir] = "/opt/zabbix"
#
# Cookbook Name:: zabbix
# Recipe:: ssh_agent
#
# Copyright 2014, REDALARM Company Limited.
#
# All rights reserved - Do Not Redistribute
#
 
cookbook_file "#{node[:zabbix][:optdir]}/ssh/id_rsa" do
  source "ssh/id_rsa"
  owner "zabbix"
  group "zabbix"
  mode 0600
  backup false
  FileUtils.mkdir_p("#{node[:zabbix][:optdir]}/ssh")
end
 
cookbook_file "#{node[:zabbix][:optdir]}/ssh/id_rsa.pub" do
  source "ssh/id_rsa.pub"
  owner "zabbix"
  group "zabbix"
  mode 0600
  backup false
  FileUtils.mkdir_p("#{node[:zabbix][:optdir]}/ssh")
end
 
cookbook_file "#{node[:zabbix][:optdir]}/ssh/known_hosts" do
  source "ssh/known_hosts"
  owner "zabbix"
  group "zabbix"
  mode 0600
  backup false
  FileUtils.mkdir_p("#{node[:zabbix][:optdir]}/ssh")
end
 
directory "/var/lib/zabbix/.ssh" do
  owner "zabbix"
  group "zabbix"
  mode  00700
  recursive true
  action :create
end

ああ・・・スクリプトを設置するrecipeを書き忘れている。

2014/09/05 追記
SSHでZabbixの途中経路を暗号化(zabbix2.2系)の調整
これを書きました。復帰トリガーを追加しました。

2014/11/27 追記
SSHでZabbixの途中経路を暗号化(zabbix2.2系)のスクリプト修正
これを書きました。sshのオプションをまとめたのと、known_hostsへの追加モードを加えました。
今気が付いたのですが、known_hostsへの追加時は、最後の1行を出力するとよさげですね。次また修正したいと思います。