# # Author:: Philip (flip) Kromer (flip@infochimps.com) # Copyright:: Copyright (c) 2011 Infochimps, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # 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 File.expand_path('ironfan_script', File.dirname(File.realdirpath(__FILE__))) require File.expand_path('cluster_ssh', File.dirname(File.realdirpath(__FILE__))) class Chef class Knife # # Runs the ssh command to open a SOCKS proxy to the given host, and writes a # PAC (automatic proxy config) file to # /tmp/ironfan_proxy-YOURNAME.pac. Only the first host is used, even if # multiple match. # # Why not use Net::Ssh directly? The SOCKS proxy support was pretty # bad. Though ugly, exec'ing the command works. # class ClusterProxy < Ironfan::Script import_banner_and_options(Chef::Knife::ClusterSsh, :except => [:concurrency, ]) banner 'knife cluster proxy CLUSTER[-FACET[-INDEXES]] (options) - Runs the ssh command to open a SOCKS proxy to the given host, and writes a PAC (automatic proxy config) file to /tmp/ironfan_proxy-YOURNAME.pac. Only the first host is used, even if multiple match.' option :background, :long => "--[no-]background", :description => "Requests ssh to go to background after setting up the proxy", :boolean => true, :default => true option :socks_port, :long => '--socks-port', :short => '-D', :description => 'Port to listen on for SOCKS5 proxy', :default => '6666' def relevant?(server) server.machine.running? end def perform_execution(target) svr = target.first cmd = command_for_target(svr) dump_proxy_pac exec(*cmd) end def command_for_target(svr) config[:attribute] ||= Chef::Config[:knife][:ssh_address_attribute] || "fqdn" config[:ssh_user] ||= Chef::Config[:knife][:ssh_user] #config[:identity_file] ||= svr.server.selected_cloud.ssh_identity_file config[:host_key_verify] ||= Chef::Config[:knife][:host_key_verify] || (not config[:no_host_key_verify]) # pre-vs-post 0.10.4 address = svr.machine.public_hostname if address.blank? && (svr.chef_node) address = format_for_display( svr.chef_node )[config[:attribute]] end cmd = [ 'ssh', '-N' ] cmd += [ '-D', config[:socks_port].to_s ] cmd += [ '-p', config[:port].to_s ] if config[:port].present? cmd << '-f' if config[:background] cmd << "-#{'v' * config[:verbosity].to_i}" if (config[:verbosity].to_i > 0) cmd += %w[ -o StrictHostKeyChecking=no ] if config[:host_key_verify] cmd += %w[ -o ConnectTimeout=10 -o ServerAliveInterval=60 -o ControlPath=none ] cmd += [ '-i', File.expand_path(config[:identity_file]) ] if config[:identity_file].present? cmd << (config[:ssh_user] ? "#{config[:ssh_user]}@#{address}" : address) Chef::Log.debug("Cluster proxy config: #{config.inspect}") Chef::Log.debug("Cluster proxy command: #{cmd.inspect}") ui.info(["SOCKS Proxy on", "local port", ui.color(config[:socks_port], :cyan), "for", ui.color(svr.name, :cyan), "(#{address})" ].join(" ")) cmd end # # Write a .pac (automatic proxy configuration) file # to /etc/ironfan_proxy-YOURNAME.pac # def dump_proxy_pac pac_filename = File.expand_path(File.join('/tmp', "ironfan_proxy-#{ENV['USER']}.pac")) ui.info("point your browser at PAC (automatic proxy config file) file://#{pac_filename}") File.open(pac_filename, 'w') do |f| f.print proxy_pac_contents end end EC2_PROXY_PATTERNS = [] unless defined?(EC2_PROXY_PATTERNS) EC2_PROXY_PATTERNS.unshift("*ec2*.amazonaws.com", "*ec2.internal*", "*compute-*.amazonaws.com", "*compute-*.internal*", "*domu*.internal*", "10.*",) def proxy_pac_contents proxy_patterns = EC2_PROXY_PATTERNS proxy_patterns += Array(Chef::Config[:cluster_proxy_patterns]) rules = proxy_patterns.compact.map{|str| "(shExpMatch(host, %-28s))" % %Q{"#{str}"} } %Q{function FindProxyForURL(url, host) { if (#{rules.join(" ||\n ")} ) { return "SOCKS5 localhost:#{config[:socks_port]}"; } return "DIRECT"; }\n} end def prepares? false end def aggregates? false end end end end