cloudamatic/mu

View on GitHub
bin/mu-tunnel-nagios

Summary

Maintainability
Test Coverage
#!/usr/local/ruby-current/bin/ruby
# Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
#
# Licensed under the BSD-3 license (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License in the root of the project or at
#
#     http://egt-labs.com/mu/LICENSE.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'syslog'
require 'syslog/logger'

logger = Syslog::Logger.new "mu-tunnel-nagios"

#Syslog.open("mu-tunnel-nagios", Syslog::LOG_PID, Syslog::LOG_DAEMON | Syslog::LOG_LOCAL3)

require 'net/ssh/config'
require 'net/ssh/gateway'
require 'optimist'
require 'etc'
SSH_CMD = "env -i /usr/bin/ssh"
NAGIOS_HOME = "/opt/mu/var/nagios_user_home"


$opts = Optimist::options do
  banner <<-EOS
A utility to run a command through a remote SSH tunnel. Intended to serve as an intermediary for Nagios plugins attempting to contact private nodes through a bastion host.
Usage:
#{$0} -d <host> -p <port> -c <command> [-a <arg>] [-k </path/to/key>] [-g <host> [-u <user>]]
  EOS
  opt :desthost, "The host to which we'll be connecting on the other side of the tunnel.", :require => true, :type => :string
  opt :port, "The port to which we'll be connecting on the other side of the tunnel.", :require => true, :type => :integer
  opt :command, "The command which we will be tunneling to the remote host.", :require => true, :type => :string
  opt :argument, "An argument to append to our command to specify a (local tunnel) port.", :require => false, :default => "-p", :type => :string
  opt :gateway, "The gateway host to use. If not specified, we'll assume we have something correct in our global ssh config.", :require => false, :type => :string
  opt :key, "Path to an SSH private key to use when connecting to the gateway host. If not specified, we'll assume we have something correct in our global ssh config.", :require => false, :type => :string
  opt :user, "The SSH user with which to connect to the gateway. If not specified, we'll assume we have something correct in our global ssh config.", :require => false, :type => :string, :default => nil
#    opt :local_ip, "The local (private) IP to which we'll be connecting on the other side of the tunnel.", :require => false, :type => :string, :default => "127.0.0.1"
  opt :directcommand, "The command to run if we don't need to tunnel to get to the remote host.", :require => false, :type => :string
  opt :verbose, "Debugging noise.", :require => false, :type => :boolean, :default => false
end

dest_host = $opts[:desthost]
dest_port = $opts[:port]
cmd = $opts[:command]
nat_host = $opts[:gateway]
nat_ssh_user = $opts[:user]
port_arg = $opts[:argument]
nat_ssh_key = $opts[:key]
#local_ip = $opts[:local_ip]
verbose = $opts[:verbose]

if !dest_host or !dest_port or !cmd
  Optimist::die "Missing required arguments"
end
nat_host = dest_host if !nat_host

ENV.clear
ENV['HOME'] = NAGIOS_HOME

needs_gateway = true
begin
  ssh_conf = File.read("#{NAGIOS_HOME}/.ssh/config")
  opts = Net::SSH::Config.for(dest_host, ["#{NAGIOS_HOME}/.ssh/config"])
  needs_gateway = false if !$opts.has_key?(:proxy)
rescue Errno::EACCES => e
  Syslog.log(Syslog::LOG_NOTICE, "Couldn't read #{NAGIOS_HOME}/.ssh/config: #{e.message}")
  puts "Couldn't read #{NAGIOS_HOME}/.ssh/config: #{e.message}"
  exit 3
end

full_cmd = output = nil
begin
  if !needs_gateway
    puts "No SSH gateway configured for #{dest_host}, running #{cmd} directly" if verbose
    cmd = $opts[:directcommand] if $opts[:directcommand_given]
    full_cmd = cmd
  else
    gateway = nil
    if verbose
      gateway = Net::SSH::Gateway.new(
        nat_host,
        nil,
        :config => ["#{NAGIOS_HOME}/.ssh/config"],
        :keys_only => true,
        :logger => logger,
        :auth_methods => ['publickey'],
        :use_agent => false,
        :verbose => :debug
      )
    else
      gateway = Net::SSH::Gateway.new(
        nat_host,
        nil,
        :config => ["#{NAGIOS_HOME}/.ssh/config"],
        :keys_only => true,
        :auth_methods => ['publickey'],
        :use_agent => false
      )
    end
    port = gateway.open("127.0.0.1", dest_port)
    if port_arg.empty?
      full_cmd = cmd
    else
      full_cmd = "#{cmd} #{port_arg} #{port}"
    end
    if verbose
      puts "Opening gateway to #{dest_host}:#{dest_port} by tunneling local #{port} to #{nat_host} and running #{full_cmd}"
      Syslog.log(Syslog::LOG_NOTICE, "Opening gateway to #{dest_host}:#{dest_port} by tunneling local #{port} to #{nat_host} and running #{full_cmd}")
    end
  end
  output = %x{#{full_cmd} 2>&1}
  to_return = $?.exitstatus > 3 ? 3 : $?.exitstatus
  puts output
rescue Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout  => e
  Syslog.log(Syslog::LOG_NOTICE, e.message)
  puts e
  to_return = 2
rescue Exception => e
  Syslog.log(Syslog::LOG_NOTICE, "Tunnel failure for #{dest_host}:#{dest_port} from config #{NAGIOS_HOME}/.ssh/config, `#{full_cmd}`: #{e.inspect} **** #{output} **** ENV: #{envhash.to_s}")
  puts e
  to_return = 3
ensure
  begin
    gateway.shutdown! if !gateway.nil?
  rescue Exception => e
    if verbose
      puts "Got #{e.inspect} closing down gateway tunnel (remote port may have been dead)"
      Syslog.log(Syslog::LOG_NOTICE, "Got #{e.inspect} closing down gateway tunnel (remote port may have been dead)")
    end
  end
  if verbose
    puts "Exiting with status #{to_return}"
    Syslog.log(Syslog::LOG_NOTICE, "Exiting with status #{to_return}")
  end
  exit to_return.to_i
end