Nagios "PING WARNING - System call sent warnings to stderr" Output

If you're using Nagios and seeing the below output with in your notification output, you may be a bit frustrated at it obscuring what's actually going on. I've seen one person state it was due to a clock skew problem for them (that was obviously easy to fix). On my side, however, there is no such clock issue and frankly, I don't know why it happens. I can't reproduce the error manually, it only seems to happen during a RECOVERY for a host.

This sort of output is likely giving you a headache:

[1335553705] HOST ALERT: MYHOST;UP;HARD;1;PING WARNING - System call sent warnings to stderr  System call sent warnings to stderr  System call sent warnings to stderr Packet loss = 37%, RTA = 42.34 ms

But, if you're not too proud to take the following solution, there's an easy fix!

define command{
  command_name  check-host-alive
  command_line  $USER1$/check_ping -H $HOSTADDRESS$ -w 2000.0,80% -c 3000.0,100% -p 5 2> /dev/null
}

By redirecting STDERR to /dev/null, only the 'important output' will be leftover. I didn't see this solution anywhre in my quick searches, so perhaps it will save someone else a headache troubleshooting an innocuous error.

 

CISSP != PhD, PharmD, JD, CPA, MBA -- so stop acting like it

Here's the preface: I have my CISSP. That being said, if you're one of the people who attaches the fact that they are a CISSP onto their name on Twitter (or hell, even Linked In), please stop.

It's already well stated among the technical elite of the information security community that the CISSP is sort of a joke. It's a joke because for every solid security professional who has it, you can find a dozen other people who can't name a single mode of operation for AES or are unable to name a common service port-number. The CISSP, like most certifications, can be boot-camped in a week if you are somewhat technically competent and have good reasoning and memorization skills.

The reason why professionals put their credential after their name is because it either implies a certain threshold of education or status in their profession has been achieved, likely tied to some form of post-baccalaureate degree. In most cases, it matters whether or not you are a PharmD, PhD, JD, or even have a professional certification like a CPA for your profession, to even perform said profession.

The lack of a CISSP, however, does not restrict anyone from being a professional in information security. Nor does the CISSP provide any real assurances that you are even truly competent in the field. Where-as a pharmacist will go through 6-8 years of college education, earn a doctorate, deal with state boards and licensing, and be heavily tracked by not only their employer, but also the DEA and other regulating bodies, a CISSP has none of that attached or implied.

As a CISSP, I am not ashamed of the certification and I don't think it's a joke, but I do think there needs to be a reality check among those that are certified (and not just limited to this certification). No matter what one-week boot camp you did; no matter how many months you crammed information into your brain; no matter how many times you can recite portions of COBIT -- you do not have a doctorate in information security. You also do not have anyone at a state, regional, or federal level watching your every move to ensure that you abide by any standards to be an information security professional.

ISC^2 does a decent job at regulating their own people, but that is a far-cry from the sort of regulation that goes on in industries and professions that those who carry the aforementioned professional and educational suffixes do.

Be proud of your credential -- any credential. But please, get some perspective on what it really means in context with other professions and among your own peers. Stand by your name (or handle) as your claim to fame, for all of the awesome work you've done, conferences you've spoken at, communities you lead, and not because of a single certification exam.

Mark Stanislav, B.S., M.S., CISSP, CCSK, Security+, Linux+ ;)

Using winbind in CentOS 6 for Active Directory Authentication

It's been a while since I've had to integrate a Windows AD deployment with Linux so I figured I'd take stock of the choices that seemed to be 'preferred' by folks these days. I remember reading about winbind years ago but going a different route (primarily pam_ldap) for authentication needs. Having a fresh pair of eyes on the situation, I thought I'd give it a shot.

To my surprise, I think the winbind route is actually much more straight-forward and provides deeper integration than simply using a pam_ldap integration. Below are my notes related to a deployment I did with [brackets] around items you need to configure for your specific deployment. I've tested these and now deployed to production, so hopefully they are quickly useful without too much of a headache.

Also, pay attention to capitalization, especially with respect to smb.conf, krb5.conf, and when you run the 'net join...' command.

Install Required Packages:
# yum install samba samba-winbind oddjob-mkhomedir

/etc/samba/smb.conf

[global]
log file = /var/log/samba/log.%m
max log size = 50
security = ads
netbios name = [NETBIOS NAME FOR HOST]
realm = [REALM.INTERNAL]
password server = [ad-host.realm.internal]
workgroup = [DOMAIN]
idmap uid = 10000-500000 
idmap gid = 10000-500000
winbind separator = +
winbind enum users = no
winbind enum groups = no
winbind use default domain = yes
template homedir = /home/%U
template shell = /bin/bash
client use spnego = yes
domain master = no

/etc/nsswitch.conf:
Change the following lines in your configuration to include winbind:

passwd:     files winbind
group:      files winbind

/etc/krb5.conf:
[libdefaults]
ticket_lifetime = 24000
default_realm = [REALM.INTERNAL]
[realms] 
[REALM.INTERNAL] = { 
kdc = [ad-host.realm.internal]
admin_server = [ad-host.realm.internal]
default_domain = [realm.internal]

[domain_realm] 
.[realm.internal] = [REALM.INTERNAL] 
[realm.internal] = [REALM.INTERNAL]

Start Samba:
# service smb start; chkconfig smb on

Join Machine to Domain:
# net ads join -U [your AD account]@[REALM.INTERNAL]

Start winbind & Message Bus (DBUS):
# service winbind start; chkconfig winbind on
# service messagebus start; chkconfig messagebus on

/etc/pam.d/system-auth:
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth        required      pam_env.so
auth        sufficient    pam_unix.so nullok try_first_pass
auth        requisite     pam_succeed_if.so uid >= 500 quiet
auth        sufficient    pam_winbind.so use_first_pass
auth        required      pam_deny.so

account     required      pam_unix.so
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 500 quiet
account     required      pam_permit.so

password    requisite     pam_cracklib.so try_first_pass retry=3 type=
password    sufficient    pam_unix.so md5 shadow nullok try_first_pass use_authtok
password    sufficient    pam_winbind.so use_authtok
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_mkhomedir.so umask=0022 skel=/etc/skel
session     required      pam_unix.so

/etc/pam.d/password-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth        required      pam_env.so
auth        sufficient    pam_unix.so nullok try_first_pass
auth        requisite     pam_succeed_if.so uid >= 500 quiet
auth        sufficient    pam_winbind.so use_first_pass
auth        required      pam_deny.so

account     required      pam_unix.so
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 500 quiet
account     [default=bad success=ok user_unknown=ignore] pam_winbind.so
account     required      pam_permit.so

password    requisite     pam_cracklib.so try_first_pass retry=3 type=
password    sufficient    pam_unix.so md5 shadow nullok try_first_pass use_authtok
password    sufficient    pam_winbind.so use_authtok
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
session     optional      pam_oddjob_mkhomedir.so
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so

/etc/oddjobd.conf.d/oddjobd-mkhomedir.conf:
Change both lines like this to contain the new umask shown...
<helper exec="/usr/libexec/oddjob/mkhomedir -u 0077"

Start Oddjob Daemon:
# service oddjobd restart; chkconfig oddjobd on

You should now be able to authenticate using your Active Directory users if configured properly. Check /var/log/messages and /var/log/samba/* if you need to debug.

Ghost in the Shellcode - 2012; Stage #2 "Hashish" - 'Warez' Write-Up

Quite frankly, this one annoyed the hell out of us for a long time. When you're chaining together a process and hit a dead-end, sometimes you really just need to re-evaluate what you're looking at. Anyways, we finally got this sucker squashed and were very glad it was done :)

The file was again xz compressed and after a quick rename the download to  3fdcf74b01bd93635e653eb29e43ee8c.xz it can be decompressed with:

xz -d 3fdcf74b01bd93635e653eb29e43ee8c.xz

The resulting content of the decompressed file is (sans linebreaks):

425a68393141592653592e45594800006c490040007fe03f00300159b4db4553f09a68f21484553fd3
26c84ca53114f04d3349a2029371ed412bb78a8b838cf39d35a34f7951757516dada7d2366ba56e823867
9a2775952e0a8219be68d55f27777bbde08bea5382b2d1cd8f6011f242d9fbcf1da93776cda92231c1
1caf66b72bc0faf81c24f2e93d91960eee496dd8d1e8a6c9a6a8276e803fdb530d9f0a3d66268749c6
91a870e1bd130b49ce9865f522dd4b3f525f24a6ae1029efb7bcd5ac31e25fce9b175b703cc8ec6515
14a9a580d7d057ac1847c9578bf60c301219ebac1fc7d95a45fd0d69c6fa396ce7a8b533d6dafd6b17c
0a7c603d4870d93d8082f5f23c57a1b8a2225c6f17043fb6049f9552b340e3d9c53e46d4dceb8963
b9309590bfc5dc914e14240b91565200

The resulting content appeared as (sans linebreaks):

BZh91AY&SY.EYH^@^@lI^@@^@^?à?^@0^AY´ÛESð<9a>hò^T<84>U?Ó&ÈL¥1^TðM3I¢^B<93>qíA+·<8a><8b><83><8c>ó<9d>5£OyQuu^VÚÚ}#fºVè#<86>y¢wYRà¨!<9b>æ<8d>Uòww»Þ^H¾¥8+-^\Øö^A^_$-<9f>¼ñÚ<93>wlÚ<92>#^\^QÊökr¼^O¯<81>ÂO.<93>Ù^Y`îä<96>Ý<8d>^^<8a>l<9a>j<82>vè^Cýµ0Ùð£Öbht<9c>i^Z<87>^N^[Ñ0´<9c>é<86>_R-Ô³õ%òJjá^B<9e>û{ÍZÃ^^%üé±u·^CÌ<8e>ÆQQJ<9a>X^M}^EzÁ<84>|<95>x¿`Ã^A!<9e>ºÁü}<95>¤_ÐÖ<9c>o£<96>Îz<8b>S=m¯Ö±||`=HpÙ=<80><82>õò<W¡¸¢"\o^W^D?¶^D<9f><95>R³@ãÙÅ>FÔÜë<89>c¹0<95><90>¿ÅÜ<91>N^T$^K<91>VR^@

Notably, the header of this file is bzip2 (BZh). Running a bunzip2 on the resulting output created another file.

789c358e516bc3201446dffd1561980741b3da1b6392b53298da1996c1924866a00f1d2d632f7ddaff67371
d7d94fb79cef9ba7cff5cb3aaaab2ddf9f47b32a4072725b7475a412e5a4b87fa101a5ea6a59d9d90b98d7
008c294912aed8ad6bf0da0432368ec369017a4577948e083e05edc091f50ca95f0be05f6d27a1ef1cef87
3102ab7e39d56cf017f3fd969a8275cfff3d0e73e71c1b867cb56ef0b45e9ed6e68ea0018bedd8cb4066d0
be96b2c44df08566251eab0f04678b0ab5b08e3f9886bc5a7d8ad34e3903607b69f5247748575c68b843e
b92eb0be30761a41ef8e8f9bd78c64e4723d933f37f34798

This is where things went bad for me. Wolfgang Goerlich luckily figured out what I didn't, which is that this stream of data was actually using zlib! I had kept doing hex->ascii and would get 'x<9c>' which meant nothing to me or google. Well the problem was, the x should have just been '78' (you know, like the original hex!). Once we realized this, I was able to use Python again and the zlib.decompress method to get another result in uuencoded data.

begin 666 <data>
M3E11,D]$63%-:D$R8GI9,4YZ:WE-1%DU3GI->4U$57E.:FLR37I9-$UJ03%.
M5%IY3FI-,F-$63%-:D$Q3419,4YN23)C:F,U3FI),@I-5%DS3GI->4U$8WI.
M;DTR8T19,$UJ03%-1%EX3GI),F)Z27=.5$$R8T19>$YJ33).5$EW3FI9,F-Z
M8WE-:D$S3D19-$YJ57E-1%EX"DYN23-->F,S3FI5,TUJ27=.>E$R8WI)=TYJ
763)C>F-Y3GI1,TUJ63%.>DTS37<]/0H 
end

Running this data through uudecode, the result is an (obviously) base64 encoded string.

NTQ2ODY1MjA2bzY1NzkyMDY5NzMyMDUyNjk2MzY4MjA1NTZyNjM2cDY1MjA1MDY1NnI2cjc5NjI2
MTY3NzMyMDczNnM2cDY0MjA1MDYxNzI2bzIwNTA2cDYxNjM2NTIwNjY2czcyMjA3NDY4NjUyMDYx
NnI3Mzc3NjU3MjIwNzQ2czIwNjY2czcyNzQ3MjY1NzM3Mw==

Guess what! This results in more hex :)

546865206o6579206973205269636820556r636p652050656r6r796261677320736s6p64205061726o2050
6p61636520666s722074686520616r7377657220746s20666s727472657373

Throwing this through hex->ascii we finally get something usable!

The ?ey is Rich U?c?e Pe??ybags s??d Par? P?ace f?r the a?swer t? f?rtress

With a little deduction we realized the correct string was:

"The key is Rich Uncle Pennybags sold Park Place for the answer to fortress"

Submitting the key as 'Rich Uncle Pennybags sold Park Place for the answer to fortress' did in-fact work as expected.

For those keeping track that makes this process..

xz -> hex -> bzip2 -> hex -> zlib -> uuencoded -> base64 -> hex -> guessing letters :)

Ghost in the Shellcode - 2012; Stage #15 "Joey's Login" - 'Obj-C' Write-Up

Challenge #15 was I believe worth the least points of the entire CTF (75 points). That being said, it was extremely fast to get done! I had previously worked with a Flash Decompiler called Trillix for some security auditing of a customer's Flash application prior so I was aware of what to do once I saw that the decompressed c8addea611847a31ab0fac281eee3acd.zip file was composed of an html file, two Flash SWF files, a javascript file, and a 'history' folder.

Upon opening the HTML file, the primary Flash SWF was shown below:

Screen_shot_2012-01-29_at_3
Knowing there was a login, the next logical step is to figure out how the authentication mechanism works and how we can exploit that to allow us to 'login'.

Opening up Trillix, I loaded the joeyslogin.swf file and was met with the usual pile of libraries and classes. One trick I learned a while ago is to just immediately stop showing all of the base Flash libraries (package mx) and focus on the custom code written for the actual application.

I immediately noted a file called class FlexChallenge1 and figured it was a great place to start! Near the top of the file was the important chunk of code for this entire challenge (not much searching, eh?).

Screen_shot_2012-01-29_at_4
It's rather apparent quickly that loc2 is set to the Base64 encoded string of our uname field that was populated with our team-name (as directed by the challenge). Once this value is set, a comparison is done to ensure that the resulting base64 encoded string of our team name matches the code that was entered. Pending that is true, we get a response of "Valid code! Now go submit it.

Understanding this, we simply just have to base64 encode the team name, enter the values as assessed and we get the proper response. Submitting our base64 encoded team name as the key results in the correct answer.

Ghost in the Shellcode - 2012; Stage #5 "VoxVeritas" - 'GDB' Write-Up

Like many of the challenges, the initial download c45f9e8e52cf8872da03c9f6e35dbbf1 was actually compressed using xz. Renaming the file with the .xz extension and then running the command below will decompress it.

xz -d c45f9e8e52cf8872da03c9f6e35dbbf1.xz

Opening the resulting file in vim, we see what looks like a crazy amount of UTF-16 or otherwise unusual characterset. 

^@^@ nˈaʊ dɪskɹˈaɪb tə mˌiː wɒt mˈɑːsɛləs wˈɒleɪs lˈʊks lˈaɪk vˈɛl hˈeːs hˈeːs blˈak ɡˌəʊ ˈɒn ˈant hˈeːs hˈeːs bˈalt dˈʌz hiː lˈʊk lˈaɪk ɐ bˈɪtʃ vˈɑːt dˈʌzhiːlˈʊklˈaɪkɐbˈɪtʃ nˈoː ðˈɛn wˌaɪ dˈɪd juː tɹˈaɪ tə fˈʌk ˈɪm lˈaɪk ɐ bˈɪtʃ ˈiː dˈɪdnt jˈɛs jɐ dˈɪd bɹˈɛt jɐ tɹˈaɪd tˈɑː fˈʌk ˈɪm juː ˈɛvə ɹˈiːd ðə bˈaɪbəL bɹˈɛt jˈeːs ðeəz ɐ pˈasɪdʒ aɪ ɡɒt mˈɛmɔːɹˌaɪzd sˈiːmz ɐpɹˈəʊpɹɪət fɔː ðɪs sˌɪtjuːˈeɪʃən ˈɛzɪkˌiːl twˈɛntɪfˈaɪv sˈɛvəntˌiːn ðə pˈaθ ɒvðə ɹˈaɪtʃəs mˈan ɪz bɪsˈɛt ˌɒn ˈɔːl sˈaɪdz baɪ ðɪ ɪnˈɛkwɪtɪz ɒvðə sˈɛlfɪʃ and ðə tˈɪɹɐnˌɪ ɒv ˈiːvɪl mˈɛn blˈɛst ɪz hiː hˈuː ɪnðə nˈeɪm ɒv tʃˈaɹɪtɪ and ɡˈʊd wˈɪl ʃˈɛpɜːdz ðə wˈiːk θɹuː ðə vˈalɪ ɒv dˈɑːknəs fɔː hiː ɪz tɹˈuːlɪ hɪz bɹˈʌðəz kˈiːpə and ðə fˈaɪndəɹ ɒv lˈɒst tʃˈɪldɹən and aɪ wɪl stɹˈaɪk dˌaʊn əpˌɒn ðˌiː wɪð ɡɹˈeɪt vˈɛndʒəns and fjˈʊəɹɪəs ˈaŋɡə ðəʊz hˌuː ɐtˈɛmpt tə pˈɔɪsən and dɪstɹˈɔɪ maɪ bɹˈʌðəz and juː wɪl nˈəʊ maɪ nˈeɪm ɪz ðə lˈɔːd wɛn aɪ lˈeɪ maɪ vˈɛndʒəns əpˌɒn juː

Despite this challenge being worth 400 points (which was a lot for this competition) I actually had it solved in about 30 minutes from start to finish. I credit this to some basic investigation into the contents of the file.

I started reading through the 'words' and noted that quite a bit sounded like english but didn't always look like english. Factoring in the 'Question' of the challenge (which translates to 'Speak Truth') it was obvious language was really important to the challenge.

I noted one specific word vˈɛndʒənswhich I googled for and saw it was the phonetic for vengeance! Great, so vengeance isn't a super common word in every day speech so I figured I'd look for other words I could google for and find the phonetic of. Eventually I ended up with a few words in row, on a couple separate lines.

Googling around, I finally found a phrase (vengeance and furious anger) linked off the Pulp Fiction Wikipedia Page and that got me nearly there. I first tried 'Pulp Fiction' as the key but that didn't work. Looking closer I noted it was from Ezekiel 25:17 which was in-fact the key for this challenge.

Again, pretty simple if you kept to not overthinking the challenge. Lucky for me, I went down the right path immediately, but I know others doing the CTF had some issues.

CentOS 6 + Puppet 2.7 + mCollective + Foreman + RabbitMQ + Apache + Passenger Tutorial

So perhaps less than a ‘blog post’ this is more of a bash code-dump from my Veewee definition + postinstall.sh for this deployment. For anyone who needs a quick stack (especially if you are still on older releases of CentOS/Puppet or otherwise) take a run through this and see if you don’t have something useful pretty quickly! I recommend keeping tabs on my Veewee definitions on Github for updates to this or new ones.

If nothing else, hopefully someone finds this useful to fix a bug they may have been having in their own deployment. Note that I’ve taken out some Veewee/Vagrant/Virtual Box specific pieces of the original postinstall.sh. Cheers.

# Configuration Parameters
MYSQL_PASSWORD="puppetized"
RABBIT_USER="mcollective"
RABBIT_PASSWORD="rabbitMCrabbit"
MCOLLECTIVE_PSK="mcollectivePSKmcollective"
FOREMAN_EMAIL="root@test.local"
DOMAIN="test.local"

# Initial CentOS system clean-up + upgrades
yum -y erase wireless-tools gtk2 libX11 hicolor-icon-theme avahi freetype bitstream-vera-fonts
yum -y upgrade
yum -y clean all

# Configure hostname
echo -e "127.0.0.1 puppet.${DOMAIN} puppet foreman.${DOMAIN} foreman localhost" > /etc/hosts
echo -e "NETWORKING=yes\nHOSTNAME=puppet.${DOMAIN}" > /etc/sysconfig/network
hostname puppet.${DOMAIN}

# Puppet Labs repository
cat > /etc/yum.repos.d/puppetlabs.repo < < "EOF"
[puppetlabs]
name=Puppet Labs Packages
baseurl=http://yum.puppetlabs.com/
gpgcheck=0
enabled=1
EOF

# Foreman repository
cat > /etc/yum.repos.d/foreman.repo < < "EOF"
[foreman]
name=Foreman Repo
baseurl=http://yum.theforeman.org/stable
gpgcheck=0
enabled=1
EOF

# Installation of majority of stack packages
yum -y install rubygems ruby-devel rubygem-stomp
yum -y install httpd httpd-devel mod_ssl
yum -y install mysql mysql-server mysql-devel
yum -y install libcurl-devel openssl-devel openssl098e tcl tk unixODBC unixODBC-devel augeas

rpm -ivh http://download.fedora.redhat.com/pub/epel/6/x86_64/rubygem-rest-client-1.6.1-2.el6.noarch.rpm
rpm -ivh http://download.fedora.redhat.com/pub/epel/6/x86_64/rubygem-json-1.4.3-3.el6.x86_64.rpm
rpm -ivh http://download.fedora.redhat.com/pub/epel/6/x86_64/rubygem-mime-types-1.16-3.el6.noarch.rpm

# Installation of stack gems
gem install --no-rdoc --no-ri puppet passenger rack mysql net-ping
gem install --no-rdoc --no-ri -v 3.0.10 rails activerecord

# Deploy required Puppet user, files, and directories
adduser puppet

mkdir -p /etc/puppet/{manifests,modules}
mkdir -p /usr/share/puppet/rack/puppetmasterd/{public,tmp}

mkdir -p /var/lib/puppet/{bucket,yaml,rrd,server_data,reports}
chown puppet:puppet /var/lib/puppet/{bucket,yaml,rrd,server_data,reports}

cp /usr/lib/ruby/gems/1.8/gems/puppet-2.7.3/ext/rack/files/config.ru /usr/share/puppet/rack/puppetmasterd/config.ru
chown puppet:puppet /usr/share/puppet/rack/puppetmasterd/config.ru

# Install Foreman
rpm -ivh http://yum.theforeman.org/stable/RPMS/foreman-0.3-1.noarch.rpm --nodeps

# mCollective & Plugins
yum -y install mcollective mcollective-common mcollective-client

cd /usr/libexec/mcollective/mcollective/application
for i in filemgr nettest package puppetd service; do
wget https://raw.github.com/puppetlabs/mcollective-plugins/master/agent/$i/application/$i.rb
done
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/etcfacts/application/etcfacts.rb
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/shellcmd/application/shellcmd.rb
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/yum/application/yum.rb

cd /usr/libexec/mcollective/mcollective/agent
for i in nettest filemgr puppetd puppetral puppetca; do
wget https://raw.github.com/puppetlabs/mcollective-plugins/master/agent/$i/agent/$i.rb
wget https://raw.github.com/puppetlabs/mcollective-plugins/master/agent/$i/agent/$i.ddl
done

wget -O package.rb https://raw.github.com/puppetlabs/mcollective-plugins/master/agent/package/agent/puppet-package.rb
wget https://raw.github.com/puppetlabs/mcollective-plugins/master/agent/package/agent/package.ddl
wget -O service.rb https://raw.github.com/puppetlabs/mcollective-plugins/master/agent/service/agent/puppet-service.rb
wget https://raw.github.com/puppetlabs/mcollective-plugins/master/agent/service/agent/service.ddl
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/etcfacts/etc_facts.rb
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/etcfacts/etc_facts.ddl
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/shellcmd/shellcmd.rb
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/shellcmd/shellcmd.ddl
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/yum/yum.rb
wget https://raw.github.com/phobos182/mcollective-plugins/master/agent/yum/yum.ddl

cd /usr/libexec/mcollective/mcollective/facts/
wget https://raw.github.com/puppetlabs/mcollective-plugins/master/facts/facter/facter_facts.rb

# Fix ODBC requirement for Erlang
ln -s /usr/lib64/libodbc.so.2 /usr/lib64/libodbc.so.1

# Install Erlang
rpm -ivh http://yum.puppetlabs.com/prosvc/5/x86_64/erlang-R12B-5.10.el5.x86_64.rpm --nodeps

# Install RabbitMQ & Plugins
rpm -ivh http://www.rabbitmq.com/releases/rabbitmq-server/v2.5.1/rabbitmq-server-2.5.1-1.noarch.rpm

cd /usr/lib/rabbitmq/lib/rabbitmq_server-2.5.1/plugins
wget http://www.rabbitmq.com/releases/plugins/v2.5.1/amqp_client-2.5.1.ez
wget http://www.rabbitmq.com/releases/plugins/v2.5.1/rabbitmq_stomp-2.5.1.ez

chkconfig rabbitmq-server on
service rabbitmq-server start

# Configure RabbitMQ user/privileges
rabbitmqctl add_user ${RABBIT_USER} ${RABBIT_PASSWORD}
rabbitmqctl set_permissions ${RABBIT_USER} ".*" ".*" ".*"
rabbitmqctl delete_user guest

# Install Apache Passenger module
passenger-install-apache2-module -a

# Configuration files for mCollective
cat > /etc/mcollective/server.cfg < < "EOF"
topicprefix = /topic/
main_collective = mcollective
collectives = mcollective
libdir = /usr/libexec/mcollective
logfile = /var/log/mcollective.log
loglevel = info
daemonize = 1

securityprovider = psk
plugin.psk = MCOLLECTIVE_PSK_PH

connector = stomp
plugin.stomp.host = localhost
plugin.stomp.port = 61613
plugin.stomp.user = RABBIT_USER_PH
plugin.stomp.password = RABBIT_PASSWORD_PH

factsource = facter
EOF

cat > /etc/mcollective/client.cfg < < "EOF"
topicprefix = /topic/
main_collective = mcollective
collectives = mcollective
libdir = /usr/libexec/mcollective
logfile = /dev/null
loglevel = info

securityprovider = psk
plugin.psk = MCOLLECTIVE_PSK_PH

connector = stomp
plugin.stomp.host = localhost
plugin.stomp.port = 61613
plugin.stomp.user = RABBIT_USER_PH
plugin.stomp.password = RABBIT_PASSWORD_PH

factsource = facter
EOF

# Configure MySQL
chkconfig mysqld on && service mysqld start
mysql -u root -e "CREATE DATABASE puppet;"
mysql -u root -e "GRANT ALL PRIVILEGES ON puppet.* TO puppet@localhost IDENTIFIED BY '${MYSQL_PASSWORD}';"

# Puppet configuration
cat > /etc/puppet/puppet.conf < < "EOF"
[main]
logdir = /var/log/puppet
rundir = /var/run/puppet
ssldir = $vardir/ssl
factpath = $vardir/lib/facter
templatedir = $confdir/templates
pluginsync = true
classfile = $vardir/classes.txt
localconfig = $vardir/localconfig
reportdir = /var/lib/puppet/reports

[agent]
report = true
ignorecache = true

[master]
reports = http,store,log,foreman
ssl_client_header = SSL_CLIENT_S_DN
ssl_client_verify_header = SSL_CLIENT_VERIFY
storeconfigs = true
dbadapter = mysql
dbuser = puppet
dbpassword = MYSQL_PASSWORD_PH
dbname = puppet
dbserver = localhost
dbsocket = /var/lib/mysql/mysql.sock
EOF

# Foreman configuration files
cat > /usr/share/foreman/config/database.yml < < "EOF"
production:
adapter: mysql
database: puppet
username: puppet
password: MYSQL_PASSWORD_PH
host: localhost
socket: "/var/lib/mysql/mysql.sock"
EOF

cat > /usr/share/foreman/config/settings.yaml < < "EOF"
---
:modulepath: /etc/puppet/modules/
:tftppath: tftp/
:ldap: false
:puppet_server: puppet
:unattended: false
:puppet_interval: 30
:document_root: /usr/share/foreman/public
:administrator: FOREMAN_EMAIL_PH
:foreman_url: foreman.DOMAIN_PH
EOF

cat > /usr/share/foreman/config/email.yaml < < "EOF"
production:
delivery_method: :smtp
smtp_settings:
address: localhost
port: 25
domain: DOMAIN_PH
authentication: :none
EOF

# Foreman report for Puppet
cat > /usr/lib/ruby/gems/1.8/gems/puppet-2.7.3/lib/puppet/reports/foreman.rb < < "EOF"
$foreman_url="https://foreman.DOMAIN_PH:443"

require 'puppet'
require 'net/http'
require 'uri'

Puppet::Reports.register_report(:foreman) do
Puppet.settings.use(:reporting)
desc "Sends reports directly to Foreman"

def process
begin
uri = URI.parse($foreman_url)
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == 'https' then
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
req = Net::HTTP::Post.new("/reports/create?format=yml")
req.set_form_data({'report' => to_yaml})
response = http.request(req)
rescue Exception => e
raise Puppet::Error, "Could not send report to Foreman at #{$foreman_url}/reports/create?format=yml: #{e}"
end
end
end
EOF

# Apache configuration files
cat > /etc/httpd/conf.d/puppet.conf < < "EOF"
Listen 8140

SSLEngine on
SSLCipherSuite SSLv2:-LOW:-EXPORT:RC4+RSA
SSLCertificateFile /var/lib/puppet/ssl/certs/puppet.DOMAIN_PH.pem
SSLCertificateKeyFile /var/lib/puppet/ssl/private_keys/puppet.DOMAIN_PH.pem
SSLCertificateChainFile /var/lib/puppet/ssl/ca/ca_crt.pem
SSLCACertificateFile /var/lib/puppet/ssl/ca/ca_crt.pem
SSLCARevocationFile /var/lib/puppet/ssl/ca/ca_crl.pem
SSLVerifyClient optional
SSLVerifyDepth 1
SSLOptions +StdEnvVars

RackAutoDetect On
DocumentRoot /usr/share/puppet/rack/puppetmasterd/public/

Options None
AllowOverride None
Order allow,deny
allow from all

EOF

cat > /etc/httpd/conf.d/passenger.conf < < "EOF"
LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-3.0.8/ext/apache2/mod_passenger.so
PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-3.0.8
PassengerRuby /usr/bin/ruby
EOF

cat > /etc/httpd/conf.d/foreman.conf < < "EOF"
Listen 443
NameVirtualHost *:443
LoadModule ssl_module modules/mod_ssl.so
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl


ServerName foreman.DOMAIN_PH

RailsAutoDetect On
DocumentRoot /usr/share/foreman/public


Options FollowSymLinks
DirectoryIndex index.html
AllowOverride None
Order allow,deny
allow from all

SSLEngine On
SSLCertificateFile /var/lib/puppet/ssl/certs/puppet.DOMAIN_PH.pem
SSLCertificateKeyFile /var/lib/puppet/ssl/private_keys/puppet.DOMAIN_PH.pem

EOF

# Remove stock Apache configuration files
rm -f /etc/httpd/conf.d/ssl.conf
rm -f /etc/httpd/conf.d/welcome.conf

# IPTables configuration
cat > /etc/sysconfig/iptables < < "EOF"
# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 8140 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 61613 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
EOF

# Enable IPTables ruleset
service iptables restart

# Replace placeholder values for configuration
sed -i "s/MYSQL_PASSWORD_PH/${MYSQL_PASSWORD}/g" /etc/puppet/puppet.conf /usr/share/foreman/config/database.yml
sed -i "s/MCOLLECTIVE_PSK_PH/${MCOLLECTIVE_PSK}/g" /etc/mcollective/server.cfg /etc/mcollective/client.cfg
sed -i "s/RABBIT_USER_PH/${RABBIT_USER}/g" /etc/mcollective/server.cfg /etc/mcollective/client.cfg
sed -i "s/RABBIT_PASSWORD_PH/${RABBIT_PASSWORD}/g" /etc/mcollective/server.cfg /etc/mcollective/client.cfg
sed -i "s/FOREMAN_EMAIL_PH/${FOREMAN_EMAIL}/g" /usr/share/foreman/config/settings.yaml
sed -i "s/DOMAIN_PH/${DOMAIN}/g" /etc/httpd/conf.d/puppet.conf
sed -i "s/DOMAIN_PH/${DOMAIN}/g" /etc/httpd/conf.d/foreman.conf
sed -i "s/DOMAIN_PH/${DOMAIN}/g" /usr/lib/ruby/gems/1.8/gems/puppet-2.7.3/lib/puppet/reports/foreman.rb
sed -i "s/DOMAIN_PH/${DOMAIN}/g" /usr/share/foreman/config/email.yaml
sed -i "s/DOMAIN_PH/${DOMAIN}/g"/usr/share/foreman/config/settings.yaml

# Set Foreman symlinks
ln -sf /usr/share/foreman/config/database.yml /etc/foreman/database.yml
ln -sf /usr/share/foreman/config/settings.yaml /etc/foreman/settings.yaml
ln -sf /usr/share/foreman/config/email.yaml /etc/foreman/email.yaml

# Enable mCollective
chkconfig mcollective on
service mcollective start

# Generate Puppet master CA
puppet cert --generate puppet.${DOMAIN}

# Enable Apache
chkconfig httpd on
service httpd start

# Rake Foreman
cd /usr/share/foreman
RAILS_ENV=production rake db:migrate

# Execute Puppet agent
puppet agent -t

# Finished
exit

CentOS 6 with chrooted SFTP-only users + SSH hardening

Having a new server deployment to do, I wanted to take some time to get a working OpenSSH implementation under CentOS 6 to allow for SFTP-only users in a chrooted environment. This process is rather simple (these days) and here’s both my sshd_config file as well as some other notes to help you along your way as well.

You’ll note some of the restrictions are excessive for most people but for my implementation the crypto overhead is fine.

/etc/ssh/sshd_config

AddressFamily inet
#ListenAddress 0.0.0.0
Protocol 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
KeyRegenerationInterval 1h
ServerKeyBits 4096
SyslogFacility AUTHPRIV
LogLevel VERBOSE
LoginGraceTime 1m
PermitRootLogin no
StrictModes yes
MaxAuthTries 4
MaxSessions 5
PasswordAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
RSAAuthentication no
IgnoreRhosts yes
RhostsRSAAuthentication no
HostbasedAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
UsePAM yes
Ciphers aes256-ctr,aes256-cbc
MACs hmac-sha1
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no
PrintMotd no
PrintLastLog no
TCPKeepAlive yes
UsePrivilegeSeparation yes
ClientAliveInterval 300
ClientAliveCountMax 0
ShowPatchLevel no
UseDNS yes
PidFile /var/run/sshd.pid
MaxStartups 20
PermitTunnel no
Subsystem sftp internal-sftp
Match Group sftpusers
ChrootDirectory /home/%u
PasswordAuthentication no
ForceCommand internal-sftp

ServerKeyBits Note If you change your ServerKeyBits be sure to purge your existing keys (/rm /etc/ssh/ssh_host_*) and restart sshd to allow them to regenerate.

Configure proper permissions

chown root:root /home/[username]
chmod 711 /home/[username]

Setup the .ssh directory

mkdir /home/[username]/.ssh
chown root:sftpusers /home/[username]/.ssh
chmod 750 /home/[username]/.ssh

Setup the authorized_keys file

touch /home/[username]/.ssh/authorized_keys
chown root:sftpusers /home/[username]/.ssh/authorized_keys
chmod 440 /home/[username]/.ssh/authorized_keys

Create a directory accessible by the user

mkdir /home/[username]/storage
chown [username]:[username] /home/[username]/storage
chmod 760 /home/[username]/storage

Note, you’ll likely want to generate a public/private SSH keypair (ssh-keygen -t rsa) for the user and ensure permissions are as they should be above. This must be done unless you re-enable password authentication.

Ruby on Rails (RoR) nifty-generators nifty:layout + Rails 3.1 bug ; DELETE methods stop working

If you recently installed nifty-generators and are using nifty:layout you may notice that your DELETE methods stopped working. This is due to a change in Rails 3.1 code apparently. You’ll likely note that in app/views/layouts/application.html.erb the line below after your generation of nifty:layout

<%= javascript_include_tag :defaults %>

This needs to be changed to:

<%= javascript_include_tag :application %>

Once that change is in place, you should see successful DELETE methods again. I could be wrong, but in my case, this did the job!