require "clusterlb/version" require 'json' require 'console_table' require 'colorize' require 'inifile' require 'aws-sdk' require 'fileutils' require 'net/ssh' module Clusterlb class << self attr_accessor :config_dir attr_accessor :config_file end self.config_dir = "#{ENV["CLUSTERLB_HOME"]}/configs" self.config_file = "#{self.config_dir}/clusterlb.json" def list_lbs ensure_dir_exitsts config["clusterlb"]["lb_nodes"] end def config envCheck writeSampleConfig(config_file) if !File.exists? config_file begin config = JSON.parse(File.read config_file) rescue puts "ERROR: Unable to load config file: #{ENV["CLUSTERLB_HOME"]}/configs/clusterlb.json " exit 1 end end def cmd_nginx(cmd,node) # test | reload | restart | stop | start , lb01 or "all" allowed_commands=["test","reload","restart","stop","start"] hosts=[] if !allowed_commands.include? cmd puts "command not in the allowed list: #{allowed_commands.join(',')}".colorize(:red) exit 1 end cmd= "configtest" if cmd == "test" if node == "all" list_lbs.each do |h| hosts.push "#{h}.#{config["clusterlb"]["lb_nodes_dns_postfix"]}" end else hosts.push "#{node}.#{config["clusterlb"]["lb_nodes_dns_postfix"]}" end hosts.each do |h| puts "#{h}:".colorize(:light_blue) go_ssh(h,"sudo /etc/init.d/nginx #{cmd}",ENV['USER']) puts "--\n".colorize(:light_blue) end end def aws_config aws_config_file="#{ENV["HOME"]}/.aws/credentials" if !File.exists? aws_config_file puts "Please configure your AWS credentials in #{ENV["HOME"]}/.aws/credentials, and setup profile with the name of #{config["aws"]["profile"]}" exit 1 end awsconfig = IniFile.load(aws_config_file)[config["aws"]["profile"]] if awsconfig.empty? puts "It looks like you're #{ENV["HOME"]}/.aws/credentials file has no section configured for #{config["aws"]["profile"]} profile. Please Fix this." exit 1 end Aws.config.update({ region: config["aws"]["region"], credentials: Aws::Credentials.new(awsconfig['aws_access_key_id'], awsconfig['aws_secret_access_key']) }) end def letsEncryptExampleConfig config=[] config.push "location ^~ /.well-known/acme-challenge/ {" config.push "\tdefault_type \"text/plain\";" config.push "\troot #{ENV["CLUSTERLB_HOME"]}/#{config["LetsEncrypt"]["challange_dir"]};" config.push "}\n" config.push "location = /.well-known/acme-challenge/ {" config.push "\treturn 404;" config.push "}\n" config.join("\n") end def letsEncrypt_getCert(fqdn) le_env = { "le_challange_dir" => "#{ENV["CLUSTERLB_HOME"]}/#{config["LetsEncrypt"]["challange_dir"]}", "le_cert_dir" => "#{ENV["CLUSTERLB_HOME"]}/#{config["LetsEncrypt"]["certificates_dir"]}", "acme" => config["LetsEncrypt"]["acme_bin"], "le_home" => "#{ENV["CLUSTERLB_HOME"]}/#{config["LetsEncrypt"]["acme_home_dir"]}", } puts "Trying to renew Certificate: #{fqdn}".colorize(:light_blue) cmd = "sudo ${acme} --cron --home \"${le_home}\" --issue -d #{fqdn} \ -w ${le_challange_dir} \ --cert-file ${le_cert_dir}/#{fqdn}.pem \ --key-file ${le_cert_dir}/#{fqdn}.key \ --fullchain-file ${le_cert_dir}/#{fqdn}.full.pem" system(le_env, cmd) puts "--\n".colorize(:light_blue) end def letsEncrypt(fqdn) # fqdn | all if fqdn == "all" && config["LetsEncrypt"]["sites_enabled"].count > 0 config["LetsEncrypt"]["sites_enabled"].each do |site| letsEncrypt_getCert(site) end cmd_nginx("reload","all") else letsEncrypt_getCert(fqdn) cmd_nginx("reload","all") end end def get_s3_cert(fqdn) ensure_dir_exitsts aws_config certs_dir="#{ENV["CLUSTERLB_HOME"]}/#{config["clusterlb"]["certificates_dir"]}" Dir.chdir certs_dir s3 = Aws::S3::Client.new resp = s3.list_objects_v2({ bucket: config["aws"]["ssl_cert_bucket"], prefix: fqdn }) date_list=[] if resp["contents"].length <= 0 puts "Sorry, i found no Certificates with that prefix #{fqdn}" exit 1 end resp["contents"].each do |k| date_list.push(DateTime.strptime(k["key"].match('\d{1,2}-\d{1,2}-\d{4}').to_s, "%m-%d-%Y")) end file_extensions=["full.pem","key","pem","ca.pem"] newest_cert_date_string = date_list.sort.reverse.first.strftime("%-m-%-d-%Y") file_extensions.each do |ext| filename="#{fqdn}.#{newest_cert_date_string}.#{ext}" puts "Getting: #{filename} " sourceObj = Aws::S3::Object.new(config["aws"]["ssl_cert_bucket"], "#{filename}") sourceObj.get(response_target: "#{certs_dir}/#{filename}") if !File.exists? "#{certs_dir}/#{filename}" FileUtils.ln_s filename, "#{fqdn}.#{ext}", force: true FileUtils.chown(nil,config["clusterlb"]["nginx_unix_group"],filename) end end def enable_site(site,node) Dir.chdir ENV["CLUSTERLB_HOME"] link_to_dir="#{config["clusterlb"]["sites_enabled"]}/#{node}" if !config["clusterlb"]["lb_nodes"].include? node puts "#{node} is not listed as an active loadballancer, check your configs".colorize(:yellow) exit 1 end if !File.directory?(link_to_dir) puts "#{link_to_dir} does not exist, check your configs".colorize(:yellow) exit 1 end FileUtils.ln_s "../../#{config["clusterlb"]["sites"]}/#{site}", "#{link_to_dir}/#{site}", force: true puts "Enableing Site #{site} on #{node}" end def disable_site(site,node) Dir.chdir ENV["CLUSTERLB_HOME"] link_dir="#{config["clusterlb"]["sites_enabled"]}/#{node}" FileUtils.rm("#{link_dir}/#{site}") puts "Disableing Site #{site} on #{node}" end def matrix_list table_config = [{:key=>:site, :size=>35, :title=>"Site"}] config["clusterlb"]["lb_nodes"].each do |node| table_config.push( {:key=>node.to_sym, :size=>9, :title=>node, :justify=>:center}) end ConsoleTable.define(table_config) do |table| Dir.glob("#{ENV["CLUSTERLB_HOME"]}/#{config["clusterlb"]["sites"]}/*").each do |full_path_file| filename=full_path_file.split('/')[-1] result={:site=>filename} config["clusterlb"]["lb_nodes"].each do |node| result[node.to_sym]="✓".colorize(:green) if File.exists?("#{ENV["CLUSTERLB_HOME"]}/#{config["clusterlb"]["sites_enabled"]}/#{node}/#{filename}") end table << result end end end private def go_ssh(hostname,cmd,username) Net::SSH.start(hostname, username, :keys_only => true ) do |ssh| puts ssh.exec!(cmd) end end def ensure_dir_exitsts [ "#{ENV["CLUSTERLB_HOME"]}/#{config["clusterlb"]["sites_enabled"]}", "#{ENV["CLUSTERLB_HOME"]}/#{config["clusterlb"]["sites"]}", "#{ENV["CLUSTERLB_HOME"]}/#{config["clusterlb"]["certificates_dir"]}" ].each do |dir_path| FileUtils.mkdir dir_path if !File.directory?(dir_path) end config["clusterlb"]["lb_nodes"].each do |node| node_dir="#{ENV["CLUSTERLB_HOME"]}/#{config["clusterlb"]["sites_enabled"]}/#{node}" FileUtils.mkdir node_dir if !File.directory?(node_dir) end end def envCheck if ENV["CLUSTERLB_HOME"].nil? puts "Please set your ENV 'CLUSTERLB_HOME' to point to the directory where your clusterlb lives" exit 1 end end def writeSampleConfig(path) FileUtils.mkdir config_dir if !File.directory?(config_dir) example_config = { :clusterlb => { :sites => "sites", :sites_enabled => "sites-enabled", :lb_nodes => ["lb01","lb02","lb03"], :lb_nodes_dns_postfix => "service_domain.internal.vpc", :certificates_dir => "certs", :nginx_unix_group => "nginx" }, :aws => { :profile => "yourprofile", :region => "us-west-2", :ssl_cert_bucket => "s3-bucket-name" }, :LetsEncrypt => { :sites_enabled => [], :challange_dir => "LetsEncrypt/challage", :certificates_dir => "LetsEncrypt/certs", :acme_home_dir => "LetsEncrypt/.acme.sh", :acme_bin => "/srv/lb-config/lets-encrypt/.acme.sh" } } File.open(path,"w") do |f| f.write(JSON.pretty_generate(example_config)) end end end