Archive for category Linux
Automatically Deploy Debian Load Balancers with bash scripting
Posted by Kevin in Linux, Shell Scripting on June 14, 2010
In yet another post in our automation series, we will share a bash script that automates the deployment of debian based load balancers (specifically with LVS / Linux Virtual Server project).
Even though the environments and systems you deploy may start to get more complicated such as with load balancers, there will always be a baseline level with which these systems can be brought to before further configuration and customization needs to be done.
There are many things that can be automated with this process, as you will see in the script below. In most round-robin load balancing scenarios, there wouldn’t be much more that needs to be done as far as configuration beyond what this script can do.
Obviously you will likely need to modify the script to suit your needs and requirements for the organization and standards therein.
Hopefully this will help you roll out many debian load balancers! May the load be split evenly between all your systems
#!/bin/sh
# Debian LVS deployer script
# Version 1.0
PROGNAME="$0"
VERSION="1.0"
# working directory for deployer process.
WORKDIR="/root"
# tasks left (this is updated every step to accommodate recovery during
# the deployer process)
TASKS="./deploy-lvs.tasks"
init_tasks() {
# This function will write a new tasks file.
# it's called from the main body of the script if a tasks file does not exist.
cat > $TASKS<
add_pkgs
get_lvs
configure_lvs
set_hostname
EOS
return 0
}
installer_splash() {
echo "[+] LVS deployer script starting..."
echo " Version: $VERSION"
echo
return 0
}
nopasswd_ssh() {
# disable passwd auth on SSH
echo "[+] Disabling password authentication for SSH... "
perl -pi -e 's/^PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
perl -pi -e 's/^#PermitRootLogin yes/PermitRootLogin without-password/g' /etc/ssh/sshd_config
/etc/init.d/ssh restart
return 0
}
add_pkgs() {
PKGS="libssl0.9.7 exim4 iproute ethtool tcpdump snmpd pciutils less python"
echo "[+] Installing packages: $PKGS... "
apt-get -y install $PKGS || return 1
return 0
}
get_lvs() {
echo "[+] Downloading packages... "
# download the latest version of the Client firewall package.
wget --no-check-certificate http://your.domain.com/lvs.tgz -O /tmp/firewall.tgz || return 1
# unpack firewall scripts
tar --no-same-owner --no-same-permissions --directory / -zxvf /tmp/firewall.tgz || return 1
rm /tmp/firewall.tgz || return 1
return 0
}
configure_lvs() {
# time to configure the FW
KAD=/etc/keepalived/keepalived.conf
FW=/etc/network/firewall
COMMIT=/usr/local/bin/lvs-commit.sh
HOSTS=/etc/hosts
INTERFACES=/etc/network/interfaces
NRPE=/etc/nagios/nrpe_local.cfg
EXIM=/etc/exim4/update-exim4.conf.conf
CONFIGURE_LVS=/etc/network/configure-lvs.pl
echo "[+] Configuring LVS..."
perl $CONFIGURE_LVS
if [ $? -ne 0 ]; then
echo "[!] ERROR: Configuring LVS script failed!"
return 1
fi
echo "[+] Moving files into place..."
rm ${KAD}-template || return 1
rm ${FW}-template || return 1
rm ${COMMIT}-template || return 1
rm ${CONFIGURE_LVS}
mv ${HOSTS}.new ${HOSTS} || return 1
mv ${INTERFACES}.new ${INTERFACES} || return 1
mv ${NRPE}.new ${NRPE} || return 1
mv ${EXIM}.new ${EXIM} || return 1
chmod 700 ${FW}
chmod 700 ${COMMIT}
update-rc.d keepalived defaults || return 1
update-exim4.conf || return 1
# for compatibility
echo "[+] Generating RSA Keys"
ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' || return 1
return 0
}
clean_up_and_reboot() {
# remove:
# -- temp task file
rm $TASKS
# remove self from .bashrc
if [ -f /root/.bashrc.orig ]; then
mv /root/.bashrc.orig /root/.bashrc
fi
if [ -z /root/.bashrc ]
then
rm /root/.bashrc
fi
# delete self
rm $0
# and reboot.
echo "[+] Please reboot system."
#reboot -n
exit 0
}
debug_quit() {
# hard exit the script in appropriately referenced files
# so that no reboot happens.
echo "debug_quit seen in tasks file, exiting."
exit 0
}
set_hostname() {
echo "[+] Setting LVS hostname... "
echo `hostname` > /etc/hostname
echo `hostname` > /etc/mailname
return 0
}
usage() {
echo "[+] Usage: $PROGNAME"
echo
return 0
}
###############################
### MAIN SCRIPT STARTS HERE ###
###############################
# installer_splash
installer_splash
# fix working dir.
cd $WORKDIR
# does our installer file exist? if not, initalize it.
if [ ! -f $TASKS ]
then
echo "[+] No task file found, installation will start from beginning."
init_tasks
if (($? != 0))
then
echo "[!] ERROR: Cannot create tasks file. Installation will not continue."
exit 1
fi
else
echo "[+] Tasks file located - starting where you left off."
fi
# start popping off tasks from the task list and running them.
# pop first step off of the list
STEP=`head -n 1 $TASKS`
while [ ! -z $STEP ]
do
# execute the function.
echo -e "\n\n###################################"
echo "[+] Running step: $STEP"
echo -e "###################################\n\n"
$STEP
if (($? != 0))
then
# command failed.
echo "[!] ERROR: Step $STEP failed!"
echo " Installation will now abort - you can pick it up after fixing the problem"
echo
exit 1
fi
# throw up a newline just so things don't look so crowded
echo
# remove function from function list.
perl -pi -e "s/$STEP\n?//" $TASKS || exit 1
STEP=`head -n 1 $TASKS`
done
# clean_up_and_reboot
echo "[+] Installation finished - cleaning up."
clean_up_and_reboot
# script is done now - termination should happen with clean_up_and_reboot.
echo "[!] Should not be here!"
exit 1
Related Links:
Automatically Deploy Debian Firewalls with bash scripting
Posted by Kevin in Linux, Shell Scripting on June 2, 2010
Automation is as necessary as any other aspect of systems administration in any critical or production environment where growth and scalability are moving at a significant pace.
Growth in any organization is obviously a good thing. In the systems administrator’s perspective, however, growth can mean more time spent deploying systems and less time spent focusing on other duties.
Automating the server deployment process is the natural next step when your organization has grown to a point where time efficiency becomes more relevant and noticeable to your business owners.
This is the first in a series of posts here where we will explain and share shell scripts that automate the deployment process of several key debian linux based systems. These scripts automate the patching, configuration and implementation of said systems.
They will certainly have to be modified to fit your organization’s needs and standards obviously, but hopefully it will give you a starting point to base your automation / roll-out policies.
Making your life easier and more automated is always a good thing!
#!/bin/sh
# Debian FW deployer script
# Version 1.0
PROGNAME="$0"
VERSION="1.0"
# working directory for deployer process.
WORKDIR="/root"
# tasks left (this is updated every step to accommodate recovery during
# the deployer process)
TASKS="./deploy-fw.tasks"
init_tasks() {
# This function will write a new tasks file.
# it's called from the main body of the script if a tasks file does not exist.
cat > $TASKS<
add_pkgs
get__fw
configure_fw
set_hostname
EOS
return 0
}
installer_splash() {
echo "[+] Firewall deployer script starting..."
echo " Version: $VERSION"
echo
return 0
}
nopasswd_ssh() {
# disable passwd auth on SSH
echo "[+] Disabling password authentication for SSH... "
perl -pi -e 's/^PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
perl -pi -e 's/^#PermitRootLogin yes/PermitRootLogin without-password/g' /etc/ssh/sshd_config
/etc/init.d/ssh restart
return 0
}
add_pkgs() {
PKGS="libssl0.9.7 exim4 iproute ethtool tcpdump snmpd pciutils less python"
echo "[+] Installing packages: $PKGS... "
apt-get -y install $PKGS || return 1
return 0
}
get__fw() {
echo "[+] Downloading packages... "
# download the latest version of the Client firewall package.
wget --no-check-certificate http://www.yoursite.com/fw.tgz -O /tmp/firewall.tgz || return 1
# get the latest firewall.trusted file
wget --no-check-certificate http://www.yoursite.com/firewall.trusted -O /tmp/firewall.trusted || return 1
# unpack firewall scripts
tar --no-same-owner --no-same-permissions --directory / -zxvf /tmp/firewall.tgz || return 1
mv /tmp/firewall.trusted /etc/network/firewall.trusted || return 1
chmod +x /etc/network/firewall.trusted || return 1
rm /tmp/firewall.tgz || return 1
echo "done."
return 0
}
configure_fw() {
# time to configure the FW
KAD=/etc/keepalived/keepalived.conf
FW=/etc/network/firewall
RELOAD=/etc/network/reload.sh
HOSTS=/etc/hosts
INTERFACES=/etc/network/interfaces
NRPE=/etc/nagios/nrpe_local.cfg
EXIM=/etc/exim4/update-exim4.conf.conf
CONFIGURE_FW=/etc/network/configure-fw.pl
echo "[+] Configuring Firewall..."
perl $CONFIGURE_FW
if [ $? -ne 0 ]; then
echo "[!] ERROR: Configuring firewall script failed!"
return 1
fi
echo "[+] Moving files into place..."
rm ${KAD}-template || return 1
rm ${FW}-template || return 1
rm ${RELOAD}-template || return 1
rm ${CONFIGURE_FW}
mv ${HOSTS}.new ${HOSTS} || return 1
mv ${INTERFACES}.new ${INTERFACES} || return 1
mv ${NRPE}.new ${NRPE} || return 1
mv ${EXIM}.new ${EXIM} || return 1
chmod 700 ${FW}
chmod 700 ${RELOAD}
update-rc.d keepalived defaults || return 1
update-exim4.conf || return 1
# for compatibility
echo "[+] Generating RSA Keys"
ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' || return 1
return 0
}
clean_up_and_reboot() {
# remove:
# -- temp task file
rm $TASKS
# remove self from .bashrc
if [ -f /root/.bashrc.orig ]; then
mv /root/.bashrc.orig /root/.bashrc
fi
if [ -z /root/.bashrc ]
then
rm /root/.bashrc
fi
# delete self
rm $0
# and reboot.
echo "[+] Please reboot system."
#reboot -n
exit 0
}
debug_quit() {
# hard exit the script in appropriately referenced files
# so that no reboot happens.
echo "debug_quit seen in tasks file, exiting."
exit 0
}
set_hostname() {
echo "[+] Setting FW hostname... "
echo `hostname` > /etc/hostname
echo `hostname` > /etc/mailname
echo "done."
return 0
}
usage() {
echo "[+] Usage: $PROGNAME"
echo
return 0
}
###############################
### MAIN SCRIPT STARTS HERE ###
###############################
# installer_splash
installer_splash
# fix working dir.
cd $WORKDIR
# does our installer file exist? if not, initalize it.
if [ ! -f $TASKS ]
then
echo "[+] No task file found, installation will start from beginning."
init_tasks
if (($? != 0))
then
echo "[!] ERROR: Cannot create tasks file. Installation will not continue."
exit 1
fi
else
echo "[+] Tasks file located - starting where you left off."
fi
# start popping off tasks from the task list and running them.
# pop first step off of the list
STEP=`head -n 1 $TASKS`
while [ ! -z $STEP ]
do
# execute the function.
echo -e "\n\n###################################"
echo "[+] Running step: $STEP"
echo -e "###################################\n\n"
$STEP
if (($? != 0))
then
# command failed.
echo "[!] ERROR: Step $STEP failed!"
echo " Installation will now abort - you can pick it up after fixing the problem"
echo
exit 1
fi
# throw up a newline just so things don't look so crowded
echo
# remove function from function list.
perl -pi -e "s/$STEP\n?//" $TASKS || exit 1
STEP=`head -n 1 $TASKS`
done
# clean_up_and_reboot
echo "[+] Installation finished - cleaning up."
clean_up_and_reboot
# script is done now - termination should happen with clean_up_and_reboot.
echo "[!] Should not be here!"
exit 1
Related Links:
Integrate your custom IPTables script with Linux
How do I integrate my custom iptables script with Red Hat Enterprise Linux?
A custom iptables script is sometimes necessary to work around the limitations of the Red Hat Enterprise Linux firewall configuration tool. The procedure is as follows:
1. Make sure that the default iptables initialization script is not running:
service iptables stop
2. Execute the custom iptables script:
sh [custom iptables script]
3. Save the newly created iptables rules:
service iptables save
4. Restart the iptables service:
service iptables restart
5. Verify that the custom iptables ruleset have taken effect:
service iptables status
6. Enable automatic start up of the iptables service on boot up:
chkconfig iptables on
The custom iptables script should now be integrated into the operating system.
Related Links:
Patch Scanning / Information Gathering Script for RedHat / CentOS
Posted by Kevin in Linux, Shell Scripting on April 30, 2010
With all the patch management solutions, local repositories and other options, it is rarely necessary to manually scan all servers on your network to build a “report” of the patch levels in your environment.
Sometimes it is, however. For instance, if you are brought into an environment that has not been properly managed and require some quick audits to evaluate how much actual work needs to be done bringing all the patch levels up to standard, then there are ways to produce these reports with simple bash scripting.
I have developed such a script for similar situations — quick reporting is sometimes necessary even when you are evaluating a large commercial patch management solution. It can even be implemented to coincide such solutions, for independent reporting perhaps.
This script would work well either by distributing it to each server and running the script via ssh key based authentication for centralized reporting. Alternatively, you could modify this script to perform each command via SSH over the network to gather information that way. It is probably more ideal to centrally distribute the script to each server so only one ssh command is executed per server.
Find the script below — note that it only works with RedHat / CentOS systems. Obviously if you are paying for Red Hat enterprise support you already are using satellite; If you are using CentOS then this script may be useful for you.
Enjoy!
#!/bin/sh
# Basic Information Gathering
# Star Dot Hosting
# http://www.stardothosting.com
HOSTNAME=`hostname`
UNAME=`uname -a | awk '{print $3}'`
# Begin Package Scanning
# SSH
SSHON="0"
SSHRUN="NULL"
SSHRPM="NULL"
SSHMATCH="NULL"
if [ -f /usr/sbin/sshd ]
then
SSHON="1"
SSHMATCH="0"
SSHRUN=`ssh -V 2>&1 | awk 'BEGIN { FS = "_" } ; { print $2 }' | awk '{print $1}' | cut -b 0-5`
TESTRPM=`rpm -qa openssh`
if [ "$TESTRPM" <> 0 ]
then
SSHRPM=`rpm -qa openssh | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$SSHRUN" == "$SSHRPM" ]
then
SSHMATCH="1"
fi
fi
# Apache
HTTPDON="0"
HTTPDRUN="NULL"
HTTPDRPM="NULL"
HTTPDMATCH="NULL"
if [ -f /usr/sbin/httpd ]
then
HTTPDON="1"
HTTPDMATCH="0"
HTTPDRUN=`httpd -v | grep version | awk 'BEGIN {FS="/"};{print$2}'`
TESTRPM=`rpm -qa httpd`
if [ "$TESTRPM" <> 0 ]
then
HTTPDRPM=`rpm -qa httpd | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$HTTPDRUN" == "$HTTPDRPM" ]
then
HTTPDMATCH="1"
fi
fi
# MySQL
MYSQLON="0"
MYSQLRUN="NULL"
MYSQLRPM="NULL"
MYSQLMATCH="NULL"
if [ -f /usr/bin/mysql ]
then
MYSQLON="1"
MYSQLMATCH="0"
MYSQLRUN=`mysql -V | awk '{print $5}' | cut -b 0-6`
TESTRPM=`rpm -qa mysql`
if [ "$TESTRPM" <> 0 ]
then
MYSQLRPM=`rpm -qa mysql | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$MYSQLRUN" == "$MYSQLRPM" ]
then
MYSQLMATCH="1"
fi
fi
# PHP
PHPON="0"
PHPRUN="NULL"
PHPRPM="NULL"
PHPMATCH="NULL"
if [ -f /usr/bin/php ]
then
PHPON="1"
PHPMATCH="0"
PHPRUN=`php -v | grep built | awk '{print $2 }'`
TESTRPM=`rpm -qa php`
if [ "$TESTRPM" <> 0 ]
then
PHPRPM=`rpm -qa php | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$PHPRUN" == "$PHPRPM" ]
then
PHPMATCH="1"
fi
fi
# Exim
# Needs to be tested on RH box
EXIMON="0"
EXIMRUN="NULL"
EXIMRPM="NULL"
EXIMMATCH="NULL"
if [ -f /usr/sbin/exim ]
then
EXIMON="1"
EXIMMATCH="0"
EXIMRUN=`exim -bV | grep version | awk '{print $3}'`
TESTRPM=`rpm -qa exim`
if [ "$TESTRPM" <> 0 ]
then
EXIMRPM=`rpm -qa exim | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$EXIMRUN" == "$EXIMRPM" ]
then
EXIMMATCH="1"
fi
fi
# OpenSSL
OSSLON="0"
OSSLRUN="NULL"
OSSLRPM="NULL"
OSSLMATCH="NULL"
if [ -f /usr/bin/openssl ]
then
OSSLON="1"
OSSLMATCH="0"
OSSLRUN=`openssl version | awk '{print $2}'`
TESTRPM=`rpm -qa openssl`
if [ "$TESTRPM" <> 0 ]
then
OSSLRPM=`rpm -qa openssl | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$OSSLRUN" == "$OSSLRPM" ]
then
OSSLMATCH="1"
fi
fi
# PERL
PERLON="0"
PERLRUN="NULL"
PERLRPM="NULL"
PERLMATCH="NULL"
if [ -f /usr/bin/perl ]
then
PERLON="1"
PERLMATCH="0"
PERLRUN=`perl -v | grep built | awk '{print $4}' | awk 'BEGIN { FS = "v" } ; { print $2 }'`
TESTRPM=`rpm -qa perl`
if [ "$TESTRPM" <> 0 ]
then
PERLRPM=`rpm -qa perl | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$PERLRUN" == "$PERLRPM" ]
then
PERLMATCH="1"
fi
fi
# PYTHON
PYON="0"
PYRUN="NULL"
PYRPM="NULL"
PYMATCH="NULL"
if [ -f /usr/bin/python ]
then
PYON="1"
PYMATCH="0"
PYRUN=`python -V 2>&1 | awk '{print $2}'`
TESTRPM=`rpm -qa python`
if [ "$TESTRPM" <> 0 ]
then
PYRPM=`rpm -qa python | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$PYRUN" == "$PYRPM" ]
then
PYMATCH="1"
fi
fi
# GPG
GPGON="0"
GPGRUN="NULL"
GPGRPM="NULL"
GPGMATCH="NULL"
if [ -f /usr/bin/gpg ]
then
GPGON="1"
GPGMATCH="0"
GPGRUN=`gpg --version | grep gpg | awk '{print $3}'`
TESTRPM=`rpm -qa gnupg`
if [ "$TESTRPM" <> 0 ]
then
GPGRPM=`rpm -qa gnupg | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$GPGRUN" == "$GPGRPM" ]
then
GPGMATCH="1"
fi
fi
# RPM
RPMON="0"
RPMRUN="NULL"
RPMRPM="NULL"
RPMMATCH="NULL"
if [ -f /bin/rpm ]
then
RPMON="1"
RPMMATCH="0"
RPMRUN=`rpm --version | awk '{print $3}'`
TESTRPM=`rpm -qa rpm`
if [ "$TESTRPM" <> 0 ]
then
RPMRPM=`rpm -qa rpm | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$RPMRUN" == "$RPMRPM" ]
then
RPMMATCH="1"
fi
fi
# SENDMAIL
SENDON="0"
SENDRUN="NULL"
SENDRPM="NULL"
SENDMATCH="NULL"
if [ -f /usr/sbin/sendmail ]
then
SENDON="1"
SENDMATCH="0"
SENDRUN=`echo 'quit' | nc localhost 25 | grep Sendmail | awk '{print $5}' | awk 'BEGIN { FS = "/" } ; { print $1 }'`
TESTRPM=`rpm -qa sendmail`
if [ "$TESTRPM" <> 0 ]
then
SENDRPM=`rpm -qa sendmail | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
if [ "$SENDRUN" == "$SENDRPM" ]
then
SENDMATCH="1"
fi
fi
### Non running packages
# bind-libs
BINDLIB="NULL"
TESTRPM=`rpm -qa bind-libs`
if [ "$TESTRPM" <> 0 ]
then
BINDLIB=`rpm -qa bind-libs | awk 'BEGIN { FS = "-" } ; { print $3 }'`
fi
# bind-utils
BINDUTIL="NULL"
TESTRPM=`rpm -qa bind-utils`
if [ "$TESTRPM" <> 0 ]
then
BINDUTIL=`rpm -qa bind-utils | awk 'BEGIN { FS = "-" } ; { print $3 }'`
fi
# coreutils
COREUTIL="NULL"
TESTRPM=`rpm -qa coreutils`
if [ "$TESTRPM" <> 0 ]
then
COREUTIL=`rpm -qa coreutils | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
# chkconfig
CHKCONFIG="NULL"
TESTRPM=`rpm -qa chkconfig`
if [ "$TESTRPM" <> 0 ]
then
CHKCONFIG=`rpm -qa chkconfig | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
# initscripts
INITSCR="NULL"
TESTRPM=`rpm -qa initscripts`
if [ "$TESTRPM" <> 0 ]
then
INITSCR=`rpm -qa initscripts | awk 'BEGIN { FS = "-" } ; { print $2 }'`
fi
# redhat-release
RHRELEASE="NULL"
TESTRPM=`rpm -qa redhat-release`
if [ "$TESTRPM" <> 0 ]
then
RHRELEASE=`rpm -qa redhat-release | awk 'BEGIN { FS = "-" } ; { print $3"-"$4 }'`
fi
echo $HOSTNAME,$UNAME,$SSHMATCH,$HTTPDMATCH,$MYSQLMATCH,$PHPMATCH,$EXIMMATCH,$OSSLMATCH,$PYMATCH,$PERLMATCH,$GPGMATCH,
$RPMMATCH,$SENDMATCH,$BINDLIB,$BINDUTIL,$COREUTIL,$CHKCONFIG,$INITSCR,$RHRELEASE,$SSHON,$SSHRUN,$SSHRPM,$HTTPDON,$HTTPDRUN,
$HTTPDRPM,$MYSQLON,$MYSQLRUN,$MYSQLRPM,$PHPON,$PHPRUN,$PHPRPM,$EXIMON,$EXIMRUN,$EXIMRPM,$OSSLON,$OSSLRUN,$OSSLRPM,$PERLON,
$PERLRUN,$PERLRPM,$PYON,$PYRUN,$PYRPM,$GPGON,$GPGRUN,$GPGRPM,$RPMON,$RPMRUN,$RPMRPM,$SENDON,$SENDRUN,$SENDRPM
Note that you can modify the echo output to produce whatever output you need in order to present it in a nice human readable report.
Related Links:
Script to distribute SSH Keys across many servers
Posted by Kevin in Hosting, Linux, Security, Shell Scripting on December 7, 2009
Hello once again!
You may remember an earlier post that detailed how to implement SSH Key based authentication.
We believe it is important, when administering many (sometimes hundreds or thousands) of servers, to implement a strategy that can allow systems administrators to seamlessly run scripts, system checks or critical maintenance across all the servers.
SSH Key authentication allows for this potential. It is a very powerful strategy and should be maintained and implemented with security and efficiency as a top priority.
Distributing keys for all authorized systems administrators is something that would allow for the maintenance of this authentication system much easier — when an admin leaves or is dismissed, you need to be able to remove his or her’s keys from the “pool” quickly.
The idea behind this script is to have a centralized, highly secure and restricted key repository server. Each server in your environment would run this script to “pull” the updated key list from the central server. The script would run as a cron job and can run as often as you like. Ideally every 5-10 minutes would allow for quick key updates / distribution.
Here is the perl script :
#!/usr/bin/perl
#
# A script to sync ssh keys on UNIX servers automatically. This
# will not overwrite user installed ssh keys
#
use strict;
use IPC::Open3;
use File::Copy;
use POSIX ":sys_wait_h";
# This is overkill but FreeBSD may install wget in
# /usr/local/bin in some cases.
$ENV{PATH} = "/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin";
####################################################
use constant URL => 'https://keys.td.topscms.com/ssh-keys.txt';
use constant WGET => 'wget --no-check-certificate -q -O - ';
use constant KEYS_FILE => '/root/.ssh/authorized_keys';
use constant RESTRICTED => 'https://keys.td.topscms.com/restricted.txt';
####################################################
my ($url, $wget, $keys_file, $restricted, %restrict);
for (my $i=0;$i) {
chomp;
$restrict{$_}++;
}
}
$pid = open3(\*WTR, \*RTR, \*ERR, "$wget");
while () {
next if $restrict{$1};
$company_keys .= $_;
}
$user_keys = read_key_file();
# Sanity check
my @rows = split('\n', $company_keys);
if (scalar @rows < 1) {
print "Less than 1 company keys found, not installing keys..\n";
exit(1);
}
open(TMP, ">$keys_file.$$.tmp") or die "Could not open tmp keys file: $!\n";
print TMP $company_keys;
print TMP $user_keys;
close(TMP);
# Sanity check
my (undef,undef,undef,undef,undef,undef,undef,$size,undef,undef,undef,undef,undef) = stat("$keys_file.$$.tmp");
if ($size < 100) {
print "Keys file less than 100bytes, not writing";
exit(1);
}
move("$keys_file.$$.tmp", $keys_file);
sub read_key_file {
my $user_buf;
open(KEY_FILE, "< $keys_file") or die "Could not open ssh key file; $!\n";
while () {
next if $_ =~ /company$/;
$user_buf .= $_;
}
close(KEY_FILE);
return($user_buf);
}
sub sig_chld {
my $pid = waitpid(-1, WNOHANG);
}
sub usage {
print STDERR <<"EOS";
Usage: $0 -[kuh]
-k Keys file to write to (default: @{[KEYS_FILE]})
-u URL to download keys from (default: @{[URL]})
-h This screen
EOS
exit(1);
}
1;
__END__
Note that it downloads the public keys via http with wget. This can be easily modified to utilize https, if necessary, or perhaps even another protocol to make the transfer. HTTP Was chosen because the public keys are harmless and http is the easiest method. HTTPS would be desirable, however.
We hope this script helps you along the way towards making your life easier!
Related Links:
Setup Up Exim with ClamAV and Spamassassin
I decided to post this article on implementing a simple single mail server with anti-spam and anti-virus capabilities.
This guide hopefully will help you on your way to configuring a basic mail system on Linux (specifically Debian).
Installing and configuring Exim 4 on Debian
1. First, install all the necessary Debian packages are on the system as the root user. (The exim4 package will REPLACE the exim package.)
NOTE: If you are using the stable branch, it is suggested to use the debian volatile packages (along with the security packages) so that your system is using the most up-to-date critical packages (like ClamAV) for security purposes. For production servers, you may not want to run a mixed stable/testing/unstable system (though I know some of you do!). To use these packages, see http://volatile.debian.net/ for more information. For those of you who are impatient and don’t want to find the correct mirror, here’s is what I added to my /etc/apt/sources.list file:
deb http://volatile.debian.net/debian-volatile sarge/volatile main contrib
I used aptitude to install these packages, but you could also use the old apt-get method:
apt-get install clamav-daemon \ clamav-freshclam exim4-daemon-heavy exim4 \ courier-base courier-authdaemon courier-imap \ courier-pop spamassassin wget spamc sa-exim
When going through the exim4 config, be sure to select the multiple file configuration layout. If you didn’t (or weren’t prompted for it), simply set dc_use_split_config to true in the /etc/exim4/update-exim.conf.conf file. (Thanks Mike!)
2. Create your Maildir directory
maildirmake ~/Maildir/
3. Now we want to make exim4 use Maildir format mailboxes. Modify the file /etc/exim4/update-exim4.conf.conf so that it contains:
dc_localdelivery='maildir_home'
4. We need to Edit /etc/default/spamassassin to enable spamd.
5. Each user can set up their own filters by creating a .forward file in their home directory. If the first line of this file reads
# Exim filter then Exim4 will treat it as a filter.
Here is an example of an Exim filter that checks the headers that SpamAssassin adds and puts the mail in the appropriate Maildir folder:
# Exim filter
if $h_X-Spam-Status: CONTAINS "Yes"
or
$h_X-Spam-Flag: CONTAINS "Yes"
then
save $home/Maildir/.Spam/
finish
endif
Exim’s Interface To Mail Filtering (PDF format) – Local copy
6. Many system administrators like to set up the Maildir directories and .forward filter file in the /etc/skel directory so that when they make a new user on the system, everything is automatically copied over. I suggest that you do this as well as it makes things easier.
7. Before going live with the mail server, we will want to test it!
Testing the implementation
1. Generate the new configuration:
update-exim4.conf
If you made it through this, then your config files don’t have any syntax errors.
exim4 -bV
If that works, then there are no config issues
2. Next, start exim by issuing:
/etc/init.d/exim4 start
Above assumes that you are running exim4 as a daemon, and not through inetd
3. Now, check a local address:
exim4 -bt local_user@example.com
4. Check sending an email:
exim4 -v mailbox_you_can_check@dom.ain
From: user@your.domain
To: mailbox_you_can_check@dom.ain
Subject: Testing exim
Testing exim
.
You should now see some messages to let you know that the email was sent or information about what went wrong.
5. To test with full debug output using a specific config file, use something like:
exim4 -C /etc/exim/exim_example.conf -d -bt user@example.com
6. To test the config coming from a specified ip address, use:
exim4 -bh 192.168.1.10
HELO example.com
MAIL FROM:
RCPT TO:
DATA
Subject: something
your message here
.
QUIT
8. Add the following to your /etc/exim4/conf.d/main/01_exim4-config_listmacrosdefs file:
# This tells what virus scanner to use
av_scanner = clamd:/var/run/clamav/clamd.ctl
9. Edit /etc/exim4/conf.d/acl/40_exim4-config_check_data to inlude the following before the “# accept otherwise” line:
# Reject messages that have serious MIME errors.
# This calls the demime condition again, but it
# will return cached results.
deny message = Serious MIME defect detected ($demime_reason)
demime = *
condition = ${if >{$demime_errorlevel}{2}{1}{0}}
# Reject file extensions used by worms.
# Note that the extension list may be incomplete.
deny message = This domain has a policy of not accepting certain types of attachments \
in mail as they may contain a virus. This mail has a file with a .$found_extension \
attachment and is not accepted. If you have a legitimate need to send \
this particular attachment, send it in a compressed archive, and it will \
then be forwarded to the recipient.
demime = exe:com:vbs:bat:pif:scr
# Reject messages containing malware.
deny message = This message contains a virus ($malware_name) and has been rejected
malware = *
10. Then, you need to enable ClamAV.
a) Firstly, you will want to be sure that it is running against messages. In /etc/exim4/sa-exim.conf, search for SAEximRunCond:
SAEximRunCond: ${if and {{def:sender_host_address} {!eq {$sender_host_address}{127.0.0.1}} {!eq {$h_X-SA-Do-Not-Run:}{Yes}} } {1}{0}}
That is simply skipping the scan on anything from the local machine or if the X-SA-Do-Not-Run header in the message is set to Yes. If you just want exim to run ClamAV on all messages, use this:
SAEximRunCond: 1
b) Before restarting ClamAV, we need to be sure that all of the access rights are in place so that the scans actually happen. The best way to handle this is to add the clamav user to the Debian-exim group. Either manually edit /etc/group, or simple run:
adduser clamav Debian-exim
c) Be sure that /etc/clamav/clamd.conf contains a line that reads:
AllowSupplementaryGroups
d) Set the file permissions for the /var/run/clamav directory to allow for the correct user to use it:
chown Debian-exim.Debian-exim /var/run/clamav
chmod g+w /var/run/clamav
e) A restart of ClamAV is necessary for the changes to take effect:
/etc/init.d/clamav-daemon restart
11. You should now be able to get your mail via IMAP with a mail client like Mozilla.
Check your headers (View Source) and see that SpamAssassin has added its headers. SMTP-end virus scanning should also be taking place. Check your /var/log/clamav/clamav.log to monitor this.
Multiple Domain Alias Files
The steps below are used to enable support for having multiple virtual domains each with its own alias file.
1. Exim will need to have the alias files for each domain.
a) Create the /etc/exim4/virtual directory.
b) For each virtual domain, create a file that contains the aliases to be used named as the domain.
For example, if I example.com was one of my domains, I’d do the following:
a) Create the /etc/exim4/virtual/example.com file.
b) If my system users were sys1, sys2, and sys3, and their email addresses were to be joe, john, jason, I’d put the following into the domain alias file:
joe: sys1@localhost
john: sys2@localhost
jason: sys3@localhost
If john was also to get all mail addressed to info@example.com, you would add this entry:
info: sys2@localhost
If you wanted all mail to user1@example.com to go to another email account outside of this domain, you would enter:
user1: a.user@some.domain
If you wanted all mail directed at any address other than what is defined in the alias file to go to joe, you’d enter:
*: sys1@localhost
In the above examples, the “@localhost” suffix to the user names forces the delivery to a system user. I found that if you do not include this in the alias files and your machine’s host name is within one of the domains handled by exim, every system user would need an entry in the machine’s domain in order to be delivered corectly.
For instance, if your host name was mail.example1.com and example1.com was handled by this server this would be needed. This would allow delivery to all the system user names at example1.com.
The reason is simple, and I will try to illustrate it for you here:
a) exim receives a message delivered to joe.blow@example3.com
b) The alias file for this domain has joe.blow: jblow in it.
c) This would translate to jblow@domain-of-the-system
d) The process would be repeated using jblow@domain-of-the-system
e) If there was no entry in the domain-of-the-system alias file for jblow, the message would be undeliverable (or non-routable)
You could even have special redirects like the following:
script: "| /path/to/some/script" prev: :fail: $local_part left! kill: :blackhole:
2. Edit /etc/exim4/conf.d/main/01_exim4-config_listmacrosdefs by replacing the current local_domains line with:
domainlist local_domains = @:localhost:dsearch;/etc/exim4/virtual
3. Create /etc/exim4/conf.d/router/350_exim4-config_vdom_aliases with the following content:
vdom_aliases:
driver = redirect
allow_defer
allow_fail
domains = dsearch;/etc/exim4/virtual
data = ${expand:${lookup{$local_part}lsearch*@{/etc/exim4/virtual/$domain}}}
retry_use_local_part
pipe_transport = address_pipe
file_transport = address_file
4. Now, regenerate your exim4 config:
update-exim4.conf
5. If there were no errors, restart exim4:
/etc/init.d/exim4 restart
Domain Dependent Maximum Message Size
The next step for my server is to give each domain a configurable message size limit. Then, when the server get’s a message that is larger than the target domain’s size limit, I want to send a message back to the original sender telling them why the message was not delivered. However, I also want to have that message customized for each domain. That way, the domain owners can provide detailed instructions on how to send large messages to their domain if it is necessary. Of course, there will also need to be some kind of default size limit and message for domains that do not need the customization.
1. Create /etc/exim4/domain-size-limits to contain the list of domains and their maximum message size limits. You can also add a wildcard at the end entry if you want to set a default limit. The file may look something like the following:
example.com: 20M
example1.com: 5M
*: 15M
This provides you a quick way to edit the values. The values will also take effect as soon as the file is saved – no need to restart exim!
2. OK, now we know what domains we want to customize the size for. Now it’s time to create a message to send for those domains. Create /etc/exim4/domain-size-limit-messages with content similar to:
exmaple.com: The largest acceptable message size for Example.com is\
${expand:${lookup{$domain}lsearch*@{/etc/exim4/domain-size-limits}}}.\
Your message was $message_size. If you feel that $local_part@$domain\
should really get your message, then visit http://www.example.com/files/\
where you can upload any large files. If you select $local_part@$domain\
from the "notify" list, they will receive a message with a link directly\
to your file.
*: The largest acceptable message size for $domain is\
${expand:${lookup{$domain}lsearch*@{/etc/exim4/domain-size-limits}}}.\
Your message size was $message_size. Please revise your message so it\
does not exceed this maximum file size and resend. If this is not\
possible, contact the recipient in another way.
As you see, we have one domain that has a custom message sent out, and have defined a default message for all other domains. These messages can be edited at any time and do not need an exim restart to take effect.
3. Now for the fun part! We need a way to catch the messages that are too large for the domain! First, create /etc/exim4/conf.d/router/325_exim4-config_large_messages with the following:
large_messages:
driver = accept
domains = dsearch;/etc/exim4/virtual
condition = ${if >{$message_size}{${expand:${lookup{$domain}lsearch*@{/etc/exim4/domain-size-limits}}}} {yes}{no}}
transport = bounce_large_messages
no_verify
This router dynamically checks which domains are available and what their limits are set to.
4. Now create /etc/exim4/conf.d/transport/40_exim4-config_bounce_large_messages with the following content:
# This bounces a message to people who send files too large for that domain
bounce_large_messages:
driver = autoreply
from = $local_part@$domain
to = $sender_address
subject = Re: ${escape:$h_subject:}
text = ${expand:${lookup{$domain}lsearch*@{/etc/exim4/domain-size-limit-messages}}}
This transport then sends the original sender a message using the text looked up from the domain-size-limit-messages file for that domain. The From: field is filled in with the intended recipient of the message – appearing to be a reply.
This was actually very simple to put together once I realized what I needed to do. The above is based on what I found in the Exim FAQ
Configuration Tips
Maybe this is something I should have said in the beginning, but at the time or writing this document, I had never set up an exim4 server, and the only exim3 server I had was used with the default debconf install. Therefore, if you see something on this page that could be done in a more elegant, more efficient or just plain better way, please send me a note.
Related Links:
ProFTPD with MySQL Authentication
Since this setup uses one FTP account to create user home directories and upload files, a compromise to this FTP user would cause the attacker to gain access to all FTP user home directories. I guess it just depends on how much you trust the DefaultRoot directive in Proftpd. I run Proftpd in its own chroot environment in addition to using DefaultRoot, so I’m used to feeling pretty safe with my Proftpd install. Anyway, here’s how I did the install/configuration
1. install proftpd-mysql from the ports with WITH_QUOTA set:
cd /usr/ports/ftp/proftpd-mysql/ env WITH_QUOTA=yes make env WITH_QUOTA=yes make install
2. Add the global proftpd user & Proftpd group to your system.
I used uid & gid 5500 simply because that’s what was used at one of the sites I was referencing (listed below).
pw groupadd -n Proftpd -g 5500 pw useradd proftpd -u 5500 -g Proftpd -s /sbin/nologin -d /dev/null -c "Proftpd User"
3. Create the mySQL database
create database proftpd; grant all on proftpd.* to 'proftpd'@'localhost' identified by 'password'
( change ‘password’ to something secret! )
4. Create the mySQL tables for the users & quota
create table proftpdUsers (
sqlUID int unsigned auto_increment not null,
userName varchar(30) not null unique,
passwd varchar(80) not null,
uid int unsigned not null unique,
gid int unsigned not null,
homedir tinytext,
shell tinytext,
primary key(sqlUID)
) ;
create table proftpdGroups (
sqlGID int unsigned auto_increment not null,
groupName varchar(30) not null unique,
gid int unsigned not null unique,
members tinytext,
primary key(sqlGID)
);
CREATE TABLE proftpdQuotaLimits (
name VARCHAR(30),
quota_type ENUM("user", "group", "class", "all") NOT NULL,
per_session ENUM("false", "true") NOT NULL,
limit_type ENUM("soft", "hard") NOT NULL,
bytes_in_avail FLOAT NOT NULL,
bytes_out_avail FLOAT NOT NULL,
bytes_xfer_avail FLOAT NOT NULL,
files_in_avail INT UNSIGNED NOT NULL,
files_out_avail INT UNSIGNED NOT NULL,
files_xfer_avail INT UNSIGNED NOT NULL
);
CREATE TABLE proftpdQuotaTallies (
name VARCHAR(30) NOT NULL,
quota_type ENUM("user", "group", "class", "all") NOT NULL,
bytes_in_used FLOAT NOT NULL,
bytes_out_used FLOAT NOT NULL,
bytes_xfer_used FLOAT NOT NULL,
files_in_used INT UNSIGNED NOT NULL,
files_out_used INT UNSIGNED NOT NULL,
files_xfer_used INT UNSIGNED NOT NULL
);
5. Add a test user to the proftpd database
(assumes /home/ftp is where you keep your ftp users. Otherwise, change the homedir location). This is certainly not a necessary step, but you should probably check to see if your configuration is working. You can delete this user later.
insert into proftpdUsers values ( 0, 'test', 'test', 5500, 5500, '/home/ftp/test', '/sbin/nologin' );
6. Set your proftpd configuration to use the mySQL authentication and quotas:
(NOTE: this is not a complete configuration file, it’s basically just the default config file with mySQL auth & quotas added, but note that the User and Group directives are the user & group we added in step 2. )
MaxInstances 30 # Set the user and group under which the server will run. User proftpd Group Proftpd # To cause every FTP user to be "jailed" (chrooted) into their home # directory, uncomment this line. DefaultRoot ~ # Normally, we want files to be overwriteable. AllowOverwrite on # Bar use of SITE CHMOD by defaultDenyAll # Log format and location LogFormat default "%t %h %a %s %m %f %b %T \"%r"\" ExtendedLog /var/log/proftpd.log ALL default SystemLog /var/log/proftpd.log ALL default TransferLog /var/log/proftpd.log ALL default # Uncomment this if you have "invalid shell" errors in your proftpd.log #RequireValidShell off # The passwords in MySQL are encrypted using CRYPT SQLAuthTypes Plaintext Crypt SQLAuthenticate users* groups* # used to connect to the database # databasename@host database_user user_password SQLConnectInfo proftpd@localhost proftpd yourdatabasepassword # Here we tell ProFTPd the names of the database columns in the "usertable" # we want it to interact with. Match the names with those in the db SQLUserInfo proftpdUsers userName passwd uid gid homedir shell # Here we tell ProFTPd the names of the database columns in the "grouptable" # we want it to interact with. Again the names match with those in the db SQLGroupInfo proftpdGroups groupName gid members # set min UID and GID - otherwise these are 999 each SQLMinID 5000 #============ # User quotas # =========== QuotaEngine on QuotaDirectoryTally on QuotaDisplayUnits Mb QuotaShowQuotas on # create a user's home directory on demand if it doesn't exist SQLHomedirOnDemand on SQLNamedQuery get-quota-limit SELECT "name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail FROM proftpdQuotaLimits WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used, files_in_used, files_out_used, files_xfer_used FROM proftpdQuotaTallies WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used = files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name = '%{6}' AND quota_type = '%{7}'" proftpdQuotaTallies SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4}, %{5}, %{6}, %{7}" proftpdQuotaTallies QuotaLimitTable sql:/get-quota-limit QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally
Related Links:
Generate a self-signed SSL Certificate with OpenSSL
Occasionally it may be necessary to generate a self-signed SSL certificate. This could be for internal websites, or for other internal uses that may require secure encrypted network transmissions.
Generating a self-signed certificate may be an easy task for the intermediate or senior level admin, however we decided to post this guide for everyone to use, since using the guide as a reference may hopefully be useful to those of you out there
1. Generate an SSL key without a passphrase, enter:
openssl genrsa -out /etc/httpd/ssl/mycorp.com.key 1024
2: Create a self-signed certificate, enter:
openssl req -new -key /etc/httpd/ssl/mycorp.com.key -x509 -out /etc/httpd/ssl/mycorpcom.crt -days 999
Sample output:
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:US State or Province Name (full name) [Some-State]:CA Locality Name (eg, city) []:CA Organization Name (eg, company) [Internet Widgits Pty Ltd]:mycorp, LLC Organizational Unit Name (eg, section) []:Sales Common Name (eg, YOUR name) []: Email Address []:you@mycorp.com
My Sample Apache httpd.conf virtual host file:
DocumentRoot "/var/www/html/ssl_doc_root/" ServerAdmin you@mycorp.com ServerName www.mycorp.com SSLEngine On SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2: +EXP:+eNULL SSLCertificateFile /etc/httpd/ssl/mycorpcom.crt SSLCertificateKeyFile /etc/httpd/ssl/mycorp.com.key SetEnvIf User-Agent ".*MSIE.*" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0
3. Restart httpd/Apche:
service httpd restart
Thats it! Test the SSL Cert to ensure it loads fine and reflects the proper values when you examine the properties.
Related Links:
Manage Nagios with Scripts
Posted by Kevin in Hosting, Linux, Shell Scripting on September 17, 2009
Working at many different organisations over the past 10 years, I have been involved in the implementation and maintenance of many different monitoring implementations. These include commercial and open source implementations, such as :
- Nagios
- IP Monitor
- Uptime
- OpenNMS
- Zabbix
Although Nagios may not be the most scalable or dynamic solution, for some organisations that perhaps have 1-100 servers, Nagios may be the best solution.
Additionally, the ability to write custom plugins, as well as the inherent SSL / TLS encryption of the NRPE checks, it may be the most viable. There are pro’s and con’s for each solution out there, and it is completely dependant on the skill level, nature of environment and available time for management / maintenance.
During the course of utilising Nagios, we noticed that one of the most time consuming tasks was maintaining the flat file configuration for adding, removing and modifying hosts within Nagios.
As a result, it was decided to write a quick Perl based script to manage the day-to-day tasks of adding and removing hosts within Nagios. When all is said and done, it really does save ALOT of time. This script can be integrated with existing control based management situations or other automation scripts / solutions where command line options and external scripting / plugins are possible. This way, you can encompass a more rounded, standardised and reliable way of managing your systems in Nagios.
In order for the script to work, you need to have 3 types of servers :
- Windows
- Unix/Linux
- VPS (Virtual Private Server)
Obviously you can modify the script to encompass an unlimited number of categories. Basically the script has defined three pre-existing hosts in the nagios hosts.cfg / hostgroups.cfg and services.cfg files to model them when adding the new server, based on your input.
Please take a look at the script, hopefully it will help make your life a little easier!
#!/usr/bin/perl
# Don't break me, I'm used by automated scripts.
###############################################################################
# Star Dot Hosting : www.stardothosting.com
# Nagios Config Manager
# Description: This program will add/remove entries from nagios.
# The files will be backed up in a archive before any changes are made.
###############################################################################
# Perl Libraries
use File::Copy;
use Switch;
###############################################################################
# Variables
###############################################################################
# Nagios file handlers
my $host_file = "/usr/local/nagios/etc/objects/hosts.cfg";
my $group_file = "/usr/local/nagios/etc/objects/hostgroups.cfg";
my $services_file = "/usr/local/nagios/etc/objects/services.cfg";
my $unixmatch = "sdh-unix" ;
my $windowsmatch = "sdh-windows";
my $vpsmatch = "vps-server";
my $date = `date "+%d%m%y-%H%M%S"`;
###############################################################################
# Verify Arguments
if ((!$ARGV[0]) || (!$ARGV[1])) {
&usage;
}
if (length($ARGV[1]) gt 1 ) { print "Command options too long!\n"; &usage; }
# Verify Nagios is working before we start
my $nagios = `nagios -v /usr/local/nagios/etc/nagios.cfg`;
if ($nagios =~ /One or more problems was encountered while processing the config files/) {
print "CRITICAL ERROR!\n\nNagios is already broken and we cannot continue!\nPlease fix it!\n";
@error_array = split(/\./, $nagios);
for $error (@error_array) {
$error=~s/^\n//g;
print "$error\n" if $error=~ /Error:/;
}
die "\n\nProgram Aborting before even starting due to nagios config error!\n"
}
# Clean up any old tmp files.
unlink("/tmp/hosts.cfg.tmp");
unlink("/tmp/hostgroups.cfg.tmp");
unlink("/tmp/services.cfg.tmp");
###############################################################################
# The Main Program control statement.
###############################################################################
switch ($ARGV[1]) {
case /d/i { &delete; }
case /x/i { &addEntry("x"); }
case /w/i { &addEntry("w"); }
case /v/i { &addEntry("v"); }
else {
print "Option: $ARGV[1] not found \n";
&usage;
}
}
###############################################################################
###############################################################################
# Subroutines
###############################################################################
###############################################################################
## sub backup - Backs up the nagios config files that are to be modified
###############################################################################
sub backup {
# Backup The Nagios files into an archive.
$date =~s/\n//g;
mkdir("/var/backup/nagios/$date", 0755 ) || die "Cannot create directory /var/backup/nagios/$date\n";
copy($host_file, "/var/backup/nagios/$date/hosts.cfg.bck"); #|| die "Cannot copy $host_file to /var/backup/nagios/$date/hosts.cfg.bck\n";
copy($group_file, "/var/backup/nagios/$date/hostgroups.cfg.bck"); #|| die "Cannot copy $host_file to /var/backup/nagios/$date/hostgroups.cfg.bck\n";
copy($services_file, "/var/backup/nagios/$date/services.cfg.bck"); #|| die "Cannot copy $service_file to /var/backup/nagios/$date/services.cfg.bck\n";
}
###############################################################################
## sub openFile($filename) - returns the file to a buffer for parsing
###############################################################################
sub openFile {
my $blob;
my $file = shift;
open (F, "< $file") or die "Can't open $file : $!";
while( ) {
$blob .= $_;
}
close(F);
return $blob;
}
###############################################################################
###############################################################################
###############################################################################
## sub delete - Deletes the servername from the config files.
###############################################################################
sub delete {
&backup; # Backup the files before we do anything to them.
&delete_host;
&delete_hostgroup;
&delete_services;
&checkNagios;
}
###############################################################################
## sub delete_host - deletes the host entry from hosts.cfg
###############################################################################
sub delete_host {
my $host_str = &openFile($host_file);
my $pattern=$ARGV[0]; # The parser doesn't like the array so we just pass it to a variable.
# parse the hosts.cfg file first
# This regular expression is a defined host entry, if it can't find it
# and assert that the hostname is part of that context, it will die.
if ($host_str =~/define[^_]*.name.*(?s-i:$pattern)[^}]*./i) {
print "command: $ARGV[1] : Deleting $ARGV[0] $1\n" if $host_str =~s/define[^_]*.name.*(?s-i:$pattern)[^}]*.//g;
print "Match: $ARGV[0]\n" if $host_str =~/define[^_]*.name.*(?s-i:$pattern)[^}]*./i;
print "Deleted $ARGV[0] from hosts.cfg\n";
# Write the successfull deleteion to a tmp file.
open(HF, ">/tmp/hosts.cfg.tmp") || die "Cannot open /tmp/hosts.cfg.tmp";
print HF $host_str;
close(HF);
} else { die "Could not find and entry for $ARGV[0] in hosts.cfg\n"; };
}
###############################################################################
## sub delete_hostgroup - deletes the hostgroup entry
###############################################################################
sub delete_hostgroup {
my $hostgrp_str = &openFile($group_file);
my $pattern=$ARGV[0]; # The parser doesn't like the array so we just pass it to a variable.
# search/replace the hostgroup.cfg file
if ($hostgrp_str =~ /$pattern/i) {
# If the server has a comma after it, we need to remove that too.. or breakage.
if ($hostgrp_str =~ /$pattern,/i ) {
print "Deleted $ARGV[0], from hostgroups.cfg\n" if $hostgrp_str =~ s/$pattern,//g;
}
print "Deleted $ARGV[0] from hostgroups.cfg\n" if $hostgrp_str =~ s/$pattern//g;
} else {
die "Could not find and entry for $ARGV[0] in hostgroups.cfg\n";
}
open(HGF, ">/tmp/hostgroups.cfg.tmp") || die "Cannot open /tmp/hostgroups.cfg.tmp";
print HGF $hostgrp_str;
close(HGF);
}
###############################################################################
## sub delete_services - delete the serivices.cfg entry
###############################################################################
sub delete_services {
my $services_str= &openFile($services_file);
my $pattern=$ARGV[0]; # The parser doesn't like the array so we just pass it to a variable.
# search/replace the hostgroup.cfg file
if ($services_str =~ /$pattern/i) {
# If the server has a comma after it, we need to remove that too.. or breakage.
if ($services_str =~ /$pattern,/i ) {
print "Deleted $ARGV[0], from services.cfg\n" if $services_str =~ s/$pattern,//g;
}
print "Deleted $ARGV[0] from services.cfg\n" if $services_str =~ s/$pattern//g;
} else {
die "Could not find and entry for $ARGV[0] in services.cfg\n";
}
open(SF, ">/tmp/services.cfg.tmp") || die "Cannot open /tmp/services.cfg.tmp";
print SF $services_str;
close(SF);
}
###############################################################################
## sub checkNagios - checks nagios for errors and rolesback if so.
###############################################################################
sub checkNagios {
copy("/tmp/hosts.cfg.tmp", $host_file) || print "Cannot copy /tmp/hosts.cfg.tmp to $host_file\n";
copy("/tmp/hostgroups.cfg.tmp", $group_file) || print "Cannot copy /tmp/hostgroups.cfg.tmp to $host_file\n";
copy("/tmp/services.cfg.tmp", $services_file) || print "Cannot copy /tmp/services.cfg.tmp $service_file\n";
my $success = `nagios -v /etc/nagios/nagios.cfg`;
if ($success =~ /One or more problems was encountered while processing the config files/) {
print "CRITICAL FAILURE - See Errors!\n";
@error_array = split(/\./, $success);
for $error (@error_array) {
$error=~s/^\n//g;
print "$error\n" if $error=~ /Error:/;
}
print "\nRestoring from backup\nCheck /tmp/hosts.cfg /tmp/hostgroup.cfg /tmp/service.cfg\n";
copy("/var/backup/nagios/$date/hosts.cfg.bck", $host_file) || die "Cannot copy /var/backup/nagios/$date/hosts.cfg.bck to $host_file\n";
copy("/var/backup/nagios/$date/hostgroups.cfg.bck", $group_file) || die "Cannot copy /var/backup/nagios/$date/hostgroups.cfg.bck to $group_file\n";
copy("/var/backup/nagios/$date/services.cfg.bck", $services_file) || die "Cannot copy /var/backup/nagios/$date/services.cfg.bck $services_file\n";
} else {
print "Nagios config reports success, restarting nagios\n";
my $restart = `/etc/init.d/nagios reload`;
print $restart;
}
}
###############################################################################
## sub addEntry - adds the unix or windows host entry.
###############################################################################
sub addEntry {
my $type = shift;
my $pattern = $ARGV[0];
my $host_str = &openFile($host_file);
my $hostgrp_str = &openFile($group_file);
my $services_str= &openFile($services_file);
if ($host_str=~/$pattern/) { die "$ARGV[0] already in hosts.cfg, aborting!\n"; }
if ($hostgrp_str=~/$pattern/) { die "$ARGV[0] already in hostgroups.cfg, aborting!\n"; }
if(($type eq 'w') || ($type eq 'x')) {
if ($services_str=~/$pattern/) { die "$ARGV[0] already in services.cfg, aborting!\n"; }
}
# Some sanity checks to help prevent data entry errors
if (!$ARGV[2]) { print "\nNo Server Alias, aborting!\n\n"; &usage; }
#if ($ARGV[2]=~/[0-9]{5,8}$/i) {} else { print "No Member ID!\n"; exit 0}
if (!$ARGV[3]) { print "\nNo IP Address specified, aborting!\n\n"; &usage; }
if ($ARGV[3]=~/[a-z]/i) { print "\nIP Address $ARGV[3] is invalid, please double check\n\n"; exit 0; }
if ($ARGV[3]=~/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/) {
@ip = split(/\./, $ARGV[3]);
} else {
print "\nIP Address $ARGV[3] is invalid, please double check\n\n"; exit 0;
}
if (($ip[0] > 254) || ($ip[1] > 254) ||
($ip[3] > 254) || ($ip[4] > 254)) {
print "\nIP Address $ARGV[3] is invalid, please double check\n\n"; exit 0;
}
# Passes sanity checks, back up the mo fo.
&backup;
# Check if windows or unix
switch ($type) {
case "x" { print "Unix!\n";
$hostgrp_str =~ s/$unixmatch/$unixmatch,$pattern/g;
$services_str =~ s/$unixmatch/$unixmatch,$pattern/g; }
case "w" { print "Windows\n";
$hostgrp_str =~ s/$windowsmatch/$windowsmatch,$pattern/g;
$services_str =~ s/$windowsmatch/$windowsmatch,$pattern/g; }
else { print "VPS\n";
$hostgrp_str =~ s/$vpsmatch/$vpsmatch,$pattern/g;
}
} # end switch
# Add it to the host_str buffer.
$host_str .= "define host{
use sdh-dedicated
host_name $ARGV[0]
alias $ARGV[2]
address $ARGV[3]
}\n\n";
open(HF, ">/tmp/hosts.cfg.tmp") || die "Cannot open /tmp/hosts.cfg.tmp";
print HF $host_str;
close(HF);
open(HGF, ">/tmp/hostgroups.cfg.tmp") || die "Cannot open /tmp/hostgroups.cfg.tmp";
print HGF $hostgrp_str;
close(HGF);
if(($type eq 'w') || ($type eq 'x')) {
open(SF, ">/tmp/services.cfg.tmp") || die "Cannot open /tmp/services.cfg.tmp";
print SF $services_str;
close(SF);
}
&checkNagios;
}
###############################################################################
## sub usage - prints the usage when things don't add up from args
###############################################################################
sub usage{
print "Usage: /usr/local/bin/nagios-add.pl \n\n";
print "Optional Flags:\n";
print "\td delete a server\n";
print "\tw add a windows server\n";
print "\tx add a unix server\n";
print "\tv add a VPS server\n\n";
print "eg delete:\t./usr/local/bin/nagios-add.pl sdh-server12 d\n";
print "eg add:\t\t./usr/local/bin/nagios-add.pl sdh-server12 x \"sdh-server12 sdh-server12.stardothosting.com MemID:155\" 192.168.111.10\n";
exit 0;
}
Related Links:
How to setup a slave DNS Nameserver with Bind
Posted by Kevin in Database, Linux, Uncategorized on May 5, 2009
When implementing redundancy as far as DNS is concerned, automated is always better. In a hosting environment, new zone files are constantly being created.
This need for a DNS master/slave implementation where new zone files are transferred between the master nameserver and the slave became apparent as operations grew and geographic DNS redundancy became apparent.
Obviously some commercial dns products provide this type of functionality out-of-the-box, but I will show you how to do this with a simple Bind DNS distribution.
I wrote this tutorial to help you, hopefully, to create an automated DNS slave / zone file transfer environment. Obviously you can create as many slave servers as you feel necessary.
MASTER Server
1. Edit /etc/named.conf and add the following to the options section where xx.xx.xx.xx is the ip of your slave server.:
allow-transfer { xx.xx.xx.xx; };
2. Create a script with the following, where somedirectory is the directory on your SLAVE server to store the slave zones and where yy.yy.yy.yy is your MASTER server ip and somewwwdir is a directory browsable via http and finally someslavefile.conf is the output file to write you slave config:
#!/bin/sh
#
for domain in `/bin/grep ^zone /etc/named.conf |/bin/grep "type master" |/bin/awk '{print $2}' |/bin/awk -F\" '{print $2}'`
do
/usr/bin/printf "zone \"${domain}\" { type slave; file \"/var/named/slaves/somedirectory/${domain}.db\"; masters { yy.yy.yy.yy; }; };\n"
done > /var/www/html/somewwwdir/someslavefile.conf
3. Test the script to ensure it is writing out the appropriate format.
4. Run the script as any user with permission to write to an http visible directory via cron.
0 4 * * * /path/to/script > /dev/null 2>&1
SLAVE SERVER
1. Transfer the rndc.key file from your master server to the slave :
scp MASTERSERVER:/etc/rndc.key /etc/ns1rndc.key
2. Edit ns1rndc.key and change the name of the key definition.
3. Edit named.conf and add the following to the options section:
allow-transfer { zz.zz.zz.zz; };
4. Append the following to the named.conf file:
include "/etc/ns1rndc.key";
include "/path/to/someslavefile.conf";
5. Run the following commands
touch /path/to/someslavefile.conf
mkdir /var/named/slaves/somedirectory/
chown -R named:named /var/named/slaves/somedirectory/
/etc/init.d/named restart
6. Create a script:
#!/bin/sh
/usr/bin/wget http://yy.yy.yy.yy/somewwwdir/someslavefile.conf -O /var/named/slaves/someslavefile.conf
/etc/init.d/named restart
7. Add to root’s crontab
0 4 * * * /path/to/script
In the second slave script, you see that the transfer is done via wget. This can be replaced by many other more secure methods. If ssh based key authentication is employed, a simple scp or even rsync can be utilized to accomplish the actual zone transfer.
