From 2892d257a82c7ae1ea7e55a485fe10d865641e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Deckert?= Date: Fri, 2 Apr 2021 18:20:07 +0200 Subject: [PATCH] zabbix: new appliance --- zabbix/Makefile | 90 +++ zabbix/appliance/02firstboot.start | 90 +++ zabbix/appliance/cert-renew.sh | 66 ++ zabbix/mariadb/my.cnf.root | 7 + zabbix/package.keywords | 3 + zabbix/package.mask | 2 + zabbix/package.use | 11 + zabbix/snmp/snmpd.conf | 1 + zabbix/snmp/snmptrapd.conf | 2 + zabbix/snmp/snmptt.conf | 2 + zabbix/world | 18 + zabbix/zabbix.cfg | 2 + zabbix/zabbix/userparameter_mysql.conf | 1 + .../zabbix-syslog/70-zabbix_rsyslog.conf | 19 + zabbix/zabbix/zabbix-syslog/lib/ZabbixAPI.pm | 587 ++++++++++++++++++ zabbix/zabbix/zabbix-syslog/zabbix_syslog.cfg | 5 + .../zabbix_syslog_create_urls.pl | 207 ++++++ .../zabbix-syslog/zabbix_syslog_lkp_host.pl | 272 ++++++++ 18 files changed, 1385 insertions(+) create mode 100644 zabbix/Makefile create mode 100755 zabbix/appliance/02firstboot.start create mode 100755 zabbix/appliance/cert-renew.sh create mode 100644 zabbix/mariadb/my.cnf.root create mode 100644 zabbix/package.keywords create mode 100644 zabbix/package.mask create mode 100644 zabbix/package.use create mode 100644 zabbix/snmp/snmpd.conf create mode 100644 zabbix/snmp/snmptrapd.conf create mode 100644 zabbix/snmp/snmptt.conf create mode 100644 zabbix/world create mode 100644 zabbix/zabbix.cfg create mode 100644 zabbix/zabbix/userparameter_mysql.conf create mode 100644 zabbix/zabbix/zabbix-syslog/70-zabbix_rsyslog.conf create mode 100644 zabbix/zabbix/zabbix-syslog/lib/ZabbixAPI.pm create mode 100644 zabbix/zabbix/zabbix-syslog/zabbix_syslog.cfg create mode 100644 zabbix/zabbix/zabbix-syslog/zabbix_syslog_create_urls.pl create mode 100644 zabbix/zabbix/zabbix-syslog/zabbix_syslog_lkp_host.pl diff --git a/zabbix/Makefile b/zabbix/Makefile new file mode 100644 index 0000000..fcd182a --- /dev/null +++ b/zabbix/Makefile @@ -0,0 +1,90 @@ +preinstall: + # hardcoded users and groups + $(inroot) useradd --system --comment="created from appliance building - zabbix user" --home-dir="/var/lib/zabbix/home" --shell="/sbin/nologin" --no-create-home --uid 600 --user-group zabbix + +postinstall: + # MariaDB-Konfiguration + sed -i "s/^character-set-server.*$$/character-set-server = utf8mb4/" $(CHROOT)/etc/mysql/mariadb.d/50-distro-server.cnf + sed -iE 's/^\(log-bin\)/#\1/' $(CHROOT)/etc/mysql/mariadb.d/50-distro-server.cnf + echo >> $(CHROOT)/etc/mysql/mariadb.d/50-distro-server.cnf + echo "# innodb tuning" >> $(CHROOT)/etc/mysql/mariadb.d/50-distro-server.cnf + echo "innodb_buffer_pool_size = 2G" >> $(CHROOT)/etc/mysql/mariadb.d/50-distro-server.cnf + echo "innodb_strict_mode = OFF" >> $(CHROOT)/etc/mysql/mariadb.d/50-distro-server.cnf + cp mariadb/my.cnf.root $(CHROOT)/root/.my.cnf + chmod 0600 $(CHROOT)/root/.my.cnf + rm -rf $(CHROOT)/var/lib/mysql/* + $(inroot) bash -c 'yes gentoo | emerge --config dev-db/mariadb' + + # Apache-/PHP-Konfiguration + sed -i 's:APACHE2_OPTS=\":APACHE2_OPTS=\"-D PHP :' $(CHROOT)/etc/conf.d/apache2 + find $(CHROOT)/etc/php/apache2-*/ -iname php.ini -print | xargs \sed -i \ + -e 's:.*date.timezone =.*:date.timezone = Europe/Berlin:' \ + -e 's:.*max_execution_time =.*:max_execution_time = 300:' \ + -e 's:.*max_input_time =.*:max_input_time = 300:' \ + -e 's:.*post_max_size =.*:post_max_size = 16M:' \ + -e 's:.*always_populate_raw_post_data =.*:always_populate_raw_post_data = -1:' + $(inroot) systemctl enable apache2 + + # Add zabbix service definitions + echo "zabbix-agent 10050/tcp Zabbix Agent" >> $(CHROOT)/etc/services + echo "zabbix-agent 10050/udp Zabbix Agent" >> $(CHROOT)/etc/services + echo "zabbix-trapper 10051/tcp Zabbix Trapper" >> $(CHROOT)/etc/services + echo "zabbix-trapper 10051/udp Zabbix Trapper" >> $(CHROOT)/etc/services + + # Install Zabbix webapp + $(inroot) webapp-config -h localhost -d zabbix -I zabbix `ls $(CHROOT)/usr/share/webapps/zabbix` + cp $(CHROOT)/var/www/localhost/htdocs/zabbix/conf/zabbix.conf.php.example $(CHROOT)/var/www/localhost/htdocs/zabbix/conf/zabbix.conf.php + # enable any language + sed -i "s:'display' => false]:'display' => true]:" $(CHROOT)/var/www/localhost/htdocs/zabbix/include/locales.inc.php + # im Zabbix-Ebuild wird fowners und fperms vor webapp_src_install aufgerufen und deswegen wieder überschrieben + $(inroot) chown -R zabbix:zabbix \ + /etc/zabbix \ + /var/lib/zabbix \ + /var/lib/zabbix/home \ + /var/lib/zabbix/scripts \ + /var/lib/zabbix/alertscripts \ + /var/lib/zabbix/externalscripts \ + /var/log/zabbix + chmod 0750 \ + $(CHROOT)/etc/zabbix \ + $(CHROOT)/var/lib/zabbix \ + $(CHROOT)/var/lib/zabbix/home \ + $(CHROOT)/var/lib/zabbix/scripts \ + $(CHROOT)/var/lib/zabbix/alertscripts \ + $(CHROOT)/var/lib/zabbix/externalscripts \ + $(CHROOT)/var/log/zabbix + + # Zabbix Agent + cp zabbix/userparameter_mysql.conf $(CHROOT)/var/lib/zabbix/userparameter_mysql.conf + + # Zabbix Syslog (https://github.com/v-zhuravlev/zabbix-syslog) + mkdir -p $(CHROOT)/etc/zabbix/scripts/lib + cp zabbix/zabbix-syslog/zabbix_syslog_create_urls.pl $(CHROOT)/etc/zabbix/scripts/zabbix_syslog_create_urls.pl + cp zabbix/zabbix-syslog/zabbix_syslog_lkp_host.pl $(CHROOT)/etc/zabbix/scripts/zabbix_syslog_lkp_host.pl + cp zabbix/zabbix-syslog/zabbix_syslog.cfg $(CHROOT)/etc/zabbix/zabbix_syslog.cfg + cp zabbix/zabbix-syslog/lib/ZabbixAPI.pm $(CHROOT)/etc/zabbix/scripts/lib/ZabbixAPI.pm + cp zabbix/zabbix-syslog/70-zabbix_rsyslog.conf $(CHROOT)/etc/rsyslog.d/70-zabbix_rsyslog.conf + $(inroot)chown -R zabbix:zabbix /etc/zabbix/scripts + chmod +x $(CHROOT)/etc/zabbix/scripts/zabbix_syslog_create_urls.pl + chmod +x $(CHROOT)/etc/zabbix/scripts/zabbix_syslog_lkp_host.pl + + # FPing + $(inroot)chmod u=rwsx,g=rx,o=rx /usr/sbin/fping + $(inroot)chmod u=rwsx,g=rx,o=rx /usr/sbin/fping6 + + # SNMP + cp snmp/snmpd.conf $(CHROOT)/etc/snmp/snmpd.conf + cp snmp/snmptrapd.conf $(CHROOT)/etc/snmp/snmptrapd.conf + cp snmp/snmptt.conf $(CHROOT)/etc/snmp/snmptt.conf + sed -i \ + -e 's:net_snmp_perl_enable = 0:net_snmp_perl_enable = 1:' \ + -e 's:translate_integers = 1:translate_integers = 0:' \ + -e 's:#mibs_environment = ALL:mibs_environment = ALL:' \ + -e 's.#date_time_format =.date_time_format = %H:%M:%S %Y/%m/%d.' \ + -e 's:daemon_uid = snmptt:daemon_uid = zabbix:' \ + -e 's:log_system_enable = 0:log_system_enable = 1:' \ + -e 's:unknown_trap_log_enable = 0:unknown_trap_log_enable = 1:' \ + $(CHROOT)/etc/snmp/snmptt.ini + mkdir -p $(CHROOT)/var/log/snmptt + $(inroot)chmod 0775 /var/log/snmptt + $(inroot)chown zabbix:zabbix /var/log/snmptt diff --git a/zabbix/appliance/02firstboot.start b/zabbix/appliance/02firstboot.start new file mode 100755 index 0000000..c369bcf --- /dev/null +++ b/zabbix/appliance/02firstboot.start @@ -0,0 +1,90 @@ +#!/bin/bash + +# variables +LABEL="DATA" +DATABASE_PASS="Di1sgMySQLPwd." +TLD="example.com" +HOST="zabbix" +ORGNAME="Zabbix example" + +# start +set -e + +[ -e /01firstboot ] && exit 0 +[ -e /02firstboot ] || exit 0 + +# Database +systemctl stop mariadb +if [ ! -d "/$LABEL/var/lib/mysql/zabbix" ]; then + echo 'Initialize MariaDB...' + mkdir -p "/$LABEL/var/lib" + rm -rf "/$LABEL/var/lib/mysql" + rm -rf "/$LABEL/var/lib/mysql.orig" + cp -a "/var/lib/mysql" "/$LABEL/var/lib/mysql.orig" + mv "/var/lib/mysql" "/$LABEL/var/lib/mysql" + ln -s "/$LABEL/var/lib/mysql" "/var/lib/mysql" + systemctl start mariadb + sleep 5 + + echo 'Create Zabbix database...' + mysql -u root -e "CREATE USER 'zabbix'@'localhost' IDENTIFIED BY '$DATABASE_PASS'" + mysql -u root -e "CREATE DATABASE zabbix DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;" + mysql -u root -e "GRANT ALL PRIVILEGES ON zabbix.* TO 'zabbix'@'localhost' IDENTIFIED by '$DATABASE_PASS';" + mysql -u root -e "FLUSH PRIVILEGES;" + + echo 'Import Zabbix MySQL data' + mysql -u root zabbix < /usr/share/zabbix/database/mysql/schema.sql + mysql -u root zabbix < /usr/share/zabbix/database/mysql/images.sql + mysql -u root zabbix < /usr/share/zabbix/database/mysql/data.sql +else + echo 'Start MariaDB...' + if [ ! -L /var/lib/mysql ]; then + rm -rf "/$LABEL/var/lib/mysql.orig" + mv "/var/lib/mysql" "/$LABEL/var/lib/mysql.orig" + ln -s "/$LABEL/var/lib/mysql" "/var/lib/mysql" + fi + systemctl start mariadb +fi +echo 'Enable database...' +systemctl enable mariadb + +# Certificates +if [ ! -f "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD-cert.pem" ]; then + echo 'Create certificates...' + mkdir -p "/$LABEL/CERTS/KEYS/" + mkdir -p "/$LABEL/CERTS/$HOST.$TLD" + echo "FQDN = $HOST.$TLD" > "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD.cnf" + echo "ORGNAME = $ORGNAME" >> "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD.cnf" + echo "ALTNAMES = DNS:$HOST.$TLD , DNS:$TLD" >> "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD.cnf" + echo -e "\n[ req ]\ndefault_bits = 4096\ndefault_md = sha256\nprompt = no\nencrypt_key = no\ndistinguished_name = dn\nreq_extensions = req_ext\ndefault_keyfile = ../KEYS/\$FQDN-key.pem\n" >> "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD.cnf" + echo -e "\n[ dn ]\nC = DE\nO = \$ORGNAME\nCN = \$FQDN\n" >> "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD.cnf" + echo -e "\n[ req_ext ]\nsubjectAltName = \$ALTNAMES" >> "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD.cnf" + openssl req -x509 -new -config "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD.cnf" -out "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD-cert.pem" -keyout "/$LABEL/CERTS/KEYS/$HOST.$TLD-key.pem" + cp "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD-cert.pem" "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD-fullchain.pem" + touch "/$LABEL/CERTS/$HOST.$TLD/$HOST.$TLD-chain.pem" +fi + +# Zabbix +echo 'Start Zabbix...' +if [ ! -f "/$LABEL/etc/zabbix/zabbix_server.conf" ]; then + mkdir -p "/$LABEL/etc/zabbix" + chown zabbix:zabbix "/$LABEL/etc/zabbix" + cp /etc/zabbix/zabbix_server.conf "/$LABEL/etc/zabbix/zabbix_server.conf.orig" + mv /etc/zabbix/zabbix_server.conf "/$LABEL/etc/zabbix/zabbix_server.conf" + sed -i "s:# DBPassword=:DBPassword=${DATABASE_PASS}:" "/$LABEL/etc/zabbix/zabbix_server.conf" + ln -s "/$LABEL/etc/zabbix/zabbix_server.conf" "/etc/zabbix/zabbix_server.conf" + + mkdir -p "/$LABEL/etc/zabbix" + chown zabbix:zabbix "/$LABEL/etc/zabbix" + cp /etc/zabbix/zabbix_server.conf "/$LABEL/etc/zabbix/zabbix_server.conf.orig" + mv /etc/zabbix/zabbix_server.conf "/$LABEL/etc/zabbix/zabbix_server.conf" + sed -i "s:\$DB\['PASSWORD'\].*:\$DB\['PASSWORD'\] = '${DATABASE_PASS}';:" /var/www/localhost/htdocs/zabbix/conf/zabbix.conf.php +else + +fi +systemctl start zabbix-server +systemctl enable zabbix-server +systemctl start zabbix-agentd +systemctl enable zabbix-agentd + +rm /firstboot diff --git a/zabbix/appliance/cert-renew.sh b/zabbix/appliance/cert-renew.sh new file mode 100755 index 0000000..93dbfca --- /dev/null +++ b/zabbix/appliance/cert-renew.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +HOST="ejabberd" +TLD="example.com" +FQDN="$HOST.$TLD" +LABEL="DATA" + +CERT_DIR=/$LABEL/CERTS +CERT_EJABBERD=/$LABEL/etc/ssl/ejabberd +CERT_NGINX=/$LABEL/etc/ssl/nginx +GETREPO="" +GETUSER="" +GETPASS="" + +function getCurrentVersion() { +# Get hash from latest revision + git log --format=format:%H -1 +} + +cd $CERT_DIR + +if [ -z "$GETREPO" ]; then + GIT_REVISION=0 + GIT_NEW_REVISION=1 + cd $FQDN +elif [ ! -d "$FQDN" ]; then + GIT_REVISION=0 + git clone "https://$GETUSER:$GETPASS@$GETREPO" + cd $FQDN + GIT_NEW_REVISION=$(getCurrentVersion) +else + cd $FQDN + GIT_REVISION=$(getCurrentVersion) + git commit -m "CRON: auto commit" + git fetch + git merge origin/master -m "Auto Merge" + GIT_NEW_REVISION=$(getCurrentVersion) +fi + +echo "old: $GIT_REVISION" +echo "new: $GIT_NEW_REVISION" + +if [ $GIT_REVISION != $GIT_NEW_REVISION ] +then + echo "Update Ejabberd certificate..." + mkdir -p $CERT_EJABBERD + cp $CERT_DIR/$FQDN/$FQDN-fullchain.pem $CERT_EJABBERD/server.pem + cp $CERT_DIR/KEYS/$FQDN-key.pem $CERT_EJABBERD/server.key + chown root:jabber $CERT_EJABBERD/server.* + chmod 444 $CERT_EJABBERD/server.pem + chmod 440 $CERT_EJABBERD/server.key + echo "Restart Ejabberd..." + systemctl restart ejabberd + + echo "Update Nginx certificate..." + mkdir -p $CERT_NGINX + cp $CERT_DIR/$FQDN/$FQDN-fullchain.pem $CERT_NGINX/nginx.pem + cp $CERT_DIR/KEYS/$FQDN-key.pem $CERT_NGINX/nginx.key + chown nginx:nginx $CERT_NGINX/nginx.* + chmod 444 $CERT_NGINX/nginx.pem + chmod 400 $CERT_NGINX/nginx.key + echo "Restart Nginx..." + systemctl restart nginx +fi + +exit 0 diff --git a/zabbix/mariadb/my.cnf.root b/zabbix/mariadb/my.cnf.root new file mode 100644 index 0000000..12ef61a --- /dev/null +++ b/zabbix/mariadb/my.cnf.root @@ -0,0 +1,7 @@ +[mysqladmin] +user = root +password = gentoo + +[mysql] +user = root +password = gentoo diff --git a/zabbix/package.keywords b/zabbix/package.keywords new file mode 100644 index 0000000..93f6d82 --- /dev/null +++ b/zabbix/package.keywords @@ -0,0 +1,3 @@ +# Zabbix +net-analyzer/zabbix +net-analyzer/snmptt diff --git a/zabbix/package.mask b/zabbix/package.mask new file mode 100644 index 0000000..7a9de96 --- /dev/null +++ b/zabbix/package.mask @@ -0,0 +1,2 @@ +# nur LTS-Versionen 5.0.x bauen +>=net-analyzer/zabbix-5.1 diff --git a/zabbix/package.use b/zabbix/package.use new file mode 100644 index 0000000..5cfa4a5 --- /dev/null +++ b/zabbix/package.use @@ -0,0 +1,11 @@ +# Zabbix +app-admin/rsyslog dbi mysql openssl snmp systemd +app-eselect/eselect-php apache2 +app-text/poppler -introspection -jpeg -jpeg2k +dev-java/openjdk-bin headless-awt +dev-lang/php apache2 bcmath curl gd ldap mysql mysqli sockets sysvipc truetype xmlreader xmlwriter +media-libs/gd jpeg png +net-analyzer/zabbix curl frontend java ldap libxml2 mysql openipmi -postgres proxy server snmp ssh xmpp +net-analyzer/net-snmp perl +net-analyzer/fping suid +net-print/cups-filters -foomatic -postscript diff --git a/zabbix/snmp/snmpd.conf b/zabbix/snmp/snmpd.conf new file mode 100644 index 0000000..250e7f8 --- /dev/null +++ b/zabbix/snmp/snmpd.conf @@ -0,0 +1 @@ +mibs +ALL \ No newline at end of file diff --git a/zabbix/snmp/snmptrapd.conf b/zabbix/snmp/snmptrapd.conf new file mode 100644 index 0000000..c91bbcd --- /dev/null +++ b/zabbix/snmp/snmptrapd.conf @@ -0,0 +1,2 @@ +traphandle default /usr/sbin/snmptt +disableAuthorization yes diff --git a/zabbix/snmp/snmptt.conf b/zabbix/snmp/snmptt.conf new file mode 100644 index 0000000..c06e586 --- /dev/null +++ b/zabbix/snmp/snmptt.conf @@ -0,0 +1,2 @@ +EVENT general .* "General event" Normal +FORMAT ZBXTRAP $aA $ar severity:$s $Fn$+* diff --git a/zabbix/world b/zabbix/world new file mode 100644 index 0000000..5ac46c0 --- /dev/null +++ b/zabbix/world @@ -0,0 +1,18 @@ +app-admin/rsyslog +dev-db/mariadb +dev-db/phpmyadmin +dev-java/openjdk-bin:8 +dev-perl/Config-General +dev-perl/JSON-RPC +dev-perl/JSON-XS +dev-perl/libwww-perl +dev-tcltk/expect +net-analyzer/l2dm-lldp +net-analyzer/net-snmp +net-analyzer/nmap +net-analyzer/snmptt +net-dns/bind-tools +net-misc/netkit-telnetd +sys-apps/ipmitool +sys-libs/openipmi +sys-process/lsof diff --git a/zabbix/zabbix.cfg b/zabbix/zabbix.cfg new file mode 100644 index 0000000..5a469a1 --- /dev/null +++ b/zabbix/zabbix.cfg @@ -0,0 +1,2 @@ +REPO_NAMES = unitas-zabbix +REPO_URI_unitas-zabbix = https://git.unitas-network.de/Gentoo/unitas-zabbix.git diff --git a/zabbix/zabbix/userparameter_mysql.conf b/zabbix/zabbix/userparameter_mysql.conf new file mode 100644 index 0000000..099ed1c --- /dev/null +++ b/zabbix/zabbix/userparameter_mysql.conf @@ -0,0 +1 @@ +UserParameter=mysql.status[*],echo "show global status where Variable_name='$1';" | HOME=/etc/ mysql -N | awk '{print $$2}' \ No newline at end of file diff --git a/zabbix/zabbix/zabbix-syslog/70-zabbix_rsyslog.conf b/zabbix/zabbix/zabbix-syslog/70-zabbix_rsyslog.conf new file mode 100644 index 0000000..08afc2c --- /dev/null +++ b/zabbix/zabbix/zabbix-syslog/70-zabbix_rsyslog.conf @@ -0,0 +1,19 @@ +# provides UDP syslog reception +$ModLoad imudp +$UDPServerRun 514 + +#enables omrpog module +$ModLoad omprog + +$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%" +$template network-fmt,"%TIMESTAMP:::date-rfc3339% [%fromhost-ip%] %pri-text% %syslogtag%%msg%\n" + +#exclude unwanted messages(examples): +:msg, contains, "Child connection from" stop +:msg, contains, "exit after auth (ubnt): Disconnect received" stop +:msg, contains, "password auth succeeded for 'ubnt' from" stop +:msg, contains, "exit before auth: Exited normally" stop +if $fromhost-ip != '127.0.0.1' then { + action(type="omprog" binary="/etc/zabbix/scripts/zabbix_syslog_lkp_host.pl" template="network-fmt") + stop +} diff --git a/zabbix/zabbix/zabbix-syslog/lib/ZabbixAPI.pm b/zabbix/zabbix/zabbix-syslog/lib/ZabbixAPI.pm new file mode 100644 index 0000000..2c41751 --- /dev/null +++ b/zabbix/zabbix/zabbix-syslog/lib/ZabbixAPI.pm @@ -0,0 +1,587 @@ +package ZabbixAPI; +use Data::Dumper; +use LWP; +use JSON::XS; +use MIME::Base64 qw(encode_base64); + +my @no_auth_methods = ('user.login','apiinfo.version'); +sub new { + my $class = shift; + my $args = shift; + + my $api_url = $args->{api_url} + || 'http://localhost/zabbix/api_jsonrpc.php'; + my $username = $args->{username} || 'Admin'; + my $password = $args->{password} || 'zabbix'; + + my $self = bless { + api_url => $api_url, + username => $username, + password => $password, + ua => LWP::UserAgent->new(), + auth => undef, + id => 1, + }, $class; + + $self->{req} = HTTP::Request->new( POST => $self->{api_url} ); + $self->{req}->content_type('application/json-rpc'); + + return $self; +} +sub id { + my $self = shift; + return $self->{id}++; +} + + +sub prepare_auth { + my $self = shift; + my $method = shift; + + if (grep /$method/,@no_auth_methods) { + return undef; + } + else + { + return $self->{auth}; + } +}; + +sub do { + + my $self = shift; + my $method = shift; + my $params = shift; + + my $json = JSON::XS->new->utf8->encode( + { + jsonrpc => '2.0', + method => $method, + params => $params, + id => $self->id, + auth => $self->prepare_auth($method) + } + ); + + + + $self->{req}->content($json); + + # Pass request to the user agent and get a response back + my $res = $self->{ua}->request( $self->{req} ); + + # Check the outcome of the response + if ( $res->is_success ) { + + my $return = JSON::XS->new->utf8->decode( $res->content ); + die $return->{error}->{data}."\n" if $return->{error}; + return $return->{result}; + } + else { + die $res->status_line, "\n"; + } + +} + +sub do_raw { + + my $self = shift; + my $method = shift; + my $params = shift; + + my $json = JSON::XS->new->utf8->encode( + { + jsonrpc => '2.0', + method => $method, + params => JSON::XS->new->utf8->decode($params), + id => $self->id, + auth => $self->prepare_auth($method) + } + ); + + + $self->{req}->content($json); + + # Pass request to the user agent and get a response back + my $res = $self->{ua}->request( $self->{req} ); + + # Check the outcome of the response + if ( $res->is_success ) { + my $return = JSON::XS->new->utf8->decode( $res->content ); + die $return->{error}->{data}."\n" if $return->{error}; + return $return->{result}; + } + else { + die $res->status_line, "\n"; + } + + +} + +sub import_configuration_from_file { + my $self = shift; + my $file = shift; + my $configuration; + { + local $/ = undef; + open(my $fh,'<:encoding(UTF-8)', $file) or die "Error opening $file: $!"; + + $configuration = <$fh>; + close $fh; + } + + my $json = <<'END_PARAMS'; +{ + "format": "xml", + "rules": { + "groups": { + "createMissing": true + }, + "hosts": { + "createMissing": true, + "updateExisting": true + }, + "templates": { + "createMissing": true, + "updateExisting": true + }, + "templateLinkage": { + "createMissing": true + }, + "templateScreens": { + "createMissing": true, + "updateExisting": true, + "deleteMissing": true + }, + "applications": { + "createMissing": true, + "deleteMissing": true + }, + "discoveryRules": { + "createMissing": true, + "updateExisting": true, + "deleteMissing": true + }, + "items": { + "createMissing": true, + "updateExisting": true, + "deleteMissing": true + }, + "triggers": { + "createMissing": true, + "updateExisting": true, + "deleteMissing": true + }, + "graphs": { + "createMissing": true, + "updateExisting": true, + "deleteMissing": true + }, + "screens": { + "createMissing": true, + "updateExisting": true + }, + "maps": { + "createMissing": true, + "updateExisting": true + }, + "images": { + "createMissing": true, + "updateExisting": true + }, + "valueMaps": { + "createMissing": true, + "updateExisting": true + } + + }, + "source": "" +} +END_PARAMS + my $params = JSON::XS->new->utf8->decode($json); + $params->{source}=$configuration; + $self->do('configuration.import', $params); + + + +} + +sub login { + my $self = shift; + my $params = { + user => $self->{username}, + password => $self->{password} + }; + my $content = $self->do("user.login",$params); + $self->{auth}=$content; + +} + +sub logout { + my $self = shift; + $self->do( 'user.logout', {} ); +} + + +sub create_or_update_mediatype { + + my $self = shift; + my $params = shift; + my $result; + eval { #try to create JSON + $result = $self->do('mediatype.create', $params); + }; + if ($@) { + if($@ =~ /already exists/) { + warn "WARN: $params->{description} already exists. Updating instead..."."\n"; + #get mediatypeid + $json = { output => ['mediatypeid'], filter =>{description=>[$params->{description}]}}; + my $id = $self->do('mediatype.get',$json); + $params->{mediatypeid}= $id->[0]->{mediatypeid}; + #update instead of creating.... + $result = $self->do('mediatype.update', $params); + return $result; + } + else { + + die $@; + + } + } + else { + + return $result; + + } +} + + +sub create_or_update_user { + + my $self = shift; + my $params = shift; + my $result; + eval { #try to create JSON + $result = $self->do('user.create', $params); + }; + if ($@) { + if($@ =~ /already exists/) { + warn "WARN: $params->{alias} already exists. Updating instead..."."\n"; + #get mediatypeid + $json = { output => ['userid'], filter =>{alias=>[$params->{alias}]}}; + my $id = $self->do('user.get',$json); + + $params->{userid}= $id->[0]->{userid}; + #update instead of creating.... + my $medias = $params->{user_medias}->[0]; + + delete $params->{user_medias}; # remove user_medias, not possible in 'user.update' call + $result = $self->do('user.update', $params); + + my $result_media = $self->do('user.updatemedia',{users => [ {userid=>$params->{userid}} ], + medias => $medias + }); + + return $result; + } + else { + + die $@; + + } + } + else { + + return $result; + + } + + +} + +sub create_or_update_action { + + my $self = shift; + my $params = shift; + my $result; + eval { #try to create JSON + $result = $self->do('action.create', $params); + }; + if ($@) { + if($@ =~ /already exists/) { + warn "WARN: $params->{name} already exists. Updating instead..."."\n"; + #get mediatypeid + $json = { output => ['actionid'], filter =>{name=>[$params->{name}]}}; + my $id = $self->do('action.get',$json); + $params->{actionid}= $id->[0]->{actionid}; + #update instead of creating.... + delete $params->{eventsource}; #cannot be update, must be removed + $result = $self->do('action.update', $params); + return $result; + } + else { + + die $@; + + } + } + else { + + return $result; + + } + +} + + +sub create_or_update_drule { + + my $self = shift; + my $params = shift; + my $result; + eval { #try to create JSON + $result = $self->do('drule.create', $params); + }; + if ($@) { + if($@ =~ /already exists/) { + warn "WARN: $params->{name} already exists. Updating instead..."."\n"; + $json = { output => ['druleid'], filter =>{name=>[$params->{name}]}}; + my $id = $self->do('drule.get',$json); + $params->{druleid}= $id->[0]->{druleid}; + #update instead of creating.... + $result = $self->do('drule.update', $params); + return $result; + } + else { + + die $@; + + } + } + else { + + return $result; + + } + +} + + +sub get_template_id { + + my $self = shift; + my $template_name = shift; + + my $json = { output => ['host','templateid'], filter =>{host=>[$template_name]}}; + my $result = $self->do('template.get',$json); + return $result->[0]->{templateid}; + +} + +sub get_hostgroup_id { + + my $self = shift; + my $hgroup_name = shift; + + my $json = { output => ['groupid'], filter =>{name=>[$hgroup_name]}}; + my $result = $self->do('hostgroup.get',$json); + return $result->[0]->{groupid}; + +} + + +sub get_host_id { + + my $self = shift; + my $host_name = shift; + + my $json = { output => ['hostid'], filter =>{host=>[$host_name]}}; + my $result = $self->do('host.get',$json); + return $result->[0]->{hostid}; + +} + +sub get_host_by_name { + + my $self = shift; + my $host_name = shift; + + my $json = { + output => ['hostid'], + filter =>{host=>[$host_name]}, + selectGroups => ['groupid','name'], + selectParentTemplates => ['templateid','name'], + selectMacros => ['macro','value'] + }; + my $result = $self->do('host.get',$json); + return $result->[0]; + +} + + + +sub create_or_merge_host { + + my $self = shift; + my $host_name = shift; + my $params = shift; + my $result; + + my $hostid = $self->get_host_id($host_name); + + if ($hostid) { + print "WARN: Cannot create host $host_name ... going to merge instead\n"; + #print $hostid."\n"; + #update (merge mode currently) + $params->{hostid}=$hostid; + + my $host = $self->get_host_by_name($host_name); + if ($params->{templates}) { + #merge with already existed + my @templates; + foreach my $template (@{$params->{templates}}){ + push @templates,$template->{templateid}; + } + foreach my $template (@{$host->{parentTemplates}}){ + push @templates,$template->{templateid}; + } + my %seen = (); # see http://perldoc.perl.org/perlfaq4.html#How-can-I-remove-duplicate-elements-from-a-list-or-array%3f + + @templates = grep { ! $seen{ $_ }++ } @templates; + + my $i=0; + delete $params->{templates}; + foreach my $templateid (@templates) { + $params->{templates}->[$i]->{templateid} = $templateid; + $i++; + } + + } + + if ($params->{groups}) { + #merge with already existed + my @groups; + foreach my $group (@{$params->{groups}}){ + push @groups,$group->{groupid}; + } + foreach my $group (@{$host->{groups}}){ + push @groups,$group->{groupid}; + } + my %seen = (); # see http://perldoc.perl.org/perlfaq4.html#How-can-I-remove-duplicate-elements-from-a-list-or-array%3f + + @groups = grep { ! $seen{ $_ }++ } @groups; + + my $i=0; + delete $params->{groups}; + foreach my $groupid (@groups) { + $params->{groups}->[$i]->{groupid} = $groupid; + $i++; + } + + } + + if ($params->{macros}) { + #merge with already existed + my @macros; + foreach my $macro (@{$params->{macros}}){ + push @macros, + { + macro => $macro->{macro}, + value => $macro->{value} + }; + } + foreach my $macro (@{$host->{macros}}){ + push @macros, + { + macro => $macro->{macro}, + value => $macro->{value} + }; + } + my %seen = (); # see http://perldoc.perl.org/perlfaq4.html#How-can-I-remove-duplicate-elements-from-a-list-or-array%3f + @macros = grep { ! $seen{ $_->{macro} }++ } @macros; + + my $i=0; + delete $params->{macros}; + + foreach my $macro (@macros) { + $params->{macros}->[$i] = $macro; + $i++; + } + + } + + if ($params->{interfaces}) { + #merge with already existed + #warn "WARN: host interfaces merge is not supported yet... skipping interfaces part\n"; + delete $params->{interfaces}; + } + + #print Dumper $params; + $result = $self->do('host.update', $params); + + } + else { + #create + $params->{host}=$host_name; + #print Dumper $params; + $result = $self->do('host.create', $params); + + } + + return $result; + + +} + + +sub import_image_from_file { + my $self = shift; + my $file = shift; + my $imagename = shift || $file; + my $imagetype = shift || 1; # 1 - icon , 2 - background + my $image; + my $result; + my $json; + { + local $/ = undef; + open(my $fh,, $file) or die "Error opening $file: $!"; + + $image = <$fh>; + close $fh; + } + + my $params = {name => $imagename, imagetype => 1, image => encode_base64($image)}; + + eval {#try to import image + $result = $self->do('image.create', $params); + }; + if ($@) { + if($@ =~ /already exists/) { + warn "WARN: $params->{name} already exists. Updating instead..."."\n"; + #get imageid and update instead + $json = { output => ['imageid'], filter =>{name=>[$params->{name}]}}; + my $id = $self->do('image.get',$json); + $params->{imageid}= $id->[0]->{imageid}; + delete($params->{imagetype}); + #update instead of creating.... + $result = $self->do('image.update', $params); + return $result; + } + else { + + die $@; + + } + } + else { + + return $result; + + } +} + + + + + +1; \ No newline at end of file diff --git a/zabbix/zabbix/zabbix-syslog/zabbix_syslog.cfg b/zabbix/zabbix/zabbix-syslog/zabbix_syslog.cfg new file mode 100644 index 0000000..39d6a4f --- /dev/null +++ b/zabbix/zabbix/zabbix-syslog/zabbix_syslog.cfg @@ -0,0 +1,5 @@ +url = http://localhost/zabbix/api_jsonrpc.php +user = Admin +password = zabbix +server = localhost +debug = 0 \ No newline at end of file diff --git a/zabbix/zabbix/zabbix-syslog/zabbix_syslog_create_urls.pl b/zabbix/zabbix/zabbix-syslog/zabbix_syslog_create_urls.pl new file mode 100644 index 0000000..56dac4e --- /dev/null +++ b/zabbix/zabbix/zabbix-syslog/zabbix_syslog_create_urls.pl @@ -0,0 +1,207 @@ +#!/usr/bin/perl + +use 5.010; +use strict; +use warnings; + +use FindBin qw($Bin); +use lib "$Bin/lib"; +use Data::Dumper; +use Config::General; +use ZabbixAPI; +our $VERSION = 3.1; +my $conf; +$conf = eval {Config::General->new('/usr/local/etc/zabbix_syslog.cfg')}; +if ($@) { + eval {$conf = Config::General->new('/etc/zabbix/zabbix_syslog.cfg')}; + if ($@) {die "Please check that config file is available as /usr/local/etc/zabbix_syslog.cfg or /etc/zabbix/zabbix_syslog.cfg\n";} +} + +my %Config = $conf->getall; + +#Authenticate yourself +my $url = $Config{'url'} || die "URL is missing in zabbix_syslog.cfg\n"; +my $user = $Config{'user'} || die "API user is missing in zabbix_syslog.cfg\n"; +my $password = $Config{'password'} || die "API user password is missing in zabbix_syslog.cfg\n"; +my $server = $Config{'server'} || die "server hostname is missing in zabbix_syslog.cfg\n"; + +my $debug = $Config{'debug'}; +my ( $authID, $response, $json ); + + +my $zbx = ZabbixAPI->new( { api_url => $url, username => $user, password => $password } ); +$zbx->login(); + +my $syslog_url_base = 'history.php?action=showvalues'; + + my @selements; + + foreach my $map ( @{ map_get_extended() } ) { + my $mapid=$map->{sysmapid}; + #put all map elements into array @selements (so you can update map later!) + @selements = @{ $map->{selements} }; + + print "INFO: Checking map with mapid $map->{sysmapid}\n"; + foreach my $selement (@selements) { + my $syslog_button_exists = 0; + + if ( $debug > 0 ) { + print 'Object ID: ' + . $selement->{selementid} + . ' Type: ' + . $selement->{elementtype}."\n"; + } + + # elementtype=0 hosts + if ( $selement->{elementtype} == 0 ) { + my $hostid; + #Zabbix API 3.4+ + if (exists($selement->{elements}->[0]->{hostid})) { + $hostid = $selement->{elements}->[0]->{hostid}; + } + #Zabbix API before 3.4 + elsif (exists($selement->{elementid})) { + $hostid = $selement->{elementid}; + } + else { + die "Cannot get hostid of selement $selement->{selementid}\n"; + } + + my $itemid = get_syslogid_by_hostid($hostid); + if ($itemid) { + + #and add urls: + + my $syslog_exists = 0; + foreach my $syslog_url ( @{ $selement->{urls} } ) { + $syslog_exists = 0; + + if ( $syslog_url->{name} =~ 'Syslog' ) { + + $syslog_exists = 1; + $syslog_url->{'name'} = 'Syslog'; + + $syslog_url->{'url'} = + $syslog_url_base + . '&itemids[' + . $itemid . ']=' + . $itemid; + } + } + if ( $syslog_exists == 0 ) { + + #syslog item doesn't exist... add it + push @{ $selement->{urls} }, + { + 'name' => 'Syslog', + 'url' => $syslog_url_base + . '&itemids[' + . $itemid . ']=' + . $itemid + }; + } + + } + + } + + } + map_update($mapid,\@selements); + } + + + +$zbx->logout(); + +#______SUBS +sub get_syslogid_by_hostid { + + + my $hostid = shift; + + my $params = { + output => ['itemid'], + hostids => $hostid, + filter => {'key_' => 'syslog' }, + limit => 1, + }; + my $result = $zbx->do('item.get',$params); + + + # Check if response was successful + if ( !$result ) { + $zbx->logout(); + die "item.get failed\n"; + } + + #return itemid of syslog key (trapper type) + return ${ $result }[0]->{itemid}; +} + + +sub map_get { + + #retrieve all maps + my $params = { + output => ['sysmapid'] + }; + my $result = $zbx->do('map.get',$params); + + # Check if response was successful + if ( !$result ) { + $zbx->logout(); + die "map.get failed\n"; + } + + if ( $debug > 1 ) { print Dumper $result; } + return $result; + +} + + +sub map_get_extended { + my $params = { + selectSelements => 'extend', + #sysmapids => $map, + }; + + my $result = $zbx->do('map.get',$params); + + # Check if response was successful + if ( !$result ) { + $zbx->logout(); + die "map.get failed\n"; + } + if ( $debug > 1 ) { + + print Dumper $result; + } + + return $result; +} + +sub map_update { + my $mapid = shift; + my $selements_ref = shift; + my $params = { + selements => [@{$selements_ref}], + sysmapid => $mapid, + }; + my $result; + eval {$result=$zbx->do('map.update',$params);}; + if($@){ + warn "Failed to update map with mapid $mapid, check for write permissions for this map\n"; + } + else { + if ( $debug > 0 ) { + print "About to map.update this\n:"; + print Dumper $params; + } + + if ( $debug > 0 ) { + print Dumper $result; + } + } + + return; +} \ No newline at end of file diff --git a/zabbix/zabbix/zabbix-syslog/zabbix_syslog_lkp_host.pl b/zabbix/zabbix/zabbix-syslog/zabbix_syslog_lkp_host.pl new file mode 100644 index 0000000..7d0ab22 --- /dev/null +++ b/zabbix/zabbix/zabbix-syslog/zabbix_syslog_lkp_host.pl @@ -0,0 +1,272 @@ +#!/usr/bin/perl + +use 5.010; +use strict; +use warnings; + +use FindBin qw($Bin); +use lib "$Bin/lib"; +use Data::Dumper; +use Config::General; +use ZabbixAPI; +use English '-no_match_vars'; +use MIME::Base64 qw(encode_base64); +use IO::Socket::INET; +use Storable qw(lock_store lock_retrieve); +our $VERSION = 3.1; + +my $CACHE_TIMEOUT = 600; +my $CACHE_DIR = '/tmp/zabbix_syslog_cache_n'; +die "No argumets required anymore since script version 3.0\n" if @ARGV > 0; +my $conf; +$conf = eval {Config::General->new('/usr/local/etc/zabbix_syslog.cfg')}; +if ($@) { + eval {$conf = Config::General->new('/etc/zabbix/zabbix_syslog.cfg')}; + if ($@) {die "Please check that config file is available as /usr/local/etc/zabbix_syslog.cfg or /etc/zabbix/zabbix_syslog.cfg\n";} +} +my %Config = $conf->getall; + +#Authenticate yourself +my $url = $Config{'url'} || die "URL is missing in zabbix_syslog.cfg\n"; +my $user = $Config{'user'} || die "API user is missing in zabbix_syslog.cfg\n"; +my $password = $Config{'password'} || die "API user password is missing in zabbix_syslog.cfg\n"; +my $server = $Config{'server'} || die "server hostname is missing in zabbix_syslog.cfg\n"; +my $zbx; + +my $debug = $Config{'debug'}; +my ( $authID, $response, $json ); +#IP regex patter part +my $ipv4_octet = q/(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/; + +#rsyslog omprog loop +#http://www.rsyslog.com/doc/master/configuration/modules/omprog.html +while (defined(my $message = <>)) { + chomp($message); + + #get ip from message + my $ip; + + if ( $message =~ / \[ ((?:$ipv4_octet[.]){3}${ipv4_octet}) \]/msx ) { + $ip = $1; + } + else { + warn "No IP in square brackets found in '$message', cannot continue\n"; + next; + } + + my $hostname = ${retrieve_from_store($ip)}->{'hostname'}; + + + if ( !defined $hostname ) { + + my $result; + + $zbx = ZabbixAPI->new( { api_url => $url, username => $user, password => $password } ); + $zbx->login(); + + + my @hosts_found; + my $hostid; + my @hostinterfaces; + eval {@hostinterfaces=hostinterface_get($ip)}; + if($@){ + warn "Failed to retrieve any host interface with IP = $ip. Unable to bind message to item, skipping\n"; + next; + } + + foreach my $host (@hostinterfaces) { + + $hostid = $host->{'hostid'}; + if ( grep { /$hostid/msx } @hosts_found ) { + next; + }#check if $hostid already is in array then skip(next) + else { push @hosts_found, $hostid; } + + #now get hostname + if ( get_zbx_trapper_syslogid_by_hostid($hostid) ) { + + my $result = host_get($hostid); + + #return hostname if possible + if ( $result->{'host'} ) { + + if ( $result->{'proxy_hostid'} == 0 ) #check if host monitored directly or via proxy + { + #lease $server as is + } + else { + #assume that rsyslogd and zabbix_proxy are on the same server + $server = 'localhost'; + } + $hostname = $result->{'host'}; + } + last; + } + + } + $zbx->logout(); + store_message( $ip, $hostname ); + } + + zabbix_send( $server, $hostname, 'syslog', $message ); +} + + +#______SUBS +sub hostinterface_get { + + my $ip = shift; + my $params = { + output => [ 'ip', 'hostid' ], + filter => { ip => $ip, } + }; + + my $result = $zbx->do('hostinterface.get',$params); + + if ( $debug > 0 ) { print Dumper $result; } + # Check if response was successful (not empty array in result) + if ( !@{ $result } ) { + $zbx->logout(); + die "hostinterface.get failed\n"; + } + return @{ $result }; + +} + +sub get_zbx_trapper_syslogid_by_hostid { + + my $hostid = shift; + my $params = { + output => ['itemid'], + hostids => $hostid, + search => { + 'key_' => 'syslog', + type => 2, #type => 2 is zabbix_trapper + status => 0, + }, + limit => 1, + }; + my $result = $zbx->do('item.get',$params); + + if ( $debug > 0 ) { print Dumper $result; } + # Check if response was successful + if ( !@{ $result } ) { + warn "item.get failed\n"; + } + #return itemid of syslog key (trapper type) + return ${ $result }[0]->{itemid}; +} + +sub host_get { + my $hostid = shift; + my $params = { + hostids => [$hostid], + output => [ 'host', 'proxy_hostid', 'status' ], + filter => { status => 0, }, # only use hosts enabled + limit => 1, + }; + + + my $result = $zbx->do('host.get',$params); + + if ( $debug > 0 ) { print Dumper $result; } + + # Check if response was successful + if ( !$result ) { + $zbx->logout(); + die "host.get failed\n"; + } + return ${ $result }[0]; #return result +} + +sub zabbix_send { + my $zabbixserver = shift; + my $hostname = shift; + my $item = shift; + my $data = shift; + my $SOCK_TIMEOUT = 10; + my $SOCK_RECV_LENGTH = 1024; + + my $result; + + my $request = + sprintf + "\n%s\n%s\n%s\n\n", + encode_base64($hostname), encode_base64($item), encode_base64($data); + + my $sock = IO::Socket::INET->new( + PeerAddr => $zabbixserver, + PeerPort => '10051', + Proto => 'tcp', + Timeout => $SOCK_TIMEOUT + ); + + die "Could not create socket: $ERRNO\n" unless $sock; + $sock->send($request); + my @handles = IO::Select->new($sock)->can_read($SOCK_TIMEOUT); + if ( $debug > 0 ) { print "host - $hostname, item - $item, data - $data\n"; } + + if ( scalar(@handles) > 0 ) { + $sock->recv( $result, $SOCK_RECV_LENGTH ); + if ( $debug > 0 ) { + print "answer from zabbix server $zabbixserver: $result\n"; + } + } + else { + if ( $debug > 0 ) { print "no answer from zabbix server\n"; } + } + $sock->close(); + return; +} + +#helpers +sub store_message { + my $ip = shift; + my $hostname = shift; + my $storage_file = $CACHE_DIR; + my ( $stored, $to_store ); + + $to_store->{$ip} = { + hostname => $hostname, + created => time() + }; + + + if ( -f $storage_file ) { + $stored = lock_retrieve $storage_file; + lock_store { %{$stored}, %{$to_store} }, $storage_file; + } + else { + +#first time file creation, apply proper file permissions and store only single event + lock_store $to_store, $storage_file; + chmod 0666, $storage_file; + } + +} + +sub retrieve_from_store { + my $ip = shift; + my $storage_file = $CACHE_DIR; + my $stored; + my $message_to_retrieve; + + if ( -f $storage_file ) { + + $stored = lock_retrieve $storage_file; + + #remove expired from cache + if (defined($stored->{$ip})){ + if (time() - $stored->{$ip}->{created} > $CACHE_TIMEOUT){ + delete $stored->{$ip}; + lock_store $stored, $storage_file; + } + else { + $message_to_retrieve = $stored->{$ip}; + } + } + } + + return \$message_to_retrieve; + +} \ No newline at end of file