Here is a screenshot showing all time traffic stats for this blog.
Thursday, 27 March 2014
When long tail meets viral
Here is a screenshot showing all time traffic stats for this blog.
Friday, 1 November 2013
Dirty, dirty devops
I want to play with http://sensuapp.org/ as an alternative to Nagios and Zabbix etc. I've already got a nice git repo full of our cookbooks etc, but I don't want to go near them. I don't want to even risk messing up master etc.
Note: This assumes you have a chef workstation set up with knife available etc.
Anyway, I wrote this tiny little knife plugin that just calls a couple of bash commands:
require 'chef/knife'
# other require attributes, as needed
module Sandbox
class SandboxInit < Chef::Knife
deps do
end
banner "knife sandbox init (options)"
def run
puts "Creating chef repo as chef-repo"
system("git clone git://github.com/opscode/chef-repo.git")
puts "Creating .chef dir"
system("mkdir chef-repo/.chef")
puts "Creating knife config file"
system("echo 'current_dir = File.dirname(__FILE__)\ncookbook_path [\"\#{current_dir}/../cookbooks\"]' > chef-repo/.chef/knife.rb")
puts "Done. You can know cd into chef-repo, run knife cookbook site install BLAH and use vagrant etc"
end
end
end
See what I mean with hacky? Eww. Anyway, stick this file in ~/.chef/plugins/knife/ then you can use it as follows:
$ mkdir playing-around
$ cd playing-around
$ knife sandbox init
WARNING: No knife configuration file found
Creating chef repo as chef-repo
Cloning into 'chef-repo'...
remote: Counting objects: 223, done.
remote: Compressing objects: 100% (139/139), done.
remote: Total 223 (delta 80), reused 178 (delta 49)
Receiving objects: 100% (223/223), 38.14 KiB | 0 bytes/s, done.
Resolving deltas: 100% (80/80), done.
Checking connectivity... done
Creating .chef dir
Creating knife config file
Done. You can know cd into chef-repo, run knife cookbook site install BLAH and use vagrant etc
$ cd chef-repo
Now you can follow the instructions here https://github.com/sensu/sensu-chef
$ knife cookbook site install sensu
...
$ cd cookbooks/sensu/examples
$ bundle install
...
$ librarian-chef install
...
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Importing base box 'opscode-ubuntu-13.04'...
[default] Matching MAC address for NAT networking...
[default] Clearing any previously set forwarded ports...
[default] Creating shared folders metadata...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 22 => 2222 (adapter 1)
[default] -- 8080 => 8080 (adapter 1)
[default] Running 'pre-boot' VM customizations...
[default] Booting VM...
[default] Waiting for machine to boot. This may take a few minutes...
[default] Machine booted and ready!
...
[2013-11-01T17:16:35+00:00] INFO: Chef Run complete in 227.664146883 seconds
[2013-11-01T17:16:35+00:00] INFO: Running report handlers
[2013-11-01T17:16:35+00:00] INFO: Report handlers complete
[2013-11-01T17:12:47+00:00] INFO: Forking chef instance to converge...
We now have a machine that should have Sensu on it:
$ vagrant ssh
Welcome to Ubuntu 13.04 (GNU/Linux 3.8.0-19-generic x86_64)
* Documentation: https://help.ubuntu.com/
94 packages can be updated.
55 updates are security updates.
New release '13.10' available.
Run 'do-release-upgrade' to upgrade to it.
Last login: Sun May 12 22:41:29 2013 from 10.0.2.2
vagrant@vagrant:~$ ps -ef | grep sens
sensu 6097 1 0 17:16 ? 00:00:00 /opt/sensu/embedded/bin/ruby /opt/sensu/bin/sensu-server -b -c /etc/sensu/config.json -d /etc/sensu/conf.d -e /etc/sensu/extensions -p /var/run/sensu/sensu-server.pid -l /var/log/sensu/sensu-server.log -L info
sensu 6154 1 0 17:16 ? 00:00:00 /opt/sensu/embedded/bin/ruby /opt/sensu/bin/sensu-api -b -c /etc/sensu/config.json -d /etc/sensu/conf.d -e /etc/sensu/extensions -p /var/run/sensu/sensu-api.pid -l /var/log/sensu/sensu-api.log -L info
sensu 6211 1 0 17:16 ? 00:00:00 /opt/sensu/embedded/bin/ruby /opt/sensu/bin/sensu-dashboard -b -c /etc/sensu/config.json -d /etc/sensu/conf.d -e /etc/sensu/extensions -p /var/run/sensu/sensu-dashboard.pid -l /var/log/sensu/sensu-dashboard.log -L info
sensu 6276 1 0 17:16 ? 00:00:00 /opt/sensu/embedded/bin/ruby /opt/sensu/bin/sensu-client -b -c /etc/sensu/config.json -d /etc/sensu/conf.d -e /etc/sensu/extensions -p /var/run/sensu/sensu-client.pid -l /var/log/sensu/sensu-client.log -L info
vagrant 6740 6651 0 17:21 pts/0 00:00:00 grep --color=auto sens
Woot. You can even browse the dashboard on http://localhost:8080 (username is admin and the password is secret).
When you're done, you can quickly clean up after yourself:
$ vagrant destroy
$ cd
$ rm -rf playing-around
Friday, 25 October 2013
How to ask about connectivity issues
As well as a bit of background to the problem (applications involved, environments etc), you should provide at least the following:
- Has it ever worked?
- Have you changed anything recently?
- Source IP
- Destination IP
- Protocol (TCP or UDP etc) if it isn't obvious from the context
- Port if it isn't obvious from the context
- Wrong information - are you connecting to the right host?
- Routing
- Firewalls
- Network or system outages
Have you tried the following?
- Doing a host, dig or nslookup if dealing with a domain
- Pinging the remote host
- Connecting using netcat or telnet
- Traceroute
Friday, 16 August 2013
How do I report a vulnerability?
Thursday, 16 May 2013
A little python vs ruby
Coming from a perl background, parsing strings and manipulating data structures is the bread and butter of sysadmin scripting. Here we're going to split a string, add to the array, then join into a string again.
First, in python:
a = "a b c".split()
>>> a
['a', 'b', 'c']
>>> len(a)
3
>>> a.append("d")
>>> ",".join(a)
'a,b,c,d'
Ok, fair enough I guess. Now lets look at ruby:
a = "a b c".split
=> ["a", "b", "c"]
a.size
=> 3
a.push "d"
=> ["a", "b", "c", "d"]
a.join ","
=> "a,b,c,d"
Tuesday, 16 April 2013
Passphrase Generators Part II
Speaking of passphrases and passwords, I should probably clarify what I mean by the two terms. A password I take to mean a private string which more or less resembles a "word" in the traditional sense. Typical and poor examples would be "password", "Password1", "aksdfjslfjdf" etc. Something like "8yDcBPAllEFOLNNI89ZN4nq5" being a good example. A passphrase I take to mean a private string composed of multiple "words". For example "That's me in the corner, that's me in the spot light", "slgl sdf mcr asrj" are poor examples, and "Cupertino teenage fell Paradox" is a good example. The analogy being to word and phrase in spoken languages. So generally, a good passphrase will be better than a good password because of length, assuming you havn't done something silly like using the lyrics of your favorite song. The passphrase would still need to be random, but easier to remember.
Anyway, having spent some time chatting on IRC about the flaw of using % 10000, using Bash's $RANDOM variable and a few other things, I thought I would update the script to be more secure. The entropy lost from using % 10000 isn't particularly significant, but the way $RANDOM works means you could probably reduce the choice of possible words significantly if you knew when approximately the passphrase was being generated. Basically, $RANDOM seeds off gettimeofday's seconds, useconds and the bash PID. I haven't done the maths for exactly what the impact is, but I thought it would be worth this blog post.
I am actually going to provide two new scripts. The previous method used a dictionary file with 10000 common words downloaded from the internet, and embedded them in the Bash script. Now, I still think this is a good approach because 10000^4 still gives enough possible combinations and because there are only 10000 words, many words will be familiar to most people and should therefore be easier to remember. I will first present an alternative approach.
#!/bin/bash
words=${1:-4}
while true; do
clear
for i in $(seq $words); do
word=$(/usr/bin/shuf -n1 --random-source=/dev/urandom /usr/share/dict/words)
echo -n "$word "
done
echo ""
read -p "Accept (y/n) [n]: " answer
if [ "$answer" == "y" ]; then
exit 0
fi
done
$ wget http://wortschatz.uni-leipzig.de/Papers/top10000en.txt
Perhaps something like this might help:
$ wc -l top10000en.txt
10000 top10000en.txt
$ sort < top10000en.txt | uniq | wc -l
10000
That way at least you know there are 10000 unique words. Either that or find a new dictionary available over HTTPS (and let me know where you found it if possible).
Hopefully you'll find either of these approaches useful.
[1] http://www.ncbi.nlm.nih.gov/pubmed/17888582
Thursday, 14 March 2013
Ultra Light Graphs (ULG)
Edit:
Installation can do be done using ruby gems:$ gem install ulg
Using ULG
ULG is very easy to use. Just give it a file with the text in it and it will generate a png by default.
$ ulg -h
Usage: ulg.rb [-o OUTPUT] [-s FILE] [-d] FILE FILE...
Reads from stdin if no input FILE given
Other options:
-o OUTPUT Output format (png, dot, svg). Default png
-s FILE Save to file
-d Debug mode
-h This help
$ ulg basic.ulg
Reading basic.ulg
Saving output to basic.ulg.png
Basic example
Right, lets say you want to draw a basic graph. You open up your favourite text editor such as vim. You want two nodes and an arrow, so you might intuitively type something like this:
internet ==> web dmz
Now, this is what ULG would make of just that one line:
Nice and simple.
Node shapes
Maybe you don't want two boxes. Perhaps you want one of them to be oval. That's easy too:
(internet) ==> [web dmz]
That would give you:
Notice also how we've also explicitly marked "web dmz" as a box.
Edge label
You've probably noticed that the arrow, or edge in graph-speak, doesn't have a label. We can give it one by just inserting some text somewhere inside the arrow:
(internet) ==http==> [web dmz]
This would give us:
Arrows
You can also control the shape of the arrow head, for example:
(internet) ==http==<> [web dmz]
Gives us:
Edge line
Of course, you might like your lines to be dashed instead of solid. No problem:
(internet) --http--> [web dmz]
Here we go:
Interesting example
Now, lets make things a little bit more interesting. I'll show you the text and image first, then I'll explain a few things afterwards:
option layout dot
option overlap false
option splines true
include backoffice.ulg
Internet ==http==> (web dmz|orange)
web dmz ==8080==> [app stack 1|blue]
web dmz ==8000==> [app stack 2|purple]
app stack 1 ==25==> internet
app stack 2 ==5432|red==> [database|green]
(back office) ==5432|red==> database
(back office) ==80==> (internet|red)
Quite a few things have happened here. Looking at the text, you will have noticed three lines starting with the word "option". These are basically just option names and values that get passed directly to Graphviz as global options.
We also include a ULG file called "backoffice.ulg", the contents of which is shown afterwards. You'll see that backoffice defines an "internet" node and the main text refers to an "Internet" and an "internet" node and that the graph only shows one "internet" node. This is because ULG ignores case and spaces when determining what the node is. So "Internet", "internet" and "inter net" would all be the same thing. This allows you to be a little be messy and inconsistent with your typing ;)
The "internet" node was labelled "internet" and not "Internet". This is because ULG defines styles based on the first occurrence of the node or edge. Because we included backoffice.ulg first, it defined the style for the internet node. This also means that once we have defined a node, such as the "(web dmz|orange)" we ignore any style characters and simply use the label. The next two occurrences were referred to as just "web dmz".
Another thing you will have noticed is the use of colour. This is achieved by simply adding "|colour" to the node or edge label. Any colours Graphviz understands should be fine.
Its probably also worth mentioning at this point that ULG doesn't care about whitespace between nodes and arrows, so you can use spaces or tabs - whatever is easiest to read. It does however require that an arrow is made up of at least two line characters (such as == or --)
Other things you can do
Finally, this example shows the different combinations of supported node and arrow styles.
# This line is ignored
# Include a file
include common-opts.ulg
# An option
option rankdir TB
# Graph
[box] = normal ==> (oval|blue)
oval =dot|red=o <diamond>
oval ..box....x box
box --diamond--<> diamond
another box === no arrow == {hexagon}
Monday, 11 March 2013
OpSec Basics
Take regular backups
Regularly backup systems and data using a combination of incremental and full backups.
Encrypt transmitted data whenever possible
Even on local networks. You may have perimeter firewalls and a switched network, but you have to assume someone is listening.
Minimize software to minimize vulnerability
Reduce the attack surface for remote access and local privilege escalation by only having essential software installed and services running.
Run different network services on separate systems
Not only does this simplify diagnosing operational issues, but means a compromise of one service doesn't directly affect other services. It also reduces the possibility of another service being used in the next stage of an attack.
Least privilege
This should apply at all levels, from system accounts to application logins. Don't use shared credentials and use systems that have granular enough permissions. Someone might not want to do something malicious, but if their system has been compromised the attacker might.
Patch your sh!#
Unpatched software is a liability. Every unpatched system is low-hanging fruit for an attacker.
Be informed
You should have a clear insight into the standard operational and opsec behaviour of your environment. This means having good operational monitoring and metrics, security systems such as HIDS and NIDS and a hardened centralized log collector for system logs and SIEM. Once you have that, take the time to review logs, events and set up alerts if possible.
Segment your network
Compromise is inevitable so the trick is to limit damage once it happens. Network segmentation stops the attack from moving around freely and thus adds complexity and delay to the next stages of their attack.
Use good crypto and don't roll your own
This applies to everything from choosing a password, to storing passwords and encrypting backups. Don't rely on home-made encryption or obfuscation tricks.
Have a response
It is a question of when you get hacked, not if. You should have an incident response plan well in advance of anything happening. Basics should include how to limit damage, how to collect information for forensics or the authorities and details of who to contact.
Default configuration
When you buy a new piece of kit, build a new system or install an application, or should remember to check and adjust any default configuration. Most things try to be easy, not secure, so leaving in default configuration can be dangerous. Default users are a classic example, but other more subtle configuration might give too much information away or might be permissive in terms of access or actions.Saturday, 22 September 2012
What I like about my 2002 Volvo S40 SE 2.0 Auto
Ok, so, for a completely off-topic post about a car. I bought a 2002 Volvo S40 SE 2.0 Automatic a few weeks ago for family day trips etc and not commuting. So I wanted something cheap, comfortable and safe. This car seems to pretty much fit exactly, so much so that I thought I'd share some of the things I like about it. Perhaps you are in a similar situation and are considering buying a second-hand Volvo.
Comfort
For long day trips or weekends away this is quite important. Bouncing down the motorway in a old Fiesta isn't exactly comfortable. So, what does my car give me?
- Climate control - Not just aircon, but proper climate control. Set it to AUTO, choose a temp and voila.
- Automatic - Not to everyone's taste, but I'm kinda liking it. Makes traffic jams bearable.
- Cruise control - Haven't used it yet, but nice idea for those long motorway journeys.
- Heated front seats - Still too warm for them, but I think they'll come in handy in the winter :)
- Adjustable dashboard light brightness - Nice little touch.
- CD and tape player - CD player is a requirement, and the tape player is even handy for playing mp3s or Spotify from my phone.
- 2.0 Engine - Car is pretty responsive.
- Heated door mirrors - Again, too warm to have used them yet, but will come in handy later.
Safety
The most important thing as a family man is of course safety.
- Side markers - Little orange lights on the sides of the car at the front and back to help visibility of the car from the side. Required in North America but not in Europe, I didn't see a single other car last night with them whilst walking in town.
- Headlight wipers - Not only do they look kinda cool, but sort of handy.
- Driver and passenger airbags - The basics.
- Side impact cushions - A nice extra little touch, adding to safety.
- 4 star Euro NCAP rating - "The S40 was awarded four stars for protection in frontal and side impacts, the only one of 13 family cars tested to achieve this result"
Family Friendly
The idea was to get a car that was big enough for day trips without being too big for the terrible on road parking where I live.
- Parking voucher clip - Silly, but very cool. A little clip on the right of the windscreen to place parking vouchers etc. Handy, really handy and a nice little touch.
- Boot size - 415 litres, big enough to put the pram base in without taking off the wheels.
- Lots of random storage - Little storage spaces throughout the car.
- Cup holders in central arm rest - Another nice little touch.
- Full electric windows with driver-side control - All four door windows are electric.
- Leather seats - Easy to clean and spill friendly, perfect for kids in the back.
Sunday, 19 August 2012
Visualisation with Scratch
- 0 - Success kid
- 1 - Fry not sure if
- 2 - Y U NO
- 3 - Insanity wolf
- 4 - First world problems
Not bad for a bit of dragging-and-dropping, and some python. I'll leave it as an exercise for the reader to integrate it into something like Splunk or Nagios :)
You can download the meme cloud python here, and the meme cloud scratch file here. The line viz python can be downloaded here, the scratch file here.
Wednesday, 18 January 2012
Passphrase generator in bash
The script below has been improved upon and can be found here: http://blog.0x10.co.uk/2013/04/passphrase-generators-part-ii.html
Passwords are dead. Long live passphrases. Or something like that anyway. As perfectly summarized by XKCD here http://xkcd.com/936/, passwords are hard to remember and easy to guess. Passphrases on the other hand are easy to remember and hard to guess.
To help generate passphrases, I've put together a little bash script.
Firstly, download the dictionary containing 10000 most common English words. If you have a 4 word passphrase, thats 10^16 possible combinations.
$ wget http://wortschatz.uni-leipzig.de/Papers/top10000en.txtNow save the following script:
#!/bin/bash
dict=$1
script=$2
chars="a-zA-Z0-9 "
size=$(cat $dict | wc -l)
echo "#!/bin/bash" > $script
( echo -n "dict=( "; i=0; cat $dict | while read word; do echo -n "$word " | tr -c -d "$chars"; if [ "$i" -eq "10" ]; then i=1; echo ""; fi; ((i++)); done; echo ")" ) >> $script
echo "size=$size" >> $script
echo 'count=${1:-10}
words=${2:-4}
for i in $(seq 1 $count); do
first=1
for j in $(seq 1 $words); do
r=$RANDOM
let "r %= $size"
word=${dict[$r]}
if [ "$first" == "0" ]; then
echo -n " "
fi
echo -n "$word"
first=0
done
echo ""
done
' >> $script
chmod +x $script
Using modulo on a random number? tut tut tut. Anyway, I've called this script make.sh.Now run it, giving it the dictionary file and an output script name:
$ ./make.sh top10000en.txt ppgen.shWhat it basically does is embed an array of words into ppgen.sh plus the code necessary to show passphrases.
It will look something like:
#!/bin/bash
dict=( the of to and a in for is The that on
said with be was by as are at from
it has an have will or its he not
were which this but can more his been would
...
definite fragile rewards antiabortion respects careers backers seize inefficient
conceptual densities EPS Me Sparc spirits experimentally shallow )
size=10000
count=${1:-10}
words=${2:-4}
for i in $(seq 1 $count); do
first=1
for j in $(seq 1 $words); do
r=$RANDOM
let "r %= $size"
word=${dict[$r]}
if [ "$first" == "0" ]; then
echo -n " "
fi
echo -n "$word"
first=0
done
echo ""
done
We can now run it:$ ./ppgen.sh Does Brazil quotas message amount losers sing Oregon patient misleading rebels smoking refuse subordinated clerk wrote have handle Rockwell strong Committee Insurance consumer else tariffs Unit mouth loved V singled graduate kidney uranium Hungary scientists Mar fined Hercules Kemp IndianOr, if we wanted to create just one 5 word phrase:
$ ./ppgen.sh 1 5 Commodity dioxide Boris unit drop
Thursday, 12 January 2012
Visualising rules from Firewall Builder - Part 1
Firewall Builder (available here http://www.fwbuilder.org/) is a GUI that lets you configure a variety of firewall devices, for example iptables on a linux box or Cisco ASAs.
The idea is to take the configuration file from Firewall Builder and generate some sort of rule visualisation. The ruleset I'm using is basically just the built-in template called "fw template 3". I then saved the ruleset as testfw.fwb.
So, lets process the file:
user@host:~/fwbvis$ ./fwbvis.py testfw.fwb ignoreme testfw.dot user@host:~/fwbvis$
Where the arguments are as follows:
- fwbvis.py - the script (below)
- testfw.fwb - the firewall config saved by Firewall Builder
- ignoreme - to be ignored for now. It will become a configuration file for colours etc.
- testfw.dot - the output file. We just generate a png at the moment, in this case called testfw.dot.png
Here is the result:
It isn't perfect, but it is a pretty good start. The colours are as follows:
- red - reject
- orange - deny
- green - accept
- blue - anything else
So, here is the script so far. It's pretty ugly btw...
#!/usr/bin/python
import sys
import pydot
from xml.etree.ElementTree import ElementTree
sFWFile = sys.argv[1];
sConfigFile = sys.argv[2];
sOutputFile = sys.argv[3];
try:
ds = open(sFWFile)
except:
print "Could not open file", sFWFile
sys.exit(1)
try:
tree = ElementTree()
tree.parse(sFWFile)
except:
print "Could not parse file", sFWFile
sys.exit(1)
graph = pydot.Dot(graph_type='digraph')
prefix = ""
rules = []
objects = {}
for element in tree.iter():
tag = element.tag
if tag[0] == "{":
uri,tag = tag[1:].split("}")
if prefix == "":
prefix = uri
oid = element.get('id')
if oid is None:
next
objects[oid] = {'tag': tag }
for (key,value) in element.items():
if key != 'id':
objects[oid][key] = value
for rule in tree.findall('.//{%s}PolicyRule' % prefix):
oid = rule.get('id')
action = objects[oid]['action']
color = "black"
if action == "Reject":
color = "red"
elif action == "Deny":
color = "orange"
elif action == "Accept":
color = "green"
else:
color = "blue"
src = rule.find('.//{%s}Src' % prefix).find('.//{%s}ObjectRef' % prefix).get("ref")
if src in objects:
src = objects[src]['name']
dst = rule.find('.//{%s}Dst' % prefix).find('.//{%s}ObjectRef' % prefix).get("ref")
if dst in objects:
dst = objects[dst]['name']
srv = rule.find('.//{%s}Srv' % prefix).find('.//{%s}ServiceRef' % prefix).get("ref")
if srv in objects:
srv = objects[srv]['name']
else:
srv = "unknown"
graph.add_edge(pydot.Edge(src, dst, label=srv, color=color))
#graph.write(sOutputFile)
graph.write_png(sOutputFile+".png")
So, there you have it. More to follow as things improve.
Sunday, 1 January 2012
Some bash array functions
Test if a variable is an array:
# is_array - Tests whether variable name is an array
# Parameters:
# name - name of variable to test
# Returns:
# 0 if an array
# 1 otherwise
is_array()
{
# Only takes one argument
if [ "${#}" -ne "1" ]; then
return 1
fi
# Name of the variable to test
local name="$1"; shift
# Run the declare command against the variable
local x=$(declare -p $name 2>/dev/null)
# If it errored then the name probably isn't even
# a variable
if [ "$?" -ne "0" ]; then
return 1
else
# It is at least declared, test if it is an array
if [ "${x:8:2}" == "-a" ]; then
return 0
else
return 1
fi
fi
}
Push values onto an array:# array_push - Pushes one or more values onto an array
# Parameters:
# name - name of the destination array
# ... - values to push onto array
# Returns:
# Nothing
array_push()
{
# Need two or more arguments
if [ "${#}" -lt "2" ]; then
return
fi
# Name of the destination array
local name="$1"; shift
# Number of elements we're going to push
local numElements=${#}
# Counter
local i=0
# Starting position of destination array is the
# size of the array
eval local start=\$\{\#$name\[\@\]\}
# Iterate over the elements supplied, adding them
# to the destination array
while [ "$i" -lt "$numElements" ]; do
pos=$((start+i))
eval $name\[$pos\]=\"$1\";
shift
((i++))
done
}
Pop from an array:# array_pop - pops a value from the end of the array
# Parameters:
# name - name of array
# dest - optional name of variable to put value in
# Returns:
# Nothing
array_pop()
{
# Need at least one argument
if [ "${#}" -eq "0" ]; then
return
fi
# Name of the array
local name="$1"; shift
# If we have a second argument, use it to write value
if [ -n "$1" ]; then
local dest="$1"; shift
else
local dest=""
fi
# Size of the array
eval local numElements=\$\{\#$name\[\@\]\}
# Index of element is one less than size
local index=$((numElements-1))
# Get the value
eval local value=\"\$\{$name\[$index\]\}\"
if [ -n "$dest" ]; then
# Update the specified dest variable
eval $dest=\"$value\"
else
# Simply echo the value
echo "$value"
fi
# Unset from the array
eval unset $name\[$index\]
}
One thing to note is the clash between variable names. E.g. if you wanted to return the value into the name variable, it would be bad to call that variable 'value'. local doesn't help here, best option would probably to use obscure variable names that are unlikely to clash.Walk an array:
# array_walk - Walks through an array, calling the
# callback function for each element
# Parameters:
# name - name of array to walk
# callback - name of callback function
# ... - any parameters to be passed to callback
# Returns:
# Nothing
array_walk()
{
# Name of array to walk over
local name="$1"; shift
# Name of call back function
local callback="$1"; shift
# TODO - We should probably check whether callback
# is actually a function
# Number of elements in array
eval local numElements=\$\{\#$name\[\@\]\}
# Counter
local i=0
# Iterate over the elements in the array
while [ "$i" -lt "$numElements" ]; do
# Get the value
eval value=$\{$name\[$i\]\}
# Check whether it is an array
is_array $value
if [ "$?" -eq "0" ]; then
# If so, recursively walk that array
array_walk "$value" "$callback" "$@"
else
# Just a variable, so call the callback
# function giving it the args and value
$callback "$@" "$value"
fi
((i++))
done
}
Now, a little play with those functions:declare -a aTest
echo First push
array_push aTest 'Hello' 'there' 'you' ':)'
echo new array is ${aTest[@]}
echo
declare -a aTest2
echo Second push
array_push aTest2 'How' 'are' 'you today?'
echo second array is ${aTest2[@]}
echo
echo Third push to add second array to the first
array_push aTest 'aTest2'
echo first array is ${aTest[@]}
echo
echo Defining destination array
declare -a aMerged
echo defining callback function
merge()
{
local name="$1"
local value="$2"
# Simple push the value onto the destination
array_push "$name" "$value"
}
echo Doing walk
array_walk aTest merge aMerged
echo merged array is ${aMerged[@]}
echo
echo Popping last element of merged array by reference
array_pop aMerged val
echo Value is $val
echo and again
array_pop aMerged val
echo Value is $val
echo merged array is now ${aMerged[@]}
Running it gives us:$ ./arrays2.sh First push new array is Hello there you :) Second push second array is How are you today? Third push to add second array to the first first array is Hello there you :) aTest2 Defining destination array defining callback function Doing walk merged array is Hello there you :) How are you today? Popping last element of merged array by reference Value is you today? and again Value is are merged array is now Hello there you :) How $So that sort of works. Next I should add an array_indexes() and array_values(). Oh, and happy new year :D
Edit:
Added array_pop function.
Thursday, 29 December 2011
A different 2D array in bash
I have recently been writing a pretend Object Oriented Programming layer on top of bash. Basically, it uses some global vars, function-within-functions and a ton of eval to create the illusion of OOP in bash.
Something like:
#!/usr/lib/oobash.sh # Import the Car class `import Car` # Create a new "object" new Car as mycar # Set a variable mycar.speed 100 # Call a function mycar.drive
This will be made available here http://code.google.com/p/object-oriented-bash/ soon, but in the meantime I have needed to implement a 2D array in bash. A quick Google search shows some different ways of doing this. Firstly is to dynamically create arrays using eval. The other approach is to use a huge offset, e.g. 1000, then refer to array[i][j] as array[i*1000+j] etc. The latter example requires you to keep track of two indexes which is sort of annoying.
Edit:
I noticed someone has already done something similar at http://oobash.sourceforge.net/. I'l take a look at it some point and if it does the job I'll abandon my project.
So, in an attempt to find something different, I came up with the following. The idea is to store some metadata about how many elements we're adding.
An array with just one element might look like this:
[0] = 1 [1] = "Hello there"
The element at index 0 is the number of elements that follow. As the value is 1, the next element contains the value, which would basically be a scalar. Now, an array might look like this:
[0] = 2 [1] = "Hello" [2] = "there"
Element 0 tells us to expect 2 values. Nice and simple huh? A more complete example:
[0] = 1 [1] = "Hello" [2] = 3 [3] = "How" [4] = "are" [5] = "you?"
You get the idea. But, we have a problem. With arrays, one often wants to pop from the end of the array. In order to do that, we have to start at the end and work backwards. But the following example would break that:
[0] = 1 [1] = "Hi" [2] = 3 [3] = "aaa" [4] = 1 [5] = "bbb"
If we worked backwards from 5, we would get to element 4, which looks like an array count and conclude that (4,5) was a scalar pair. That isn't actually the case. But, there is a solution.
[0] = 1 [1] = "Hi" [2] = 1 [3] = 3 [4] = "aaa" [5] = 1 [6] = "bbb" [7] = 3
At the cost of some space, we've added a count element at the end of each array. That way we can go to the last element, and know how far backwards to go.
Well, that's the theory. Now lets look at a push:
#!/bin/bash
# Function: array_push()
# Params:
# name - name of array receiving data
# indexVar - name of variable to store index used
# ... - list of one or more values to be pushed
# Returns:
# Nothing really
array_push()
{
# Require at least 3 parameters
if [ "${#}" -lt "3" ]; then
return
else
# Set parameter variables
local name="$1"; shift
local indexVar="$1"; shift
# Get the next index
eval local index=\$\{\#$name\[\@\]\}
# Make a note of how many elements we're pushing
local count="${#}"
# Set the index var to our index. This can then be used later
# to retrieve data from the array.
eval $indexVar=$index
# Record the count
eval $name\[$index\]=$count
# Moving up in the array
((index++))
# We now iterate over the values, adding them to the array
end=$((index+count))
while [ "$index" -lt "$end" ]; do
eval $name\[$index\]=\"$1\"
shift
((index++))
done
# Finally, make another record of the count
eval $name\[$index\]=$count
fi
}
declare -a aTest
echo First push
array_push aTest iIndexA "Good day."
echo "index: $iIndexA"
echo new array is ${aTest[@]}
echo
echo Second push
array_push aTest iIndexB "To" "you" "sir!"
echo "index: $iIndexB"
echo new array is ${aTest[@]}
Running it gives us:
$ ./push.sh First push index: 0 new array is 1 Good day. 1 Second push index: 3 new array is 1 Good day. 1 3 To you sir! 3 $
Wonderful. We're not doing anything useful, but the data is there. It was at this point that I got bored of this approach. The problem is that doesn't extend to beyond two dimensions. The only way around this would be some sort of JSON like beginning/end tags instead of a count. E.g.
[0] = {
[1] = 'a'
[2] = }
[3] = {
[4] = 'b'
[5] = 'c'
[6] = {
[7] = 'd'
[8] = }
[9] = }
This is could get a bit slow to process. We'd have to do a lot of string comparisons, and we'd need to quote values to prevent confusion.
Alternatively, we could use an array as usual, but have our functions automatically test values to see if they're an array. That blog post shall follow shortly.
Wednesday, 26 October 2011
Fundamentals principles of better security
Speaking of cryptography, it is difficult. Very difficult. So unless your clever idea to protect data has just been rigorously tested by the crypto community, you probably shouldn't use it. Use a standard library instead, don't roll your own. You're going to get it wrong. Even implementing standard libraries can be tricky, so be careful.
So, why would that cronjob that simply dumps data need to run as an admin? It was probably created when the database only had one user - the admin. Applying the principle of least privilege means that people and systems should only be authorised to do what is required for them to successfully do their jobs. Another example is developers having root access on production systems. Is that really necessary? Or even sysadmins having full access to the development source code repositories. Following this principle helps guard against accidental and malicious abuse of power.
But you can throw it away. This is an operational point that extends beyond security. You can do some very interesting and insightful things with operational metrics, giving you new visibility into your system and therefore greater understanding. From a security perspective, an IDS alarming might coincide with a jump in open file descriptors on a system, or a sudden jump in memory used. Perhaps the number of lines per minute in an application log file jumps or drops. By collecting, graphing and reviewing this data you could lower the time it takes to understand an attack. There is more to life than just network traffic. Not using certain data? Archive or delete it after 12 months. Simple.
Almost finally. It's not a matter of if, but when. Work on the assumption that you are going to get hacked and look at how it's going to happen and how you can minimize the impact on the business. Make sure you have an incident response process.The idea is to stop an attack as soon as possible, reducing the impact whilst recording data for later forensic analysis (a hardened centralised syslog server might help).
Monday, 30 May 2011
London Sky - Flickr pictures
The rest can be found here: Flickr
Sunday, 1 May 2011
M4sl0w's hierarchy of needs (for geeks)

What is Maslow's hierarchy of needs?
I suggest you look here or Google for it, but in summary it is a psychological theory to describe the levels of human needs and desires and how higher levels of needs can only be satisfied once all the levels below are satisfied.
Does this apply to geeks?
Well, even geeks have basic needs that must be met. I'll discuss them in turn below.
Caffeine
All geeks need caffeine in some form. There are a few crazy exceptions, but they're by far the minority. Popular sources of caffeine are coffee and soft drinks. Energy drinks are of course an excellent source of this essential nutrient and have helped many a geek through a late night coding frenzy.
Awesome workstation and shiny gadgets
So, you're getting your regular dose of free caffeine. This will no doubt be the first thing you do when you get to the office, if you haven't already picked a drink up on the way in. You sit down at you're desk and you're greeted with what you're going to be staring at for the next eight hours. Do you look at it and have flashbacks to the eighties or do you admire the technical marvel before you? As they say, "if you need something done, you may as well do it on something cool and expensive".
Fun and interesting colleagues
As a geek you probably dread social interaction. Or perhaps not. I think that cliché is somewhat dated. But you probably have to work with people around you. Perhaps even regularly. The goal here is to have fun at the same time, building up a sense of team and therefore a shared sense of purpose. That way you are more productive and get things done sooner leaving you with more time to sip your caffeine drink of choice. If you're lucky, you might even meet your colleagues after work in some form of social event.
Interesting work, a sense of control and doing things right.
Do it. Do it now. Do it right now. Do it right.
So, you're sipping your regular dose of caffeine, sitting back at your shiny workstation after a interesting and thought provoking conversation with a colleague. It is now time to get on with the day and do some work.
I think most people, and especially geeks, have a basic desire work hard, work efficiently and to do the right thing. As a geek, you are being paid not just to mash the keyboard from time to time, but to use your expertise to assess and prioritize what work needs to be done and how. A sense of control is very important here - if you and your colleagues know what needs to be done, know how it needs to be done, know that it can be done and that it is the right thing to do, but cannot do it for political reasons, then you're probably going to be somewhat demotivated (wow, that last 'sentence' was an abuse of the English language, but you get my point I hope).
It is the role of a good manager to align the goals and opinions of the geek to the business requirements.
And, if you're lucky, your work might even be particularly interesting. Everything is interesting if you think about it the right way :) What was the other one? There are no boring tasks, only boring people.
Play time and training
Now that you're being productive and the business is happy with your and your team's work, what else is there?
Geeks do what they do because they love it, not because it is a career path (by definition, otherwise they're just random people who happen to work in IT etc). It is a geek's natural instinct to experiment, play, create and break things. And to learn during the process - understand how things work, what makes things tick and where the boundaries lie.
So, what is the ultimate geek motivation? To play, experiment, test, break, fix, create and get paid for it. That is why Google has their 20% time. This means that you get to spend 20% of your time doing whatever you like, which can sometimes result in the birth of a new product or service. It gives geeks a chance to think "outside of the box" (Bingo!) and to try or learn new things.
Why bother? Isn't it a waste of time? Well, perhaps. But there is an asymmetry in the potential gains. Basically, there is a fixed loss - 20% of an employee's time. However, the potential gain has no boundaries. Perhaps someone comes up with something truly phenomenal which ends up being worth millions to the business. Or perhaps they create something useful that gets release to the community and makes the world a slightly better place.
And who knows, the company might even lower its staff turnover and save some money on recruitment.
Finally, a word on training. There are two types of training found during employment. Basic training required to do the job effectively and personal training which indirectly benefits the company but adds to the employee's sense of purpose and career progression. Both forms of training are motivating, with the latter being more so. Unfortunately, training is rarely a priority for companies and doesn't seem to exist much in the wild. Lucky for them geeks tend to take responsibility for their own training, but this does generally result in higher staff turnover - if someone has dedicated most of their free time and spare cash into training themselves, why wouldn't they move on to reap the rewards of a better income or more interesting work?
Sunday, 27 February 2011
10 useful little bash tricks
Please note, this stuff was tested on Bash 4.1.5. You can find further details and examples for all these tricks in the Bash manpage.
1::Using previous command
This trick is very simple. If you run a command and then have to repeat it (for example using sudo)
then instead of typing the whole command again you can refer to it using !!.
user@host:~$ aptitude install blah [100%] Reading package lists^C user@host:~$ sudo !! sudo aptitude install blah [sudo] password for user: ... user@host:~$
As you can see, it even prints out the new full command.
2::Using previous argument(s)
Similar to the first trick, bash will allow you to easily reference the last argument of the previous command.
In this first example we're going to touch a few files, then decide to remove the last one.
user@host:~$ touch a b c user@host:~$ rm !$ rm c user@host:~$ ls a b user@host:~$
As you can see, !$ only expanded to c. If you only had one argument, it would exand to that one argument.
We can also reference all the previous arguments using !*
user@host:~$ touch a b c user@host:~$ rm !* rm a b c user@host:~$ ls user@host:~$
3::Process Substitution
Right, now its time to move on to something a little different.
We want to compare the files in two directories to see which are in common, missing or changed etc. It would make sense to use the diff utility to help. We are also going to use md5sum to get a fingerprint of the files so we can see if they have changed. All we have to do is look through each directory (using find), do an md5sum of each file and compare the two lists.
Now, diff usually expects to operate on two files. We could redirect our results into files and then diff those files, but there is a smarter way. By using the <(list) for we can run our finds and diff will treat the results files.
diff <(cd boot-a; find . -exec md5sum {} \;) <(cd boot-b; find . -exec md5sum {} \;)
< 9c830b456ed37e0c0d63f2528fb43de5 ./initrd.img-2.6.35-23-generic
7,8d5
< f70fd0262a6f8e1e82028237e94657fc ./config-2.6.32-25-generic
< c16c8e1705a54db8b96adce7de17710a ./vmcoreinfo-2.6.32-25-generic
10d6
< b1f002028905e42f594c83603606ca0b ./config-2.6.35-23-generic
12,16d7
< 668d54c704f22d85cb548f27a7e19d41 ./config-2.6.35-22-generic
< 882721477fcf705c02b60a4bb219b0c8 ./vmlinuz-2.6.32-25-generic
< 88472c27c3d18a832b5d85aea1edc541 ./abi-2.6.35-22-generic
< 496b4aed0005b07574e9dd3895089b23 ./abi-2.6.35-23-generic
< 6ee40238bdabb40a862c6f63262efa9d ./System.map-2.6.32-25-generic
18d8
< 4e02de05c66cb0b322dc5c71f43882e9 ./System.map-2.6.35-23-generic
84c74
< ac9641014f0b460fc6eb5d2936bd869c ./grub/menu.lst
---
> c5cce31e9e9eb884685137aac1ab8a8a ./grub/menu.lst
As you can see, boot-a had quite a few files that boot-b was missing, and grub/menu.lst was different between the two directories.
4::Here document and redirect
Sometimes you need to put together a little config file, or perhaps a test HTML page. It is possible to do it quickly without having to resort to opening some sort of editor such as vi.
user@host:~$ cat << EOF > test.conf > option1=blah > option2=something else > debug=true > # end of config > EOF user@host:~$ cat test.conf option1=blah option2=something else debug=true # end of config user@host:~$
What we did is specified a here document using << EOF. This keeps reading from the terminal until it encounters the string EOF. That is then redirected into the file, in this case test.conf.
5::List expansion when copying
This a very simple but extremely useful little trick that I'm sure you already know, but because of how often it is useful, I thought I'd put it here anyway.
Let us say you have a file you want to quickly backup. Instead of doing something like cp file file.backup we can use list expansion to shorten the command:
user@host:~$ ls
special.conf
user@host:~$ cp special.conf{,.backup}
user@host:~$ ls
special.conf special.conf.backup
user@host:~$
6::Nested list expansion
Sticking with list expansion, it is worth remembering that you can have nested lists:
user@host:~$ touch {a{1..5},b{1,2,4},c{5..9}}
user@host:~$ ls
a1 a2 a3 a4 a5 b1 b2 b4 c5 c6 c7 c8 c9
7::Quick maths
Need to do a quick calculation? Already have a terminal open? Don't bother firing up a calculator, just do the calculation in bash:
user@host:~$ echo $((2**32)) 4294967296 user@host:~$
See the Bash manpage for the supported operators.
8::Tilda expansion
Bash allows you to quickly reference the current $PWD and old one ($OLDPWD). This could be useful if you forgot to use pushd/popd:
user@host:~/a$ cd ../b user@host:~/b$ ls ~+ inside_b user@host:~/b$ ls ~- inside_a user@host:~/b$ echo $OLDPWD /home/user/a user@host:~/b$
So, ~+ expands to $PWD and ~- expands to $OLDPWD.
9::Setting default values
When you have a bash script that takes command line arguments you might want to set default values if no arguments are supplied by the user.
In the bash script below, we default arg1 to "a" if argument 1 is not supplied. We default arg2 to "b" and arg3 to "c". Of course, the order of the arguments matters.
#!/bin/bash
arg1=${1:-"a"}
arg2=${2:-"b"}
arg3=${3:-"c"}
echo $arg1 $arg2 $arg3
When we run the script without arguments, it uses the defaults. But when we give it arguments, it uses those values.
user@host:~/test$ bash test.sh a b c user@host:~/test$ bash test.sh x y z x y z user@host:~/test$
10::TCP and UDP
Finally, bash allows you to send TCP and UDP traffic directly using a special device of the format /dev/tcp/host/port (use udp instead of tcp if necessary).
Firstly, we set file descriptor 3 to use that device. We can do that with exec, as it suggests in the manual:
"Note that the exec builtin command can make redirections take effect in the current shell."
user@host:~$ exec 3<>/dev/tcp/localhost/80 user@host:~$ ls -la /proc/$$/fd/ total 0 dr-x------ 2 user user 0 2011-02-27 16:34 . dr-xr-xr-x 7 user user 0 2011-02-27 16:34 .. lr-x------ 1 user user 64 2011-02-27 16:34 0 -> /dev/pts/8 lrwx------ 1 user user 64 2011-02-27 16:34 1 -> /dev/pts/8 lrwx------ 1 user user 64 2011-02-27 16:34 2 -> /dev/pts/8 lrwx------ 1 user user 64 2011-02-27 16:34 255 -> /dev/pts/8 lrwx------ 1 user user 64 2011-02-27 16:34 3 -> socket:[156914] user@host:~$
We quickly use $$ to see the open file descriptors for our process. As you can see, FD 3 has been opened for a socket connection, in this case a TCP connection to localhost on port 80.
We can now use redirect echo into FD 3. Note that we use the -e option so that bash interprets the escaped characters giving us new lines needed by the HTTP protocol. We then redirect into cat to read the output.
user@host:~$ echo -e "GET / HTTP/1.0\n\n" >&3 user@host:~$ cat <&3 HTTP/1.1 200 OK Date: Sun, 27 Feb 2011 16:34:26 GMT Server: Apache/2.2.16 (Ubuntu) Last-Modified: Sun, 27 Feb 2011 16:25:27 GMT ETag: "901-e-49d4602cddfad" Accept-Ranges: bytes Content-Length: 14 Vary: Accept-Encoding Connection: close Content-Type: text/html <h1>Hai!</h1> user@host:~$
Cool huh? Not a telnet, netcat or wget in sight...










