Archive for the ‘System Administration’ Category

Puppet Training

Thursday, May 3rd, 2012

For the last three days I have been in Puppet training (April 28th – May 1st), which rocked, completely. The trainer Hunter Haugen was awesome, not only is he extremely knowledgeable (just don’t throw down the W word) but thoughtful, honest, and took the time to listen to our pain points and give some good advice (you can read his blog here for fantastic tips on vim). I am not going to bore you with the details of the training, other than to say if you’re considering using Puppet in your environment, shell out for the training, it is definitely worth it.

Aliases – Linux

Thursday, March 29th, 2012

So sitting here on the train I got to thinking about some of the standard aliases that I use, or add to a system upon first installation and use.

This does not stave off the infamous “rm -rf – pebkac error” but to keep myself from general stupidity I tend alias rm, cp and mv first and foremost (that way for the most part if I make a bad decision at least I was prompted to):

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

Also, back in the day, when I started messing with Linux, I loved the terminal colors, now not so much. So I alias away the colors, make them appear in columns and with characters to denote the type of file (‘/’ for directory, ‘*’ for executable, etc.).

alias ls='ls -CF'

Because “maths are teh hard” I tend to alias df so that it appears in human readable format. Sometimes I’ll alias free, but not always – sometimes I like to live dangerously. Conversely since I tend to work mainly on OSX and free isn’t a built in (yes I know it can be added with ports), I tend to just forget about it.

At the end I will then setup personal aliases that are meaningful to me, but may not have any meaning to someone not living inside my head. For instance if there are a few hosts that I know that I will be sshing to quite frequently, like say jump hosts, I will alias them down to singular commands, usually the hosts name to make getting there quicker and easier.

Printing column headers from commands

Thursday, March 22nd, 2012

As with most things in *nix world there are more than one way of doing things. Here is a nifty trick I picked up from a former coworker. Sometimes when I needed to do disk reporting it was handy to have the column header (remember this is my use case) for those who don’t necessarily know what the columns mean when doing a df or similar and grepping for a particular volume / disk (read upper management). I am fairly certain that there are even more ways of accomplishing this, but here are two that I’ve used. Also be aware, that you need to know what you’re looking for, or the pattern you need to match.

Note: this was done from my MBP, so in Linux ymmv*.

sed:

Before:
df -h | grep disk
 
/dev/disk0s2    465Gi  123Gi  342Gi    27%    /
/dev/disk1s2    110Mi   94Mi   16Mi    86%    /Volumes/VirtualBox
/dev/disk2s0s2   25Mi   22Mi  2.7Mi    90%    /Volumes/VZAccess Manager
 
After:
df -h | sed -n '/^Filesystem/p;/disk/p'
 
Filesystem       Size   Used  Avail Capacity  Mounted on
/dev/disk0s2    465Gi  123Gi  342Gi    27%    /
/dev/disk1s2    110Mi   94Mi   16Mi    86%    /Volumes/VirtualBox
/dev/disk2s0s2   25Mi   22Mi  2.7Mi    90%    /Volumes/VZAccess Manager

awk:

Before:
df -h | grep disk
 
/dev/disk0s2    465Gi  123Gi  342Gi    27%    /
/dev/disk1s2    110Mi   94Mi   16Mi    86%    /Volumes/VirtualBox
/dev/disk2s0s2   25Mi   22Mi  2.7Mi    90%    /Volumes/VZAccess Manager
 
After:
df -h | awk '/^Filesystem {print;}; /disk/ {print;}; {next;};'
 
Filesystem       Size   Used  Avail Capacity  Mounted on
/dev/disk0s2    465Gi  123Gi  342Gi    27%    /
/dev/disk1s2    110Mi   94Mi   16Mi    86%    /Volumes/VirtualBox
/dev/disk2s0s2   25Mi   22Mi  2.7Mi    90%    /Volumes/VZAccess Manager

sed breakdown:
With the sed line we are matching any line that begins with Filesystem “/^Filesystem” and printing it “/p”, then matching any line that has disk “/disk” and printing it “/p”. The “-n” option to sed tells it to only print what we are looking for – from the sed manpage on OSX – “-n By default, each line of input is echoed to the standard output after all of the commands have been applied to it. The -n option suppresses this behavior.” If you were to remove the “-n” switch it would look something like this:

 
df -h | sed '/^Filesystem/p;/disk/p'
 
Filesystem       Size   Used  Avail Capacity  Mounted on
Filesystem       Size   Used  Avail Capacity  Mounted on
/dev/disk0s2    465Gi  123Gi  342Gi    27%    /
/dev/disk0s2    465Gi  123Gi  342Gi    27%    /
devfs           199Ki  199Ki    0Bi   100%    /dev
map -hosts        0Bi    0Bi    0Bi   100%    /net
map auto_home     0Bi    0Bi    0Bi   100%    /home
/dev/disk1s2    110Mi   94Mi   16Mi    86%    /Volumes/VirtualBox
/dev/disk1s2    110Mi   94Mi   16Mi    86%    /Volumes/VirtualBox
/dev/disk2s0s2   25Mi   22Mi  2.7Mi    90%    /Volumes/VZAccess Manager
/dev/disk2s0s2   25Mi   22Mi  2.7Mi    90%    /Volumes/VZAccess Manager

printing the matches multiple times due to matching on the “/^Filesystem” and matching on the “/disk”.

awk breakdown:
With the awk one, we are matching the line that begins with “/^Filesystem” and printing it, then matching the line with “/disk/” and printing, then followed by a statement “{next;}” to keep processing and skip unless the pattern matches.

Also while writing the post, thought of another one that it might be useful, though it really depends on what you’re running on your system. Thought of a little netstat example though I will go with the caveat here that netstat can easily make this a moot point, take this with a grain of salt.

Before:
 
netstat -anl | grep -i listen
 
tcp46      0      0  *.9292                 *.*                    LISTEN     
tcp46      0      0  *.9302                 *.*                    LISTEN     
tcp46      0      0  *.9301                 *.*                    LISTEN     
tcp46      0      0  *.9200                 *.*                    LISTEN     
tcp46      0      0  *.9300                 *.*                    LISTEN     
tcp46      0      0  *.62056                *.*                    LISTEN     
tcp4       0      0  *.62056                *.*                    LISTEN     
tcp4       0      0  127.0.0.1.51093        *.*                    LISTEN     
tcp4       0      0  127.0.0.1.26164        *.*                    LISTEN     
tcp4       0      0  *.17500                *.*                    LISTEN     
tcp4       0      0  127.0.0.1.631          *.*                    LISTEN     
tcp6       0      0  ::1.631                                       *.*                                           LISTEN     
ffffff80147a27d0 stream      0      0 ffffff8017e13f00                0                0                0 /tmp/launch-H5oHbg/Listeners
 
After:
 
netstat -anl | sed -n '/^Proto/p;/LISTEN/p'
 
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)    
tcp46      0      0  *.9292                 *.*                    LISTEN     
tcp46      0      0  *.9302                 *.*                    LISTEN     
tcp46      0      0  *.9301                 *.*                    LISTEN     
tcp46      0      0  *.9200                 *.*                    LISTEN     
tcp46      0      0  *.9300                 *.*                    LISTEN     
tcp46      0      0  *.62056                *.*                    LISTEN     
tcp4       0      0  *.62056                *.*                    LISTEN     
tcp4       0      0  127.0.0.1.51093        *.*                    LISTEN     
tcp4       0      0  127.0.0.1.26164        *.*                    LISTEN     
tcp4       0      0  *.17500                *.*                    LISTEN     
tcp4       0      0  127.0.0.1.631          *.*                    LISTEN     
tcp6       0      0  ::1.631                                       *.*                                           LISTEN

Again, this makes the presumption that you know what you’re looking for, and if you tried to do that with ESTABLISHED in netstat it may be moot unless you narrowed down to less than a screen. Also I’ll leave the awk version and looking for specific ports / port ranges as an exercise for the reader. Also astute readers will notice that we lost the last output from the first netstat command due to not grepping indiscriminately.

I hope this information comes in handy, share and enjoy!

*For those that may not know, ymmv = Your Mileage May Vary.

Making multiple directories (with parents)

Wednesday, March 21st, 2012

I know that this is general knowledge, but putting it down here so I remember where I put it. So to create multiple directories with the structure ~/projects/code/ I would use:

localhost: mkdir -p ~/projects/code/{c,perl,python,ruby,shell}

Got the reference for it here. *I always forget the which is correct “{” vs “[“.

Normalize MAC address with Python

Tuesday, August 16th, 2011

In an earlier post, Scott did this in a simple shell script. I was curious if I could do it in Python since I am learning Python.

Below is my first attempt. Watch for upcoming updated version.

import sys
 
if not sys.argv[1:]:
   mac = raw_input ("Enter MAC address\n")
   print "Mac is %s\n"; % (mac)
   #sys.exit(0)
else:
   mac = sys.argv[1]
   print "Mac is %s\n" % (mac)
 
##I'm checking to see if someone is giving me in the right format
maclen=len(mac)
if maclen == 12:
    ## converting to list, since strings are immuatable
    maclist=list(mac)
    ## defining positions on list
    positions=2,5,8,11,14
 
    for i in positions:
        maclist.insert(i,':')
    ##joining list at the ','
    newmac=",".join(maclist)
    ## stripping ',' out and converting to lowercase.  
    final12mac=newmac.replace(',','').lower()
 
    print final12mac
 
##assuming that 17 characters will either have - or : in MAC address    
elif maclen == 17:
    ##replacing - with : and converting to lowercase
    mac17final=mac.replace('-',':').lower()
    print mac17final
else:
    ## I'm telling them that if it isn't 12 or 17 characters, it has X chars
    print "There are only %s characters" % (maclen)

Interview questions … Get IP address from Apache Logs

Monday, August 15th, 2011

TL;DR Some Apache log processing one liners to get IP addresses from the access and error logs that I have found handy.

There may be some log processing questions asked during the course of an interview, so I am going to concentrate on a couple that will get the IP addresses from the apache log files. If you do have log processing quesitons I sincerely hope you get to play at a command line, as off the top of the head can be difficult, unless you’re good at visualizing commands.

Q: How would you get the IP address from the access logs?
A: This one is fairly straight forward, I would:
cat access.log | awk '{print $1}' | uniq

This will output (if you choose not to use uniq you will see multiple of the same ip, I’ll leave to the discretion of the reader):

127.0.0.1
127.0.0.2
127.0.0.3

The breakdown is as follows:

    1. read through the contents of the file (cat access.log)
    2. pipe output to awk to print the first field ($1)
    3. pipe output to only show unique data (uniq)

Another question might be to parse out the IP address from the error.log, while this is a bit more difficult, it is fairly straight forward using readily available system tools.

Q: Can you please extract the IP address from the error.log?
A: Here is my solution (Thanks Malcolm!)
cat error.log | awk '{ if($7 == "[client") {print $8} }' | sed -e 's/]$//g' | uniq

The breakdown is as follows, this one is a bit more complex so I will walk through each step of it:

First part: cat error.log - read through the log file.
 
[Sun Aug 14 13:27:14 2011] [error] [client 96.126.120.254] Invalid method in request \x80e\x01\x03\x01
[Sun Aug 14 13:27:14 2011] [error] [client 96.126.120.254] Invalid method in request \x80e\x01\x03\x01
[Sun Aug 14 13:27:14 2011] [error] [client 96.126.120.254] Invalid method in request \x80e\x01\x03\x01
[Sun Aug 14 13:27:14 2011] [error] [client 96.126.120.254] Invalid method in request \x80e\x01\x03\x01
[Mon Aug 15 15:16:21 2011] [error] [client 194.72.238.62] Invalid method in request \x16\x03\x01
[Mon Aug 15 15:50:27 2011] [notice] caught SIGWINCH, shutting down gracefully
[Mon Aug 15 15:50:37 2011] [notice] mod_python: Creating 8 session mutexes based on 75 max processes and 0 max threads.
[Mon Aug 15 15:50:37 2011] [notice] mod_python: using mutex_directory /tmp 
PHP Warning:  Module 'gd' already loaded in Unknown on line 0
PHP Warning:  Module 'mysql' already loaded in Unknown on line 0
PHP Warning:  Module 'mysqli' already loaded in Unknown on line 0
[Mon Aug 15 15:50:38 2011] [warn] mod_wsgi: Compiled for Python/2.5.1.
[Mon Aug 15 15:50:38 2011] [warn] mod_wsgi: Runtime using Python/2.5.2.
 
2. Second part: cat error.log | awk '{ if ($7 == "[client") {print $8} }' - if the 7th field matches client (this seems to be pretty standard though ymmv) print out eighth field (which should be the IP address, also notice the trailing "]" character).
 
96.126.120.254]
96.126.120.254]
96.126.120.254]
96.126.120.254]
194.72.238.62]
 
3. Third Part: cat error.log | awk '{ if ($7 == "[client") {print $8} }' | sed -e 's/]$//g' - use sed to remove the trailing "]" character.
 
96.126.120.254
96.126.120.254
96.126.120.254
96.126.120.254
194.72.238.62
 
4. Fourth Part: cat error.log | awk '{ if ($7 == "[client") {print $8} }' | sed -e 's/]$//g' | uniq - lets output unique ips and not all of them (multiple matches).
 
96.126.120.254
194.72.238.62

Addendum:
If you would like you can then add another awk on the end (or pipe to any other command you feel like) for instance. The following pipes the output to the host command:

[Edit: for host might want to specify the -W <time> flag just in case, it could try forever on some unless specified]
cat error.log | awk '{ if ($7 == "[client") {print $8} }' | sed -e 's/]$//g' | uniq | awk '{ print | "host -W 3 " $1 }'

I hope this helps, share and enjoy! Thanks for reading!

-Scott.

Disclaimer: I make no claims to the viability of the code/script/commands and make no guarantees that it will work on your system, use at your own risk.

Interview questions … part one

Friday, August 12th, 2011

TL;DR
I like interview questions, this is completely subjective, name 5 two letter linux commands and what they do.

Everyone once in a while I’ll go looking for good technical interview questions to challenge myself, to see how far I’ve grown, and it is for me a good way to ground myself and figure out how far I need to grow. I tend to be of the mindset that it’s good to keep the questions somewhat open ended, I think a lot of what shows a persons aptitude is their ability to reason out answers to difficult questions, not whether or not they remember what the theoretical throughput of 8Gig Fibre Channel (about 1600MBps if I remember correctly, but I digress). So what I am going to do is go out find / make up what I think are good interview questions (or ones that just strike my fancy, and why yes this is completely subjective thanks for pointing that out) and answer them from my own experience. I can’t say that my solutions will be the *right* way to do it, but hopefully give the correct results, but I make no promises.

So for the first round I am going to echo a question that I got as an interview question and really liked, and maybe take it a bit further:

Q: Can you name five two letter Linux commands?
A: Yes I can (and did).

Now this is a good question for those starting out to gauge how far they have gone into the system admin side of things, stuff that you can’t necessarily (or as quickly) do from just the gui. So the thing that I thought of to make this more interesting is:

Q: Can you name five two letter linux commands, and what they do? Give an example of each two letter command you give.

I personally think this is interesting because not only does it cause some of the more senior people to have to think (muscle memory is a helluva drug), but it also will show a good basic understanding of what is going with the commands that are being used. In all honesty I don’t remember all of them, and I don’t remember ever single command line switch for them, that is why man pages exist.

Share and enjoy!

-Scott.

Normalize MAC address for DHCP reservations

Sunday, August 7th, 2011

So part of what I am doing at my current job is helping one of the Unix admins with DNS and DHCP. For the DHCP portion to setup the reservations we need the MAC addy in a certain format, of which the people requesting never seem to get consistently right, so I wrote a small shell script (it’s still rough) that will normalize the MAC address for what we need (colon separated, alpha characters lower case).

[Edit: the below only works on FreeBSD and Linux, for Solaris swap the awk '{print tolower($0)}' with tr '[:upper:]‘ ‘[:lower:]‘ <- or you could just do that for all of them as well).

Original:

#!/usr/bin/env sh
 
mac="$1"
 
#check to see if input is empty
if [ ! -n "$mac" ]
    then
        echo "Please enter a MAC address."
        exit
else 
    # echo the input, strip out dot(.), strip out colon(:), strip out dash(-)
    # add colon(:) every two chars, remove last colon(:)
    # awk to lowercase characters [EDIT: updated the sed for . seps and escape]
    echo $mac | sed -e 's/\.//g' -e 's/\://g' -e 's/\-//g' -e 's/../&:/g' -e 's/:$//g' \
| awk '{print tolower($0)}'
fi

I have updated the code to check for length, though it does not strip the newline character so it’s mac.length + 1.

#!/usr/bin/env sh
 
mac="$1"
len="${#mac}"
 
#check to see if input is empty and we're not getting a valid mac, less than is ok, just make sure it's not greater than 17 (mac.length +1)
 
if [[ ! -n "$mac" || "$len" -gt "17" ]]
    then
        echo "Please enter a MAC address."
        exit
else
    # echo the input, strip out dot(.), strip out colon(:), strip out dash(-)
    # add colon(:) every two chars, remove last colon(:)
    # awk to lowercase characters [EDIT: updated the sed for . seps and escape]
    echo $mac | sed -e 's/\.//g' -e 's/\://g' -e 's/\-//g' -e 's/../&:/g' -e 's/:$//g' \
| awk '{print tolower($0)}'
fi

Some of the standard formats we see are:
1A:BC:35:57:33:08
1A-BC-35-57:33:08
1abc35573308
1a.bc.35.57.33.08
1A.BC.35.57.33.08

The script will take all of these and make them look like: 1a:bc:35:57:33:08.

Here is the breakdown for those that are curious:
1. if [ ! -n "$mac" ] checks to make sure first input variable is not empty.
2. echo $mac outputs the first argument (does not do any input validation)
3. The sed is in five parts:

    [Edited to escape the sequences, just in case they should have special meaning]
        a. 's/\.//g' strips out the period should it be in that format
        b. 's/\://g' strips out the colons should they be there (to avoid extra colons)
        c. 's/\-//g' strips out the dashes should they be in that format
        d. 's/../&:/g' every two characters append a colon
        e. 's/:$//g' remove the last trailing colon
    

4. awk ‘{print tolower($0)}’ make all of the upper case alpha chars lower case to match our format needs.
4a. tr ‘[:upper:]‘ ‘[:lower:]‘ for Solaris (yes I know tr ‘[A-Z]‘ ‘[a-z]‘ will work as well, but this is easier to read IMHO)

This will work on a file full of MAC addresses, just throw it in a for loop (this is if you name the file macnorm.sh):

for i in `cat filename`; do ./macnorm.sh $i; done

Again it is important to realize that there is no checking of the input, so if you did it over /etc/passwd it would add a colon (:) every two characters. Hope this helps.

-Scott.

Set Default Printer with Powershell

Wednesday, July 13th, 2011

Quick, Dirty, Simple. Set the default printer with Powershell. Needed this as a part of a logon script to set default printer for certain users on a terminal server.

## Get the Printer with WMI
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name = 'PDFCreator'"
 
## Set printer as default printer
$printer.SetDefaultPrinter()

Quick python to parse a file and get IP’s

Thursday, May 12th, 2011

Being busy with sysadminy type stuff, I haven’t been able to practice my Python as much as I would like, though I am eating my own words and practicing. I was reading somewhere about adding IP’s to a iptables block list, and thought about creating a quick and dirty parser to go through a log file (or any other file for that matter) and grep out any ip addy’s. The caveat here is that it was quick and dirty and only works on ipv4, and only outputs to STDOUT, but that can all be fix0red real quickstyle (which I think I’ll do as I carry on and make it better). I am still learning how to do things the pythonic way, as opposed to just getting things working, so please be gentle, the style comes with time. *Should have mentioned this before, but I am on CentOS 5.6, and the python version is 2.4.3 (system) so that is why there is no with statement.

#!/usr/bin/env python
 
#import the necessary modules
import re #for regular expressions - to match ip's
import sys #for parsing command line opts
 
# I need to probably make this more pythonic but am working on that...
# if file is specified on command line, parse, else ask for file
if sys.argv[1:]:
    print "File: %s" % (sys.argv[1])
    logfile = sys.argv[1]
else:
    logfile = raw_input("Please enter a file to parse, e.g /var/log/secure: ")
 
try:
    # open the file
    file = open(logfile, "r")
    # create an empty list
    ips = []
    # read through the file
    for text in file.readlines():
       #strip off the \n
        text = text.rstrip()
       #this is probably not the best way, but it works for now
        regex = re.findall(r'(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})$', text)
        # if the regex is not empty and is not already in ips list append
        if regex is not None and regex not in ips:
            ips.append(regex)
 
 
    #loop through the list
    for ip in ips:
        #I know there is argument as to whether the string join method is pythonic
        addy = "".join(ip)
        if addy is not '':
            print "IP: %s" % (addy)
    #cleanup and close file
    file.close()
#catch any standard error (we can add more later)
except IOError, (errno, strerror):
    print "I/O Error(%s) : %s" % (errno, strerror)

Output looks like:

File: /var/log/secure
IP: 68.169.40.177
IP: 14.63.253.14
IP: 113.105.159.162
IP: 125.208.5.78
IP: 89.187.142.141
IP: 212.116.152.36

Actually, I’ll fix the output so it writes it to a file for keeping, mind you this is just a quick addition so it won’t double check to see if the ip address is already there, so it will write dupes…also learned that one should not update vim tab options in mid-coding, as python doesn’t like mixing tabs and spaces…if you need to check “python -m tabnanny file.py” will tell you where you went wrong :), then in vi you can type :retab and it should fix it…

original:
#loop through the list
    for ip in ips:
        #I know there is argument as to whether the string join method is pythonic
        addy = "".join(ip)
        if addy is not '':
            print "IP: %s" % (addy)
    #cleanup and close file
    file.close()
 
new (for output to file):
    for ip in ips:
        outfile = open("/tmp/blocked_ips", "a")
        addy = "".join(ip)
        if addy is not '':
           print "IP: %s" % (addy)
           outfile.write(addy)
           outfile.write("\n")
    file.close()
    outfile.close()

It seems to work, though it’s probably not the best way to do it, and is most definitely not pythonic, though again I’ll plead that I am still learning the style portion. So now that we have the ip’s to block here is a small shell script to add them to your iptables configs, if you so choose, you might need to update for your configuration.

BLOCKDB="/tmp/blocked_ips"
IPS=$(grep -Ev "^#" $BLOCKDB) #so you can use # to add comments to and it will grep them out
for i in $IPS
do
    iptables -A INPUT -s $i -j DROP
    iptables -A OUTPUT -d $i -j DROP
done

As noted many times, these are both quick and dirty, neither do any real error checking or sanity checking, but it took me 20 mins, so use at your own risk, I make no claims as to the usability or completeness of said code. As a sidenote if you want to loop through all the log files or a bunch of files, you can do the following “for i in `ls -1 /var/log/`; do ./get_ips.py /var/log/$i; done” – Two things, 1. I named the file get_ips.py, 2. This is extremely hackish.

As mentioned by someone, I fixed the outer try statement:

#!/usr/bin/env python
#fixed the import, just red PEP 8
import sys
import re
 
try:
    if sys.argv[1:]:
        print "File: %s" % (sys.argv[1])
        logfile = sys.argv[1]
    else:
        logfile = raw_input("Please enter a log file to parse, e.g /var/log/secure: ")
    try:
        file = open(logfile, "r")
        ips = []
        for text in file.readlines():
           text = text.rstrip()
           regex = re.findall(r'(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})$',text)
           if regex is not None and regex not in ips:
               ips.append(regex)
 
        for ip in ips:
           outfile = open("/tmp/blocked_ips_test", "a")
           addy = "".join(ip)
           if addy is not '':
              print "IP: %s" % (addy)
              outfile.write(addy)
              outfile.write("\n")
    finally:
        file.close()
        outfile.close()
except IOError, (errno, strerror):
        print "I/O Error(%s) : %s" % (errno, strerror)

Ok, fixed for the last time (hopefully) imabonehead refactored, moved the finally clause inside, and moved the except outside.

Meta:
Contact:
Scott:
Systems Engineer, Geek, Horror fan.

Ways to reach Scott:
Twitter
FriendFeed
Facebook
LinkedIn
Tumblr
Zerply
About.me

Barry:
Systems Administrator / Geek.

Ways to reach Barry:
Twitter
Friendfeed
Associations:
Twitter:

View more tweets | Powered by HL Twitter