DigitalOcean, Chef and Ohai – Retrieving a Droplet’s Private IP Address

Recently, I attempted to use the Ohai value for node['cloud_v2']['local_ipv4'] and node['cloud']['local_ipv4']['ip_address'] to determine the Private IP address of my Cloud-based nodes in a Chef cookbook.  Unfortunately, it does not work accurately for DigitalOcean instances any longer.

According to DigitalOcean documentation, if Private Networking is enabled, the IP will be assigned to eth1.  Recently, I noticed that a second Private IP address has begun to be assigned to the eth0 interface.  This is/was causing Ohai to assign the eth0 secondary (private) IP address to node['cloud_v2']['local_ipv4'] and node['cloud']['local_ipv4']['ip_address']

As you can see below, there is a second, private IP address assigned to eth0. I believe this has something to do with the recent release of Floating IP Addresses.

bdwyertech@dummy-droplet:~$ cat /etc/network/interfaces
# This file describes the network interfaces available on your
# system and how to activate them. For more information, see
# interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth1 eth0
      iface eth0 inet static
      address 123.234.123.234
      netmask 255.255.255.0
      gateway 123.234.123.1
      up ip addr add 10.13.0.123/16 dev eth0
      dns-nameservers 8.8.8.8 8.8.4.4
iface eth1 inet static
      address 10.128.123.123
      netmask 255.255.0.0

Initially, I just wrote a simple function to detect the IP address on eth1 via the node hash if DigitalOcean is detected by Ohai as the cloud provider. However, querying DigitalOcean metadata seems to be the more robust solution.

DigitalOcean has released a metadata service, similar to AWS, where you can query http://169.254.169.254/metadata/{API_VERSION} for droplet information.  DigitalOcean conveniently allows you to query the droplet’s metadata in its entirety and return it in JSON format.  I’ve went ahead and written a simple library to query this data and bring it into Ruby as a hash.

# DigitalOcean Metadata Chef Library
# rubocop:disable LineLength

require 'net/http'

# Public: This defines a module to retrieve Metadata from DigitalOcean
module DoMetadata
  DO_METADATA_ADDR = '169.254.169.254' unless defined?(DO_METADATA_ADDR)
  DO_SUPPORTED_VERSIONS = %w( v1 )
  DO_DEFAULT_API_VERSION = 'v1'

  def self.http_client
    Net::HTTP.start(DO_METADATA_ADDR).tap { |h| h.read_timeout = 600 }
  end

  # Get metadata for a given path and API version
  def metadata_get(id, api_version = DO_DEFAULT_API_VERSION, json = false)
    path = "/metadata/#{api_version}/#{id}"
    path = "/metadata/#{api_version}.json" if json
    response = http_client.get(path)
    case response.code
    when '200'
      response.body
    when '404'
      Chef::Log.info("Encountered 404 response retreiving DO metadata path: #{path} ; continuing.")
      nil
    else
      fail "Encountered error retrieving DO metadata (#{path} returned #{response.code} response)"
    end
  end
  module_function :metadata_get

  # Retrieve the JSON metadata, and return it as a Ruby hash
  def parse_json_metadata(api_version = DO_DEFAULT_API_VERSION)
    retrieved_metadata = metadata_get(nil, api_version, true)
    return unless retrieved_metadata
    JSON.parse(retrieved_metadata) if retrieved_metadata
  end
  module_function :parse_json_metadata
end

Code also available on my Github.

This conveniently allows you to query the resulting Ruby hash, and use it in your code.

# => Get the Droplet's Metadata
metadata = DoMetadata.parse_json_metadata

metadata['interfaces']['private'][0]['ipv4']['ip_address'] # => Droplet's Private IP Address
metadata['interfaces']['private'][0]['ipv4']['netmask'] # => Droplet's Private Subnet Mask
Advertisements

Chef – Ohai in AWS EC2 VPC

This is a quick tip to those of you who are using Chef inside an AWS VPC. The EC2 Ohai plugin does not run by default, which prevents some meaningful node attributes from being collected.

The EC2-specific node attributes I find most useful are:

node['ec2']['instance_id'] # => Instance's ID
node['ec2']['local_ipv4'] # => Instance's IPv4 Address
node['ec2']['placement_availability_zone'] # => Instance's Region & Availability Zone
node['ec2']['ami_id'] # => Instance's Baseline AMI

To get your instances inside a VPC to pick up meaningful node attributes related to EC2, you have to create an Ohai hint file for the EC2 plugin. To do so, simply throw this into your initial bootstrap.

mkdir -p /etc/chef/ohai/hints && touch ${_}/ec2.json

Make sure you don’t do that blindly on non-EC2 instances, as it will significantly increase the execution time of Ohai.  You might want to wrap this in an if statement, and use something like the example below.

if [[ $(dmidecode | grep -i amazon) ]] ; then
 mkdir -p /etc/chef/ohai/hints && touch ${_}/ec2.json 
fi

Chef & Berkshelf – SSL Certificate Validation Error on Windows

When using Windows, Chef, Vagrant and Berkshelf, you may encounter an issue with certificate validation. The error you may encounter is as follows:

Faraday::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed

The problem is Ruby is expecting the SSL_CERT_FILE environmental variable to be set. This should point to a CA bundle to use for SSL certificate validation. If this is not set, certificate validation will fail. You can adjust the Berkshelf configuration to not verify SSL certificates, but I have found this setting to be problematic and sporadically not work. The better option, is to actually set the environmental variable.

Luckily, Vagrant comes with a CA bundle. We can leverage Vagrant’s installation information in the registry to determine the installation location of Vagrant and use this to set the SSL_CERT_FILE variable appropriately. The following PowerShell script can be used to do this. It has a set and unset function. By default, running ./Set-SSLCert_Chef_Vagrant.ps1 set will set SSL_CERT_FILE as a user-specific environmental variable.

After running this script, close and re-open any open command prompts for the new variable to take effect. Vagrant is required for this script to work correctly.

# Filename: Set-SSLCert_Chef_Vagrant.ps1
# Brian Dwyer - 5/22/14

# ***USAGE***
# To Setup the SSL_CERT_FILE Variable
# ./Set-SSLCert_Chef_Vagrant.ps1 set

# To Remove the SLL_CERT_FILE Variable
# ./Set-SSLCert_Chef_Vagrant.ps1 unset

# System-Wide Environmental Variables
$System_Vars='HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'

# User-Specific Environmental Variables
$User_Vars='HKCU:\Environment'

# Check if Vagrant is installed
If (!$env:Path.Contains('Vagrant'))
  {
    echo ""
    echo "/======| Error: Vagrant does not seem to be installed. |=====\"
    echo ""
    pause
    exit
    }

# Get Vagrant Installation Directory
$Vagrant_DIR = $env:Path.Split(';') -like "*vagrant*" | Out-String -Stream | Split-Path -Parent

# Registry Property Key/Value
$Reg_Key='SSL_CERT_FILE'
$Reg_Value="$Vagrant_DIR\embedded\cacert.pem"

# Setup
If ( $args[0] -eq 'set' )
  {
  echo "/======| Setting up Registry Key... |=====\"
  Set-ItemProperty $User_Vars -Name $Reg_Key -Value $Reg_Value
  pause
  exit
  }
Elseif ( $args[0] -eq 'unset' )
  {
  echo "/======| Removing Registry Key... |=====\"
  Remove-ItemProperty $User_Vars -Name $Reg_Key
  pause
  exit
  }
 Else
  {
  echo '------------------------------------------------'
  echo "|*|-Set SSL_CERT_FILE Environmental Variable-|*|"
  echo '------------------------------------------------'
  echo "|   Use 'set' or 'unset' to control script     |"
  echo '------------------------------------------------'
  pause
  exit
  }