#!/usr/bin/env ruby # # William Stearns # Copyright (c) 2013, William Stearns # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the CloudPassage, Inc. nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL CLOUDPASSAGE, INC. BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED ANDON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Based on: # demo ruby cloudpassage API stuff # Tim Spencer # Thanks, Tim! # # you may need to install the oauth2, rest-client, and json gems with: # sudo gem install oauth2 rest-client json #Version 0.4 #======== User-modifiable values api_key_file = '/etc/halo-api-keys' default_host = 'api.cloudpassage.com' report_dir = "./" #Timeouts manually extended to handle long setup time for large numbers #of events. Set to -1 to wait forever (although nat, proxies, and load #balancers may cut you off externally. timeout=600 open_timeout=600 #Add the directory holding this script to the search path so we can find wlslib.rb $:.unshift File.dirname(__FILE__) #======== End of user-modifiable values #======== Functions #======== End of functions #======== Loadable modules require 'rubygems' require 'optparse' require 'oauth2' require 'rest-client' require 'json' load 'wlslib.rb' #======== End of loadable modules #======== Initialization api_client_ids = [ ] api_secrets = { } api_hosts = { } my_proxy = nil con_ip_of = { } alt_ips_of = { } default_key = "" #======== End of initialization optparse = OptionParser.new do |opts| opts.banner = "Generate host files from the active list on the Halo Portal. Usage: gen_host_files.rb [options]" opts.on("-i keyid", "--api_client_id keyid", "API Key ID (can be read only or full access). If no key specified, use first key. If ALL , use all keys.") do |keyid| api_client_ids << keyid unless api_client_ids.include?(keyid) end opts.on_tail("-h", "--help", "Show help text") do $stderr.puts opts exit end end optparse.parse! default_key = load_api_keys(api_key_file,api_secrets,api_hosts,default_host) if default_key == "" $stderr.puts "Unable to load any keys from #{api_key_file}, exiting." exit 1 end #Validate all user params if (api_client_ids.length == 0) $stderr.puts "No key requested on command line; using the first valid key in #{api_key_file}, #{default_key}." api_client_ids << default_key elsif (api_client_ids.include?('ALL')) or (api_client_ids.include?('All')) or (api_client_ids.include?('all')) $stderr.puts "\"ALL\" requested; using all available keys in #{api_key_file}: #{api_secrets.keys.join(',')}" api_client_ids = api_secrets.keys.sort end #To accomodate a proxy, we need to handle both RestClient with the #following one-time statement, and also as a :proxy parameter to the #oauth2 call below. if ENV['https_proxy'].to_s.length > 0 my_proxy = ENV['https_proxy'] RestClient.proxy = my_proxy $stderr.puts "Using proxy: #{RestClient.proxy}" end #Use a simple file lock to make sure that only one copy of the script is running at a time. lock_file = "/tmp/gen_host_files.lock" File.open(lock_file, "a") {} unless File.new(lock_file).flock( File::LOCK_NB | File::LOCK_EX ) $stderr.puts "It appears another copy of this script is running and holds the lock on #{lock_file}. Exiting." exit end api_client_ids.each do |one_client_id| if (api_secrets[one_client_id].to_s.length == 0) $stderr.puts "Invalid or missing api_client_secret for key id #{one_client_id}, skipping this key." $stderr.puts "The mode 600 file #{api_key_file} should contain one line per key ID/secret like:" $stderr.puts "myid1|mysecret1" $stderr.puts "myid2|mysecret2[|optional apihost:port]" else $stderr.puts "Pulling host information from #{api_hosts[one_client_id]} using key #{one_client_id}" #Acquire a session key from the Halo Portal for use by the rest of this script token = get_auth_token(one_client_id,api_secrets[one_client_id],my_proxy,api_hosts[one_client_id]) if token == "" $stderr.puts "Unable to retrieve a token, skipping account #{one_client_id}." else servers_json = api_get("https://#{api_hosts[one_client_id]}/v1/servers",timeout,open_timeout,token) servers_json['servers'].each do |one_server| hostname_to_use = one_server['hostname'] while con_ip_of.has_key?(hostname_to_use) hostname_to_use += "_" end con_ip_of[hostname_to_use] = one_server['connecting_ip_address'] alt_ips_of[hostname_to_use] = [ ] unless alt_ips_of.has_key?(hostname_to_use) one_server['interfaces'].each do |one_interface| altip = one_interface['ip_address'] if ( ! alt_ips_of[hostname_to_use].include?(altip) ) and ( altip != con_ip_of[hostname_to_use] ) alt_ips_of[hostname_to_use] << altip end end end end end end begin #Create, or truncate existing file for write File.open(report_dir + "/etc_hosts", "w") { |hosts_handle| hosts_handle.puts "#Auto-generated etc_hosts file" con_ip_of.keys.sort.each do |one_hostname| hosts_handle.puts "#{con_ip_of[one_hostname]}\t\t#{one_hostname}" alt_ips_of[one_hostname].each do |one_alt_address| hosts_handle.puts "#{one_alt_address}\t\t#{one_hostname}-alt" end end hosts_handle.puts "#End of auto-generated etc_hosts file" hosts_handle.close } rescue $stderr.puts "Unable to write to #{report_dir}/etc_hosts ; permissions?" end begin #Create, or truncate existing file for write File.open(report_dir + "/etc_ssh_config", "w") { |config_handle| config_handle.puts "#Auto-generated ssh config file" con_ip_of.keys.sort.each do |one_hostname| config_handle.puts "" config_handle.puts "Host #{one_hostname} #{con_ip_of[one_hostname]} #{alt_ips_of[one_hostname].join(" ")}" config_handle.puts "\tHostname\t#{con_ip_of[one_hostname]}" config_handle.puts "\tHostKeyAlias\t#{one_hostname}" end config_handle.puts "#End of auto-generated ssh config file" config_handle.close } rescue $stderr.puts "Unable to write to #{report_dir}/etc_ssh_config ; permissions?" end