Getting Started With Ansible – First Steps

Standard

Previously I wrote about installing Ansible on CentOS 6.5. On this one I’ll be going over some first steps with Ansible, things like configuring users on your hosts, creating the Ansible hosts file, and testing that everything works.

Configuring Ansible Users

A lot of the tutorials I have seen seem to just use root for there examples. But I prefer not to allow SSH access to the root user on my boxes.

NOTE: I’m not an Ansible pro. So in the event that you’re following along with this, there may be a better way of doing this. I couldn’t find anything on it, but please do your own research.

Create the user

On the host you want to manage with Ansible first create a new user.

useradd ansible

Allow the user access

Now we’re going to add them to the sudoers file to allow them access to perform tasks that require root access.

You’ll first need to chmod the file as it’s 440 (read only) by default.

chmod 640 /etc/sudoers

NOTE: Yes, another note. I’m giving this one access to all sudo actions. Because at this point I’m still not entirely sure what it does and doesn’t require access to. Preferably I’d limit this user only to commands that I know it needs.

vi /etc/sudoers

Your sudoers file will probably have a bunch of examples in it. But it’s cool to add this line pretty much anywhere in there. I usually remove the examples add what I need after the line for the root user. You’ll want it to look similar to what is below.

root    ALL=(ALL)       ALL
ansible ALL=(ALL)       ALL

Now make sure to change back the permissions of the sudoers file.

chmod 440 /etc/sudoers

Then reload ssh.

/etc/init.d/sshd reload

Add public keys

Lastly we’ll want to generate and push up the public key for this user.

To generate the key we’ll run ssh-keygen. It’ll prompt for the file to save this at. I recommend overriding the default and putting it as /home/your_user/.ssh/ansible, where your_user is the name of the user you’re running ansible from. This has a number of advantages that I won’t get into. After that it’ll ask for a passphrase. I don’t actually set one here, that’s kind of bad practice on my part, but I have some ansible jobs set up via cron and I don’t want those to be missed because I forgot to log back in after a reboot and unlock the key.

Once that has been created we now need to copy it up to the remote host.

There are two ways to do this. Either copy/paste between your local ansible.pub file and the authorized_keys file for the ansible user on the remote host. The other is to use ssh-copy-id, but that’s a little convoluted to do on a box where SSH is configured to only allow key’s and not passwords so I won’t cover it.

First create the .ssh directory for the ansible user on the remote box.

mkdir .ssh

Then set the permissions to 700 (all access to owner only).

chmod 700 .ssh

Then simply copy the contents of your local .ssh/ansibile.pub file and paste it into the /home/ansible/.ssh/authorized_keys file on the remote box.

Finally change the permissions on that file to 600 (read and write for owner only).

chmod 600 .ssh/authorized_keys

Unfortunately I don’t have an automated way of doing this yet, if you have one please let me know.

Setting Up The Hosts File

I’m not going to set up an overly complicated hosts file or directory structure yet. This will just be a simple file listing my hosts for now.

On the box you’re running ansible from first create /etc/ansible.

sudo mkdir /etc/ansible

Then create a file at /etc/ansible/hosts and add your remote hosts that accept the ansible key you created earlier to it. All you need is the IP address or hostnme and a couple of other settings.

sudo vi /etc/ansible/hosts

Setting Up Ansible Configuration

Since we installed from pip we’re also going to setup an Ansible configuration file.

sudo curl -o /etc/ansible/ansible.cfg https://raw.githubusercontent.com/ansible/ansible/devel/examples/ansible.cfg

Then we’re going to edit a couple of options in it.

First find the line:

#remote_user = root

and remove the # so it looks like below and set ansible as the user.

remote_user = ansible

Then find the line:

#private_key_file = /path/to/file

and remove the # like above and modify it so it looks like below.

private_key_file = /home/your_user/.ssh/ansible

Again replace your_user with the actual user you’re running Ansible from.

Testing

Now finally run a test.

ansible all -m ping

You should receive a response similar to the one below.

[michael@minimus ~]$ ansible all -m ping 
web.cultofsort-h.org | success >> {
    "changed": false, 
    "ping": "pong"
}

Errors?

FAILED => paramiko is not installed

Try installing the python-paramiko package from yum.

`sudo yum install python-paramiko`

Closing

Please don’t take this as gospel, I’m sure there are easier ways of doing this, but as I said in the first post I’m still learning Ansible. Please do your own research as a lot of this is just intended as notes for myself.

Getting Started With Ansible – Installing

Standard

I’ve recently been reading up on different automation tools as Puppet really didn’t seem to fit what I wanted exactly. I wanted something that allowed me a lot of freedom in not just managing the configuration of my various machines, manage them all or a subset of them all easily, and most importantly that I could just point at and run stuff against. Ansible does all of this for me really well on top of being easily extended and integrated into other tools.

To help myself remember more about all this in the future I’ve decided to document it along the way here.

NOTE: I use CentOS 6 on my boxes, so if you’re following along you’ll either want to do so as well or do a bit of poking around for the alternate method of doing things on your distro.

Installing Ansible

Ansible can likely be installed from a repo somewhere, but since it’s written in Python and can be installed through pip, that’s the way I’m pulling it down.

CentOS 6.5, does not come with Python’s pip utility installed though unfortunately. So we’ll first need to grab a package from the CentOS repos, then grab it down from PyPi using easy_install which is a part of Python’s setuptools package. We’ll also need the python-devel packages later as well so we’re grabbing them as well.

sudo yum install python-devel python-setuptools

NOTE: You’ll see me using sudo most places, hopefully you’ll have to as well because its just silly to allow root logins over SSH. If you aren’t, you should fix that. (http://wiki.centos.org/HowTos/Network/SecuringSSH)

Once python-setuptools is installed through yum we can go ahead and use easy_install to install pip on the system.

sudo easy_install pip

You’ll also need a bunch of packages for building Ansible’s dependencies from source through pip as well.

sudo yum groupinstall 'Development Tools'

This is going to pull down a bunch of packages and may take a bit, on my box it had to grab 111 packages.

Once it completes you can then install Ansible via pip.

sudo pip install ansible

Closing

So this pretty much mirrors what you’ll find on the official Ansible pip installation page (http://docs.ansible.com/intro_installation.html#latest-releases-via-pip). But you’ll notice there are a number of gotchas when installing on a fresh CentOS 6.5 system. Luckily most of the error messages pip was spitting at me I had seen before and quickly knew what steps to take to correct them. If you’ve stumbled upon this and hadn’t seen them before, hopefully I’ve saved you some time from having to Google around.

Installing Puppet on CentOS 6

Standard

PLAN

Always plan out your actions on a system before taking them. Think of it like a trial run. By planning out beforehand you are mentally running through the steps and are able to catch any flaws and revise before you start mucking around on a system.

We’re just going to be configuring a stand alone Puppet installation here so this will be pretty short.

  1. Add official Puppet repos
  2. Install Puppet agent
  3. Set up cron job to apply Puppet manifests

ADD OFFICIAL PUPPET REPOS

Puppet helpfully provides their own Yum repository for us to grab what we need. EPEL has it as well, a) their versions of Puppet aren’t typically up to date, b) I try and avoid using EPEL for anything if I can. That second part is a discussion for another time and is only a personal preference.

Adding the Puppet repository is dead simple.

rpm -ivh https://yum.puppetlabs.com/el/6/products/x86_64/puppetlabs-release-6-7.noarch.rpm

This will pull down their RPM and install it. It has the .repo files needed to access their repositories.

INSTALL PUPPET

Again, this is super simple as well. Since we’re only doing a standalone deployment of Puppet here we only need to worry about a single package.

yum install puppet

ADD CRON JOB TO APPLY PUPPET MANIFESTS

In a standalone deployment of Puppet you really only need a cron job to read from the site.pp manifest and apply it on a regular basis. Puppet makes this easy to do without needed to edit crontab directly.

puppet resource cron puppet-agent ensure=present user=root minute=30 command='/usr/bin/puppet agent --onetime --no-daemonize --splay'

Once you run that you’ll see the following output if everything went according to plan.

[root@minimus ~]# puppet resource cron puppet-agent ensure=present user=root minute=30 command='/usr/bin/puppet agent --onetime --no-daemonize --splay'
Notice: /Cron[puppet-agent]/ensure: created
cron { 'puppet-agent':
ensure => 'present',
command => '/usr/bin/puppet agent --onetime --no-daemonize --splay',
minute => ['30'],
target => 'root',
user => 'root',
}

And confirm that it was added to the crontab like so.

[root@minimus ~]# crontab -l
# HEADER: This file was autogenerated at Sun Mar 30 15:02:13 -0500 2014 by puppet.
# HEADER: While it can still be managed manually, it is definitely not recommended.
# HEADER: Note particularly that the comments starting with 'Puppet Name' should
# HEADER: not be deleted, as doing so could cause duplicate cron jobs.
# Puppet Name: puppet-agent
30 * * * * /usr/bin/puppet agent --onetime --no-daemonize --splay

Installing BIND as a DNS caching server for your local network.

Standard

What’s a caching DNS server and why do I want one?

A caching DNS server’s purpose in life is to pass along DNS requests to an upstream DNS server and then store the response.

This is handy because it shortens the length of time it takes for your browser (or other application) on your local machine to go out and find the associated IP address of the domain you are trying to connect to.

There are some caveats to this of course that mean you may not notice much if any difference in request times.  First, many operating systems cache DNS requests already locally.  Second, if the owner of the domain has set a low TTL (Time To Live) the caching server will still need to lookup DNS requests for that domain frequently.

You’ll likely notice the biggest improvements over time on networks that have multiple systems making DNS requests out as they’ll cause zones to be cached regularly for each other.

Preparing

Whenever you are getting ready to make changes to a system it’s best to plan your steps out as best as possible beforehand.

  1. Configure the DNS servers the server that will be running BIND will send lookups to.
  2. Install the BIND packages.
  3. Edit named.conf.
    Add listen IP.
    Allow only queries from the local network.
  4. Test the changes to named.conf.
  5. Start the service.
  6. Add IP tables rules.
  7. Test both locally from the server and remotely from another device on the same network.
  8. Save firewall changes.
  9. Chkconfig named on.

Installing

Configure Upstream DNS Servers

As noted above we’re first going to configure which DNS servers the server will be sending lookups to.  You can do this by either manually editing /etc/resolv.conf, or better yet to ensure that the changes stay persistent between restarts by editing the network startup scripts.  I’ll be showing by editing the network startup scripts.

On CentOS/RHEL installations you’ll find these in /etc/sysconfig/network-scripts/.  The file we want to modify is whichever one is connecting out to your network connection.  They all start with ifcfg-.

In this case for me I have three network cards on this box so the contents of /etc/sysconfig/network-scripts/ looks like the following.

[root@minimus ~]# ll /etc/sysconfig/network-scripts/ifcfg-*
-rw-r--r--. 1 root root 136 Mar 29 10:24 /etc/sysconfig/network-scripts/ifcfg-eth0
-rw-r--r--. 1 root root 281 Mar 29 10:44 /etc/sysconfig/network-scripts/ifcfg-eth1
-rw-r--r--. 1 root root 136 Mar 29 10:24 /etc/sysconfig/network-scripts/ifcfg-eth2
-rw-r--r--. 1 root root 254 Oct 10 09:48 /etc/sysconfig/network-scripts/ifcfg-lo

Loading the correct file in your editor (vi/emacs/please god don’t use nano) should show something like what is below.

DEVICE=eth1
TYPE=Ethernet
UUID=9dbfacd1-de47-48c2-9825-3f1c2000122f
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=none
HWADDR=50:46:5D:A5:3D:40
IPADDR=192.168.1.149
PREFIX=24
GATEWAY=192.168.1.1
DEFROUTE=yes
IPV4_FAILURE_FATAL=yes
IPV6INIT=no
NAME="System eth1"
DNS1=8.8.8.8
DNS2=8.8.4.4

Now you’ll see two lines at the end DNS1, and DNS2. These may or may not be in there depending on how you’ve set your system up at install, if the network interface is a new one, etc. I personally use the Google DNS service at home. The two you see there, 8.8.8.8 and 8.8.4.4, are Google’s DNS servers.

If you don’t have anything set for those two lines you will want to add at least two entries. They can be your ISP’s servers, Google’s DNS service or OpenDNS. It doesn’t really matter, again though you’ll want to have two there just in case one becomes unavailable at some point.

Install the BIND Packages

Next we’ll need to install the BIND packages.  These are available from the base CentOS repos, and honestly if you have any third party repos enabled on your server, I recommend that you disable lookups to them for BIND.

The packages we’ll be installing are bind, bind-libs, and bind-utils.  Its likely that they’ll already be installed if this is a base CentOS server installation.  Running this though will update the versions on your system and ensure that they are installed.

yum install bind bind-libs bind-utils

Which should output something similar to what is below.

[root@minimus ~]# yum install bind bind-libs bind-utils
Loaded plugins: fastestmirror, security
Determining fastest mirrors
* base: yum.singlehop.com
* extras: mirror.anl.gov
* updates: mirror.es.its.nyu.edu
base | 3.7 kB 00:00
base/primary_db | 4.4 MB 00:02
extras | 3.4 kB 00:00
extras/primary_db | 19 kB 00:00
updates | 3.4 kB 00:00
updates/primary_db | 2.5 MB 00:05
Setting up Install Process
Resolving Dependencies
--> Running transaction check
---> Package bind.x86_64 32:9.8.2-0.23.rc1.el6_5.1 will be installed
---> Package bind-libs.x86_64 32:9.8.2-0.17.rc1.el6_4.6 will be updated
---> Package bind-libs.x86_64 32:9.8.2-0.23.rc1.el6_5.1 will be an update
---> Package bind-utils.x86_64 32:9.8.2-0.17.rc1.el6_4.6 will be updated
---> Package bind-utils.x86_64 32:9.8.2-0.23.rc1.el6_5.1 will be an update
--> Finished Dependency Resolution

Dependencies Resolved

======================================================================================================================================================================================================
Package Arch Version Repository Size
======================================================================================================================================================================================================
Installing:
bind x86_64 32:9.8.2-0.23.rc1.el6_5.1 updates 4.0 M
Updating:
bind-libs x86_64 32:9.8.2-0.23.rc1.el6_5.1 updates 879 k
bind-utils x86_64 32:9.8.2-0.23.rc1.el6_5.1 updates 182 k

Transaction Summary
======================================================================================================================================================================================================
Install 1 Package(s)
Upgrade 2 Package(s)

Total download size: 5.0 M
Is this ok [y/N]: y
Downloading Packages:
(1/3): bind-9.8.2-0.23.rc1.el6_5.1.x86_64.rpm | 4.0 MB 00:06
(2/3): bind-libs-9.8.2-0.23.rc1.el6_5.1.x86_64.rpm | 879 kB 00:01
(3/3): bind-utils-9.8.2-0.23.rc1.el6_5.1.x86_64.rpm | 182 kB 00:00
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total 629 kB/s | 5.0 MB 00:08
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
Updating : 32:bind-libs-9.8.2-0.23.rc1.el6_5.1.x86_64 1/5
Installing : 32:bind-9.8.2-0.23.rc1.el6_5.1.x86_64 2/5
Updating : 32:bind-utils-9.8.2-0.23.rc1.el6_5.1.x86_64 3/5
Cleanup : 32:bind-utils-9.8.2-0.17.rc1.el6_4.6.x86_64 4/5
Cleanup : 32:bind-libs-9.8.2-0.17.rc1.el6_4.6.x86_64 5/5
Verifying : 32:bind-libs-9.8.2-0.23.rc1.el6_5.1.x86_64 1/5
Verifying : 32:bind-9.8.2-0.23.rc1.el6_5.1.x86_64 2/5
Verifying : 32:bind-utils-9.8.2-0.23.rc1.el6_5.1.x86_64 3/5
Verifying : 32:bind-libs-9.8.2-0.17.rc1.el6_4.6.x86_64 4/5
Verifying : 32:bind-utils-9.8.2-0.17.rc1.el6_4.6.x86_64 5/5

Installed:
bind.x86_64 32:9.8.2-0.23.rc1.el6_5.1

Updated:
bind-libs.x86_64 32:9.8.2-0.23.rc1.el6_5.1 bind-utils.x86_64 32:9.8.2-0.23.rc1.el6_5.1

Complete!

Edit named.conf

Now we need to edit named.conf to allow BIND to listen on more than just localhost to allow lookups from other devices on the network as well as allow only queries from on the network.

The file is located at /etc/named.conf.  Before making any changes to a configuration file I always make copy of it as a backup.  This allows me to go back to something that works should something go wrong.

cp -p /etc/named.conf{,.bak}

Now open /etc/named.conf up in your editor and you’ll see something similar to what is below.


//
// named.conf
//
// Provided by Red Hat bind package to configure the ISC BIND named(8) DNS
// server as a caching only nameserver (as a localhost DNS resolver only).
//
// See /usr/share/doc/bind*/sample/ for example named configuration files.
//

options {
listen-on port 53 { 127.0.0.1; };
listen-on-v6 port 53 { ::1; };
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
allow-query { localhost; };
recursion yes;

dnssec-enable yes;
dnssec-validation yes;
dnssec-lookaside auto;

/* Path to ISC DLV key */
bindkeys-file "/etc/named.iscdlv.key";

managed-keys-directory "/var/named/dynamic";
};

logging {
channel default_debug {
file "data/named.run";
severity dynamic;
};
};

zone "." IN {
type hint;
file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

The line’s we’ll want to edit are line 11 where it says listen-on port, and line 17 where it says allow-query.

The line numbers may not match up with what you see in your file so be sure to look for those directives specifically.

For the listen-on port directive you’ll want to add the IP address of your server after the localhost IP (127.0.0.1).  It will end up looking like:

listen-on port 53 { 127.0.0.1; 192.168.1.149; };

Then for the allow-query directive you’ll want to add your network’s range after localhost.  It will end up looking like:

allow-query { localhost; 192.168.1.1/24; };

Test named.conf changes

Like writing out a plan / list of steps prior to getting started its equally important to test everything after you’ve made a change to ensure it’s working properly.

BIND comes named-checkconf to ensure the syntax of your named.conf file is correct.  If there are no errors there will be no output.  It’ll look like this.

[root@minimus ~]# named-checkconf /etc/named.conf
[root@minimus ~]#

If there is an error you will see something like below.

[root@minimus ~]# named-checkconf /etc/named.conf
/etc/named.conf:11: missing ';' before '}'
/etc/named.conf:17: missing ';' before '}'

The above error is saying that you forgot to put the ; following the IP address for the listen-on port directive on line 11, and after the network range for the allow-query directive on line 17.

Start the Service

Now that we’ve tested to ensure our configuration file is correct we’ll now start up BIND’s named service.

[root@minimus ~]# service named start

If everything goes according to plan you’ll see :

[root@minimus ~]# service named start
Generating /etc/rndc.key: [ OK ]
Starting named: [ OK ]

Add iptables Rules

Now we need to allow connections to port 53 to the server from the local network.

[root@minimus ~]# iptables -I INPUT -s 192.168.1.0/24 -p tcp --dport 53 -j ACCEPT
[root@minimus ~]# iptables -I INPUT -s 192.168.1.0/24 -p udp --dport 53 -j ACCEPT

These rules aren’t actually saved permanently to the iptables rule set.  And that’s good we don’t want that yet, we haven’t tested.  But if you restart your server at this point these will be lost.

Testing Locally and Remotely

Now we get to test and prove that everything is working as intended.

From the server itself run the following, make sure to replace 192.168.1.149 with the IP address that BIND is running on.

dig @192.168.1.149 google.com

If everything is working right you should see something similar to what is below.


[root@minimus ~]# dig @192.168.1.149 google.com

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.23.rc1.el6_5.1 <<>> @192.168.1.149 google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29960
;; flags: qr rd ra; QUERY: 1, ANSWER: 11, AUTHORITY: 4, ADDITIONAL: 4

;; QUESTION SECTION:
;google.com. IN A

;; ANSWER SECTION:
google.com. 300 IN A 173.194.115.105
google.com. 300 IN A 173.194.115.102
google.com. 300 IN A 173.194.115.99
google.com. 300 IN A 173.194.115.110
google.com. 300 IN A 173.194.115.104
google.com. 300 IN A 173.194.115.98
google.com. 300 IN A 173.194.115.103
google.com. 300 IN A 173.194.115.97
google.com. 300 IN A 173.194.115.96
google.com. 300 IN A 173.194.115.101
google.com. 300 IN A 173.194.115.100

;; AUTHORITY SECTION:
google.com. 172743 IN NS ns2.google.com.
google.com. 172743 IN NS ns1.google.com.
google.com. 172743 IN NS ns3.google.com.
google.com. 172743 IN NS ns4.google.com.

;; ADDITIONAL SECTION:
ns2.google.com. 172740 IN A 216.239.34.10
ns1.google.com. 172740 IN A 216.239.32.10
ns3.google.com. 172740 IN A 216.239.36.10
ns4.google.com. 172740 IN A 216.239.38.10

;; Query time: 49 msec
;; SERVER: 192.168.1.149#53(192.168.1.149)
;; WHEN: Sat Mar 29 12:13:10 2014
;; MSG SIZE rcvd: 340

Note the query time 49msec.  Run the same command again and you should see the query time like what’s below.

;; Query time: 0 msec

Pretty sweet right?

Now do the same test from another device on the network.  And you should get the same results.

Save iptables Changes

Now we can save our iptables changes since we know everything is working correctly by running:

[root@minimus ~]# iptables-save

 Enable BIND to Run at System Start

Lastly we’re going to turn on BIND to run when the system is re/starterted.

[root@minimus ~]# chkconfig named on

Done

That’s it you’ve not got a running DNS caching server on your network.

I did include a link to a book below by Michael W Lucas which covers securing BIND.  I strongly recommend picking it up and reading it over if you plan on running a publicly accessible DNS server, or even if you aren’t.  Having the extra info on hand and the practice of securing your DNS server doesn’t hurt.