Saturday, 29 June 2019

Running salt-ssh as a non-root user



Introduction

Security is of the essence in every enterprise infrastructure but so is automation. One of the requirements to maintain a healthy balance among the two is to not use root directly while working with automation tools. In this article I'll be setting up salt-ssh, the agentless version of salt and work with it as a non-root user.

This is by no means a comprehensive write up on how Salt or Salt-ssh works and is rather more of a let's get started.

First let's install the tool using yum.

[root@sahil-lab ~]# yum install salt salt-ssh -y
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.aktkn.sg
 * epel: d2lzkl7pfhq30w.cloudfront.net
 * extras: mirror.aktkn.sg
 * nux-dextop: li.nux.ro
 * updates: mirror.aktkn.sg
Resolving Dependencies
--> Running transaction check
---> Package salt.noarch 0:2015.5.10-2.el7 will be installed
--> Processing Dependency: m2crypto for package: salt-2015.5.10-2.el7.noarch
--> Processing Dependency: python-crypto for package: salt-2015.5.10-2.el7.noarch
--> Processing Dependency: python-msgpack for package: salt-2015.5.10-2.el7.noarch
--> Processing Dependency: python-zmq for package: salt-2015.5.10-2.el7.noarch
--> Processing Dependency: systemd-python for package: salt-2015.5.10-2.el7.noarch
---> Package salt-ssh.noarch 0:2015.5.10-2.el7 will be installed
 ------------------------------output truncated for brevity


Now let's create the required directory structure.

[sahil@sahil-lab ~]$ mkdir salt_setup
[sahil@sahil-lab ~]$ cd salt_setup/
[sahil@sahil-lab salt_setup]$ mkdir -p {config,salt/{files,templates,states,pillar,formulas,pki/master,logs}}
[sahil@sahil-lab salt_setup]$ mkdir cache
[sahil@sahil-lab salt_setup]$ touch ssh.log


We would also need to copy the contents of /etc/salt directory to the salt_setup directory under the user's home directory.

[root@sahil-lab ~]# cp -rp /etc/salt/* /home/sahil/salt_setup/
[root@sahil-lab ~]# chown sahil:sahil -R /home/sahil/salt_setup/*


The master config file:
The master config file has the same declarations that you would define when using Salt in master mode. Create a master config file with the following contents that points Salt SSH to the location of the previously created directories.

[sahil@sahil-lab salt_setup]$ cat master
root_dir: "/home/sahil/salt_setup"
pki_dir: "pki"
cachedir: "cache"
log_file: "salt-ssh.log"
[sahil@sahil-lab salt_setup]$


The Saltfile:
The Saltfile allows you to set command line configuration option in a file instead of declaring them at runtime. Create a Saltfile with the following contents.

[sahil@sahil-lab salt_setup]$ cat Saltfile
salt-ssh:
  config_dir: "/home/sahil/salt_setup/"
  log_file: "/home/sahil/salt_setup/ssh.log"
  pki_dir: "/home/sahil/salt_setup/pki"
  cachedir: "/home/sahil/salt_setup/cache"
  roster_file: "/home/sahil/salt_setup/roster"
  ssh_wipe: True
[sahil@sahil-lab salt_setup]$


The roster file:
The roster file is used to define remote minions and their connection parameters. The default roster file has some commented out examples that you could use. I've set up a fairly simple one as shown below:

[sahil@sahilsuri0082c salt_setup]$ cat roster
# Sample salt-ssh config file
#web1:
#  host: 192.168.42.1 # The IP addr or DNS hostname
#  user: fred         # Remote executions will be executed as user fred
#  passwd: foobarbaz  # The password to use for login, if omitted, keys are used
#  sudo: True         # Whether to sudo to root, not enabled by default
#web2:
#  host: 192.168.42.2

my-salt-vm: 172.40.36.36

In the above example, my-salt-vm is the salt id of the host I wish to connect to followed by its IP address. I could've also used the server's hostname instead of the IP address.


Testing the setup


Let's use the cmd.run module to get the uptime of our host.

[sahil@sahil-lab salt_setup]$ salt-ssh  '*'  cmd.run 'uptime' --user sahil --priv /home/sahil/.ssh/id_dsa
my-salt-vm:
     05:19:32 up  2:17,  1 user,  load average: 0.15, 0.07, 0.10
[sahil@sahil-lab salt_setup]$


You might be wondering the reason for specifying the user name and key file path explicitly. If you don't salt-ssh defaults to the root user and the following happens:

sahil@sahil-lab salt_setup]$ salt-ssh  '*'  cmd.run 'uptime'
Permission denied for host my-salt-vm, do you want to deploy the salt-ssh key? (password required):
[Y/n] y
Password for root@my-salt-vm:
my-salt-vm:
    Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
[sahil@sahil-lab salt_setup]$

The '*' implies run the command on all hosts defined in the roster.
If you do not wan to specify the user name and key file path every time you connect then you could also specify them in the roster file. 
Here is an example:

cat roster | grep -v '#'

lab-node1:
  host: 172.40.36.36
  user: sahil
  priv: /home/sahil/.ssh/id_dsa
  sudo: True

With this in place you could invoke salt-ssh as shown below:

[sahil@sahil-lab salt_setup]$ salt-ssh lab-node1 cmd.run 'uptime'
lab-node1:
     06:51:28 up  3:49,  2 users,  load average: 0.00, 0.01, 0.05
[sahil@sahil-lab salt_setup]$


Salt-ssh requires Python 2.7 or 3.x to be available on the target machines. But what if you are connecting to a system that has Python version 2.6 or what if it doesn't even Python installed?
In that case you could use -r option to execute a raw shell command.

[sahil@sahil-lab salt_setup]$ salt-ssh  '*'  -r 'uptime' --user sahil --priv /home/sahil/.ssh/id_dsa
my-salt-vm:
    ----------
    retcode:
        0
    stderr:
    stdout:
         06:47:10 up  3:45,  2 users,  load average: 0.00, 0.01, 0.05
[sahil@sahil-lab salt_setup]$


Note: For invoking all salt-ssh commands being executed as non-root user, you must be in the directory where the salt configuration, roster, Saltfile and master configuration file are located.


Last words..

Salt-ssh is a nice agentless extension to the Salt tool but having worked with Ansible I find the inventory file system in Ansible coupled with the ease of setup as a whole to be much more flexible. As a result, given the option to work with salt-ssh or Ansible, I would choose Ansible. If you've worked with both tools, I'd love to hear your experience.

Merge two consecutive lines using awk

Introduction:
As system admins we spend a lot of our time working with files. While doing so we may come across situations wherein we may need to manipulate the content of a file or the output of a command to suit our needs. I came across such a situation recently wherein I had to run nslookup on a couple of hosts and get the hostname the IP address printed on the same line with a colon and a space acting as a delimiter. As with many things in UNIX/Linux there is more than one tool for the job. My task could've been accomplished using sed or perl but I chose to go with awk.

The command:

[ssuri@ulabtestpinfra09:~] $ for i in `cat<<EOF
> ulabtestdinfap31
> ulabtestdinfap35
> ulabtestdinfap37
> EOF`
> do nslookup   $i | awk '/Name|Address: 10/  {print $2}' | awk '!(NR%2){print p ": " $0 }{p=$0}'
> done
ulabtestdinfap31.example.org: 10.47.84.34
ulabtestdinfap35.example.org: 10.47.64.58
ulabtestdinfap37.example.org: 10.47.216.14
[ssuri@ulabtestpinfra09:~] $


Explanation:
As you might've noticed I've used awk twice. The first use is basic so I won't get into it. Now let's talk about the second awk. NR represents the number of rows. % is the modulus operator (i.e. a%b is the remainder when a is divided by b)... (NR%2) is the modulus of NR by two, i.e. is true when NR is even and false when odd. !(NR%2) is true when NR is odd, thus. !(NR%2){print p ": " $0 } means the program will print the line concatenated with the variable p, only on odd lines. {p=$0} means that on every line, p is set to be the current line (but only after printing the current and previous line if the current line is odd).


Conclusion:
This concludes our quick article on how we could use awk to merge or join two consecutive lines. I hope that you found this post to be useful.

Monday, 17 June 2019

Lists in Python

Introduction

Lists in Python are analogous to arrays in Perl. A list holds a set of entities which could be strings or numbers. A list can in fact contain another list.
Declaring a list is fairly straight forward. Type the list name followed by the assignment operator (=) and then the list of items in square brackets separated by a comma.

>>> list=[1,2,3,4,'sahil']
>>> print list
[1, 2, 3, 4, 'sahil']
>>>

To access an individual element in the list type list_name[index]. Note that the indices start from 0 and not 1.

>>> print list[4]
sahil
>>>

Modifying lists:

There are a number of operations we can perform on lists to manipulate them. Here are a couple of examples.

Adding an element to a list:

>>> print list
[1, 2, 3, 4, 'sahil']
>>> list +=["hello"]
>>> print list
[1, 2, 3, 4, 'sahil', 'hello']
>>>


Substituting an element in the list:

>>> list=[1,2,3,4,'sahil']
>>> list[2]=9
>>> print list
[1, 2, 9, 4, 'sahil']


Replacing multiple items in a list:

>>> list[1:3]=[7,8]
>>> print list
[1, 7, 8, 4, 'sahil']
>>>
>>> list=[1, 7, 8, 4, 'sahil']
>>> list[1:2]=[2,3]
>>> print list
[1, 2, 3, 8, 4, 'sahil']
>>>


Removing multiple items in a list:

>>> list[1:3]=[]
>>> print list
[1, 4, 'sahil']
>>>


Add an element using append function:

>>> list.append('world')
>>> print list
[1, 2, 3, 8, 4, 'sahil', 'world']
>>>


Remove list element using pop function:

>>> list.pop(2)
3
>>> print list
[1, 2, 8, 4, 'sahil', 'world']
>>>


Remove list element using it's value:

>>> list.remove('sahil')
>>> print list
[1, 2, 8, 4, 'world']
>>>


Conclusion

This concludes our discussion on lists in Python. We hope that you found this quick and simple explanation to be useful.

Strings in Python

Introduction

In this quick article we'll be talking about strings in Python. Strings are a common data type found in most programming languages. The most basic definition of a string is a sequence of characters that can store anything.
For example 'hello world' is a string. If we type it in the Python REPL, it will print the string back.

>>> 'hello world'
'hello world'
>>>

To assign a string to a variable, type: variable='string' and then print the value of the variable using the print function. For example, on the REPL

>>> greeting='Good Morning'
>>> print greeting
Good Morning
>>>

Concatenating strings:
We often need to concatenate strings in our everyday scripts. To concatenate two strings we use the plus (+) operator. Here is an example.

>>> first_word='hello'
>>> second_word='world'
>>> print (first_word+' '+second_word)
hello world
>>>

Converting a number to a string:
To convert a number to a string we use the str() function. Here is an example.

>>> year=1990
>>> print ('I was born in'+' '+str(year))
I was born in 1990
>>>

If you do not perform the conversion you get a type error.

>>> print ('I was born in'+' '+year)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects


String methods:
Python is an object oriented language and a string in Python is a type of object. An object encapsulates some sort of state. In case of a string the state is a sequence of characters. We can call methods on strings since they are objects. Methods contain functions. Here are two methods that you can use on string objects:

1. Find method:
This searches of a character/s in a string and returns the index value where the character was found in the string.

>>> name="sahil"
>>> name.find("il")
3

2. Lower method:
This converts all characters in a string to lower case.

>>> name="sAhIl"
>>> name.lower()
'sahil'


Slicing strings:
Extracting a part of a string or slicing it is something that Python excels at. To slice a string we need to specify tell Python the starting and ending indices where the string needs to be sliced at. These indices need to be enclosed within square brackets and separated by a colon(:) symbol. We can also assign the sliced strings to variables. Here are a few examples:

>>> name='sahil suri'
>>> fname=name[0:5]
>>> fname
'sahil'
>>> lname=name[6:]
>>> lname
'suri'
>>>


Conclusion:

This concludes our discussion on strings in Python. I hope that you found this post to be useful and there are more Python tutorials on the way.
Here are a few examples.

Tuesday, 4 June 2019

Perl one liner to extract LUNid and disk alias from /etc/multipath.conf file

Introduction:

We may run into situations wherein we need to fetch the LUN id and alias mapping for disks under multipath on a Linux machine. Obtaining this data manually would prove to be cumbersome. One way to fetch this data would be to use the combination of grep and paste commands. But I felt that my Perl was getting a bit rusty so I decided to go the Perl way.

First take a look at the entries from the sample file.

        multipath {
                wwid                    36000d3100008f20000000000000001f4
                alias                   dvd-rhel5-2-64
        }
        multipath {
                wwid                    36000d3100008f20000000000000001f6
                alias                   dvd-rhel5-2-32
        }
        multipath {
                wwid                    36000d3100008f20000000000000003e2
                alias                   dvd-rhel4-7-32
        }

The above output shows the multipath stanzas for a couple of disks. We are basically interested in the wwid and alias section. To extract the required information we will be using the below combination of two Perl one liners.

[root@sahil-lab1 ~]# cat mpath.cf  | perl -ne 'print if(/wwid|alias/);' | perl -pne 'if($.%2){s/\n/\t/;}'
                wwid                    36000d3100008f2000000000000000356                       alias                   aleppo
                wwid                    36000d3100008f2000000000000000a3a                       alias                   dc2tst
                wwid                    36000d3100008f2000000000000000b02                       alias                   dc1tst
                wwid                    36000d3100008f20000000000000003cf                       alias                   algiers
                wwid                    36000d3100008f2000000000000000397                       alias                   algiers_local
                wwid                    36000d3100008f200000000000000004b                       alias                   chicago
                wwid                    36000d3100008f200000000000000004c                       alias                   chicago_mysql
                wwid                    36000d3100008f200000000000000004d                       alias                   chicago_local
                wwid                    36000d3100008f200000000000000004e                       alias                   chicago_assets
                wwid                    36000d3100008f20000000000000001f4                       alias                   dvd-rhel5-2-64
                wwid                    36000d3100008f20000000000000001f6                       alias                   dvd-rhel5-2-32
                wwid                    36000d3100008f20000000000000003e2                               alias                   dvd-rhel4-7-32
[root@sahil-lab1 ~]#

You could further add an additional Perl one liner to print only the alias and LUN id as shown below.

[root@sahil-lab1~]# cat mpath.cf  | perl -ne 'print if(/wwid|alias/);' | perl -pne 'if($.%2){s/\n/\t/;}' | perl -F"\s+" -lane 'print "$F[4]  $F[2]"' 
 aleppo  36000d3100008f2000000000000000356
dc2tst  36000d3100008f2000000000000000a3a
dc1tst  36000d3100008f2000000000000000b02
algiers  36000d3100008f20000000000000003cf
algiers_local  36000d3100008f2000000000000000397
chicago  36000d3100008f200000000000000004b
chicago_mysql  36000d3100008f200000000000000004c
chicago_local  36000d3100008f200000000000000004d
chicago_assets  36000d3100008f200000000000000004e
dvd-rhel5-2-64  36000d3100008f20000000000000001f4
dvd-rhel5-2-32  36000d3100008f20000000000000001f6
dvd-rhel4-7-32  36000d3100008f20000000000000003e2
[root@sahil-lab1~]#


Explanation:

The first one liner simply prints lines containing the strings wwid or alias.
The next one liner loops over the content piped from the previous one liner and uses $. variable denoting the line number. If the remainder of the division of the line number by 2 is not 0 i,e. the line is odd, then the new line after the end of the line gets replaced by a tab thereby combining the even and odd numbered lines together. 
The last one liner invokes the awk like functionality available with Perl one liners. The -F flag in conjunction with -a flag allow us to split lines based on a delimiter and the individual strings in the line get stored in an array variable named @F and we can extract the fields by using the scalar elements that make up the @F array.


Conclusion:

I'm sure there are easier and perhaps more compact versions of Perl one liners out there to accomplish this task. I would appreciate any suggestions and feedback on this approach of extracting the required fields from the /etc/multipath.conf file.

Using capture groups in grep in Linux

Introduction Let me start by saying that this article isn't about capture groups in grep per se. What we are going to do here with gr...