Archive for the ‘ Scripting ’ Category

Contents of most text files change during the life of the file , and it is common to find yourself trying to search and replace certain text across multiple files. In Linux, this is a fairly easy task. Let us go through some of the commands you will need to perform this task and then finally construct a single liner to do the job.

  • grep is your best friend when it comes to finding a string in a file. In this case we are looking for the string “REPLACEME” in current directory and across multiple files –
$ grep -r REPLACEME *
host.conf:# The "REPLACEME" line is only used by old versions of the C library.
host.conf:order hosts,REPLACEME,bind
hostname:REPLACEME
hosts.deny:ALL: REPLACEME

If we are interested only in the files which contains this particular text –

$ grep -lr REPLACEME *
host.conf
hostname
hosts.deny
  • sed is a tool of choice for inline editing of files –
$ cat data 
This text will be replaced - REPLACEME
$ sed -i 's/REPLACEME/NEWTEXT/g' data 
$ cat data 
This text will be replaced - NEWTEXT

From here, there are multiple ways to skin the cat – we can loop through the files and do the replacement or we can let the commands do the replacement with a wildcard.

For loop style update -

$ for f in $(grep -lr REPLACEME *); do echo "*** File: ${f} ***" ; sed -i 's/REPLACEME/NEWTEXT/g' $f; done
*** File: host.conf ***
*** File: hostname ***
*** File: hosts.deny ***

$ grep -lr REPLACEME *

$ grep -lr NEWTEXT *
data
host.conf
hostname
hosts.deny

Actually the above for loop is redundant, sed can make changes across multiple files –

 sed -i 's/REPLACEME/NEWTEXT/g' *

How to install Google cloud platform(GCP) sdk – gcloud cli tool


The instructions below were testing in Ubuntu Linux.

gcloud is the command line interface(CLI) tool for interacting with GCP services. Per Google’s product overview page for gcloud – “The Cloud SDK is a set of tools for Cloud Platform. It contains gcloud, gsutil, and bq, which you can use to access Google Compute Engine, Google Cloud Storage, Google BigQuery, and other products and services from the command-line. You can run these tools interactively or in your automated scripts”.

Let us download, install and initialize this tool in an interactive manner, accept all default settings for all prompts –

$ curl https://sdk.cloud.google.com | bash && exec -l $SHELL
$ gcloud init
If above installation steps go well, check its version –
$ gcloud version
Google Cloud SDK 224.0.0
bq 2.0.36
core 2018.11.02
gsutil 4.34
 
A simple way to validate if the CLI is working as expected is to list all the GCP regions –
$ gcloud compute regions list
NAME                     CPUS  DISKS_GB  ADDRESSES  RESERVED_ADDRESSES  STATUS  TURNDOWN_DATE
asia-east1               0/8   0/2048    0/8        0/1                 UP
asia-east2               0/8   0/2048    0/8        0/1                 UP
asia-northeast1          0/8   0/2048    0/8        0/1                 UP
asia-south1              0/8   0/2048    0/8        0/1                 UP
asia-southeast1          0/8   0/2048    0/8        0/1                 UP
australia-southeast1     0/8   0/2048    0/8        0/1                 UP
europe-north1            0/8   0/2048    0/8        0/1                 UP
europe-west1             0/8   0/2048    0/8        0/1                 UP
europe-west2             0/8   0/2048    0/8        0/1                 UP
europe-west3             0/8   0/2048    0/8        0/1                 UP
europe-west4             0/8   0/2048    0/8        0/1                 UP
northamerica-northeast1  0/8   0/2048    0/8        0/1                 UP
southamerica-east1       0/8   0/2048    0/8        0/1                 UP
us-central1              0/8   0/2048    0/8        0/1                 UP
us-east1                 2/8   31/2048   2/8        0/1                 UP
us-east4                 0/8   0/2048    0/8        0/1                 UP
us-west1                 0/8   0/2048    0/8        0/1                 UP
us-west2                 0/8   0/2048    0/8        0/1                 UP

Only the core components of the gcloud sdk are installed during initial installation. For any additional component to interact with GCP, you have to install the additional component. For instance, to install the component for interactive with Google Kubernetes Engine(GKE) you have to install kubectl


gcloud components install kubectl

Many features of GCP are available in Beta only, for that you have to install the beta component –


gcloud components install beta

Stay up to date with  –

gcloud components update 

.

Tab completion and running commands in Beta feature –


$ gcloud beta container  [tab][tab]
binauthz  clusters  get-server-config  images  node-pools  operations  subnets

$ gcloud beta container get-server-config
Fetching server config for us-east1-c
defaultClusterVersion: 1.9.7-gke.7
defaultImageType: COS
validImageTypes:
- COS
- UBUNTU
- COS_CONTAINERD
validMasterVersions:
- 1.11.2-gke.15
- 1.10.9-gke.3
- 1.10.7-gke.9
- 1.10.6-gke.9
- 1.9.7-gke.7
validNodeVersions:
- 1.11.2-gke.15
- 1.11.2-gke.9
- 1.10.9-gke.3
- 1.10.9-gke.0
- 1.10.7-gke.9
- 1.10.7-gke.6
- 1.10.7-gke.2
- 1.10.7-gke.1
- 1.10.6-gke.9
- 1.10.6-gke.6
- 1.10.6-gke.4
- 1.10.6-gke.3
- 1.10.6-gke.2
- 1.10.6-gke.1
- 1.10.5-gke.4
- 1.10.5-gke.3
- 1.10.5-gke.2
- 1.10.5-gke.0
- 1.10.4-gke.3
- 1.10.4-gke.2
- 1.10.4-gke.0
- 1.10.2-gke.4
- 1.10.2-gke.3
- 1.10.2-gke.1
- 1.9.7-gke.7
- 1.9.7-gke.6
- 1.9.7-gke.5
- 1.9.7-gke.4
- 1.9.7-gke.3
- 1.9.7-gke.1
- 1.9.7-gke.0
- 1.9.6-gke.2
- 1.9.6-gke.1
- 1.9.3-gke.0
- 1.8.12-gke.3
- 1.8.12-gke.2
- 1.8.12-gke.1
- 1.8.12-gke.0
- 1.8.10-gke.2
- 1.8.10-gke.0
- 1.8.9-gke.1
- 1.8.8-gke.0
- 1.7.15-gke.0
- 1.7.12-gke.2
- 1.6.13-gke.1

Reference –

Installation – https://cloud.google.com/sdk/docs/downloads-interactive#linux

SDK Components – https://cloud.google.com/sdk/docs/components

Tips and Tricks – https://cloudplatform.googleblog.com/2014/03/tips-and-tricks-command-line-access-to.html

Ansible : How to run playbooks as a shell script


Ansible is a powerful tool for automation, its syntax checking, verbose and dry run mode features make it a reliable and safe tool. It is particularly popular in IT infrastructure automation, such as application deployment or full fledged infrastructure plus app deployment. As an integral part of DevOps tool-set, it falls into the category of Chef, Puppet, Salt or CFEngine for the critical role it plays in IT infrastructure, Application Deployment, Configuration Management and Continuous Delivery.

In this short blog, I am writing about a little known or less popular usage of Ansible – executing it like a shell script. In a Unix-like operating system, any text file with its content starting with a #! aka Shebang, is executed by passing the text file as an argument to the characters following the Shebang. For instance, a text file /tmp/myscript.sh with its content starting with the characters #!/bin/bash is run by the program loader as /bin/bash /tmp/myscript. Following the same logic, we can execute any ansible playbook by simply starting the content of the playbook file with a path to the ansible executable. 

Thus for me to execute my playbooks just like a script, the first thing I need to know is the path to my Ansible executable –

$ which ansible
/usr/local/bin/ansible

And have a playbook – in this case, I will use two playbook – one which adds a user and the second one which deletes the same user as examples.
Notice that I am naming the playbook just like a shell script and made it executable –

$ cat add-user.sh 
#!/usr/local/bin/ansible-playbook
---
- hosts: localhost
  tasks:
  - name: Add user
    user: name={{ username }} comment={{ comment }} state=present shell={{ shell }}
    become: yes

When I execute this script, I will pass the parameters needed to add a user as ansible Extra variables. Now let us run the script in dry run mode first –

$ id john
id: ‘john’: no such user

$ ./add-user.sh -e "username=john comment='John Doe' shell=/bin/bash" -v --check
Using /etc/ansible/ansible.cfg as config file

PLAY [localhost] ************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************
ok: [localhost]

TASK [Add user] *************************************************************************************************************************************
changed: [localhost] => {"changed": true}

PLAY RECAP ******************************************************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0

Everything looks good, so let us execute it –

$ ./add-user.sh -e "username=john comment='John Doe' shell=/bin/bash" -v
Using /etc/ansible/ansible.cfg as config file

PLAY [localhost] ************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************
ok: [localhost]

TASK [Add user] *************************************************************************************************************************************
changed: [localhost] => {"changed": true, "comment": "John Doe", "create_home": true, "group": 1002, "home": "/home/john", "name": "john", "shell": "/bin/bash", "state": "present", "stderr": "useradd: warning: the home directory already exists.\nNot copying any file from skel directory into it.\n", "stderr_lines": ["useradd: warning: the home directory already exists.", "Not copying any file from skel directory into it."], "system": false, "uid": 1002}

PLAY RECAP ******************************************************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0

$ id john
uid=1002(john) gid=1002(john) groups=1002(john)

Deleting the user is similar, we just write an equivalent playbook and we pass only the username name as an extra var this time –

$ cat del-user.sh
#!/usr/local/bin/ansible-playbook
---
- hosts: localhost
tasks:
- name: Delete user
user: name={{ username }} state=absent
become: yes

$ ./del-user.sh -e username=john -v --check
Using /etc/ansible/ansible.cfg as config file

PLAY [localhost] ************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************
ok: [localhost]

TASK [Delete user] **********************************************************************************************************************************
changed: [localhost] => {"changed": true}

PLAY RECAP ******************************************************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0

$ ./del-user.sh -e username=john -v
Using /etc/ansible/ansible.cfg as config file

PLAY [localhost] ************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************
ok: [localhost]

TASK [Delete user] **********************************************************************************************************************************
changed: [localhost] => {"changed": true, "force": false, "name": "john", "remove": false, "state": "absent"}

PLAY RECAP ******************************************************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0

$ id john
id: ‘john’: no such user

You can find more on Ansible in the documentation section of the official site.

IP subnet calculator

Linux – IP subnet calculation with ipcalc


ipcalc is a program to perform simple manipulation of IP addresses and is useful for calculating various network masks given an IP address. Some of the uses of ipcalc are –

  • Validate IP address
  • Display calculated broadcast address
  • Show hostname determined via DNS
  • Display default mask for IP
  • Display network address or prefix

Before using ipcalc, make sure you have the binary installed in your Operating system, if not install it by following below instructions –

1. Installation instructions for various Operating Systems

a. Fedora/Red Hat/CentOS

yum install initscripts

b. Debian/Ubuntu

apt-get install ipcalc

c. MacOS

brew install ipcalc

Install ipcalc on Mac OSX

d. Windows

http://jodies.de/ipcalc-faq/win32.html

 

2. How to use ipcalc

Note below examples were tested in CentOS 6.8, it might not work for other distros or Operating systems. Check the ipcalc documentation for your OS.

a. Check if IP address is valid for IPv4 or IPv6 ( it defaults to IPv4)

[daniel@kauai ~]$ ipcalc -c 1.2.3.4
[daniel@kauai ~]$ ipcalc -c 1.2.3.4/32
[daniel@kauai ~]$ ipcalc -c 1.2.3.444
ipcalc: bad IPv4 address: 1.2.3.444

It will exit with a non-zero status code if the IP address is not valid, with zero if valid. For scripting, use ‘-s’ option for silent, that way it doesn’t display error messages.


[daniel@kauai ~]$ ipcalc -s -c 1.2.3.4
[daniel@kauai ~]$ echo $?
0

[daniel@kauai ~]$ ipcalc -s -c 1.2.3.444
[daniel@kauai ~]$ echo $?
1

b. Show boradcast address 


[daniel@kauai ~]$ ipcalc -b 10.10.0.1/24
BROADCAST=10.10.0.255
[daniel@kauai ~]$ ipcalc -b 10.10.0.1/22
BROADCAST=10.10.3.255
[daniel@kauai ~]$ ipcalc -b 10.10.0.1/8
BROADCAST=10.255.255.255

c. Reverse dns

[daniel@kauai ~]$ ipcalc -h 8.8.8.8
HOSTNAME=google-public-dns-a.google.com

$ ipcalc -h 162.247.79.246
HOSTNAME=securenet-server.net

d.  Display default netmask for IP (class A, B, or C)


[daniel@kauai ~]$ ipcalc -m 10.10.10.1
NETMASK=255.0.0.0
[daniel@kauai ~]$ ipcalc -m 192.168.10.1
NETMASK=255.255.255.0
[daniel@kauai ~]$ ipcalc -m 172.16.0.1
NETMASK=255.255.0.0

 

e. Show network address


[daniel@kauai ~]$ ipcalc -n 10.10.244.8/19
NETWORK=10.10.224.0
[daniel@kauai ~]$ ipcalc -n 10.10.244.8/20
NETWORK=10.10.240.0
[daniel@kauai ~]$ ipcalc -n 10.10.244.8/30
NETWORK=10.10.244.8

 

Split a subnet – this feature might not be supported in all ipcalc versions, check for your OS.

This is the best feature of ipcalc in my opinions, you dont’ have to do the subnet and bits calculation by hand. This feature was available in my Ubuntu 16 VM but not RedHat.


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.3 LTS
Release: 16.04
Codename: xenial

$ ipcalc -v
0.41

For instance to split a /20 subnet into two subnets of size 1024 each –


ipcalc 10.10.0.0/20 --s 1024 1024
Address: 10.10.0.0 00001010.00001010.0000 0000.00000000
Netmask: 255.255.240.0 = 20 11111111.11111111.1111 0000.00000000
Wildcard: 0.0.15.255 00000000.00000000.0000 1111.11111111
Network: 10.10.0.0/20 00001010.00001010.0000 0000.00000000
HostMin: 10.10.0.1 00001010.00001010.0000 0000.00000001
HostMax: 10.10.15.254 00001010.00001010.0000 1111.11111110
Broadcast: 10.10.15.255 00001010.00001010.0000 1111.11111111
Hosts/Net: 4094 Class A, Private Internet

1. Requested size: 1024 hosts
Netmask: 255.255.248.0 = 21 11111111.11111111.11111 000.00000000
Network: 10.10.0.0/21 00001010.00001010.00000 000.00000000
HostMin: 10.10.0.1 00001010.00001010.00000 000.00000001
HostMax: 10.10.7.254 00001010.00001010.00000 111.11111110
Broadcast: 10.10.7.255 00001010.00001010.00000 111.11111111
Hosts/Net: 2046 Class A, Private Internet

2. Requested size: 1024 hosts
Netmask: 255.255.248.0 = 21 11111111.11111111.11111 000.00000000
Network: 10.10.8.0/21 00001010.00001010.00001 000.00000000
HostMin: 10.10.8.1 00001010.00001010.00001 000.00000001
HostMax: 10.10.15.254 00001010.00001010.00001 111.11111110
Broadcast: 10.10.15.255 00001010.00001010.00001 111.11111111
Hosts/Net: 2046 Class A, Private Internet

Needed size: 4096 addresses.
Used network: 10.10.0.0/20
Unused:

 

Let us split it into 3 subnets of sizes 512, 512 and 1024

ipcalc

 

Useful links – 


https://linux.die.net/man/1/ipcalc

http://jodies.de/ipcalc

 

Python modify user-agent

How to generate user-agent header for web requests in Python


In previous post, we saw how to modify user-agent header in wget, curl and httpie programs. In this post, I will show you how to modify user-agent header in Python’s popular requests module. There are several reasons for modify user agent, one of which is to trigger a different response from a website. Many website offer different content based on user-agent header. You can find user-agent header details here.

In Python, one of the most popular libraries to query web servers is the requests module. The requests module allows you to pass header information using the headers option –

1. Simplest use case, without a header

import requests
requests.get('http://linuxfreelancer.com/status')

And this is how it is logged on the web server side, Apache in this case –

76.1.2.3 [20/May/2018:01:08:37 -0400] "GET /status HTTP/1.1" 200 359 "-" "python-requests/2.18.4" 1798

The user-agent is simply showing as “python-requests/2.18.4”, and some website might even block this to prevent web crawlers. So the next step is to modify this.

 

2. Modify user-agent header

headers = {'User-Agent': 'Mozilla/5.0 (Android 5.1; Tablet; rv:50.0) Gecko/50.0 Firefox/50.0'}
requests.get('http://linuxfreelancer.com/status', headers=headers)

And this is what the access log entry looks like on the web server side –

76.1.2.3  [20/May/2018:01:11:29 -0400] "GET /status HTTP/1.1" 200 359 "-" "Mozilla/5.0 (Android 5.1; Tablet; rv:50.0) Gecko/50.0 Firefox/50.0" 1289

As you can see above, the user-agent entry has several identifiers which is not easy to remember. The best way would be to programatically generate valid user-agents for different platforms.

 

3. Generate valid user-agents

The user_agent module is used for generating random and yet valid web user agents. You can install it with ‘pip install user_agent’.

This module generate user-agent strings for differnt devices types such as desktop, smartphone and table, as well as OS types (Windows, Linux, Mac, Android …). Let us try it in a virtual environment –

virtualenv /tmp/venv
source /tmp/venv/bin/activate
pip install user_agent

Now run Python in an interactive mode –

import requests
from user_agent import generate_user_agent 

In [8]: generate_user_agent()
Out[8]: 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2803.5 Safari/537.36'

In [9]: generate_user_agent()
Out[9]: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:49.0) Gecko/20100101 Firefox/49.0'

In [11]: generate_user_agent(os='linux')
Out[11]: 'Mozilla/5.0 (X11; Ubuntu; Linux i686 on x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2942.3 Safari/537.36'

In [13]: generate_user_agent(device_type='tablet')
Out[13]: 'Mozilla/5.0 (Linux; Android 4.4; HTC Desire 616 dual sim Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2938.21 Safari/537.36'

In [14]: generate_user_agent(device_type='desktop', os='win')
Out[14]: 'Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0'

In [20]: generate_user_agent(navigator='chrome', os='linux', device_type='desktop')
Out[20]: 'Mozilla/5.0 (X11; Ubuntu; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2894.26 Safari/537.36'

This allows us to generate from random to specific valid user-agent header information. We can then pass this randomly generated user-agent text to the requests module header option, and we will view our web server logs to validate –

from user_agent import generate_user_agent
import requests
requests.get('http://linuxfreelancer.com/status', headers={'User-Agent': generate_user_agent(navigator='firefox', os='linux')})

Log entry –

76.1.2.3 – – [20/May/2018:01:28:24 -0400] “GET /status HTTP/1.1” 200 359 “-” “Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0” 1486

 

References –


http://docs.python-requests.org/en/master/

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent

How to troubleshoot dns issues by directly querying name servers


This tip will help you troubleshooting DNS issues by directly querying DNS using only the IP address of name servers. When you run dns resolution client tools such as dig or nslookup, they will query the name server configured on your host. If the DNS with unexpired ttl is in cache, they will return it from cache. The results will return from cache by any of the intermediate name servers except for the authoritative name servers. That is why ‘dig +trace’ is useful in troubleshooting dns issues, as it starts from the root name servers and moves down all the way to the authoritative name servers to get you the dns records.

Here is a similar tool to “dig +trace“, which queries root name servers, their IPs is hard coded in the script, and follows the authoritative name servers for the subdomains by directly querying the registered IP addresses of name servers. For instance, if you use this tool to query “www.example.com”, it will get a randomly picked root name server’s IP and query it for NS records of “.com” domain. Once it gets the IP address of the name servers for “.com”, it goes on to query them for authoritative name servers of “example.com.” and does the same for “www.example.com.” as well. Throughout the query, it doesn’t use any cache or FQDN, it get the IP address of authoritative name servers and queries the IP directly.

You will need to install dnspython module first –

cd /tmp
pip install dnspython
https://github.com/danasmera/Python_scripts.git
cd Python_scripts/

Start DNS tracing now –

1. google.com

$ python dig-trace.py google.com
Splitting domain into sub-domains ...
['.', 'com.', 'google.com.']

Selected root . name server: 199.9.14.201
Selecting name server for com. domain ...

picked name server: 192.48.79.30
Selecting name server for google.com. domain ...

picked name server: 216.239.36.10
Querying name server: 216.239.36.10
google.com. 300 IN A 173.194.219.102
google.com. 300 IN A 173.194.219.101
google.com. 300 IN A 173.194.219.113
google.com. 300 IN A 173.194.219.100
google.com. 300 IN A 173.194.219.138
google.com. 300 IN A 173.194.219.139


2. www.whitegov.com txt

 python dig-trace.py www.whitegov.com txt
Splitting domain into sub-domains ...
['.', 'com.', 'whitegov.com.', 'www.whitegov.com.']

Selected root . name server: 192.33.4.12
Selecting name server for com. domain ...

picked name server: 192.54.112.30
Selecting name server for whitegov.com. domain ...

picked name server: 204.11.57.26
Selecting name server for www.whitegov.com. domain ...

picked name server: 204.11.56.26
Querying name server: 204.11.56.26
www.whitegov.com. 3600 IN TXT "~"



3. cnn.com mx

$ python dig-trace.py cnn.com mx
Splitting domain into sub-domains ...
['.', 'com.', 'cnn.com.']

Selected root . name server: 193.0.14.129
Selecting name server for com. domain ...

picked name server: 192.31.80.30
Selecting name server for cnn.com. domain ...

picked name server: 205.251.192.47
Querying name server: 205.251.192.47
cnn.com. 300 IN MX 10 mxa-000c6b02.gslb.pphosted.com.
cnn.com. 300 IN MX 10 mxb-000c6b02.gslb.pphosted.com.

4. linuxfreelancer.com [ANY | NS ]

$ python dig-trace.py www.linuxfreelancer.com ANY
Splitting domain into sub-domains ...
['.', 'com.', 'linuxfreelancer.com.', 'www.linuxfreelancer.com.']

Selected root . name server: 192.112.36.4
Selecting name server for com. domain ...

picked name server: 192.35.51.30
Selecting name server for linuxfreelancer.com. domain ...

picked name server: 208.109.255.48
Selecting name server for www.linuxfreelancer.com. domain ...
Querying name server: 208.109.255.48


$ python dig-trace.py www.linuxfreelancer.com NS
Splitting domain into sub-domains ...
['.', 'com.', 'linuxfreelancer.com.', 'www.linuxfreelancer.com.']

Selected root . name server: 202.12.27.33
Selecting name server for com. domain ...

picked name server: 192.55.83.30
Selecting name server for linuxfreelancer.com. domain ...

picked name server: 216.69.185.48
Selecting name server for www.linuxfreelancer.com. domain ...
Querying name server: 216.69.185.48
www.linuxfreelancer.com. 1800 IN CNAME linuxfreelancer.com.
linuxfreelancer.com. 3600 IN NS ns75.domaincontrol.com.
linuxfreelancer.com. 3600 IN NS ns76.domaincontrol.com.


Links –


https://github.com/danasmera/Python_scripts

https://github.com/rthalley/dnspython

https://linux.die.net/man/1/dig