From 2f1888f5687fa702952b445dd24b38b5142f5287 Mon Sep 17 00:00:00 2001 From: Bohung Date: Thu, 9 Dec 2021 17:59:15 +0800 Subject: [PATCH] Fix bug. Add batch install cert feature. --- .../admin/site_panel_controller.rb | 34 ++++-- .../client_managements_controller.rb | 4 +- app/models/site_cert.rb | 11 +- app/models/site_construct.rb | 4 +- app/models/site_server.rb | 4 +- .../admin/site_panel/_certs_table.html.erb | 107 +++++++++++++++++- .../site_panel/_server_manager_index.html.erb | 2 +- config/locales/en.yml | 1 + config/locales/zh_tw.yml | 1 + config/routes.rb | 2 +- lib/tasks/change_site_cert.rake | 45 +++++--- 11 files changed, 178 insertions(+), 37 deletions(-) diff --git a/app/controllers/admin/site_panel_controller.rb b/app/controllers/admin/site_panel_controller.rb index ec43533..2d28162 100644 --- a/app/controllers/admin/site_panel_controller.rb +++ b/app/controllers/admin/site_panel_controller.rb @@ -246,19 +246,31 @@ class Admin::SitePanelController < OrbitAdminController SiteConstruct.find(params[:id]).destroy redirect_to :back and return elsif params[:type] == 'select_cert' - if !params[:is_server] - @site_construct = SiteConstruct.find(params[:id]) - @site_construct.update(:redirect_to_https=>params[:redirect_to_https]) - is_certbot = true - if params[:site_cert_id] != "certbot" - is_certbot = false - @site_construct.update(:site_cert_id=>BSON::ObjectId(params[:site_cert_id])) + is_certbot = true + if params[:server_names] + is_certbot = false + Thread.new do + last_idx = params[:server_names].count + params[:server_names].each_with_index do |server_name, i| + ss = SiteServer.where(:server_name=>server_name).first + next if ss.nil? + system("bundle exec rake create_site:change_site_cert[#{ss.id.to_s},#{is_certbot},true,#{params[:site_cert_id]},#{params[:redirect_to_https]}#{i == (last_idx -1) ? ',true' : ''}}]") + end end else - is_certbot = true - end - Thread.new do - system("bundle exec rake create_site:change_site_cert[#{params[:id]},#{is_certbot},#{params[:is_server]}]") + if !params[:is_server] + @site_construct = SiteConstruct.find(params[:id]) + @site_construct.update(:redirect_to_https=>params[:redirect_to_https]) + if params[:site_cert_id] != "certbot" + is_certbot = false + @site_construct.update(:site_cert_id=>BSON::ObjectId(params[:site_cert_id])) + end + else + is_certbot = true + end + Thread.new do + system("bundle exec rake create_site:change_site_cert[#{params[:id]},#{is_certbot},#{params[:is_server]}]") + end end else Thread.new do diff --git a/app/controllers/client_managements_controller.rb b/app/controllers/client_managements_controller.rb index c4d8f79..6153daa 100644 --- a/app/controllers/client_managements_controller.rb +++ b/app/controllers/client_managements_controller.rb @@ -82,12 +82,12 @@ class ClientManagementsController < CPanelController def site_tickets if params[:category].present? && params[:keyword].present? - regex = Regexp.new(".*" + params[:keyword] + ".*", "i") + regex = ::Regexp.new(".*" + params[:keyword] + ".*", "i") @tickets = @site.tickets.where(:category_id => params[:category], :subject => regex).order_by([:status, :desc],[:created_at, :desc]).page(params[:page]).per(10) elsif params[:category].present? @tickets = @site.tickets.where(:category_id => params[:category]).order_by([:status, :desc],[:created_at, :desc]).page(params[:page]).per(10) elsif params[:keyword].present? - regex = Regexp.new(".*" + params[:keyword] + ".*", "i") + regex = ::Regexp.new(".*" + params[:keyword] + ".*", "i") @tickets = @site.tickets.where(:subject => regex).order_by([:status, :desc],[:created_at, :desc]).page(params[:page]).per(10) else @tickets = @site.tickets.order_by([:status, :desc],[:created_at, :desc]).page(params[:page]).per(10) diff --git a/app/models/site_cert.rb b/app/models/site_cert.rb index e2e25d0..5862045 100644 --- a/app/models/site_cert.rb +++ b/app/models/site_cert.rb @@ -34,7 +34,7 @@ class SiteCert cert_file_md5 = `openssl x509 -noout -modulus -in #{self.cert_file.file.file} | openssl md5` private_key_md5 = `openssl rsa -noout -modulus -in #{self.private_key.file.file} | openssl md5` is_valid = (cert_file_md5 == private_key_md5) - domain_names = `openssl x509 -text < #{self.cert_file.file.file} | grep 'DNS:' | sed 's/\s*DNS:\([a-z0-9.\-]*\)[,\s]\?/\1 /g'`.split('DNS:').map{|s| s.strip}.select{|s| s.present?} rescue [] + domain_names = `openssl x509 -text < #{self.cert_file.file.file} | grep 'DNS:' | sed 's/\s*DNS:\([a-z0-9.\-]*\)[,\s]\?/\1 /g'`.split('DNS:').map{|s| s.sub(',','').strip}.select{|s| s.present?} rescue [] if domain_names.blank? self.is_valid = false @skip_callback = true @@ -53,4 +53,13 @@ class SiteCert false end end + def valid_domain_names(site_names) + site_names = site_names.split(" ").map{|s| s.strip} + valid_site_names = [] + self.domain_names.each do |d| + regx = ::Regexp.new("\\A"+d.gsub('.',"\\.").gsub('*','[^\\.]+').sub(',','').strip) + valid_site_names += site_names.select{|s| !(s.match(regx).nil?)} + end + return valid_site_names + end end \ No newline at end of file diff --git a/app/models/site_construct.rb b/app/models/site_construct.rb index ae23eb6..790051e 100644 --- a/app/models/site_construct.rb +++ b/app/models/site_construct.rb @@ -83,7 +83,7 @@ class SiteConstruct }.join('\n') end def match_exact_index(text,match_character,level=1) - text.enum_for(:scan,/(?:[^#{match_character}])#{match_character}{#{level}}(?!#{match_character})/m).map { offset_index=Regexp.last_match.to_s.index(match_character);Regexp.last_match.offset(0).first + offset_index} + text.enum_for(:scan,/(?:[^#{match_character}])#{match_character}{#{level}}(?!#{match_character})/m).map { offset_index=::Regexp.last_match.to_s.index(match_character);::Regexp.last_match.offset(0).first + offset_index} end def parse_nginx_text_to_server_blocks(nginx_text,get_all_blocks=false,level=1) num = 1 @@ -231,7 +231,7 @@ class SiteConstruct domain_names = domain_name.strip().split(" ") if default_domain_idx == -1 site_server.default_domain_names.each do |default_domain_name| - default_domain_name = Regexp.new("\\A"+default_domain_name.gsub(".","\\.").gsub("*","[^.]*")) + default_domain_name = ::Regexp.new("\\A"+default_domain_name.gsub(".","\\.").gsub("*","[^.]*")) custom_default_domain_name = domain_names.select{|n| n.match(default_domain_name) }.first break if custom_default_domain_name.present? end diff --git a/app/models/site_server.rb b/app/models/site_server.rb index a1b3e98..e50d353 100644 --- a/app/models/site_server.rb +++ b/app/models/site_server.rb @@ -42,12 +42,12 @@ class SiteServer add_default_domains = self.default_domain_names - self.default_domain_names_was.to_a check_domains += removed_default_domains check_domains += add_default_domains - check_domains = check_domains.map{|domain| Regexp.new("(\\A|\\s)"+domain.gsub(".","\\.").gsub("*","[^.]*") + "($|\\s)")} + check_domains = check_domains.map{|domain| ::Regexp.new("(\\A|\\s)"+domain.gsub(".","\\.").gsub("*","[^.]*") + "($|\\s)")} tmp_site_constructs = tmp_site_constructs.where(:domain_name.in=>check_domains) need_change = true elsif need_change check_domains = self.default_domain_names - check_domains = check_domains.map{|domain| Regexp.new("(\\A|\\s)"+domain.gsub(".","\\.").gsub("*","[^.]*") + "($|\\s)")} + check_domains = check_domains.map{|domain| ::Regexp.new("(\\A|\\s)"+domain.gsub(".","\\.").gsub("*","[^.]*") + "($|\\s)")} tmp_site_constructs = tmp_site_constructs.where(:domain_name.in=>check_domains) end end diff --git a/app/views/admin/site_panel/_certs_table.html.erb b/app/views/admin/site_panel/_certs_table.html.erb index 677874c..0228244 100644 --- a/app/views/admin/site_panel/_certs_table.html.erb +++ b/app/views/admin/site_panel/_certs_table.html.erb @@ -24,6 +24,7 @@ <%=site_cert.generate_file_link('ca_bundle')%> <%=site_cert.generate_file_link('private_key')%> + <%= link_to t("client_management.batch_install"),"#",:class=>"btn btn-success btn-small batch_install_cert",:data=>{:id=>site_cert.id.to_s} %> <%= link_to t(:edit),admin_site_panel_edit_cert_path(site_cert.id),:class=>"btn btn-primary btn-small" %> <%= link_to t(:delete_),admin_site_panel_destroy_cert_path(site_cert.id),:class=>"btn btn-danger btn-small",:data=>{:method=>"delete",:confirm=>t("client_management.confirm_delete")} %> @@ -48,4 +49,108 @@ color: red; font-size: 2em; } - \ No newline at end of file + + \ No newline at end of file diff --git a/app/views/admin/site_panel/_server_manager_index.html.erb b/app/views/admin/site_panel/_server_manager_index.html.erb index e41ed75..e5ef243 100644 --- a/app/views/admin/site_panel/_server_manager_index.html.erb +++ b/app/views/admin/site_panel/_server_manager_index.html.erb @@ -210,7 +210,7 @@ $(".install_certbot_certs").off("click").on("click",function(){ var id = $(this).data("id"); $.post("<%=admin_site_panel_edit_site_path%>",{'id': id,'type':'select_cert', 'is_server': true}).done(function(response){ - show_infos_dialog("install_certbot_certs"); + show_infos_dialog("batch_install_certs"); }); }) }) diff --git a/config/locales/en.yml b/config/locales/en.yml index 8c4f7b3..f6856af 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -7,6 +7,7 @@ en: upload_cert: Upload Cert cert_management: Cert Management client_management: + batch_install: Batch Install install_certbot_certificates_in_batches: Install certbot certificates in batches update_nginx_settings: "Update nginx settings" set_to_default_domain_name: "Set to default domain name" diff --git a/config/locales/zh_tw.yml b/config/locales/zh_tw.yml index db2db3d..24eebaf 100644 --- a/config/locales/zh_tw.yml +++ b/config/locales/zh_tw.yml @@ -7,6 +7,7 @@ zh_tw: upload_cert: 上傳憑證 cert_management: 憑證管理 client_management: + batch_install: 批次安裝 install_certbot_certificates_in_batches: 批次安裝certbot憑證 update_nginx_settings: "修改nginx設定" set_to_default_domain_name: "設定為預設的domain name" diff --git a/config/routes.rb b/config/routes.rb index a96a8dc..42c60f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ Rails.application.routes.draw do locales = Site.find_by(site_active: true).in_use_locales rescue I18n.available_locales - scope "(:locale)", locale: Regexp.new(locales.join("|")) do + scope "(:locale)", locale: ::Regexp.new(locales.join("|")) do namespace :admin do # get "client_managements/completed_requests", to: 'client_managements#completed_requests' # get "client_managements/contracts", to: 'client_managements#contracts' diff --git a/lib/tasks/change_site_cert.rake b/lib/tasks/change_site_cert.rake index dc15484..e8e64e1 100644 --- a/lib/tasks/change_site_cert.rake +++ b/lib/tasks/change_site_cert.rake @@ -3,23 +3,24 @@ require 'pathname' require 'json' namespace :create_site do desc "Change Site Cert" - task :change_site_cert,[:id,:is_certbot,:id_is_server] => :environment do |task,args| + task :change_site_cert,[:id,:is_certbot,:id_is_server,:cert_id,:redirect_to_https,:exec_finish] => :environment do |task,args| begin site_server = nil site_constructs = [] @is_multithread = false @thread = nil + @default_cert = args.cert_id ? SiteCert.find(args.cert_id) : nil if args.id_is_server site_server = SiteServer.find(args.id) site_constructs = site_server.site_constructs.to_a @is_multithread = true - Multithread.where(:key=>'install_certbot_certs').each{|thread| thread.destroy if (thread.status["status"] == "error" || thread.status["status"] == "finish")} - Multithread.where(:key=>'install_certbot_certs').destroy - @thread = Multithread.where(:key=>'install_certbot_certs').first + Multithread.where(:key=>'batch_install_certs').each{|thread| thread.destroy if (thread.status["status"] == "error" || thread.status["status"] == "finish")} + Multithread.where(:key=>'batch_install_certs').destroy + @thread = Multithread.where(:key=>'batch_install_certs').first if @thread.nil? - @thread = Multithread.create(:key=>'install_certbot_certs',:status=>{"infos"=>[],"status"=>"execing"}) + @thread = Multithread.create(:key=>'batch_install_certs',:status=>{"infos"=>[],"status"=>"execing"}) else - return false + return false if @default_cert.nil? end else @site_construct = SiteConstruct.find(args.id) @@ -31,6 +32,7 @@ namespace :create_site do if !site_server.nil? @password = site_server.password Net::SSH.start(site_server.ip , site_server.account , password: site_server.password) do |ssh| + redirect_to_https = args.redirect_to_https ? true : (@site_construct.redirect_to_https rescue false) if tmp_is_certbot || (@site_cert.is_certbot rescue false) domain_names = site_constructs.flat_map{|site_construct| site_construct.domain_name.strip.split(" ")}.select{|d| d.present? && d.match(/^[\d\.]+$/).nil?} domain_names = domain_names.select{|d| `dig #{d} +short`.split("\n").select{|s| s.strip == site_server.ip}.count != 0} @@ -41,7 +43,6 @@ namespace :create_site do if site_constructs.count > 1 auto_update_infos("Generating single cert for multiple domain names...") end - redirect_to_https = (@site_construct.redirect_to_https rescue false) exec_ssh_command_by_sudo_and_see_output(ssh,"sudo -p 'sudo password:' #{certbot_path} --nginx #{domain_names.map{|d| "-d " + d}.join(" ")} -n --#{redirect_to_https ? 'redirect' : 'no-redirect'}",true,false) site_constructs.each do |site_construct| @site_construct = site_construct @@ -73,7 +74,7 @@ namespace :create_site do site_construct.update(:site_cert=>site_cert) end all_ports = (site_construct.port + ["443"]).uniq - site_construct.update(:port=> all_ports ) + site_construct.update(:port=> all_ports, :status=>'finish' ) auto_update_infos("Finish installing cert with certbot in #{site_construct.site_name} !") else auto_update_infos("Certbot generate cert failed!") @@ -89,13 +90,19 @@ namespace :create_site do end else site_constructs.each do |site_construct| - @site_cert = @site_construct.site_cert + @site_cert = @default_cert || @site_construct.site_cert next if @site_cert.nil? is_certbot = tmp_is_certbot || (@site_cert.is_certbot rescue false) @site_construct = site_construct @site_cert.change_data if @site_cert.is_valid + valid_domain_names = @site_cert.valid_domain_names(@site_construct.domain_name) + next if valid_domain_names.count == 0 + if @default_cert + @site_construct.update(:site_cert=>@default_cert,:redirect_to_https=>redirect_to_https) + end auto_update_infos("Copying Cert to #{@site_construct.server_type}...") + auto_update_infos("Installing Cert on #{valid_domain_names.join(" , ")}") cert_file_content = [(@site_cert.cert_file.file.read.strip rescue ""),(@site_cert.ca_bundle.file.read.strip rescue "")].join("\n").strip private_key_content = @site_cert.private_key.file.read cert_file_store_path = @site_construct.cert_file_remote_store_path @@ -108,7 +115,7 @@ namespace :create_site do auto_update_infos("Setting Cert...") nginx_file_content = exec_command_by_user(ssh,"cat #{@site_construct.nginx_file}") all_ports = (@site_construct.port + ["443"]).uniq - @site_construct.update(:port=> all_ports ) + @site_construct.update(:port=> all_ports,:status=>'finish' ) nginx_file_content = @site_construct.generate_nginx_text(nginx_file_content) cmd = "x='#{nginx_file_content}'; echo '#{@password}' | sudo -S sh -c \"echo '$x' > #{@site_construct.nginx_file}\"" exec_command_by_user(ssh,cmd) @@ -116,11 +123,13 @@ namespace :create_site do end end exec_ssh_command_by_sudo(ssh,"service nginx restart") - auto_update_infos("Finish!") - if @is_multithread - @thread.update(:status=>@thread.status.merge({"status"=>"finish"})) - else - @site_construct.update(:status=>"finish") + if @default_cert.nil? || args.exec_finish + auto_update_infos("Finish!") + if @is_multithread + @thread.update(:status=>@thread.status.merge({"status"=>"finish"})) + else + @site_construct.update(:status=>"finish") + end end end else @@ -130,7 +139,11 @@ namespace :create_site do rescue => e puts [e,e.backtrace] auto_update_infos(e.to_s) - @site_construct.update(:status=>"error") + if @is_multithread + @thread.update(:status=>@thread.status.merge({"status"=>"error"})) + else + @site_construct.update(:status=>"error") + end end end def exec_command_by_user(session,command)