Technologist

Tech stuff about Cloud, DevOps, SysAdmin, Virtualization, SAN, Hardware, Scripting, Automation and Development

Browsing Posts in Puppet

In this guide I go over how to use Vagrant with AWS and in the process have an automated way to install Puppet Enterprise.
I am separating data and code by having a generic Vagrantfile with the code and have a servers.yaml file with all the data that will change from user to user.
For installing the Puppet Enterprise server I am including the automated provisioning script I am using with Vagrant and using AWS Tags to set the hostname of the launched server.

Pre-requisites:

  • Vagrant
  • vagrant-aws plug-in:
  • $ vagrant plugin install vagrant-aws
    
  • AWS pre-requisites:
  • While you can add your AWS credentials to the Vagrantfile, it is not recommended. A better way is to have the AWS CLI tools installed and configured

    $ aws configure
    AWS Access Key ID [****************XYYY]: XXXXXXXXYYY
    AWS Secret Access Key [****************ZOOO]:ZZZZZZZOOO
    Default region name [us-east-1]:us-east-1
    Default output format [None]: json
    

    TL;DR To get started right away you can download the project from github vagrant-aws-puppetserver, otherwise follow the guide below.

    Create Vagrantfile

    The below Vagrantfile utilizes a yaml file (servers.yaml) to provide the data, it allows you to control data using the yaml file and not have to modify the Vagrantfile code – separating code and data.

    // Vagrantfile

    # -*- mode: ruby -*-
    # vi: set ft=ruby :
    
    # Specify minimum Vagrant version and Vagrant API version
    Vagrant.require_version ">= 1.6.0"
    VAGRANTFILE_API_VERSION = "2"
    
    # Require YAML module
    require 'yaml'
    
    # Read YAML file with box details
    servers = YAML.load_file('servers.yaml')
    
    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
    
        # Iterate through entries in YAML file
        servers.each do |server|
        
            config.vm.define server['name'] do |srv|
    
                srv.vm.box = server['box']
                srv.vm.box_url = server['box_url']
    
                srv.vm.provider :aws do |aws, override|
    
                    ### Dont do these here, better to use awscli with a profile
                    #aws.access_key_id = "YOUR KEY"
                    #aws.secret_access_key = "YOUR SECRET KEY"
                    #aws.session_token = "SESSION TOKEN"
    
                    aws.region = server["aws_region"] if server["aws_region"]
                    aws.keypair_name = server["aws_keypair_name"] if server["aws_keypair_name"]
                    aws.subnet_id = server["aws_subnet_id"] if server["aws_subnet_id"]
                    aws.associate_public_ip = server["aws_associate_public_ip"] if server["aws_associate_public_ip"]
                    aws.security_groups = server["aws_security_groups"] if server["aws_security_groups"]
                    aws.iam_instance_profile_name = server['aws_iam_role'] if server['aws_iam_role']
                    aws.ami = server["aws_ami"] if server["aws_ami"]
                    aws.instance_type = server["aws_instance_type"] if server["aws_instance_type"]
                    aws.tags = server["aws_tags"] if server["aws_tags"]
                    aws.user_data = server["aws_user_data"] if server["aws_user_data"]
    
                    override.ssh.username = server["aws_ssh_username"] if server["aws_ssh_username"]
                    override.ssh.private_key_path = server["aws_ssh_private_key_path"] if server["aws_ssh_private_key_path"]           
    
                    config.vm.synced_folder ".", "/vagrant", type: "rsync"
    
                    config.vm.provision :shell, path: server["provision"] if server["provision"]
    
                end  
            end
        end
    end
    

    Create servers.yaml

    This file contains the information that will be used by the Vagrantfile, this includes
    AWS region: Which region will this EC2 server run
    AWS keypair: Key used to connect to your launched EC2 instance
    AWS subnet id: Where will this EC2 instance sit in the AWS network
    AWS associate public ip: Do you need a public IP? true or false
    AWS security group: What AWS security group should be associated, should allow Puppetserver needed ports and whatever else you need (ssh, etc)
    AWS ami: Which AMI will you be using I am using a CentOS7
    AWS instance type: Puppetserver needs enough CPU/RAM, during my testing m3.xlarge was appropriate
    AWS SSH username: The EC2 instance user (depends on which AMI you choose), the CentOS AMI expects ec2-user
    AWS SSH private key path: The local path to the SSH key pair
    AWS User Data: I am adding user data which will execute a bash script that allows Vagrant to interact with the launched EC2 instance
    AWS Tags: This is not required for Vagrant and AWS/EC2, but in my provision script I am using the AWS Name Tag to be the system’s hostname, the other 2 tags are there for demonstration purposes
    provision: This is a provisioning script that will be run on the EC2 instance – this is the script that install the Puppet Enterprise server
    AWS IAM Role: You don’t need to add a role when working with Vagrant and AWS/EC2, but I am using a specific IAM role to allow the launched EC2 instance to be able to get information about its AWS Tags, so it is important that you provide it with a Role that allows DescribeTags, see below IAM policy:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "XXXXXXX",
                "Action": [
                    "ec2:DescribeTags"
                ],
                "Effect": "Allow",
                "Resource": "*"
            }
        ]
    }

    Now use your data in the servers.yaml file
    // servers.yaml

    ---
    - name: puppet4
      box: dummy
      box_url: https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box
      aws_region: "us-east-1"
      aws_keypair_name: "john-key"
      aws_subnet_id: "subnet-0001" 
      aws_associate_public_ip: false
      aws_security_groups: ['sg-0001'] 
      aws_ami: "ami-0001"
      aws_instance_type: m3.xlarge
      aws_ssh_username: "ec2-user"
      aws_iam_role: "iam-able-to-describe-tags"
      aws_ssh_private_key_path: "/Users/john/.ssh/john-key.pem"
      aws_user_data: "#!/bin/bash\nsed -i -e 's/^Defaults.*requiretty/# Defaults requiretty/g' /etc/sudoers"
      aws_tags: 
        Name: 'puppet4'
        tier: 'stage'
        application: 'puppetserver'
      update: false
      provision: install_puppetserver.sh
    

    At this point you can spin up EC2 instances using the above Vagrantfile and servers.yaml file. If you add provision: install_puppetserver.sh to the servers.yaml file as I did and add the below script you will have a Puppet Enterprise server ready to go.

    // install_puppetserver.sh

    #!/bin/bash -xe
    
    # Ensure pip is installed
    if ! which pip; then
    	curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
    	python get-pip.py
    fi
    
    # Upgrade pip 
    /bin/pip install --upgrade pip
    
    # Upgrade awscli tools (They are installed in the AMI)
    /bin/pip install --upgrade awscli
    
    # Get hostname from AWS Name Tag (requires the EC2 instance to have an IAM role that allows DescribeTags)
    AWS_INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
    AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | grep region | cut -d\" -f4)
    AWSHOSTNAME=$(aws ec2 describe-tags --region ${AWS_REGION} --filters "Name=resource-id,Values=${AWS_INSTANCE_ID}" --query "Tags[?Key=='Name'].Value[] | [0]" | cut -d\" -f2)
    
    # Set hostname (use the AWS Name Tag)
    hostnamectl set-hostname ${AWSHOSTNAME}.cpg.org
    
    # Update system and install wget, git
    yum update -y
    yum install wget git -y
    
    # Set puppet.cpg.org as hostname in hosts file
    echo "$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4) puppet puppet.cpg.org" >> /etc/hosts
    
    # Download puppet 
    wget "https://pm.puppetlabs.com/cgi-bin/download.cgi?dist=el&rel=7&arch=x86_64&ver=2016.4.0" -O puppet.2016.4.0.tar.gz
    tar -xvzf puppet.2016.4.0.tar.gz
    cd puppet-enterprise*
    
    # Create pe.conf file
    touch pe.conf
    echo '{' >> pe.conf
    echo '"console_admin_password": "puppet"' >> pe.conf
    echo '"puppet_enterprise::puppet_master_host": "%{::trusted.certname}"' >> pe.conf
    echo '}'  >> pe.conf
    
    echo "Install Puppetserver"
    ./puppet-enterprise-installer -c pe.conf
    
    echo "Adding * to autosign.conf"
    cat >> /etc/puppetlabs/puppet/autosign.conf <<'AUTOSIGN'
    *
    AUTOSIGN
    
    # Run puppet agent
    /usr/local/bin/puppet agent -t
    

In this post I am showing how to use generate a MySQL 5 password-hash that can be used to create MySQL GRANTS using a hash instead of a password.

To use a password-hash to create GRANTs:

GRANT ALL ON *.* to user@% identified by PASSWORD '';

A good use case is the Puppet puppetlabs-mysql module to automate the MySQL environment, You can automate/define USER and GRANT creation by using the code below, but notice that it requires a password-hash instead of a password:

users => {
  'someuser@localhost' => {
    ensure                   => 'present',
    max_connections_per_hour => '0',
    max_queries_per_hour     => '0',
    max_updates_per_hour     => '0',
    max_user_connections     => '0',
    password_hash            => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF',
  },
}

OR:

mysql_user { 'root@127.0.0.1':
  ensure                   => 'present',
  max_connections_per_hour => '0',
  max_queries_per_hour     => '0',
  max_updates_per_hour     => '0',
  max_user_connections     => '0',
  password_hash            => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF',
}

They recommend using mysql_password() for creating such a hash. But that means you need to have a MySQL server available.
In this post I am writing about getting those hashes using Python, I wrote a program/script to get the password-hash programatically.

The Python program/script can be found at:
https://github.com/parcejohn/mysql_password_hash

Usage

$ ./mysql_password_hash -h
usage: mysql_password_hash [-h] [-p PASSWORD | -r] [-l PASSWORD_LENGTH]

MySQL Password Hash Generator

optional arguments:
  -h, --help            show this help message and exit
  -p PASSWORD, --password PASSWORD
                        Enter a password
  -r, --generate_random
                        Generate a random password
  -l PASSWORD_LENGTH, --password_length PASSWORD

# Using Command line arguments – User provided password (e.g. ‘secret’)

$ mysql_password_hash -p secret
PASSWORD: secret
HASH: *14e65567abdb5135d0cfd9a70b3032c179a49ee7

# Using Command line arguments – Random password with length=20 (default length=12)

$ mysql_password_hash -r -l 20
PASSWORD: gnlrn96^g18jcblmssa6
HASH: *e3cbe60709e8abe2082c92cc5e72a762d5f18e22

# interactive mode (no arguments)

mysql_password_hash

You can use the plethora of Vagrant Boxes available online, but there are times that you want/need to create your own Vagrant Box, based on specific requirements, with specific packages and most importantly with a specific ssh key pair.

In this guide I will walk through the process of creating a Vagrant BOX from scratch. I will be using a Mac with VMware Fusion and my Vagrant Box will be based on RHEL/CentOS.

1)Create a kickstart configuration file that you will use to automate the installation.
You can do it manually but I prefer/recommend using a kickstart file because you are software-defining the resulting machine and you can source control it. I placed the kickstart on a web server, in this case in my own Mac system so that it is reachable by the VM during installation.

Pre-requisites:
root password:
You can use the password ‘vagrant‘ as is on the kickstart, or you can provide the SHA512 hash of the root password you want. You can do this in several ways from a Linux terminal

mkpasswd -m sha-512
OR
python -c 'import crypt; print crypt.crypt("password", "$6$somesalt$")'

//You then place this hash in the kickstart below, for example:
// rootpw  --iscrypted $6$AcXXXXXX$ZfE9iCSvnezqs3/t47dcPRxozcWACkdITx38T.oTMESLzVv8yjSm1pVE4JC5FmdGojpg8StRhfi.peduXXXXX

SSH key pair:
You need to create a key pair that you will use for Vagrant. The way it works is that the generated public key will be added to the authorized keys allowed to ssh as the user ‘vagrant’ in the custom Vagrant BOX while the vagrant software on the host(Mac) will use the private key to connect from the host(Mac) to the Vagrant BOX(vm)
You can create the ssh key pair as follows:

ssh-keygen -t rsa -C "Vagrant" -f 'vagrant_rsa'

// You then place the contents of vagrant_rsa.pub on the kickstart below, replacing the XXXX.....'s

Once you have the password and SSH keys, you can use the below kickstart (replacing the password and SSH public key accordingly)

# ks.cfg

install
cdrom
text
lang en_US.UTF-8
keyboard us
network --onboot yes --device eth0 --bootproto dhcp --noipv6
rootpw  vagrant
firewall --disabled
authconfig --enableshadow --passalgo=sha512
selinux --disabled
timezone --utc America/New_York
bootloader --location=mbr --driveorder=sda --append="crashkernel=auto"
zerombr
clearpart --initlabel --drives=sda --all
part /boot --fstype=ext4 --size=500
part pv.100 --grow --size=1
volgroup vg0 --pesize=4096 pv.100
logvol swap --name=lv_swap --vgname=vg0 --size=2048
logvol / --fstype=ext4 --name=lv_root --vgname=vg0 --grow --size=1024
poweroff

## Install needed software (this is for ESX5/Fusion5 compatibility)
repo  --name=vmware-tools-collection   --baseurl=http://packages.vmware.com/tools/esx/5.0u3/rhel6/x86_64

%packages --nobase
@core
vmware-tools-hgfs
vmware-tools-esx-nox
openssh-clients
%end

%post
############################### BEGIN post-kickstart actions

# See: https://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/udev.html
/bin/rm -f /etc/udev/rules.d/70-persistent-net.rules
/bin/ln -s /dev/null /etc/udev/rules.d/70-persistent-net.rules
/bin/rm -rf /dev/.udev/
/bin/rm /lib/udev/rules.d/75-persistent-net-generator.rules

# Clean hardcoded MAC address
/bin/sed -i -e 's/^HWADDR=.*$//' -e 's/^UUID=.*$//' /etc/sysconfig/network-scripts/ifcfg-eth0

# Make boot verbose
/bin/sed -i -e 's/^splash/#splash/' -e 's/^hidden/#hidden/' -e 's/rhgb//' -e 's/quiet//' /boot/grub/grub.conf

# Remove screensaver/blank screen 
/bin/sed -i -r s/"(^[\t]*kernel.*)"/"\1 consoleblank=0"/ /boot/grub/grub.conf 

### BEGIN: Vagrant specific pre-requisites ###
# Enable vmhgfs
/bin/echo "modprobe vmhgfs" > /etc/sysconfig/modules/vmhgfs.modules
/bin/chmod +x /etc/sysconfig/modules/vmhgfs.modules

# Create vagrant user
/usr/sbin/useradd vagrant
/bin/mkdir /home/vagrant/.ssh
/bin/chmod 700 /home/vagrant/.ssh
cat >           /home/vagrant/.ssh/authorized_keys <<'VAGRANT_RSA'
ssh-rsa XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX vagrant
VAGRANT_RSA

/bin/chmod 600 /home/vagrant/.ssh/authorized_keys
/bin/chown -R vagrant /home/vagrant/.ssh

## Allow vagrant user to sudo
/bin/sed -i -e 's/Defaults[[:space:]]*requiretty/###Allow vagrant user to sudo\nvagrant ALL=(ALL:ALL) NOPASSWD:ALL\nDefaults requiretty\nDefaults:vagrant !requiretty/' /etc/sudoers 
### END: Vagrant specific pre-requisites ###

# Clean yum stuff to make resulting machine leaner
yum clean all

############################### END post-kickstart actions

%end

2) Creating the custom VM

Add new VM
vm_create_new

Continue without Disc

vm_continuewithout_cd

Create a custom virtual machine
vm_create_custom

Choose RHEL6_64
vm_os_choice

Click on Customize settings
vm_customize_settings

 

Select 1CPU and 1GB of RAM (The VM should be as minimal as possible, you can increase CPU and RAM from Vagrant)
cpu_ram

 

Disable unnecessary stuff (print sharing, bluetooth, sound card)
disable_bluetooth

disable_printsharing

disable_sound

 

3) Start installation

Boot VM and add the RHEL/CentOS ISO disk (Choose Disc or Disc Image...)
connect_cd

Point to the kickstart file served by the web server
rhel_bootscreen2box

Installation will proceed and the VM will be powered off (based on the kickstart 'poweroff' option)

4) Once VM is off Disable CD drive
disable_cd

5) Quit VMware Fusion (this is to clear the lock files so that you have a pristine VM)

 

6) Difragment and Shrink the VMDK

john@mac:~$ /Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -d ~/Documents/Virtual\ Machines.localized/vagrant_rhel6_64.vmwarevm/Virtual\ Disk.vmdk
Defragment: 100% done.

john@mac:~$ /Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -k ~/Documents/Virtual\ Machines.localized/vagrant_rhel6_64.vmwarevm/Virtual\ Disk.vmdk
Shrink: 100% done.
Shrink completed successfully.

 

7) On the folder where the virtual machine was created, create the following metadata.json file:

john@mac:~/Documents/Virtual Machines.localized/vagrant_rhel6_64.vmwarevm $ printf "{\n\t\"provider\":\"vmware_desktop\"\n}\n" > metadata.json
john@mac:~/Documents/Virtual Machines.localized/vagrant_rhel6_64.vmwarevm $ cat metadata.json
{
"provider":"vmware_desktop"
}

8) Create the Vagrant box container

john@mac:~/Documents/Virtual Machines.localized/vagrant_rhel6_64.vmwarevm $ tar cvzf vagrant-rhel6.box ./*
a ./Virtual Disk-s001.vmdk
a ./Virtual Disk-s002.vmdk
a ./Virtual Disk-s003.vmdk
a ./Virtual Disk-s004.vmdk
a ./Virtual Disk-s005.vmdk
a ./Virtual Disk-s006.vmdk
a ./Virtual Disk-s007.vmdk
a ./Virtual Disk-s008.vmdk
a ./Virtual Disk-s009.vmdk
a ./Virtual Disk-s010.vmdk
a ./Virtual Disk-s011.vmdk
a ./Virtual Disk.vmdk
a ./metadata.json
a ./vagrant_rhel6_64.nvram
a ./vagrant_rhel6_64.plist
a ./vagrant_rhel6_64.vmsd
a ./vagrant_rhel6_64.vmx
a ./vagrant_rhel6_64.vmxf
a ./vmware.log

9) Add the Box to Vagrant

john@mac:~$ vagrant box add -name vagrant-rhel6 ~/Documents/workspace/vagrant/boxes/vagrant-rhel6.box
==> box: Adding box 'vagrant-rhel6' (v0) for provider:
box: Downloading: file:///Users/john/Documents/workspace/vagrant/boxes/vagrant-rhel6.box
==> box: Successfully added box 'vagrant-rhel6' (v0) for 'vmware_desktop'!

Note: You can also add this Vagrant Box to a webserver so that it can be shared with others, in my case I uploaded the Box to a web server:

john@mac:~$ scp ~/Documents/workspace/vagrant/boxes/vagrant-rhel6.box john@webserver:/var/www/html/vagrant/

Update: You can create a Vagrant Box Catalog that will allow versioning of the Boxes and allows the end-users/developers to get automatic update notifications when you have an updated Vagrant box: Versioning and Update Notifications for your Vagrant boxes