Thursday, 24 November 2016

Using jq to read json output (from AWS CLI commands)

We want to write a script to update AWS Route53 records as a server instantiates.



We're using "split horizon" DNS - that is to say, DNS where we have 2 zones ; an internal, and an external.  The internal one allows servers in the private IP space to resolve server names to internal addresses, and the external one allows the rest of the world to resolve (some of) our servers.]

We need to be able to update the internal DNS.  The AWS CLI command has this :

aws route53 list-hosted-zones(-by-name) - you can omit the -by-name if you want.   This reports back in either text or JSON information about zones.  The text format is slightly incomplete ( we believe), and splits output across 2 lines.  This isn't too hard to handle with (say) sed, but the data seems incomplete. To get all the information regarding hosted zones, you need the json output.  This, however, is not amenable to parsing with the traditional Unix tools (sed & awk, etc).  We need jq.


Note:

All examples used jq 1.5: 
$ jq --version

jq-1.5-1-a5b5cbe




An example:

aws route53 list-hosted-zones  | jq '."HostedZones"[] | select(.Config.PrivateZone==true )| select(.Name=="xxxxxxx-poc.uk.")'


jq works with filters, which you connect together with pipes - just like at the Unix shell, so the ".HostedZones[]" creates an array(list) (called HostedZones); this is piped into a select statement which filters so we only have entries in the list where the HostedZone/Config/PrivateZone has the boolean value true.  This output is piped into a further select which matches those zones named "xxxxxxx-poc.uk."  NOTE: there is a dot at the end of the zone name.

If we want to see just the Id of the selected zone, add .Id at the end:


$ aws route53 list-hosted-zones  | jq '."HostedZones"[] | select(.Config.PrivateZone==true )| select(.Name=="sainsburys-poc.uk.").Id'
"/hostedzone/Z1E7TO5JG0M9ZC"
This can then be piped to (eg) sed:

| sed 's#^.*/##'  (which uses a greedy regex (the .*) to match everything up to the final / in the line , and replace it with nothing.

You could be clever:
|sed 's#^.*/\([^\"]*\).*$#\1#'
to get rid of the " (by tagging a regex which matches everything up to the quote.

or simply | tr -d'"' to delete the quote using tr.

Then, there's passing variables in to jq.
Like so:

jq --arg jq_varname "$shell_variable"

Which is then used in jq like so:

jq --arg t_name $tag_name ' .Tags | select(.Tag==$t_name).Value' (untested) 

for example.  All very natural - except that there's no equals between the variable and it's assigned value in the command-line.


Wildcards

Sometimes we want to return records which match a regex.  For this we probably want a contains() call - eg:

aws ec2 describe--instances | jq '.Images | select(.Name!=null) |select(.VirtualizationType=="hvm") | select(.Name | contains("entos"))'

---- More needed here - that's no wildcard!


Returning Multiple Values

I want to return multiple values from a list (one of which needs to be converted to a string so I can catenate it to the end of the other):

curl -X GET -ugrahamnicholls:xxx -k https://foreman.test-poc.uk/api/architectures/1 | jq '.operatingsystems[] | [ .name, .id|tostring ] | join(": ")'

  Various Example Queries:


Show the Centos compute resource:

$ curl -X GET -ugrahamnicholls:xxxxx -k https://foreman.sainsburys-poc.uk/api/operatingsystems/ 2>/dev/null | jq '.results[] | select(.name == "CentOS")'

Startswith example:


$ aws ec2 describe-images --output=json --owners=744647245289 --region=eu-west-1 | jq -M '.Images[] | select(.Name | startswith("CentOS")).Name'




Tuesday, 3 June 2014

Logging users accessing certain accounts with PAM

 Immediate Notification of Root Access to Servers


This is an important security requirement, especially in the banking industry.

How can we achieve this with a minimum of intrusion?

The answer's simple: use pam-script as a "session" module.  This calls a script when a session is established.  In the script, read and write to the terminal normally.  Log using logger.

We need a test system; Centos 6.5 is ideal for this as we don't need a RHEL subscription, but it's essentially the same as  a RHEL installation.
Create a virtual machine running centos 6.5 using QEMU.  Create the disk first as a QCOW2 virtual disk :

$ qemu-img create -f qcow2 ~/virtuals/centos_6.5 10G
Then attach the iso image to the virtual system, boot and install as normal.  We'll need to set up networking.  We're going to use a bridged network, so configure 2 files:
BTW in my .bashrc files for myself and for root I do this:

export EC=/etc/sysconfig
export NS=$EC/network-scripts

Which makes navigating easy; just cd $NS , rather than cd /etc/sysconfig/network-scripts

Next, enable sshd:
$ service sshd enable

We can now log in to the server from our host machine. Then we can configure route-br0 with a default gateway, so that we can access the 'net.  You will need to set up name resolution, too, but if your host server can access the internet, then your route-br0 should look something like mine:

grahamn@centos-6:~/src/pam-script-1.1.6 $cat /etc/sysconfig/network-scripts/route-br0 
default via 10.172.228.199 dev br0

Where the via address is the address of your host server.  Add a line:
nameserver 8.8.8.8 to /etc/resolv.conf, and you should be able to do name lookups. Test by pinging (say) google.fr


grahamn@centos-6:~/src/pam-script-1.1.6 $ping google.fr
PING google.fr (173.194.70.94) 56(84) bytes of data.
64 bytes from fa-in-f94.1e100.net (173.194.70.94): icmp_seq=1 ttl=47 time=17.0 ms

First, then we have to download and install pam-script.  In order to build this, we need pam-devel installed, so do a

# yum install pam-devel
then it's the usual: 
$ tar xvf pam-script.1.6.7.xxx
$ cd pam-script-1.6.7.1
$ ./configure (should be OK if you've got pam-devel & gcc installed)
$ make
$ sudo make install

The make install will put the pam_script.so file in /usr/local/lib/pam_script.so.  Note the change of the hyphen in the source filename to an _ in the library.


Using the facility

Now that you've installed the pam-script.so library in /usr/local/lib, we need to configure pam to use it for the facilities which we wish to interrogate the usage of.  In this case, we're interested in the "session" operation, so that whenever a user establishes a session on our server, they are asked why.  Now, this is intrusive - a normal user logging on to the system should probably not be bothered by our script, but as soon as someone tries to log on as root, or become root with an "su" or sudo su, or any command which will establish a root session, then they should be interrogated as to why they are performing that action.


So, let's first copy the pam configuration directory so that when we mess things up entirely, we can revert to our version of the status-quo ante.  So, 

$sudo cp -r /etc/pam.d /etc/pam.d.orig 

(At this stage, it might be worth considering that a proper revision control system such as git might be a better approach to backing up config files.)  

Now we need to choose where to make our changes.  Let's list the /etc/pam.d directory to see what the possibilities are:

$ ls -l etc/pam.d

total 136
-rw-r--r--. 1 root root 272 Mar  5 16:21 atd
-rw-r--r--. 1 root root 192 Mar  5 16:21 chfn
-rw-r--r--. 1 root root 192 Mar  5 16:21 chsh
-rw-r--r--. 1 root root 232 Mar  5 16:21 config-util
-rw-r--r--. 1 root root 293 Mar  5 16:21 crond
-r--r--r--. 1 root root 146 Mar  5 16:21 cups
-rw-r--r--. 1 root root  71 Mar  5 16:21 cvs
-rw-r--r--. 1 root root 115 Mar  5 16:21 eject
lrwxrwxrwx. 1 root root  19 Mar  5 16:21 fingerprint-auth -> fingerprint-auth-ac
-rw-r--r--. 1 root root 659 Mar  5 16:21 fingerprint-auth-ac
-rw-r--r--. 1 root root 147 Mar  5 16:21 halt
-rw-r--r--. 1 root root 728 Mar  5 16:21 login
-rw-r--r--. 1 root root 172 Mar  5 16:21 newrole
-rw-r--r--. 1 root root 154 Mar  5 16:21 other
-rw-r--r--. 1 root root 146 Mar  5 16:21 passwd
lrwxrwxrwx. 1 root root  16 Mar  5 16:21 password-auth -> password-auth-ac
-rw-r--r--. 1 root root 896 Mar  5 16:21 password-auth-ac
-rw-r--r--. 1 root root 155 Mar  5 16:21 polkit-1
-rw-r--r--. 1 root root 147 Mar  5 16:21 poweroff
-rw-r--r--. 1 root root 147 Mar  5 16:21 reboot
-rw-r--r--. 1 root root 613 Mar  5 16:21 remote
-rw-r--r--. 1 root root 167 Mar  5 16:21 run_init
-rw-r--r--. 1 root root 143 Mar  5 16:21 runuser
-rw-r--r--. 1 root root 105 Mar  5 16:21 runuser-l
-rw-r--r--. 1 root root 145 Mar  5 16:21 setup
lrwxrwxrwx. 1 root root  17 Mar  5 16:21 smartcard-auth -> smartcard-auth-ac
-rw-r--r--. 1 root root 711 Mar  5 16:21 smartcard-auth-ac
lrwxrwxrwx. 1 root root  25 Mar  5 16:21 smtp -> /etc/alternatives/mta-pam
-rw-r--r--. 1 root root  76 Mar  5 16:21 smtp.postfix
-rw-r--r--. 1 root root 575 Mar  5 16:21 sshd
-rw-r--r--. 1 root root 341 Mar  5 16:21 ssh-keycat
-rw-r--r--. 1 root root 487 Mar  5 16:21 su
-rw-r--r--. 1 root root 202 Mar  5 16:21 sudo
-rw-r--r--. 1 root root 187 Mar  5 16:21 sudo-i
-rw-r--r--. 1 root root 137 Mar  5 16:21 su-l
lrwxrwxrwx. 1 root root  14 Mar  5 16:21 system-auth -> system-auth-ac
-rw-r--r--. 1 root root 937 Mar  5 16:21 system-auth-ac
-rw-r--r--. 1 root root  97 Mar  5 16:21 system-config-network

-rw-r--r--. 1 root root  97 Mar  5 16:21 system-config-network-cmd

There are lots of possibilities, and these may be different for your particular distribution.  I happen to like (K)Ubuntu, and that has a different list:

grahamn@gn-linux:~/work/go_scripts$ ls -l /etc/pam.d
total 104
-rw-r--r-- 1 root root  179 Sep 21 23:38 accountsservice
-rw-rw-r-- 1 root root  220 Apr  9  2013 atd
-rw-r--r-- 1 root root  384 Jul 26  2013 chfn
-rw-r--r-- 1 root root   92 Jul 26  2013 chpasswd
-rw-r--r-- 1 root root  581 Jul 26  2013 chsh
-rw-r--r-- 1 root root 1263 Feb 24 14:50 common-account
-rw-r--r-- 1 root root 1316 Feb 24 14:50 common-auth
-rw-r--r-- 1 root root 1563 Feb 24 14:50 common-password
-rw-r--r-- 1 root root 1572 Mar  5 16:37 common-session
-rw-r--r-- 1 root root 1492 Mar  5 16:37 common-session-noninteractive
-rw-r--r-- 1 root root  527 Feb  9  2013 cron
-rw-r--r-- 1 root root   69 Jul 16  2013 cups-daemon
-rw-r--r-- 1 root root  663 Oct  9 04:16 lightdm
-rw-r--r-- 1 root root  510 Oct  9 04:16 lightdm-autologin
-rw-r--r-- 1 root root  547 Oct  9 04:16 lightdm-greeter
-rw-r--r-- 1 root root 4887 Feb 26 11:33 login
-rw-r--r-- 1 root root   92 Jul 26  2013 newusers
-rw-r--r-- 1 root root  520 May 18  2013 other
-rw-r--r-- 1 root root   92 Jul 26  2013 passwd
-rw-r--r-- 1 root root  255 Sep 18 18:59 polkit-1
-rw-r--r-- 1 root root  168 Jan 22  2013 ppp
-rw-r--r-- 1 root root   84 Aug 25  2013 samba
-rw-r--r-- 1 root root 2057 Feb 26 15:58 sshd
-rw-r--r-- 1 root root 2402 Mar  5 16:08 su

-rw-r--r-- 1 root root  239 Feb 28  2013 sudo

I think I prefer Ubuntu's way of doing it with a common-session file.  Incidentally, this was confusing me when I'd undone my changes to su, but my script was still being called.

Anyway, I'm going to put my changes into the "su" file.


A Few Notes:

1. I seem to have pam-script installed already - why do I need to reinstall a different version?
 Well I'm not sure, but the original version wouldn't work for me - it couldn't write to stdout/stderr, or read from stdin, which meant it was useless as an interactive tool.

2.  I "spoke" (well, emailed) the author of this module, Jeroen Nijhof , who was incredibly helpful.  Apparently, ssh does it's own thing with respect to PAM, so we have to make it behave.  Change the line in /etc/ssh/sshd_config from UseLogin no (the default, which is why it may be commented out) to UseLogin yes (and uncomment it, or nothing will change), and 
restart sshd with service sshd restart. THIS WILL BREAK EVERYTHING!  But it is what you want, so let's fix it!

3. https://github.com/jeroennijhof/pam_script