Sooner or later, you will find yourself adding sensitive data into Ansible playbooks, host or group vars files.Such information might include MySQL DB credentials, AWS secret keys, API credentials etc. Including such sensitive information in plain text might not be acceptable for security compliance reasons or even lead to your systems being owned when your company hires a third party to do pen testing and worst yet by outside hackers. In addition to this, sharing such playbooks to public repositories such as github won’t be easy as you have to manually search and redact all the sensitive information from all your playbooks, and as we know manual procedure is not always error prone. You might ‘forget’ to remove some of the paswords.

One solution for this is a password vault to hold all your sensitive data, and Ansible provides a utitility called ansible-vault to create this encrypted file and the data can be extracted when running your playbooks with a single option. This is equivalent to Chef’s data bag.

In this blog post, I will share with you how to use a secret key file to protect sensitive data in Ansible with ansible-vault utility. The simplest use case is to protect the encrypted file with a password or passphrase, but that is not convinient as you have to type the password everytime you run a playbook and is not as strong as a key file with hundreds or thousands of random characters. Thus the steps below describe only the procedure for setting up a secret key file rather than a password protected encrypted file. Let us get started.

The first step is to generate a key file containing a random list of characters –

#openssl rand -base64 512 |xargs > /opt/ansible/vaultkey

Create or initialize the vault with the key file generated above –

#ansible-vault create --vault-password-file=/opt/ansible/vaultkey /opt/ansible/lamp/group_vars/dbservers.yml

Populate your vault, refer to Ansible documentation on the format of the vault file –

#ansible-vault edit --vault-password-file=/opt/ansible/vaultkey /opt/ansible/lamp/group_vars/dbservers.yml

You can view the contents by replacing ‘edit’ with ‘view’ –

#ansible-vault view --vault-password-file=/opt/ansible/vaultkey /opt/ansible/lamp/group_vars/dbservers.yml

That is it, you have a secret key file to protect and encrypt a YAML file containing all your sensitive variables to be used in your ansible playbooks.

There comes a time though when you have to change the secret key file, say an admin leaves the company after winning the Mega jackbot lottery 🙂 We have to generate a new key file and rekey the encrypted file as soon as possible –

Generate a new key file –

#openssl rand -base64 512 |xargs > /opt/ansible/vaultkey.new

Rekey to new key file –

#ansible-vault rekey --new-vault-password-file=/opt/ansible/vaultkey.new --vault-password-file=/opt/ansible/vaultkey
Rekey successful

Verify –

#ansible-vault view --vault-password-file=/opt/ansible/vaultkey.new /opt/ansible/lamp/group_vars/dbservers.yml

Last but not least, make sure the secret key file is well protected and is readable only by the owner.

#chmod 600 /opt/ansible/vaultkey.new

Finally, you can use the vault with ansible-playbook. In this case, I am running it against site.yml which is a master playbook to setup a LAMP cluster in AWS (pulling the AWS instances using ec2.py dynamic inventory script) –

#ansible-playbook -i /usr/local/bin/ec2.py site.yml --vault-password-file /opt/ansible/vaultkey.new

Web sites store information on local machines of site visitors using cookies. On subsequent visits, the browser sends the data from the cookies on the visitors machine to the web server, which might then use that information as a historical record of the users activity on the site – on the minimum the time the cookie was created, when it is set to expire and last access time or last time user visited site. Cookies are also used by sites to ‘remember’ user acitivity , say the shopping cart items or login/session information to address the shortcomings of the stateless HTTP protocol.

Most users think that only the sites they had directly visited store cookies on their computers, in reality the number is way higher than that. A single site you visit, usually has lots of links in it, especially ads, that store cookies in your computer. In this post, i will demonstrate how to list the list of all sites that left cookies in your computer, as well as extract additional information from the cookies. When i ran the script and did a count of the 10 top sites which left largest number of entries in the cookies sqlite DB, none of them except for one or two were sites I directly visited!

This Python script was written to extract cookies information on a Linux box running Firefox. The cookies information is stored as a sqlite file and thus you will need the sqlite3 python module to read the sqlite file.

The script takes the path to the cookies file as well as the path to the output file, it will write the output to this file. It will also dump the output to the screen.

root@dnetbook:/home/daniel/python# python cookie_viewer.py 
cookie_viewer.py cookie-fullpath output-file

root@dnetbook:/home/daniel/python# python /home/daniel/python/cookie_viewer.py $(find /home/daniel/ -type f -name 'cookies.sqlite' | head -1) /tmp/test.txt
doubleclick.net,Thu Feb 11 17:56:01 2016,Thu Apr 23 20:46:58 2015,Tue Feb 11 17:56:01 2014
twitter.com,Thu Feb 11 17:56:05 2016,Tue Apr 21 22:27:46 2015,Tue Feb 11 17:56:05 2014
imrworldwide.com,Thu Feb 11 17:56:12 2016,Tue Apr 21 22:19:35 2015,Tue Feb 11 17:56:12 2014
quantserve.com,Thu Aug 13 19:32:02 2015,Thu Apr 23 20:46:57 2015,Tue Feb 11 18:32:0

The output will be the domain name of the site, cookie expiry date, access time and creation time.

Code follows –

#!/usr/bin/env python

''' Given a location to firefox cookie sqlite file
    Write its date param - expiry, last accessed,
    Creation time to a file in plain text.
    id
    baseDomain
    appId
    inBrowserElement
    name
    value
    host
    path
    expiry
    lastAccessed
    creationTime
    isSecure
    isHttpOnly
    python /home/daniel/python/cookie_viewer.py $(find /home/daniel/ -type f -name 'cookies.sqlite' | head -1) /tmp/test.txt 
'''

import sys
import os
from datetime import datetime
import sqlite3

def Usage():
    print "{0} cookie-fullpath output-file".format(sys.argv[0])
    sys.exit(1)

if len(sys.argv)<3:
    Usage()

sqldb=sys.argv[1]
destfile=sys.argv[2]
# Some dates in the cookies file might not be valid, or too big
MAXDATE=2049840000

# cookies file must be there, most often file name is cookies.sqlite
if not os.path.isfile(sqldb):
    Usage()

# a hack - to convert the epoch times to human readable format
def convert(epoch):
    mydate=epoch[:10]
    if int(mydate)>MAXDATE:
        mydate=str(MAXDATE)
    if len(epoch)>10:
        mytime=epoch[11:]
    else:
        mytime='0'
    fulldate=float(mydate+'.'+mytime)
    x=datetime.fromtimestamp(fulldate)
    return x.ctime()

# Bind to the sqlite db and execute sql statements
conn=sqlite3.connect(sqldb)
cur=conn.cursor()
try:
    data=cur.execute('select * from moz_cookies')
except sqlite3.Error, e:
    print 'Error {0}:'.format(e.args[0])
    sys.exit(1)
mydata=data.fetchall()

# Dump results to a file
with open(destfile, 'w') as fp:
    for item in mydata:
        urlname=item[1]
        urlname=item[1]
        expiry=convert(str(item[8]))
        accessed=convert(str(item[9]))
        created=convert(str(item[10]))
        fp.writelines(urlname + ',' + expiry + ',' + accessed + ',' + created)
        fp.writelines('\n')

# Dump to stdout as well
with open(destfile) as fp:
    for line in fp:
        print line

TOP 10 sites with highest number of enties in the cookies file –

root@dnetbook:/home/daniel/python# awk -F, '{print $1}' /tmp/test.txt  | sort | uniq -c | sort -nr | head -10
     73 taboola.com
     59 techrepublic.com
     43 insightexpressai.com
     34 pubmatic.com
     33 2o7.net
     31 rubiconproject.com
     28 demdex.net
     27 chango.com
     26 yahoo.com
     26 optimizely.com

In Python, you can read from and write to files without import any modules. Python has built-in function “open” which can be used to view and manipulate file objects. Let us see two ways of opening a file for reading/writing, for instance –

   fp_in = open('/etc/hosts', 'r')  # default is 'r', we can omit it.
   fp_out = open('/tmp/hosts', 'w')
   for line in fp_in:
       fp_out.write(line)

   fp_in.close()
   fp_out.close()


   with open('/etc/hosts') as fp_in:
       with open('/tmp/hosts') as fp_out:
       for line in fp_in:
           fp_out.write(line)
   # No need to close file, it is automatically closed at end of block.

One of the most common reasons given why you have to close the file object in the first case is to free up resources. But there is a second reason why you should always use ‘with’ keyword. After writing to a file object, and before closing it, the whole content from the source file might not appear in the destination file. This is because write uses buffering, and the changes will not be reflected until you run flush() or close() on the file object. Here is the help page for ‘write’ –

write(...)
    write(str) -> None.  Write string str to file.
    
    Note that due to buffering, flush() or close() may be needed before
    the file on disk reflects the data written.

Let me demonstrate this by copying the /var/log/messages file to /tmp/message, the bigger the file, the more likely you will witness the effect of buffering. First i will take a copy of /var/log/messages to /var/log/messages.orig, and work with messages.orig as the former will most likely change in size as work along.

[root@kauai ~]# wc -l /var/log/messages.orig 
10544 /var/log/messages.orig

[root@kauai ~]# wc -l /tmp/messages 
10542 /tmp/messages
[root@kauai ~]# tail -1 /tmp/messages 
Nov 16 02:36:02 kauai syslog-ng[1605]: Log statistics; processed='src.internal(s_sys[root@kauai ~]# 

[root@kauai ~]# tail -1 /var/log/messages
Nov 16 02:46:02 kauai syslog-ng[1605]: Log statistics; processed='src.internal(s_sys#2)=1787', stamp='src.internal(s_sys#2)=1416123362', processed='source(s_name_servers)=0', processed='destination(d_mesg)=7693', processed='destination(d_auth)=210', processed='source(s_sys)=12643', processed='global(payload_reallocs)=3568', processed='destination(d_mail)=12', processed='destination(d_kern)=5176', processed='destination(d_mlal)=0', processed='destination(d_ns_filtered)=0', processed='global(msg_clones)=0', processed='destination(d_spol)=0', processed='destination(hosts)=12643', processed='destination(d_boot)=0', processed='global(sdata_updates)=0', processed='center(received)=0', processed='destination(d_cron)=3653', processed='center(queued)=0'

Notice how the destination file /tmp/messages got truncated, it doesn’t even have a newline character at the end.

fp_out.close()
[root@kauai ~]# wc -l /tmp/messages 
10544 /tmp/messages

[root@kauai ~]# tail -1 /var/log/messages
Nov 16 02:56:02 kauai syslog-ng[1605]: Log statistics; processed='src.internal(s_sys#2)=1788', stamp='src.internal(s_sys#2)=1416123962', processed='source(s_name_servers)=0', processed='destination(d_mesg)=7694', processed='destination(d_auth)=211', processed='source(s_sys)=12646', processed='global(payload_reallocs)=3570', processed='destination(d_mail)=12', processed='destination(d_kern)=5176', processed='destination(d_mlal)=0', processed='destination(d_ns_filtered)=0', processed='global(msg_clones)=0', processed='destination(d_spol)=0', processed='destination(hosts)=12646', processed='destination(d_boot)=0', processed='global(sdata_updates)=0', processed='center(received)=0', processed='destination(d_cron)=3654', processed='center(queued)=0'

This problem would not have happened if we had used the ‘with’ keyword, as it automatically does the flush() and close() for us at the end of the block statement –

    with open('/var/log/messages.orig') as fp_in:
    with open('/tmp/messages','w') as fp_out:
        for line in fp_in:
            fp_out.write(line)

[root@kauai ~]# wc -l /var/log/messages.orig 
10544 /var/log/messages.orig
[root@kauai ~]# wc -l /tmp/messages 
10544 /tmp/messages

There you go, both source and destination files synced immediately.

Support Net Neutrality

Support Net Neutrality –

http://www.savetheinternet.com/sti-home

Send your comments to the FCC –

http://www.fcc.gov/comments

Shell script: US federal holidays

This script is written based on the list of U.S. federal holidays I found in Wikipedia – Wikipedia – U.S. Federal holidays. Some of the dates, such as New Year, are straight forward, as the date and month are fixed. While others require some effort, take for instance Thanksgiving, which is on the fourth Thursday of November OR Memorial day – last Monday of May.

The script is written in bash, and tested in 32 and 64 bit Ubuntu box. It will exit with an error message if you try to get the holidays for the year 2038 or above. This is a know issue with UNIX dates on 32 bit Operating Systems – UNIX: Year 2038 problem

Sample output

daniel@dnetbook:~$ /usr/local/bin/federalholidays.sh
Usage: federalholidays.sh Year
Eg. federalholidays.sh 2014


daniel@linubuvma:~$ ./federalholidays.sh 1500
New Year's Day:               Monday, January 01, 1500
Martin Luther King, Jr. Day:  Monday, January 15, 1500
Washington's Birthday:        Monday, February 19, 1500
Memorial Day:                 Monday, May 28, 1500
Independence Day:             Wednesday, July 04, 1500
Labor Day:                    Monday, September 03, 1500
Columbus Day:                 Monday, October 08, 1500
Veteran's Day:                Sunday, November 11, 1500
Thanksgiving:                 Thursday, November 22, 1500
Christmas Day:                Tuesday, December 25, 1500


daniel@linubuvma:~$ ./federalholidays.sh 2014
New Year's Day:               Wednesday, January 01, 2014
Martin Luther King, Jr. Day:  Monday, January 20, 2014
Washington's Birthday:        Monday, February 17, 2014
Memorial Day:                 Monday, May 26, 2014
Independence Day:             Friday, July 04, 2014
Labor Day:                    Monday, September 01, 2014
Columbus Day:                 Monday, October 13, 2014
Veteran's Day:                Tuesday, November 11, 2014
Thanksgiving:                 Thursday, November 27, 2014
Christmas Day:                Thursday, December 25, 2014


daniel@linubuvma:~$ ./federalholidays.sh 2500
New Year's Day:               Friday, January 01, 2500
Martin Luther King, Jr. Day:  Monday, January 18, 2500
Washington's Birthday:        Monday, February 15, 2500
Memorial Day:                 Monday, May 31, 2500
Independence Day:             Sunday, July 04, 2500
Labor Day:                    Monday, September 06, 2500
Columbus Day:                 Monday, October 11, 2500
Veteran's Day:                Thursday, November 11, 2500
Thanksgiving:                 Thursday, November 25, 2500
Christmas Day:                Saturday, December 25, 2500

Here is the whole script, feel free to modify it or report any problem –


#!/bin/bash

ARCH=$(arch)
ARGC=$#

function Usage
{

echo "Usage: $(basename $0) Year"
echo "Eg. $(basename $0) 2014"
exit 1

}

# we will need the year as argument in YYYY format
[[ $ARGC -ne 1 ]] &&  Usage

myyear="$1"
dformat='+%A, %B %d, %Y'

[[ "$myyear" -ge 2038 ]] && [[ "$ARCH" = "i686" ]] && echo 'Year 2038 problem : http://en.wikipedia.org/wiki/Year_2038_problem ' && exit 1

#We will ignore any year below 1902
[[ "$myyear" -lt 1902 ]] && [[ "$ARCH" = "i686" ]] && exit 1

##Function to get the nth day week of the month, for instance, Third Monday of March.

function nth_xday_of_month
{

my_nth=$1
my_xday=$2
my_month=$3
my_year=$4

case "$my_nth" in

1)  mydate=$(echo {01..07})
    ;;
2)  mydate=$(echo {08..14})
    ;;
3)  mydate=$(seq 15 21)
    ;;
4)  mydate=$(seq 22 28)
   ;;
5)  mydate=$(seq 29 31)
    ;;
*) echo "Echo wrong day of the week"
   exit 1
   ;;
esac


for x in $mydate; do
  nthday=$(date '+%u' -d "${my_year}${my_month}${x}")
  if [ "$nthday" -eq "$my_xday" ]; then
   date "${dformat}" -d "${my_year}${my_month}${x}"
  fi
done
}


##Memorial day - Last Monday of May.

for x in {31..01}; do y=$(date '+%u' -d "${myyear}05${x}"); if [ "$y" -eq 1 ]; then memday="${x}" ; break; fi ; done

echo "New Year's Day:              " $(date "${dformat}"  -d "${myyear}0101")
echo "Martin Luther King, Jr. Day: " $(nth_xday_of_month 3 1 01 ${myyear})
echo "Washington's Birthday:       " $(nth_xday_of_month 3 1 02 ${myyear})
echo "Memorial Day:                " $(date "${dformat}" -d "${myyear}05${memday}")
echo "Independence Day:            " $(date "${dformat}" -d "${myyear}0704")
echo "Labor Day:                   " $(nth_xday_of_month 1 1 09 ${myyear})
echo "Columbus Day:                " $(nth_xday_of_month 2 1 10 ${myyear})
echo "Veteran's Day:               " $(date "${dformat}" -d "${myyear}1111")
echo "Thanksgiving:                " $(nth_xday_of_month 4 4 11 ${myyear})
echo "Christmas Day:               " $(date "${dformat}" -d "${myyear}1225")

: <<'federal_holidays_comment'

http://en.wikipedia.org/wiki/Federal_holidays_in_the_United_States

Jan 1 - New Year's Day - 1st day of the year
Third Monday of January - Martin Luther King, Jr. Day 
Third Monday of February - Washington's Birthday
Last Monday of May - Memorial Day.
July 4 - Independence Day.
First Monday of September - Labor Day.
Second Monday of October - Columbus Day.
November 11 - Veteran's Day.
Fourth Thursday of November - Thanksgiving
December 25 - Christmas Day
federal_holidays_comment