From 1f1fe00019d1a6ac5e10e4b96e620358ca7575a0 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 29 Aug 2017 10:23:43 -0400 Subject: [PATCH 01/63] some workarounds for Cygwin ssh difficulties --- Berksfile.lock | 17 ++++++++------- bin/mu-upload-chef-artifacts | 4 ++-- cookbooks/mu-tools/recipes/windows-client.rb | 7 +++++-- cookbooks/mu-tools/resources/sshd_service.rb | 13 +++++++++--- cookbooks/mu-tools/resources/windows_users.rb | 21 +++++++++++++++---- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Berksfile.lock b/Berksfile.lock index 2cb4a1aa4..50f899212 100644 --- a/Berksfile.lock +++ b/Berksfile.lock @@ -4,6 +4,7 @@ DEPENDENCIES awscli path: cookbooks/awscli build-essential (~> 8.0) + chef-vault (< 3.0.0) chef_nginx (~> 6.1.1) freebsd (~> 0.1.9) gunicorn (~> 1.1.2) @@ -56,8 +57,7 @@ GRAPH mingw (>= 1.1) seven_zip (>= 0.0.0) chef-sugar (3.4.0) - chef-vault (3.0.0) - compat_resource (>= 12.16.3) + chef-vault (2.1.1) chef_nginx (6.1.1) build-essential (>= 0.0.0) compat_resource (>= 12.16.3) @@ -98,7 +98,7 @@ GRAPH poise-service (~> 1.1) rubyzip (~> 1.0) homebrew (4.2.0) - hostsfile (2.4.5) + hostsfile (3.0.1) java (1.50.0) apt (>= 0.0.0) homebrew (>= 0.0.0) @@ -174,6 +174,7 @@ GRAPH chef-vault (>= 0.0.0) database (>= 0.0.0) java (>= 0.0.0) + mu-activedirectory (>= 0.0.0) mu-firewall (>= 0.0.0) mu-splunk (>= 0.0.0) mu-utility (>= 0.0.0) @@ -185,7 +186,7 @@ GRAPH yum-epel (>= 0.0.0) mu-utility (0.6.0) windows (>= 0.0.0) - mysql (8.4.0) + mysql (8.5.1) mysql-chef_gem (0.0.5) build-essential (>= 0.0.0) mysql (>= 0.0.0) @@ -206,9 +207,9 @@ GRAPH nrpe (2.0.2) build-essential (>= 0.0.0) yum-epel (>= 0.0.0) - nssm (3.0.2) + nssm (4.0.0) windows (>= 0.0.0) - ohai (5.1.0) + ohai (5.2.0) openssl (7.1.0) oracle-instantclient (1.1.0) build-essential (>= 0.0.0) @@ -226,7 +227,7 @@ GRAPH poise (~> 2.6) poise-service (1.5.2) poise (~> 2.0) - postfix (5.0.3) + postfix (5.1.1) postgresql (6.1.1) build-essential (>= 2.0.0) compat_resource (>= 12.16.3) @@ -252,7 +253,7 @@ GRAPH consul-cluster (~> 2.0) hashicorp-vault (~> 2.1) ssl_certificate (~> 1.11) - windows (3.1.1) + windows (3.1.2) ohai (>= 4.0.0) yum (3.13.0) yum-epel (2.1.2) diff --git a/bin/mu-upload-chef-artifacts b/bin/mu-upload-chef-artifacts index 0f4d24a7c..cfbb6ee92 100755 --- a/bin/mu-upload-chef-artifacts +++ b/bin/mu-upload-chef-artifacts @@ -192,10 +192,10 @@ add_berkshelf_cookbooks() echo "${GREEN}Uploading Berkshelf Chef cookbooks from ${BOLD}$repodir${NORM}" if [ "$match" == "" ];then - cd $repodir && $berks upload --no-freeze || exit 1 + cd $repodir && $berks upload --no-freeze --force || exit 1 elif [ "$berkshelf_cookbooks" != "" ];then echo "${GREEN}Matching only: ${BOLD}${berkshelf_cookbooks}${NORM}${GREEN}${NORM}" - cd $repodir && $berks upload $berkshelf_cookbooks --no-freeze 2>&1 || echo "${YELLOW}Missing cookbooks ok when using -m if they're not supposed to have been in $repodir/Berksfile${NORM}" + cd $repodir && $berks upload $berkshelf_cookbooks --no-freeze --force 2>&1 || echo "${YELLOW}Missing cookbooks ok when using -m if they're not supposed to have been in $repodir/Berksfile${NORM}" fi cd $MU_CHEF_CACHE } diff --git a/cookbooks/mu-tools/recipes/windows-client.rb b/cookbooks/mu-tools/recipes/windows-client.rb index f8611600c..6631bdd4e 100644 --- a/cookbooks/mu-tools/recipes/windows-client.rb +++ b/cookbooks/mu-tools/recipes/windows-client.rb @@ -21,10 +21,13 @@ include_recipe 'chef-vault' include_recipe 'mu-activedirectory' + ::Chef::Recipe.send(:include, Chef::Mixin::PowershellOut) + template "c:/bin/cygwin/etc/sshd_config" do source "sshd_config.erb" mode 0644 cookbook "mu-tools" + ignore_failure true end windows_vault = chef_vault_item(node['windows_auth_vault'], node['windows_auth_item']) @@ -37,6 +40,7 @@ if in_domain? ad_vault = chef_vault_item(node['ad']['domain_admin_vault'], node['ad']['domain_admin_item']) + login_dom = node['ad']['netbios_name'] windows_users node['ad']['computer_name'] do username ad_vault[node['ad']['domain_admin_username_field']] @@ -61,7 +65,6 @@ password ad_vault[node['ad']['domain_admin_password_field']] end - login_dom = node['ad']['netbios_name'] sshd_service "sshd" do service_username "#{node['ad']['netbios_name']}\\#{sshd_user}" username sshd_user @@ -106,6 +109,6 @@ end else - Chef::Log.info("Unsupported platform #{node['platform']}") + Chef::Log.info("mu-tools::windows-client: Unsupported platform #{node['platform']}") end end diff --git a/cookbooks/mu-tools/resources/sshd_service.rb b/cookbooks/mu-tools/resources/sshd_service.rb index 29d73aa35..f2bf70a18 100644 --- a/cookbooks/mu-tools/resources/sshd_service.rb +++ b/cookbooks/mu-tools/resources/sshd_service.rb @@ -10,15 +10,22 @@ action :config do converge_by("Configuring SSH service to run under #{new_resource.service_username}") do ssh_user_set = service_user_set?(new_resource.name, new_resource.service_username) + failed = false cmd = powershell_out("$sshd_service = Get-WmiObject Win32_service | Where-Object {$_.Name -eq '#{new_resource.name}'}; $sshd_service.Change($Null,$Null,$Null,$Null,$Null,$Null,'#{new_resource.service_username}','#{new_resource.password}',$Null,$Null,$Null)") - Chef::Log.error("Failed to change ssh service user #{cmd.stderr}") unless cmd.exitstatus == 0 + if cmd.exitstatus != 0 + Chef::Log.error("Failed to change ssh service user #{cmd.stderr}") + failed = true + end cmd = powershell_out("(Get-WmiObject Win32_service | Where-Object {$_.Name -eq '#{new_resource.name}'}).StartName") - Chef::Log.error("Failed to change ssh service user to #{new_resource.username}") if !(cmd.stdout =~ /#{new_resource.username}/) + if !(cmd.stdout =~ /#{new_resource.username}/) + Chef::Log.error("Failed to change ssh service user to #{new_resource.username}") + failed = true + end # if cmd.exitstatus == 0 and !ssh_user_set - unless ssh_user_set + unless ssh_user_set or failed # cmd = powershell_out("c:/bin/cygwin/bin/bash --login -c 'chown -R #{new_resource.username} /var/empty && chown #{new_resource.username} /var/log/sshd.log /etc/ssh*\'; Stop-Process -ProcessName #{new_resource.name} -force; Stop-Service #{new_resource.name} -Force; Start-Service #{new_resource.name}; sleep 5; Start-Service #{new_resource.name}") # We would much prefer to use the above because that wouldn't require another reboot, but in some cases the session dosen't get terminated from Mu. Throwing Chef::Application.fatal seems to work more reliably cmd = powershell_out("c:/bin/cygwin/bin/bash --login -c 'chown -R #{new_resource.username} /var/empty && chown #{new_resource.username} /var/log/sshd.log /etc/ssh*\'") diff --git a/cookbooks/mu-tools/resources/windows_users.rb b/cookbooks/mu-tools/resources/windows_users.rb index a5a29ec15..f48fb00c9 100644 --- a/cookbooks/mu-tools/resources/windows_users.rb +++ b/cookbooks/mu-tools/resources/windows_users.rb @@ -14,6 +14,11 @@ default_action :config action :config do + + cookbook_file "c:\\Windows\\SysWOW64\\ntrights.exe" do + source "ntrights" + end + if is_domain_controller?(new_resource.computer_name) [new_resource.username, new_resource.ssh_user, new_resource.ec2config_user].each { |user| unless domain_user_exist?(user) @@ -140,6 +145,12 @@ # powershell_out("new-gplink -name #{gpo_name} -target 'dc=#{new_resource.domain_name.gsub(".", ",dc=")}'").run_command end end + + %w{SeCreateTokenPrivilege SeTcbPrivilege SeAssignPrimaryTokenPrivilege}.each { |privilege| + batch "Grant local user #{new_resource.netbios_name}\\#{new_resource.ssh_user} #{privilege} right" do + code "C:\\Windows\\SysWOW64\\ntrights +r #{privilege} -u #{new_resource.netbios_name}\\#{new_resource.ssh_user}" + end + } end if in_domain? @@ -157,10 +168,11 @@ end } - directory 'C:/chef/cache' do - rights :full_control, "#{new_resource.netbios_name}\\#{new_resource.username}" - rights :full_control, "#{new_resource.netbios_name}\\#{new_resource.ssh_user}" - end + + directory 'C:/chef/cache' do + rights :full_control, "#{new_resource.netbios_name}\\#{new_resource.username}" + rights :full_control, "#{new_resource.netbios_name}\\#{new_resource.ssh_user}" + end execute "C:/bin/cygwin/bin/bash --login -c \"chown -R #{new_resource.username} /home/#{new_resource.username}\"" @@ -179,6 +191,7 @@ end else # We want to run ec2config as admin user so Windows userdata executes as admin, however the local admin account doesn't have Logon As a Service right. Domain privileges are set separately + cookbook_file "c:\\Windows\\SysWOW64\\ntrights.exe" do source "ntrights" end From 8cc3a5db2b7d51bf6a61fcd7e38bba34401fcc33 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 29 Aug 2017 12:23:05 -0400 Subject: [PATCH 02/63] backport the_goog's abstraction of deploy secret handling to cloud storage --- modules/mu/clouds/aws.rb | 17 +++++++++++++++++ modules/mu/mommacat.rb | 12 +++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/modules/mu/clouds/aws.rb b/modules/mu/clouds/aws.rb index 00256f5ed..f43258fab 100644 --- a/modules/mu/clouds/aws.rb +++ b/modules/mu/clouds/aws.rb @@ -38,6 +38,23 @@ def self.listAZs(region = MU.curRegion) return zones end + # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it + # @param deploy_id [String]: The deploy for which we're writing the secret + # @param value [String]: The contents of the secret + def self.writeDeploySecret(deploy_id, value) + name = deploy_id+"-secret" + begin + MU.log "Writing #{name} to S3 bucket #{MU.adminBucketName}" + MU::Cloud::AWS.s3(MU.myRegion).put_object( + acl: "private", + bucket: MU.adminBucketName, + key: name, + body: value + ) + rescue Aws::S3::Errors => e + raise DeployInitializeError, "Got #{e.inspect} trying to write #{name} to #{MU.adminBucketName}" + end + end # List the Amazon Web Services region names available to this account. The # region that is local to this Mu server will be listed first. diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index d6dc3d80e..3d501fb1d 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -226,15 +226,9 @@ def initialize(deploy_id, MU.log "Creating deploy secret for #{MU.deploy_id}" @deploy_secret = Password.random(256) if !@original_config['scrub_mu_isms'] - begin - MU::Cloud::AWS.s3(MU.myRegion).put_object( - acl: "private", - bucket: MU.adminBucketName, - key: "#{@deploy_id}-secret", - body: "#{@deploy_secret}" - ) - rescue Aws::S3::Errors::PermanentRedirect => e - raise DeployInitializeError, "Got #{e.inspect} trying to write #{@deploy_id}-secret to #{MU.adminBucketName}" + # TODO there's a nicer way to do this than hardcoding strings + if @clouds["AWS"] and @clouds["AWS"] > 0 + MU::Cloud::AWS.writeDeploySecret(@deploy_id, @deploy_secret) end end if set_context_to_me From ae3e15c9965a80f4b05856c9d0dfb5f93306923c Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 29 Aug 2017 15:28:27 -0400 Subject: [PATCH 03/63] add SAN to our internally-signed node certs; bump to v3; add a -winrm cert set; put those certs in S3 and grant nodes access --- modules/Gemfile | 1 + modules/Gemfile.lock | 3 +- modules/mommacat.ru | 2 +- modules/mu/cloud.rb | 31 ++++++++++- modules/mu/clouds/aws.rb | 4 +- modules/mu/clouds/aws/server.rb | 22 ++++++-- modules/mu/groomers/chef.rb | 68 +++-------------------- modules/mu/mommacat.rb | 97 ++++++++++++++++++++++++++++++++- modules/mu/userdata/windows.erb | 6 ++ 9 files changed, 159 insertions(+), 75 deletions(-) diff --git a/modules/Gemfile b/modules/Gemfile index e6f4b12f9..a588aaffd 100644 --- a/modules/Gemfile +++ b/modules/Gemfile @@ -37,3 +37,4 @@ gem 'nokogiri', "~> 1.6.8" gem 'molinillo', '< 0.6.0' gem 'solve', '>= 3.1.1' gem 'net-ldap' +gem 'winrm', '= 2.2.3' diff --git a/modules/Gemfile.lock b/modules/Gemfile.lock index 4fe5ed3c0..1c85b8d75 100644 --- a/modules/Gemfile.lock +++ b/modules/Gemfile.lock @@ -289,7 +289,8 @@ DEPENDENCIES solve (>= 3.1.1) thin trollop (~> 2.1.2) + winrm (= 2.2.3) yard BUNDLED WITH - 1.15.3 + 1.15.4 diff --git a/modules/mommacat.ru b/modules/mommacat.ru index 050997673..bff3cf845 100644 --- a/modules/mommacat.ru +++ b/modules/mommacat.ru @@ -361,7 +361,7 @@ app = proc do |env| MU.log "Found an existing node named #{mu_name}" end if !req["mu_ssl_sign"].nil? - kittenpile.signSSLCert(req["mu_ssl_sign"]) + kittenpile.signSSLCert(req["mu_ssl_sign"], req["mu_ssl_sans"].split(/,/)) elsif !req["add_volume"].nil? puts instance.cloud_id pp req diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index e328df8ea..456890b16 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +autoload :WinRM, "winrm" + module MU # Plugins under this namespace serve as interfaces to cloud providers and # other provisioning layers. @@ -736,9 +738,9 @@ def initialSSHTasks(ssh) if windows? and !@config['use_cloud_provider_windows_password'] # This covers both the case where we have a windows password passed from a vault and where we need to use a a random Windows Admin password generated by MU::Cloud::Server.generateWindowsPassword pw = @groomer.getSecret( - vault: @config['mu_name'], - item: "windows_credentials", - field: "password" + vault: @config['mu_name'], + item: "windows_credentials", + field: "password" ) win_check_for_pw = %Q{powershell -Command '& {Add-Type -AssemblyName System.DirectoryServices.AccountManagement; $Creds = (New-Object System.Management.Automation.PSCredential("#{@config["windows_admin_username"]}", (ConvertTo-SecureString "#{pw}" -AsPlainText -Force)));$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine); $DS.ValidateCredentials($Creds.GetNetworkCredential().UserName, $Creds.GetNetworkCredential().password); echo $Result}'} win_set_pw = %Q{powershell -Command "& {(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}"} @@ -812,6 +814,29 @@ def initialSSHTasks(ssh) end + def getWinRMSession(max_retries = 40, retry_interval = 60) + nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig + opts = { + endpoint: 'http://'+canonical_ip+':5985/wsman', + transport: :ssl, + ca_trust_path: "#{MU.mySSLDir}/#{@mu_name}.crt", + client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt", + client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key" +# user: ssh_user, +# password: 'Pass@word1' + } +MU.log "Tryin' a call WinRM on #{canonical_ip}", MU::WARN, details: opts + conn = nil +begin + conn = WinRM::Connection.new(opts) +rescue Exception => e +MU.log e.inspect, MU::ERR + sleep 20 +end +pp conn + conn + end + # @param max_retries [Integer]: Number of connection attempts to make before giving up # @param retry_interval [Integer]: Number of seconds to wait between connection attempts # @return [Net::SSH::Connection::Session] diff --git a/modules/mu/clouds/aws.rb b/modules/mu/clouds/aws.rb index f43258fab..76fb85939 100644 --- a/modules/mu/clouds/aws.rb +++ b/modules/mu/clouds/aws.rb @@ -41,8 +41,8 @@ def self.listAZs(region = MU.curRegion) # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it # @param deploy_id [String]: The deploy for which we're writing the secret # @param value [String]: The contents of the secret - def self.writeDeploySecret(deploy_id, value) - name = deploy_id+"-secret" + def self.writeDeploySecret(deploy_id, value, name = nil) + name ||= deploy_id+"-secret" begin MU.log "Writing #{name} to S3 bucket #{MU.adminBucketName}" MU::Cloud::AWS.s3(MU.myRegion).put_object( diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index ef26e3b15..ec410658d 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -313,6 +313,10 @@ def self.removeIAMProfile(rolename) def self.addStdPoliciesToIAMProfile(rolename, cloudformation_data: {}, cfm_role_name: nil) policies = Hash.new policies['Mu_Bootstrap_Secret_'+MU.deploy_id] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{MU.deploy_id}-secret"+'"}]}' + policies['Mu_Node_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.crt"+'"}]}' + policies['Mu_Node_Key'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.key"+'"}]}' + policies['Mu_WinRM_Client_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}-winrm.crt"+'"}]}' + policies['Mu_WinRM_Client_Key'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}-winrm.key"+'"}]}' policies.each_pair { |name, doc| if cloudformation_data.size > 0 if !cfm_role_name.nil? @@ -1002,16 +1006,24 @@ def postBoot(instance_id = nil) notify end - windows? ? ssh_wait = 60 : ssh_wait = 30 - windows? ? max_retries = 50 : max_retries = 35 begin - session = getSSHSession(max_retries, ssh_wait) - initialSSHTasks(session) + if windows? + # kick off certificate generation early; WinRM will need it + cert, key = @deploy.nodeSSLCert(self) +# session = getWinRMSession(50, 60) +# XXX account for machines behind bastion hosts that we can't tunnel through; +# maybe then it's ok to fall back to sshd + session = getSSHSession(40, 30) + initialSSHTasks(session) + else + session = getSSHSession(40, 30) + initialSSHTasks(session) + end rescue BootstrapTempFail sleep ssh_wait retry ensure - session.close if !session.nil? + session.close if !session.nil? and !windows? end if @config["existing_deploys"] && !@config["existing_deploys"].empty? diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index 36890ba61..ace8ab718 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -372,7 +372,7 @@ def preClean(leave_ours = false) # Bootstrap our server with Chef def bootstrap self.class.loadChefLib - createGenericHostSSLCert + stashHostSSLCertSecret if !@config['cleaned_chef'] begin preClean(true) @@ -759,70 +759,18 @@ def knifeCmd(cmd, showoutput = false) self.class.knifeCmd(cmd, showoutput) end - def createGenericHostSSLCert - nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = @server.getSSHConfig - # Manufacture a generic SSL certificate, signed by the Mu master, for - # consumption by various node services (Apache, Splunk, etc). - return if File.exists?("#{MU.mySSLDir}/#{@server.mu_name}.crt") - MU.log "Creating self-signed service SSL certificate for #{@server.mu_name} (CN=#{canonical_ip})" + # Upload the certificate to a Chef Vault for this node + def stashHostSSLCertSecret + cert, key = @server.deploy.nodeSSLCert(@server) - # Create and save a key - key = OpenSSL::PKey::RSA.new 4096 - if !Dir.exist?(MU.mySSLDir) - Dir.mkdir(MU.mySSLDir, 0700) - end - - open("#{MU.mySSLDir}/#{@server.mu_name}.key", 'w', 0600) { |io| - io.write key.to_pem - } - - # Create a certificate request for this node - csr = OpenSSL::X509::Request.new - csr.version = 0 - csr.subject = OpenSSL::X509::Name.parse "CN=#{canonical_ip}/O=Mu/C=US" - csr.public_key = key.public_key - open("#{MU.mySSLDir}/#{@server.mu_name}.csr", 'w', 0644) { |io| - io.write csr.to_pem - } - - - if MU.chef_user == "mu" - @server.deploy.signSSLCert("#{MU.mySSLDir}/#{@server.mu_name}.csr") - else - deploykey = OpenSSL::PKey::RSA.new(@server.deploy.public_key) - deploysecret = Base64.urlsafe_encode64(deploykey.public_encrypt(@server.deploy.deploy_secret)) - res_type = "server" - res_type = "server_pool" if !@config['basis'].nil? - uri = URI("https://#{MU.mu_public_addr}:2260/") - req = Net::HTTP::Post.new(uri) - req.set_form_data( - "mu_id" => MU.deploy_id, - "mu_resource_name" => @config['name'], - "mu_resource_type" => res_type, - "mu_ssl_sign" => "#{MU.mySSLDir}/#{@server.mu_name}.csr", - "mu_user" => MU.mu_user, - "mu_deploy_secret" => deploysecret - ) - http = Net::HTTP.new(uri.hostname, uri.port) - http.ca_file = "/etc/pki/Mu_CA.pem" # XXX why no worky? - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX this sucks - response = http.request(req) - - MU.log "Got error back on signing request for #{MU.mySSLDir}/#{@server.mu_name}.csr", MU::ERR if response.code != "200" - end - - cert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/#{@server.mu_name}.crt" - # Upload the certificate to a Chef Vault for this node certdata = { - "data" => { - "node.crt" => cert.to_pem.chomp!.gsub(/\n/, "\\n"), - "node.key" => key.to_pem.chomp!.gsub(/\n/, "\\n") - } + "data" => { + "node.crt" => cert.to_pem.chomp!.gsub(/\n/, "\\n"), + "node.key" => key.to_pem.chomp!.gsub(/\n/, "\\n") + } } saveSecret(item: "ssl_cert", data: certdata, permissions: nil) - # Any and all 'secrets' parameters should also be stuffed into our vault. saveSecret(item: "secrets", data: @config['secrets'], permissions: nil) if !@config['secrets'].nil? end diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 3d501fb1d..18328a3c0 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1918,7 +1918,7 @@ def listNodes # Given a Certificate Signing Request, sign it with our internal CA and # writers the resulting signed certificate. Only works on local files. # @param csr_path [String]: The CSR to sign, as a file. - def signSSLCert(csr_path) + def signSSLCert(csr_path, sans = []) # XXX more sanity here, this feels unsafe certdir = File.dirname(csr_path) certname = File.basename(csr_path, ".csr") @@ -1949,13 +1949,23 @@ def signSSLCert(csr_path) # Create a certificate from our CSR, signed by the Mu CA cert = OpenSSL::X509::Certificate.new cert.serial = cur_serial - cert.version = 2 + cert.version = 3 cert.not_before = Time.now cert.not_after = Time.now + 1800000 # 500 days cert.subject = csr.subject cert.public_key = csr.public_key cert.issuer = cacert.subject - cert.sign cakey, OpenSSL::Digest::SHA1.new + if !sans.nil? and sans.size > 0 + MU.log "Incorporting Subject Alternative Names: #{sans.join(",")}" + ef = OpenSSL::X509::ExtensionFactory.new + ef.issuer_certificate = cacert + ef.subject_certificate = cert + ef.subject_certificate = cert + cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false)) +# XXX only do this if we see the otherName thinger in the san list + cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth",false)) + end + cert.sign cakey, OpenSSL::Digest::SHA256.new open("#{certdir}/#{certname}.crt", 'w', 0644) { |io| io.write cert.to_pem @@ -2053,6 +2063,87 @@ def syncLitter(nodeclasses = [], triggering_node: nil, save_all_only: false) MU.log "Synchronization of #{@deploy_id} complete", MU::DEBUG, details: update_servers end + # Given a MU::Cloud::Server object, return the generic self-signed SSL + # certficate we made for it. If one doesn't exist yet, generate it first. + # @param server [MU::Cloud::Server]: The server for which to generate or return the cert + def nodeSSLCert(server) + nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = server.getSSHConfig + + # Manufacture a generic SSL certificate, signed by the Mu master, for + # consumption by various node services (Apache, Splunk, etc). + if File.exists?("#{MU.mySSLDir}/#{server.mu_name}.crt") and + File.exists?("#{MU.mySSLDir}/#{server.mu_name}.key") + [File.read("#{MU.mySSLDir}/#{server.mu_name}.crt"), File.read("#{MU.mySSLDir}/#{server.mu_name}.key")] + end + + certs = { + server.mu_name => ["DNS:#{canonical_ip}"] + } + if server.windows? + certs[server.mu_name+"-winrm"] = ["otherName:msUPN;UTF8:#{$MU_CFG['mu_admin_email']}"] + end + results = {} + + certs.each { |certname, sans| + MU.log "Creating self-signed service SSL certificate for #{certname} (CN=#{canonical_ip})" + + # Create and save a key + key = OpenSSL::PKey::RSA.new 4096 + if !Dir.exist?(MU.mySSLDir) + Dir.mkdir(MU.mySSLDir, 0700) + end + + open("#{MU.mySSLDir}/#{certname}.key", 'w', 0600) { |io| + io.write key.to_pem + } + # Create a certificate request for this node + csr = OpenSSL::X509::Request.new + csr.version = 0 + csr.subject = OpenSSL::X509::Name.parse "CN=#{canonical_ip}/O=Mu/C=US" + csr.public_key = key.public_key + open("#{MU.mySSLDir}/#{certname}.csr", 'w', 0644) { |io| + io.write csr.to_pem + } + if MU.chef_user == "mu" + signSSLCert("#{MU.mySSLDir}/#{certname}.csr", sans) + else + deploykey = OpenSSL::PKey::RSA.new(public_key) + deploysecret = Base64.urlsafe_encode64(deploykey.public_encrypt(deploy_secret)) + res_type = "server" + res_type = "server_pool" if !server.config['basis'].nil? + uri = URI("https://#{MU.mu_public_addr}:2260/") + req = Net::HTTP::Post.new(uri) + req.set_form_data( + "mu_id" => MU.deploy_id, + "mu_resource_name" => server.config['name'], + "mu_resource_type" => res_type, + "mu_ssl_sign" => "#{MU.mySSLDir}/#{certname}.csr", + "mu_ssl_sans" => sans.join(","), + "mu_user" => MU.mu_user, + "mu_deploy_secret" => deploysecret + ) + http = Net::HTTP.new(uri.hostname, uri.port) + http.ca_file = "/etc/pki/Mu_CA.pem" # XXX why no worky? + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX this sucks + response = http.request(req) + + MU.log "Got error back on signing request for #{MU.mySSLDir}/#{certname}.csr", MU::ERR if response.code != "200" + end + + cert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/#{certname}.crt" + results[certname] = [cert, key] + + if server.config['cloud'] == "AWS" + MU::Cloud::AWS.writeDeploySecret(@deploy_id, cert.to_pem, certname+".crt") + MU::Cloud::AWS.writeDeploySecret(@deploy_id, key.to_pem, certname+".key") +# XXX add google logic, or better yet abstract this method + end + } + + results[server.mu_name] + end + private # Check to see whether a given resource name is unique across all diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 8e29f6066..8a18f837a 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -1,6 +1,12 @@ Set-ExecutionPolicy Unrestricted -Force -Scope CurrentUser + $winrm = "c:/Windows/System32/winrm.cmd" + $winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' + $winrm set winrm/config '@{MaxTimeoutms="1800000"}' + + c:/Windows/system32/netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public protocol=tcp localport=5985 remoteip=localsubnet new remoteip=any + $sshdUser = "sshd_service" $logfile = "c:/Mu-Bootstrap-$([Environment]::UserName).log" $base_dir = 'c:/bin' From 47b179ef14796346e74dc1ca5b4822732458213f Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 29 Aug 2017 21:25:03 -0400 Subject: [PATCH 04/63] mu-node-manage -m certs to fill in missing certs (but especially winrm) --- bin/mu-node-manage | 28 +++++++++++++++++++--- modules/mu/clouds/aws/server.rb | 4 ++-- modules/mu/groomers/chef.rb | 2 +- modules/mu/mommacat.rb | 42 +++++++++++++++++++++------------ 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/bin/mu-node-manage b/bin/mu-node-manage index d31566367..ea13471d4 100755 --- a/bin/mu-node-manage +++ b/bin/mu-node-manage @@ -31,11 +31,11 @@ Usage: opt :environment, "Operate exclusively on one nodes with a particular environment (e.g. dev, prod). Can be used in conjunction with -a or -d.", :require => false, :type => :string opt :override_chef_runlist, "An alternate runlist to pass to Chef, in chefrun mode.", :require => false, :type => :string opt :xecute, "Run a shell command on matching nodes. Overrides --mode and suppresses some informational output in favor of scriptability.", :require => false, :type => :string - opt :mode, "Action to perform on matching nodes. Valid actions: groom, chefrun, userdata, vaults", :require => false, :default => "chefrun", :type => :string + opt :mode, "Action to perform on matching nodes. Valid actions: groom, chefrun, userdata, vaults, certs", :require => false, :default => "chefrun", :type => :string end -if !["groom", "chefrun", "vaults", "userdata"].include?($opts[:mode]) - Trollop::die(:mode, "--mode must be one of: groom, chefrun, userdata, vaults") +if !["groom", "chefrun", "vaults", "userdata", "certs"].include?($opts[:mode]) + Trollop::die(:mode, "--mode must be one of: groom, chefrun, userdata, vaults, certs") end if $opts[:platform] and !["linux", "windows"].include?($opts[:platform]) Trollop::die(:platform, "--platform must be one of: linux, windows") @@ -463,8 +463,30 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) } end +def sslCerts(deploys = MU::MommaCat.listDeploys, nodes = [], vaults_only: false) + badnodes = [] + count = 0 + deploys.each { |muid| + mommacat = MU::MommaCat.new(muid) + mommacat.listNodes.each_pair { |nodename, server| + next if server['conf'].nil? + server['conf']["platform"] = "linux" if !server['conf'].has_key?("platform") + next if nodes.size > 0 and !nodes.include?(nodename) + if server['conf'].nil? + MU.log "Failed to find config data for server #{nodename}", MU::WARN + next + end + + server_obj = mommacat.findLitterMate(type: "server", mu_name: nodename) + mommacat.nodeSSLCerts(server_obj) + } + } +end + if $opts[:xecute] runCommand(do_deploys, do_nodes, $opts[:xecute], print_output: true) +elsif $opts[:mode] == "certs" + sslCerts(do_deploys, do_nodes) elsif $opts[:mode] == "groom" reGroom(do_deploys, do_nodes) elsif $opts[:mode] == "vaults" diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index ec410658d..c7dbaf172 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -1009,11 +1009,11 @@ def postBoot(instance_id = nil) begin if windows? # kick off certificate generation early; WinRM will need it - cert, key = @deploy.nodeSSLCert(self) + cert, key = @deploy.nodeSSLCerts(self) # session = getWinRMSession(50, 60) # XXX account for machines behind bastion hosts that we can't tunnel through; # maybe then it's ok to fall back to sshd - session = getSSHSession(40, 30) + session = getSSHSession(50, 60) initialSSHTasks(session) else session = getSSHSession(40, 30) diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index ace8ab718..9e5aac86e 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -761,7 +761,7 @@ def knifeCmd(cmd, showoutput = false) # Upload the certificate to a Chef Vault for this node def stashHostSSLCertSecret - cert, key = @server.deploy.nodeSSLCert(@server) + cert, key = @server.deploy.nodeSSLCerts(@server) certdata = { "data" => { diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 18328a3c0..0a9111e1e 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1959,8 +1959,9 @@ def signSSLCert(csr_path, sans = []) MU.log "Incorporting Subject Alternative Names: #{sans.join(",")}" ef = OpenSSL::X509::ExtensionFactory.new ef.issuer_certificate = cacert +#v3_req_client ef.subject_certificate = cert - ef.subject_certificate = cert + ef.subject_request = csr cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false)) # XXX only do this if we see the otherName thinger in the san list cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth",false)) @@ -2065,27 +2066,38 @@ def syncLitter(nodeclasses = [], triggering_node: nil, save_all_only: false) # Given a MU::Cloud::Server object, return the generic self-signed SSL # certficate we made for it. If one doesn't exist yet, generate it first. + # If it's a Windows node, also generate a certificate for WinRM client auth. # @param server [MU::Cloud::Server]: The server for which to generate or return the cert - def nodeSSLCert(server) + def nodeSSLCerts(server) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = server.getSSHConfig - # Manufacture a generic SSL certificate, signed by the Mu master, for - # consumption by various node services (Apache, Splunk, etc). + certs = {} + results = {} + if File.exists?("#{MU.mySSLDir}/#{server.mu_name}.crt") and File.exists?("#{MU.mySSLDir}/#{server.mu_name}.key") - [File.read("#{MU.mySSLDir}/#{server.mu_name}.crt"), File.read("#{MU.mySSLDir}/#{server.mu_name}.key")] + results[server.mu_name] = [File.read("#{MU.mySSLDir}/#{server.mu_name}.crt"), File.read("#{MU.mySSLDir}/#{server.mu_name}.key")] + else + certs[server.mu_name] = { + "sans" => ["DNS:#{canonical_ip}"], + "cn" => canonical_ip + } end - certs = { - server.mu_name => ["DNS:#{canonical_ip}"] - } if server.windows? - certs[server.mu_name+"-winrm"] = ["otherName:msUPN;UTF8:#{$MU_CFG['mu_admin_email']}"] + if File.exists?("#{MU.mySSLDir}/#{server.mu_name}-winrm.crt") and + File.exists?("#{MU.mySSLDir}/#{server.mu_name}-winrm.key") + results[server.mu_name+"-winrm"] = [File.read("#{MU.mySSLDir}/#{server.mu_name}-winrm.crt"), File.read("#{MU.mySSLDir}/#{server.mu_name}-winrm.key")] + else + certs[server.mu_name+"-winrm"] = { + "sans" => ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{$MU_CFG['mu_admin_email']}"], + "cn" => server.config['windows_admin_username'] + } + end end - results = {} - certs.each { |certname, sans| - MU.log "Creating self-signed service SSL certificate for #{certname} (CN=#{canonical_ip})" + certs.each { |certname, data| + MU.log "Generating SSL certificate #{certname} for #{server}" # Create and save a key key = OpenSSL::PKey::RSA.new 4096 @@ -2098,14 +2110,14 @@ def nodeSSLCert(server) } # Create a certificate request for this node csr = OpenSSL::X509::Request.new - csr.version = 0 - csr.subject = OpenSSL::X509::Name.parse "CN=#{canonical_ip}/O=Mu/C=US" + csr.version = 3 + csr.subject = OpenSSL::X509::Name.parse "CN=#{data['cn']}/O=Mu/C=US" csr.public_key = key.public_key open("#{MU.mySSLDir}/#{certname}.csr", 'w', 0644) { |io| io.write csr.to_pem } if MU.chef_user == "mu" - signSSLCert("#{MU.mySSLDir}/#{certname}.csr", sans) + signSSLCert("#{MU.mySSLDir}/#{certname}.csr", data['sans']) else deploykey = OpenSSL::PKey::RSA.new(public_key) deploysecret = Base64.urlsafe_encode64(deploykey.public_encrypt(deploy_secret)) From c615c9498510bf3dbd87f02450a00182da73a1d9 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 31 Aug 2017 14:17:37 -0400 Subject: [PATCH 05/63] new Windows metadata, which in theory enables WinRM certificate auth --- bin/mu-aws-setup | 15 +- bin/mu-node-manage | 2 + modules/mommacat.ru | 13 +- modules/mu/clouds/aws/server.rb | 5 +- modules/mu/config.rb | 4 +- modules/mu/mommacat.rb | 48 +++- modules/mu/userdata/windows.erb | 416 +++++++++++++++++--------------- 7 files changed, 301 insertions(+), 202 deletions(-) diff --git a/bin/mu-aws-setup b/bin/mu-aws-setup index b10e62cc5..2836cf3ec 100755 --- a/bin/mu-aws-setup +++ b/bin/mu-aws-setup @@ -40,7 +40,7 @@ Usage: EOS opt :ip, "Attempt to configure the IP requested in the CHEF_PUBLIC_IP environment variable, or if none is set, to associate an arbitrary Elastic IP.", :require => false, :default => false, :type => :boolean opt :sg, "Attempt to configure a Security Group with appropriate permissions.", :require => false, :default => false, :type => :boolean - opt :logs, "Ensure the presence of an S3 bucket prefixed with 'Mu_Logs' for use with CloudTrails, syslog, etc.", :require => false, :default => false, :type => :boolean + opt :logs, "Ensure the presence of a cloud storage bucket for use with CloudTrails, syslog, deploy secrets, node SSL certificates, etc.", :require => false, :default => false, :type => :boolean opt :dns, "Ensure the presence of a private DNS Zone called for internal amongst Mu resources.", :require => false, :default => false, :type => :boolean opt :uploadlogs, "Push today's log files to the S3 bucket created by the -l option.", :require => false, :default => false, :type => :boolean end @@ -186,6 +186,15 @@ if $opts[:logs] body: "#{key}" ) end + if File.exists?("#{MU.mySSLDir}/Mu_CA.pem") + MU.log "Putting the Mu Master's public SSL certificate into #{$bucketname}/Mu_CA.pem" + MU::Cloud::AWS.s3.put_object( + bucket: $bucketname, + key: "Mu_CA.pem", + body: File.read("#{MU.mySSLDir}/Mu_CA.pem"), + acl: "public-read", + ) + end # MU.log "Uploading Mu_CA.pem to #{$bucketname}" # MU::Cloud::AWS.s3.put_object( @@ -196,8 +205,8 @@ if $opts[:logs] # ) resp = MU::Cloud::AWS.s3.list_objects( - bucket: $bucketname, - prefix: "log_vol_ebs_key" + bucket: $bucketname, + prefix: "log_vol_ebs_key" ) owner = MU.structToHash(resp.contents.first.owner) diff --git a/bin/mu-node-manage b/bin/mu-node-manage index ea13471d4..6c49fb7df 100755 --- a/bin/mu-node-manage +++ b/bin/mu-node-manage @@ -299,6 +299,7 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) "muUser" => MU.chef_user, "publicIP" => MU.mu_public_ip, "resourceName" => svr_class, + "windowsAdminName" => server['windows_admin_username'], "skipApplyUpdates" => server['skipinitialupdates'], "resourceType" => "server_pool" }, @@ -431,6 +432,7 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) "muUser" => MU.chef_user, "publicIP" => MU.mu_public_ip, "resourceName" => server['conf']['name'], + "windowsAdminName" => server['conf']['windows_admin_username'], "skipApplyUpdates" => server['conf']['skipinitialupdates'], "resourceType" => mytype }, diff --git a/modules/mommacat.ru b/modules/mommacat.ru index bff3cf845..ac792be30 100644 --- a/modules/mommacat.ru +++ b/modules/mommacat.ru @@ -356,15 +356,17 @@ app = proc do |env| # Now we're just checking for existence in the cloud provider, really MU.log "No existing groomed server found, verifying that a server with this cloud id exists" instance = MU::Cloud::Server.find(cloud_id: req["mu_instance_id"], region: server_cfg["region"]) +# XXX barf if this comes back empty else mu_name = instance.mu_name MU.log "Found an existing node named #{mu_name}" end - if !req["mu_ssl_sign"].nil? + if !req["mu_windows_admin_creds"].nil? + returnval[2] = [kittenpile.retrieveWindowsAdminCreds(instance).join(";")] + elsif !req["mu_ssl_sign"].nil? + kittenpile.signSSLCert(req["mu_ssl_sign"], req["mu_ssl_sans"].split(/,/)) kittenpile.signSSLCert(req["mu_ssl_sign"], req["mu_ssl_sans"].split(/,/)) elsif !req["add_volume"].nil? -puts instance.cloud_id -pp req if instance.respond_to?(:addVolume) # XXX make sure we handle mangled input safely params = JSON.parse(Base64.decode64(req["add_volume"])) @@ -376,6 +378,7 @@ pp req elsif !instance.nil? if !req["mu_bootstrap"].nil? kittenpile.groomNode(req["mu_instance_id"], req["mu_resource_name"], req["mu_resource_type"], mu_name: mu_name, sync_wait: true) + returnval[2] = ["Grooming asynchronously, check Momma Cat logs on the master for details."] else returnval = throw500 "Didn't get 'mu_bootstrap' parameter from instance id '#{req["mu_instance_id"]}'" ok = false @@ -394,6 +397,10 @@ pp req MU.purgeGlobals end end + if returnval[1] and returnval[1].has_key?("Content-Length") and + returnval[2] and returnval[2].is_a?(Array) + returnval[1]["Content-Length"] = returnval[2][0].size.to_s + end returnval end diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index c7dbaf172..2edfcb205 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -161,7 +161,7 @@ def self.fetchUserdata(platform: "linux", template_variables: {}, custom_append: end userdata = File.read(erbfile) begin - erb = ERB.new(userdata) + erb = ERB.new(userdata, nil, "<>") script = erb.result rescue NameError => e raise MuError, "Error parsing userdata script #{erbfile} as an ERB template: #{e.inspect}" @@ -177,7 +177,7 @@ def self.fetchUserdata(platform: "linux", template_variables: {}, custom_append: MU.log "Loaded userdata script from #{custom_append['path']}" if custom_append['use_erb'] begin - erb = ERB.new(erbfile, 1) + erb = ERB.new(erbfile, 1, "<>") if custom_append['skip_std'] script = +erb.result else @@ -313,6 +313,7 @@ def self.removeIAMProfile(rolename) def self.addStdPoliciesToIAMProfile(rolename, cloudformation_data: {}, cfm_role_name: nil) policies = Hash.new policies['Mu_Bootstrap_Secret_'+MU.deploy_id] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{MU.deploy_id}-secret"+'"}]}' + policies['Mu_Node_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.pfx"+'"}]}' policies['Mu_Node_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.crt"+'"}]}' policies['Mu_Node_Key'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.key"+'"}]}' policies['Mu_WinRM_Client_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}-winrm.crt"+'"}]}' diff --git a/modules/mu/config.rb b/modules/mu/config.rb index 6e34a9d92..47fa35233 100644 --- a/modules/mu/config.rb +++ b/modules/mu/config.rb @@ -331,7 +331,7 @@ def cloudCode(code, placeholder = "CLOUDCODEPLACEHOLDER") # templates. They're globals on purpose. Stop whining. $file_format = MU::Config.guessFormat(path) $yaml_refs = {} - erb = ERB.new(File.read(path)) + erb = ERB.new(File.read(path), 1, "<>") raw_text = erb.result(get_binding) raw_json = nil @@ -690,7 +690,7 @@ def self.include(file, binding = nil, param_pass = false) assume_type = :yaml end begin - erb = ERB.new(File.read(file)) + erb = ERB.new(File.read(file), 1, "<>") rescue Errno::ENOENT => e retries = retries + 1 if retries == 1 diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 0a9111e1e..b2fe73972 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1914,6 +1914,32 @@ def listNodes return nodes end + # For a given (Windows) server, return it's administrator user and password. + # This is generally for requests made to MommaCat from said server, which + # we can assume have been authenticated with the deploy secret. + # @param server [MU::Cloud::Server]: The Server object whose credentials we're fetching. + def retrieveWindowsAdminCreds(server) + if server.nil? or server.class.name != "MU::Cloud::Server" + raise MuError, "retrieveWindowsAdminCreds must be called with a Server object" + end + if !server.windows? + raise MuError, "#{server} is not a Windows node" + end + if server.config['use_cloud_provider_windows_password'] + return [server.config["windows_admin_username"], getWindowsAdminPassword] + elsif server.config['windows_auth_vault'] && !server.config['windows_auth_vault'].empty? + if server.config["windows_auth_vault"].has_key?("password_field") + return [server.config["windows_admin_username"], + server.groomer.getSecret( + vault: server.config['windows_auth_vault']['vault'], + item: server.config['windows_auth_vault']['item'], + field: server.config["windows_auth_vault"]["password_field"] + )] + else + return [server.config["windows_admin_username"], server.getWindowsAdminPassword] + end + end + end # Given a Certificate Signing Request, sign it with our internal CA and # writers the resulting signed certificate. Only works on local files. @@ -1929,11 +1955,11 @@ def signSSLCert(csr_path, sans = []) MU.log "Signing SSL certificate request #{csr_path} with #{MU.mySSLDir}/Mu_CA.pem" csr = OpenSSL::X509::Request.new File.read csr_path + key = OpenSSL::PKey::RSA.new File.read "#{certdir}/#{certname}.key" # Load up the Mu Certificate Authority cakey = OpenSSL::PKey::RSA.new File.read "#{MU.mySSLDir}/Mu_CA.key" cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem" - cur_serial = 0 File.open("#{MU.mySSLDir}/serial", File::CREAT|File::RDWR, 0600) { |f| f.flock(File::LOCK_EX) @@ -1962,9 +1988,10 @@ def signSSLCert(csr_path, sans = []) #v3_req_client ef.subject_certificate = cert ef.subject_request = csr + cert.add_extension(ef.create_extension("keyUsage","nonRepudiation,digitalSignature,keyEncipherment", false)) cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false)) # XXX only do this if we see the otherName thinger in the san list - cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth",false)) + cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth,serverAuth,codeSigning,emailProtection",false)) end cert.sign cakey, OpenSSL::Digest::SHA256.new @@ -2079,8 +2106,8 @@ def nodeSSLCerts(server) results[server.mu_name] = [File.read("#{MU.mySSLDir}/#{server.mu_name}.crt"), File.read("#{MU.mySSLDir}/#{server.mu_name}.key")] else certs[server.mu_name] = { - "sans" => ["DNS:#{canonical_ip}"], - "cn" => canonical_ip + "sans" => ["IP:#{canonical_ip}"], + "cn" => server.mu_name } end @@ -2146,9 +2173,22 @@ def nodeSSLCerts(server) cert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/#{certname}.crt" results[certname] = [cert, key] + pfx = nil + if server.windows? + cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem" +MU.log "Is this thing private? #{cert.check_private_key(key)} - #{cert.signature_algorithm}", MU::WARN, details: cert.extensions.map { |e| e.to_s } + pfx = OpenSSL::PKCS12.create(nil, nil, key, cert, [cacert], nil, nil, nil, nil) + open("#{MU.mySSLDir}/#{certname}.pfx", 'w', 0644) { |io| + io.write pfx.to_der + } + end + if server.config['cloud'] == "AWS" MU::Cloud::AWS.writeDeploySecret(@deploy_id, cert.to_pem, certname+".crt") MU::Cloud::AWS.writeDeploySecret(@deploy_id, key.to_pem, certname+".key") + if server.windows? + MU::Cloud::AWS.writeDeploySecret(@deploy_id, pfx.to_der, certname+".pfx") + end # XXX add google logic, or better yet abstract this method end } diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 8a18f837a..95c0015b0 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -1,27 +1,39 @@ Set-ExecutionPolicy Unrestricted -Force -Scope CurrentUser - $winrm = "c:/Windows/System32/winrm.cmd" - $winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' - $winrm set winrm/config '@{MaxTimeoutms="1800000"}' - - c:/Windows/system32/netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public protocol=tcp localport=5985 remoteip=localsubnet new remoteip=any - $sshdUser = "sshd_service" + $tmp = "$env:Temp/mu-userdata" + mkdir $tmp $logfile = "c:/Mu-Bootstrap-$([Environment]::UserName).log" $base_dir = 'c:/bin' $cygwin_dir = "$base_dir/cygwin" $username = (whoami).Split('\')[1] $WebClient = New-Object System.Net.WebClient $aws_meta = "http://169.254.169.254/latest" + $python_path = 'c:/bin/python/python27' + $env:Path += ";$python_path\Scripts;$python_path" function log { - $args + Write-Host $args Add-Content "c:/Mu-Bootstrap-$([Environment]::UserName).log" "$(Get-Date -f MM-dd-yyyy_HH:mm:ss) $args" Add-Content "c:/Mu-Bootstrap-GLOBAL.log" "$(Get-Date -f MM-dd-yyyy_HH:mm:ss) $args" } + function fetchSecret([string]$file){ + log "Fetching s3://<%= MU.adminBucketName %>/$file to $tmp/$file" + aws.cmd s3 cp s3://<%= MU.adminBucketName %>/$file $tmp/$file + } + + function importCert([string]$cert, [string]$store){ + fetchSecret($cert) + if($cert -Match ".pfx$"){ + return Import-PfxCertificate -FilePath $tmp/$cert -CertStoreLocation Cert:\LocalMachine\$store + } else { + return Import-Certificate -FilePath $tmp/$cert -CertStoreLocation Cert:\LocalMachine\$store + } + } + function Disable-SSHD { if ((Get-Service "sshd" -ErrorAction SilentlyContinue) -and (Test-Path "$cygwin_dir/bin/bash.exe")) { @@ -33,7 +45,7 @@ } } - log "----- Invoked as $([Environment]::UserName) (system started at $(Get-CimInstance -ClassName win32_operatingsystem | select lastbootuptime)) -----" + log "- Invoked as $([Environment]::UserName) (system started at $(Get-CimInstance -ClassName win32_operatingsystem | select lastbootuptime)) -" <% if !$mu.skipApplyUpdates %> If (!(Test-Path "c:/mu-installer-ran-updates")){ @@ -43,269 +55,297 @@ <% if $mu.platform != "win2k16" %> If ([Environment]::OSVersion.Version.Major -lt 10) { - If ("$($myInvocation.MyCommand.Path)" -ne "$env:Temp/realuserdata_stripped.ps1"){ + If ("$($myInvocation.MyCommand.Path)" -ne "$tmp/realuserdata_stripped.ps1"){ $Error.Clear() - Invoke-WebRequest -Uri "$aws_meta/user-data" -OutFile $env:Temp/realuserdata.ps1 + Invoke-WebRequest -Uri "$aws_meta/user-data" -OutFile $tmp/realuserdata.ps1 while($Error.count -gt 0){ $Error.Clear() log "Failed to retrieve current userdata from $aws_meta/user-data, waiting 15s and retrying" sleep 15 - Invoke-WebRequest -Uri "$aws_meta/user-data" -OutFile $env:Temp/realuserdata.ps1 + Invoke-WebRequest -Uri "$aws_meta/user-data" -OutFile $tmp/realuserdata.ps1 } - Get-Content $env:Temp/realuserdata.ps1 | Select-String -pattern '^#','^<' -notmatch | Set-Content $env:Temp/realuserdata_stripped.ps1 - If (Compare-Object (Get-Content $myInvocation.MyCommand.Path) (Get-Content $env:Temp/realuserdata_stripped.ps1)){ - log "Invoking $env:Temp/realuserdata.ps1 in lieu of $($myInvocation.MyCommand.Path)" - Invoke-Expression $env:Temp/realuserdata_stripped.ps1 + Get-Content $tmp/realuserdata.ps1 | Select-String -pattern '^#','^<' -notmatch | Set-Content $tmp/realuserdata_stripped.ps1 + If (Compare-Object (Get-Content $myInvocation.MyCommand.Path) (Get-Content $tmp/realuserdata_stripped.ps1)){ + log "Invoking $tmp/realuserdata.ps1 in lieu of $($myInvocation.MyCommand.Path)" + Invoke-Expression $tmp/realuserdata_stripped.ps1 exit } } } <% end %> $admin_username = (Get-WmiObject -Query 'Select * from Win32_UserAccount Where (LocalAccount=True and SID like "%-500")').name - log "Local admin account is $admin_username" + log "Local admin: $admin_username" Add-Type -Assembly System.Web $password = [Web.Security.Membership]::GeneratePassword(15,2) If (!(Test-Path $base_dir)){ - New-Item -type directory -path $base_dir + mkdir $base_dir } <% if $mu.platform != "win2k16" %> If ([Environment]::OSVersion.Version.Major -lt 10) { If (!(Get-ScheduledTask -TaskName 'run-userdata')){ log "Adding run-userdata scheduled task (user NT AUTHORITY\SYSTEM)" - Invoke-WebRequest -Uri "https://s3.amazonaws.com/cap-public/run-userdata_scheduledtask.xml" -OutFile $env:Temp/run-userdata_scheduledtask.xml - Register-ScheduledTask -Xml (Get-Content "$env:Temp/run-userdata_scheduledtask.xml" | out-string) -TaskName 'run-userdata' -Force -User "NT AUTHORITY\SYSTEM" + Invoke-WebRequest -Uri "https://s3.amazonaws.com/cap-public/run-userdata_scheduledtask.xml" -OutFile $tmp/run-userdata_scheduledtask.xml + Register-ScheduledTask -Xml (Get-Content "$tmp/run-userdata_scheduledtask.xml" | out-string) -TaskName 'run-userdata' -Force -User "NT AUTHORITY\SYSTEM" } } <% end %> - $instanceid=(New-Object System.Net.WebClient).DownloadString("http://169.254.169.254/latest/meta-data/instance-id") + $awsid=(New-Object System.Net.WebClient).DownloadString("http://169.254.169.254/latest/meta-data/instance-id") - If (!(Test-Path $env:Temp/PSWindowsUpdate.zip)){ + If (!(Test-Path $tmp/PSWindowsUpdate.zip)){ If (!(Test-Path c:/Users/$admin_username/Documents/WindowsPowerShell/Modules)){ mkdir c:/Users/$admin_username/Documents/WindowsPowerShell/Modules } - $WebClient.DownloadFile("https://s3.amazonaws.com/cap-public/PSWindowsUpdate.zip","$env:Temp/PSWindowsUpdate.zip") + $WebClient.DownloadFile("https://s3.amazonaws.com/cap-public/PSWindowsUpdate.zip","$tmp/PSWindowsUpdate.zip") Add-Type -A 'System.IO.Compression.FileSystem' If (!(Test-Path c:/windows/System32/WindowsPowerShell/v1.0/Modules/PSWindowsUpdate)){ log "Extracting PSWindowsUpdate module to c:/windows/System32/WindowsPowerShell/v1.0/Modules" - [IO.Compression.ZipFile]::ExtractToDirectory("$env:Temp/PSWindowsUpdate.zip", "c:/windows/System32/WindowsPowerShell/v1.0/Modules") + [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/PSWindowsUpdate.zip", "c:/windows/System32/WindowsPowerShell/v1.0/Modules") } If (!(Test-Path c:/Users/$admin_username/Documents/WindowsPowerShell/Modules/PSWindowsUpdate)){ log "Extracting PSWindowsUpdate module to c:/Users/$admin_username/Documents/WindowsPowerShell" - [IO.Compression.ZipFile]::ExtractToDirectory("$env:Temp/PSWindowsUpdate.zip", "c:/Users/$admin_username/Documents/WindowsPowerShell/Modules") + [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/PSWindowsUpdate.zip", "c:/Users/$admin_username/Documents/WindowsPowerShell/Modules") } } - log "Setting Windows Update parameters in registry" - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name AUOptions -Value 3 - - If (!(Test-Path "$cygwin_dir/Cygwin.bat")){ - If (!(Test-Path $env:Temp/setup-x86_64.exe)){ - $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe") - } - - If (!(Test-Path $env:Temp/cygwin.zip)){ - log "Downloading Cygwin packages" - $WebClient.DownloadFile("https://s3.amazonaws.com/mu-stuff/cygwin_20161022.zip","$env:Temp/cygwin.zip") - } + log "Setting Windows Update parameters in registry" + Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name AUOptions -Value 3 - Add-Type -A 'System.IO.Compression.FileSystem' - If (!(Test-Path $env:Temp/cygwin)){ - [IO.Compression.ZipFile]::ExtractToDirectory("$env:Temp/cygwin.zip", "$env:Temp/cygwin") - } - - log "Running Cygwin installer" - Start-Process -wait -FilePath "$env:Temp/setup-x86_64.exe" -ArgumentList "-q -n -l $env:Temp -l $env:Temp\cygwin -L -R $cygwin_dir -P openssh,mintty,vim,curl,openssl" + If (!(Test-Path "$cygwin_dir/Cygwin.bat")){ + If (!(Test-Path $tmp/setup-x86_64.exe)){ + $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$tmp/setup-x86_64.exe") } - if (!(Get-Service "sshd" -ErrorAction SilentlyContinue)){ - log "Invoking ssh-host-config to enable sshd as $sshdUser (I am $admin_username)" - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "ssh-host-config -y -c ntsec -w ''$password'' -u $sshdUser" > $cygwin_dir/sshd_setup_log.txt' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*StrictModes.*yes/StrictModes no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*PasswordAuthentication.*yes/PasswordAuthentication no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' - New-Item $cygwin_dir/sshd_installed_by.txt -type file -force -value $admin_username - log "Creating c:/$instanceid (<%= $mu.muID %>)" - New-Item c:/$instanceid -type file -force -value "<%= $mu.muID %>" - log "Value in that file: $(Get-Content c:/$instanceid)" + If (!(Test-Path $tmp/cygwin.zip)){ + log "Downloading Cygwin packages" + $WebClient.DownloadFile("https://s3.amazonaws.com/mu-stuff/cygwin_20161022.zip","$tmp/cygwin.zip") } - log "Ensuring domain or local users are in /etc/passwd for sshd" - if((Get-WmiObject win32_computersystem).partofdomain){ - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -d > /etc/passwd"' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l -d > /etc/group"' - } else { - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -l > /etc/passwd"' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l > /etc/group"' + Add-Type -A 'System.IO.Compression.FileSystem' + If (!(Test-Path $tmp/cygwin)){ + [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/cygwin.zip", "$tmp/cygwin") } - if (!(Get-WmiObject win32_computersystem).partofdomain){ - If (!(Test-Path "c:/mu-configure-initial-ssh-user")){ - log "making sure the ssh user is configured correctly" - (([adsi]("WinNT://./$sshdUser, user")).psbase.invoke('SetPassword', "$password")) - $sshd_service = Get-WmiObject Win32_Service -Filter "Name='sshd'" - $sshd_service.Change($Null,$Null,$Null,$Null,$Null,$Null,".\$sshdUser",$password,$Null,$Null,$Null) - - $editrights="$cygwin_dir/bin/editrights" - &$editrights -a SeAssignPrimaryTokenPrivilege -u $sshdUser - &$editrights -a SeCreateTokenPrivilege -u $sshdUser - &$editrights -a SeTcbPrivilege -u $sshdUser - &$editrights -a SeServiceLogonRight -u $sshdUser - Add-Content c:/mu-configure-initial-ssh-user "done" - } - } + log "Running Cygwin installer" + Start-Process -wait -FilePath "$tmp/setup-x86_64.exe" -ArgumentList "-q -n -l $tmp -l $tmp\cygwin -L -R $cygwin_dir -P openssh,mintty,vim,curl,openssl" + } - $sshd_svc_user = (Get-WmiObject -Query "SELECT * FROM win32_service WHERE name='sshd'").StartName - if ( $sshd_svc_user.contains("\") ){ - $sshd_svc_user = $sshd_svc_user.substring($sshd_svc_user.LastIndexOf("\")+1) + if (!(Get-Service "sshd" -ErrorAction SilentlyContinue)){ + log "Invoking ssh-host-config to enable sshd as $sshdUser (I am $admin_username)" + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "ssh-host-config -y -c ntsec -w ''$password'' -u $sshdUser" > $cygwin_dir/sshd_setup_log.txt' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*StrictModes.*yes/StrictModes no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*PasswordAuthentication.*yes/PasswordAuthentication no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' + New-Item $cygwin_dir/sshd_installed_by.txt -type file -force -value $admin_username + log "Creating c:/$awsid (<%= $mu.muID %>)" + New-Item c:/$awsid -type file -force -value "<%= $mu.muID %>" + log "Value in that file: $(Get-Content c:/$awsid)" + } + + log "Ensuring domain or local users are in /etc/passwd for sshd" + if((Get-WmiObject win32_computersystem).partofdomain){ + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -d > /etc/passwd"' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l -d > /etc/group"' + } else { + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -l > /etc/passwd"' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l > /etc/group"' + } + + if (!(Get-WmiObject win32_computersystem).partofdomain){ + If (!(Test-Path "c:/mu-configure-initial-ssh-user")){ + log "making sure the ssh user is configured correctly" + (([adsi]("WinNT://./$sshdUser, user")).psbase.invoke('SetPassword', "$password")) + $sshd_service = Get-WmiObject Win32_Service -Filter "Name='sshd'" + $sshd_service.Change($Null,$Null,$Null,$Null,$Null,$Null,".\$sshdUser",$password,$Null,$Null,$Null) + + $editrights="$cygwin_dir/bin/editrights" + &$editrights -a SeAssignPrimaryTokenPrivilege -u $sshdUser + &$editrights -a SeCreateTokenPrivilege -u $sshdUser + &$editrights -a SeTcbPrivilege -u $sshdUser + &$editrights -a SeServiceLogonRight -u $sshdUser + Add-Content c:/mu-configure-initial-ssh-user "done" } - log "Chowning /var/empty, /var/log/sshd.log, and /etc/ssh* to $sshd_svc_user" - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "chown $sshd_svc_user /var/empty /var/log/sshd.log /etc/ssh*; chmod 755 /var/empty"' - - If (!((Get-ItemProperty HKLM:/SYSTEM/CurrentControlSet/Control/Lsa)."Authentication Packages" | Select-String -pattern "cyglsa64.dll")){ - If ((Test-Path "$cygwin_dir/bin/cyglsa-config")){ - log "Setting Cygwin LSA support, will reboot" - Invoke-Expression '& $cygwin_dir/bin/bash --login -c "echo yes | /bin/cyglsa-config"' - $need_reboot = $TRUE - } else { - log "Need to set Cygwin LSA support, but I don't see $cygwin_dir/bin/cyglsa-config!" - } + } + + $sshd_svc_user = (Get-WmiObject -Query "SELECT * FROM win32_service WHERE name='sshd'").StartName + if ( $sshd_svc_user.contains("\") ){ + $sshd_svc_user = $sshd_svc_user.substring($sshd_svc_user.LastIndexOf("\")+1) + } + log "Chowning /var/empty, /var/log/sshd.log, and /etc/ssh* to $sshd_svc_user" + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "chown $sshd_svc_user /var/empty /var/log/sshd.log /etc/ssh*; chmod 755 /var/empty"' + + If (!((Get-ItemProperty HKLM:/SYSTEM/CurrentControlSet/Control/Lsa)."Authentication Packages" | Select-String -pattern "cyglsa64.dll")){ + If ((Test-Path "$cygwin_dir/bin/cyglsa-config")){ + log "Setting Cygwin LSA support, will reboot" + Invoke-Expression '& $cygwin_dir/bin/bash --login -c "echo yes | /bin/cyglsa-config"' + $need_reboot = $TRUE + } else { + log "Need to set Cygwin LSA support, but I don't see $cygwin_dir/bin/cyglsa-config!" } + } - $python_path = 'c:\bin\python\python27' - $env:Path += ";$python_path\Scripts;$python_path" - If (!(Test-Path "$python_path\python.exe")){ - If (!(Test-Path $env:Temp/python-2.7.9.msi)){ - log "Downloading Python installer" - $WebClient.DownloadFile("https://www.python.org/ftp/python/2.7.9/python-2.7.9.msi","$env:Temp/python-2.7.9.msi") - } - log "Running Python installer" - (Start-Process -FilePath msiexec -ArgumentList "/i $env:Temp\python-2.7.9.msi /qn ALLUSERS=1 TARGETDIR=$python_path" -Wait -Passthru).ExitCode + If (!(Test-Path "$python_path\python.exe")){ + If (!(Test-Path $tmp/python-2.7.9.msi)){ + log "Downloading Python installer" + $WebClient.DownloadFile("https://www.python.org/ftp/python/2.7.9/python-2.7.9.msi","$tmp/python-2.7.9.msi") } + log "Running Python installer" + (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\python-2.7.9.msi /qn ALLUSERS=1 TARGETDIR=$python_path" -Wait -Passthru).ExitCode + } - If (!(Test-Path "$python_path\Scripts\aws.cmd")){ - If (!(Test-Path $env:Temp/get-pip.py)){ - log "Downloading get-pip.py" - $WebClient.DownloadFile("https://bootstrap.pypa.io/get-pip.py","$env:Temp/get-pip.py") - } - python $env:Temp/get-pip.py - log "Running pip install awscli" - pip install awscli + If (!(Test-Path "$python_path\Scripts\aws.cmd")){ + If (!(Test-Path $tmp/get-pip.py)){ + log "Downloading get-pip.py" + $WebClient.DownloadFile("https://bootstrap.pypa.io/get-pip.py","$tmp/get-pip.py") } + python $tmp/get-pip.py + log "Running pip install awscli" + pip install awscli + } - function removeChef($location){ - $install_chef = $false - $my_chef = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).DisplayName - if ($my_chef) { - if ($my_chef -match '<%= MU.chefVersion %>'.split('-')[0]) { - $install_chef = $false - } else{ - log "Uninstalling Chef" - $uninstall_string = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).UninstallString - $uninstall_string = ($uninstall_string -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","").Trim() - $($uninstall_string -Replace '[\s\t]+', ' ').Split() | ForEach { - log "msiexec.exe /X $_ /gn" - start-process "msiexec.exe" -arg "/X $_ /qn" -Wait - } - $install_chef = $true + function removeChef($location){ + $install_chef = $false + $my_chef = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).DisplayName + if ($my_chef) { + if ($my_chef -match '<%= MU.chefVersion %>'.split('-')[0]) { + $install_chef = $false + } else{ + log "Uninstalling Chef" + $uninstall_string = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).UninstallString + $uninstall_string = ($uninstall_string -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","").Trim() + $($uninstall_string -Replace '[\s\t]+', ' ').Split() | ForEach { + log "msiexec.exe /X $_ /gn" + start-process "msiexec.exe" -arg "/X $_ /qn" -Wait } + $install_chef = $true } - - return $install_chef } + + return $install_chef + } - If (!(Test-Path "c:\opscode\chef\embedded\bin\ruby.exe")){ + If (!(Test-Path "c:\opscode\chef\embedded\bin\ruby.exe")){ + $install_chef = $true + } else { + if (removeChef("HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*")){ + $install_chef = $true + } elseif (removeChef("HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*")) { $install_chef = $true } else { - if (removeChef("HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*")){ - $install_chef = $true - } elseif (removeChef("HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*")) { - $install_chef = $true - } else { - $install_chef = $false - } + $install_chef = $false } + } - If ($install_chef){ - log "Installing Chef" - If (!(Test-Path $env:Temp/chef-installer-<%= MU.chefVersion %>.msi)){ - log "Downloading Chef installer" - $WebClient.DownloadFile("https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=<%= MU.chefVersion %>","$env:Temp/chef-installer-<%= MU.chefVersion %>.msi") - } - log "Running Chef installer" - (Start-Process -FilePath msiexec -ArgumentList "/i $env:Temp\chef-installer-<%= MU.chefVersion %>.msi ALLUSERS=1 /le $env:Temp\chef-client-install.log /qn" -Wait -Passthru).ExitCode - Set-Content "c:/mu_installed_chef" "yup" + If ($install_chef){ + log "Installing Chef" + If (!(Test-Path $tmp/chef-installer-<%= MU.chefVersion %>.msi)){ + log "Downloading Chef installer" + $WebClient.DownloadFile("https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=<%= MU.chefVersion %>","$tmp/chef-installer-<%= MU.chefVersion %>.msi") } + log "Running Chef installer" + (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\chef-installer-<%= MU.chefVersion %>.msi ALLUSERS=1 /le $tmp\chef-client-install.log /qn" -Wait -Passthru).ExitCode + Set-Content "c:/mu_installed_chef" "yup" + } - <% if !$mu.skipApplyUpdates %> - If (!(Test-Path "c:/mu-installer-ran-updates")){ - log "Applying Windows updates" - Import-Module PSWindowsUpdate - Get-WUInstall -AcceptAll -IgnoreReboot - Start-Sleep -s 60 - If (Test-Path "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired"){ - log "Registry fiddling says I need a reboot" - $need_reboot = $TRUE - } +<% if !$mu.skipApplyUpdates %> + If (!(Test-Path "c:/mu-installer-ran-updates")){ + log "Applying Windows updates" + Import-Module PSWindowsUpdate + Get-WUInstall -AcceptAll -IgnoreReboot + Start-Sleep -s 60 + If (Test-Path "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired"){ + log "Registry fiddling says I need a reboot" + $need_reboot = $TRUE } - <% end %> - - log "Fetching Mu deploy secret from s3://<%= MU.adminBucketName %>/<%= $mu.muID %>-secret" - aws.cmd s3 cp s3://<%= MU.adminBucketName %>/<%= $mu.muID %>-secret $env:Temp/<%= $mu.muID %>-secret + } +<% end %> - log "Encrypting Mu deploy secret" - $deploy_secret = & "c:\opscode\chef\embedded\bin\ruby" -ropenssl -rbase64 -e "key = OpenSSL::PKey::RSA.new(Base64.urlsafe_decode64('<%= $mu.deployKey %>'))" -e "print Base64.urlsafe_encode64(key.public_encrypt(File.read('$env:Temp/<%= $mu.muID %>-secret')))" + fetchSecret("<%= $mu.muID %>-secret") + log "Encrypting Mu deploy secret" + $deploy_secret = & "c:\opscode\chef\embedded\bin\ruby" -ropenssl -rbase64 -e "key = OpenSSL::PKey::RSA.new(Base64.urlsafe_decode64('<%= $mu.deployKey %>'))" -e "print Base64.urlsafe_encode64(key.public_encrypt(File.read('$tmp/<%= $mu.muID %>-secret')))" - if (!(Get-NetFirewallRule -DisplayName "Allow SSH" -ErrorAction SilentlyContinue)){ - log "Opening port 22 in Windows Firewall" - New-NetFirewallRule -DisplayName "Allow SSH" -Direction Inbound -LocalPort 22 -Protocol TCP -Action Allow - } + function callMomma([string]$act) + { + $params = @{mu_id='<%= $mu.muID %>';mu_resource_name='<%= $mu.resourceName %>';mu_resource_type='<%= $mu.resourceType %>';mu_instance_id="$awsid";mu_user='<%= $mu.muUser %>';mu_deploy_secret="$deploy_secret";$act="1"} + log "Calling Momma Cat at https://52.0.111.223:2260 with $act" + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # XXX + $resp = Invoke-WebRequest -Uri https://52.0.111.223:2260 -Method POST -Body $params + return $resp.Content + } <% if $mu.windowsAdminName %> - if ((Get-WmiObject win32_computersystem).partofdomain -ne $true){ - if ("$admin_username" -ne "<%= $mu.windowsAdminName %>"){ - log "Changing local admin account from $admin_username to <%= $mu.windowsAdminName %>" - ([adsi]("WinNT://./$admin_username, user")).psbase.rename("<%= $mu.windowsAdminName %>") - $need_reboot = $TRUE - } + if ((Get-WmiObject win32_computersystem).partofdomain -ne $true){ + if ("$admin_username" -ne "<%= $mu.windowsAdminName %>"){ + log "Changing local admin account from $admin_username to <%= $mu.windowsAdminName %>" + ([adsi]("WinNT://./$admin_username, user")).psbase.rename("<%= $mu.windowsAdminName %>") + $need_reboot = $TRUE } + } <% end %> + $muca = importCert "Mu_CA.pem" "Root" + + $myname = "FEMABASE-DEV-2017082915-RD-WINDOWS" + + $nodecert = importCert "$myname.pfx" "My" + $thumb = $nodecert.Thumbprint + winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$myname`";CertificateThumbprint=`"$thumb`"}" + + $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" + Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true + $credstr = callMomma "mu_windows_admin_creds" + if($credstr){ + $credparts = $pass.Split(";", 2) + $creds = New-Object System.Management.Automation.PSCredential($credparts[0], (ConvertTo-SecureString $credparts[1] -AsPlainText -Force)) + if($creds){ + New-Item -Path WSMan:\localhost\ClientCertificate -Subject <%= $mu.windowsAdminName %> -URI * -Issuer $muca.Thumbprint -Force -Credential $creds + } + } + + winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' + winrm set winrm/config '@{MaxTimeoutms="1800000"}' + +# XXX winrm + if (!(Get-NetFirewallRule -DisplayName "Allow SSH" -ErrorAction SilentlyContinue)){ + log "Opening port 22 in Windows Firewall" + New-NetFirewallRule -DisplayName "Allow SSH" -Direction Inbound -LocalPort 22 -Protocol TCP -Action Allow + } + <% if $mu.windowsAdminName %> - log "Creating $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys" - New-Item $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" + log "Creating $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys" + New-Item $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" <% else %> - log "Creating $cygwin_dir/home/$admin_username/.ssh/authorized_keys" - New-Item $cygwin_dir/home/$admin_username/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" + log "Creating $cygwin_dir/home/$admin_username/.ssh/authorized_keys" + New-Item $cygwin_dir/home/$admin_username/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" <% end %> - if((Get-WURebootStatus -Silent) -eq $true){ - log "Get-WURebootStatus telling me I need a reboot for real" - $need_reboot = $TRUE - } + if((Get-WURebootStatus -Silent) -eq $true){ + log "Get-WURebootStatus says to reboot" + $need_reboot = $TRUE + } - if ($need_reboot){ - log "----- REBOOT -----" - Restart-Computer -Force - exit - } else { - Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" + if ($need_reboot){ + log "- REBOOT -" + Restart-Computer -Force + exit + } else { + Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" - log "Enabling sshd service" - sleep 30; Start-Service sshd - Set-Service sshd -startuptype "Automatic" - #Get-WUInstall -AcceptAll -AutoReboot + log "Enabling sshd service" + sleep 30; Start-Service sshd + Set-Service sshd -startuptype "Automatic" + #Get-WUInstall -AcceptAll -AutoReboot - $url = 'https://<%= $mu.publicIP %>:2260' - log "Calling home to $url" - Start-Process -FilePath "c:\bin\cygwin\bin\curl.exe" -ArgumentList "-k --data mu_id='<%= $mu.muID %>' --data mu_resource_name='<%= $mu.resourceName %>' --data mu_resource_type='<%= $mu.resourceType %>' --data mu_instance_id='$instanceid' --data mu_bootstrap='1' --data mu_user='<%= $mu.muUser %>' --data mu_deploy_secret='$deploy_secret' $url" -Wait - log $(Get-Content $cygwin_dir/var/log/sshd.log) - } + callMomma "mu_bootstrap" + log $(Get-Content $cygwin_dir/var/log/sshd.log) + } Set-Content "c:/mu_userdata_complete" "yup" + Remove-Item -Recurse $tmp +# XXX Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Undefined true From 2f802fd4c60df7f18de8aff4396d2dcc8c1afbd5 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 31 Aug 2017 15:44:32 -0400 Subject: [PATCH 06/63] shrink Windows userdata script a smidge --- bin/mu-node-manage | 27 +- modules/mu/config.rb | 4 +- modules/mu/mommacat.rb | 1 - modules/mu/userdata/windows.erb | 581 ++++++++++++++++---------------- 4 files changed, 315 insertions(+), 298 deletions(-) diff --git a/bin/mu-node-manage b/bin/mu-node-manage index 6c49fb7df..f610c68cd 100755 --- a/bin/mu-node-manage +++ b/bin/mu-node-manage @@ -353,7 +353,8 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) next if !need_update # Put our Autoscale group onto a temporary launch config - MU::Cloud::AWS.autoscale.create_launch_configuration( + begin + MU::Cloud::AWS.autoscale.create_launch_configuration( launch_configuration_name: pool_name+"-TMP", user_data: Base64.encode64(userdata), image_id: server["basis"]["launch_config"]["ami_id"], @@ -365,7 +366,15 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) iam_instance_profile: launch.iam_instance_profile, ebs_optimized: server["basis"]["launch_config"]["ebs_optimized"], associate_public_ip_address: launch.associate_public_ip_address - ) + ) + rescue ::Aws::AutoScaling::Errors::ValidationError => e + if e.message.match(/Member must have length less than or equal to (\d+)/) + MU.log "Userdata script too long updating #{pool_name} Launch Config (#{Base64.encode64(userdata).size.to_s}/#{Regexp.last_match[1]} bytes)", MU::ERR + else + MU.log "Error updating #{pool_name} Launch Config", MU::ERR, details: e.message + end + next + end MU::Cloud::AWS.autoscale.update_auto_scaling_group( auto_scaling_group_name: pool_name, @@ -454,13 +463,19 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) end MU.log "Updating #{nodename} userdata (#{server["conf"]["platform"]})" - - MU::Cloud::AWS.ec2(server['region']).modify_instance_attribute( + begin + MU::Cloud::AWS.ec2(server['region']).modify_instance_attribute( instance_id: id, attribute: "userData", value: Base64.encode64(userdata) - ) - + ) + rescue ::Aws::EC2::Errors::InvalidParameterValue => e + if e.message.match(/User data is limited to (\d+)/) + MU.log "Userdata script too long updating #{nodename} (#{userdata.size.to_s}/#{Regexp.last_match[1]} bytes)", MU::ERR + else + MU.log "Error replacing userData on #{nodename}", MU::ERR, details: e.message + end + end } } end diff --git a/modules/mu/config.rb b/modules/mu/config.rb index 47fa35233..ce87ef834 100644 --- a/modules/mu/config.rb +++ b/modules/mu/config.rb @@ -331,7 +331,7 @@ def cloudCode(code, placeholder = "CLOUDCODEPLACEHOLDER") # templates. They're globals on purpose. Stop whining. $file_format = MU::Config.guessFormat(path) $yaml_refs = {} - erb = ERB.new(File.read(path), 1, "<>") + erb = ERB.new(File.read(path), nil, "<>") raw_text = erb.result(get_binding) raw_json = nil @@ -690,7 +690,7 @@ def self.include(file, binding = nil, param_pass = false) assume_type = :yaml end begin - erb = ERB.new(File.read(file), 1, "<>") + erb = ERB.new(File.read(file), nil, "<>") rescue Errno::ENOENT => e retries = retries + 1 if retries == 1 diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index b2fe73972..362d8a9b7 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -2176,7 +2176,6 @@ def nodeSSLCerts(server) pfx = nil if server.windows? cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem" -MU.log "Is this thing private? #{cert.check_private_key(key)} - #{cert.signature_algorithm}", MU::WARN, details: cert.extensions.map { |e| e.to_s } pfx = OpenSSL::PKCS12.create(nil, nil, key, cert, [cacert], nil, nil, nil, nil) open("#{MU.mySSLDir}/#{certname}.pfx", 'w', 0644) { |io| io.write pfx.to_der diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 95c0015b0..70b4310b6 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -1,351 +1,354 @@ - Set-ExecutionPolicy Unrestricted -Force -Scope CurrentUser - - $sshdUser = "sshd_service" - $tmp = "$env:Temp/mu-userdata" - mkdir $tmp - $logfile = "c:/Mu-Bootstrap-$([Environment]::UserName).log" - $base_dir = 'c:/bin' - $cygwin_dir = "$base_dir/cygwin" - $username = (whoami).Split('\')[1] - $WebClient = New-Object System.Net.WebClient - $aws_meta = "http://169.254.169.254/latest" - $python_path = 'c:/bin/python/python27' - $env:Path += ";$python_path\Scripts;$python_path" - - function log - { - Write-Host $args - Add-Content "c:/Mu-Bootstrap-$([Environment]::UserName).log" "$(Get-Date -f MM-dd-yyyy_HH:mm:ss) $args" - Add-Content "c:/Mu-Bootstrap-GLOBAL.log" "$(Get-Date -f MM-dd-yyyy_HH:mm:ss) $args" - } - - function fetchSecret([string]$file){ - log "Fetching s3://<%= MU.adminBucketName %>/$file to $tmp/$file" - aws.cmd s3 cp s3://<%= MU.adminBucketName %>/$file $tmp/$file - } - - function importCert([string]$cert, [string]$store){ - fetchSecret($cert) - if($cert -Match ".pfx$"){ - return Import-PfxCertificate -FilePath $tmp/$cert -CertStoreLocation Cert:\LocalMachine\$store - } else { - return Import-Certificate -FilePath $tmp/$cert -CertStoreLocation Cert:\LocalMachine\$store - } +Set-ExecutionPolicy Unrestricted -Force -Scope CurrentUser + +$sshdUser = "sshd_service" +$tmp = "$env:Temp/mu-userdata" +mkdir $tmp +$logfile = "c:/Mu-Bootstrap-$([Environment]::UserName).log" +$basedir = 'c:/bin' +$cygwin_dir = "$basedir/cygwin" +$username = (whoami).Split('\')[1] +$WebClient = New-Object System.Net.WebClient +$awsmeta = "http://169.254.169.254/latest" +$pydir = 'c:/bin/python/python27' +$env:Path += ";$pydir\Scripts;$pydir" + +function log +{ + Write-Host $args + Add-Content "c:/Mu-Bootstrap-$([Environment]::UserName).log" "$(Get-Date -f MM-dd-yyyy_HH:mm:ss) $args" + Add-Content "c:/Mu-Bootstrap-GLOBAL.log" "$(Get-Date -f MM-dd-yyyy_HH:mm:ss) $args" +} + +function fetchSecret([string]$file){ + log "Fetching s3://<%= MU.adminBucketName %>/$file to $tmp/$file" + aws.cmd s3 cp s3://<%= MU.adminBucketName %>/$file $tmp/$file +} + +function importCert([string]$cert, [string]$store){ + fetchSecret($cert) + if($cert -Match ".pfx$"){ + return Import-PfxCertificate -FilePath $tmp/$cert -CertStoreLocation Cert:\LocalMachine\$store + } else { + return Import-Certificate -FilePath $tmp/$cert -CertStoreLocation Cert:\LocalMachine\$store } +} - function Disable-SSHD - { - if ((Get-Service "sshd" -ErrorAction SilentlyContinue) -and (Test-Path "$cygwin_dir/bin/bash.exe")) { - log "Disabling pre-existing sshd" +function Disable-SSHD +{ + if ((Get-Service "sshd" -ErrorAction SilentlyContinue) -and (Test-Path "$cygwin_dir/bin/bash.exe")) { + log "Disabling pre-existing sshd" - Stop-Service -ErrorAction SilentlyContinue sshd - Stop-Process -ProcessName sshd -force -ErrorAction SilentlyContinue - Invoke-Expression '& $cygwin_dir/bin/bash --login -c "cygrunsrv --stop sshd; cygrunsrv --remove sshd; net user sshd /delete; net user sshd_service /delete; mkpasswd > /etc/passwd"' - } + Stop-Service -ErrorAction SilentlyContinue sshd + Stop-Process -ProcessName sshd -force -ErrorAction SilentlyContinue + Invoke-Expression '& $cygwin_dir/bin/bash --login -c "cygrunsrv --stop sshd; cygrunsrv --remove sshd; net user sshd /delete; net user sshd_service /delete; mkpasswd > /etc/passwd"' } +} - log "- Invoked as $([Environment]::UserName) (system started at $(Get-CimInstance -ClassName win32_operatingsystem | select lastbootuptime)) -" +log "- Invoked as $([Environment]::UserName) (system started at $(Get-CimInstance -ClassName win32_operatingsystem | select lastbootuptime)) -" - <% if !$mu.skipApplyUpdates %> - If (!(Test-Path "c:/mu-installer-ran-updates")){ - Stop-Service -ErrorAction SilentlyContinue sshd - } - <% end %> +<% if !$mu.skipApplyUpdates %> +If (!(Test-Path "c:/mu-installer-ran-updates")){ + Stop-Service -ErrorAction SilentlyContinue sshd +} +<% end %> - <% if $mu.platform != "win2k16" %> - If ([Environment]::OSVersion.Version.Major -lt 10) { - If ("$($myInvocation.MyCommand.Path)" -ne "$tmp/realuserdata_stripped.ps1"){ +<% if $mu.platform != "win2k16" %> +If ([Environment]::OSVersion.Version.Major -lt 10) { + If ("$($myInvocation.MyCommand.Path)" -ne "$tmp/realuserdata_stripped.ps1"){ + $Error.Clear() + Invoke-WebRequest -Uri "$awsmeta/user-data" -OutFile $tmp/realuserdata.ps1 + while($Error.count -gt 0){ $Error.Clear() - Invoke-WebRequest -Uri "$aws_meta/user-data" -OutFile $tmp/realuserdata.ps1 - while($Error.count -gt 0){ - $Error.Clear() - log "Failed to retrieve current userdata from $aws_meta/user-data, waiting 15s and retrying" - sleep 15 - Invoke-WebRequest -Uri "$aws_meta/user-data" -OutFile $tmp/realuserdata.ps1 - } - Get-Content $tmp/realuserdata.ps1 | Select-String -pattern '^#','^<' -notmatch | Set-Content $tmp/realuserdata_stripped.ps1 - If (Compare-Object (Get-Content $myInvocation.MyCommand.Path) (Get-Content $tmp/realuserdata_stripped.ps1)){ - log "Invoking $tmp/realuserdata.ps1 in lieu of $($myInvocation.MyCommand.Path)" - Invoke-Expression $tmp/realuserdata_stripped.ps1 - exit - } + log "Failed to retrieve current userdata from $awsmeta/user-data, waiting 15s and retrying" + sleep 15 + Invoke-WebRequest -Uri "$awsmeta/user-data" -OutFile $tmp/realuserdata.ps1 + } + Get-Content $tmp/realuserdata.ps1 | Select-String -pattern '^#','^<' -notmatch | Set-Content $tmp/realuserdata_stripped.ps1 + If (Compare-Object (Get-Content $myInvocation.MyCommand.Path) (Get-Content $tmp/realuserdata_stripped.ps1)){ + log "Invoking $tmp/realuserdata.ps1 in lieu of $($myInvocation.MyCommand.Path)" + Invoke-Expression $tmp/realuserdata_stripped.ps1 + exit } } - <% end %> - $admin_username = (Get-WmiObject -Query 'Select * from Win32_UserAccount Where (LocalAccount=True and SID like "%-500")').name - log "Local admin: $admin_username" - - Add-Type -Assembly System.Web - $password = [Web.Security.Membership]::GeneratePassword(15,2) - - If (!(Test-Path $base_dir)){ - mkdir $base_dir +} +<% end %> +$admin_username = (Get-WmiObject -Query 'Select * from Win32_UserAccount Where (LocalAccount=True and SID like "%-500")').name +log "Local admin: $admin_username" + +Add-Type -Assembly System.Web +$password = [Web.Security.Membership]::GeneratePassword(15,2) + +If (!(Test-Path $basedir)){ + mkdir $basedir +} + +<% if $mu.platform != "win2k16" %> +If ([Environment]::OSVersion.Version.Major -lt 10) { + If (!(Get-ScheduledTask -TaskName 'run-userdata')){ + log "Adding run-userdata scheduled task (user NT AUTHORITY\SYSTEM)" + Invoke-WebRequest -Uri "https://s3.amazonaws.com/cap-public/run-userdata_scheduledtask.xml" -OutFile $tmp/run-userdata_scheduledtask.xml + Register-ScheduledTask -Xml (Get-Content "$tmp/run-userdata_scheduledtask.xml" | out-string) -TaskName 'run-userdata' -Force -User "NT AUTHORITY\SYSTEM" } +} +<% end %> +$awsid=(New-Object System.Net.WebClient).DownloadString("$awsmeta/meta-data/instance-id") - <% if $mu.platform != "win2k16" %> - If ([Environment]::OSVersion.Version.Major -lt 10) { - If (!(Get-ScheduledTask -TaskName 'run-userdata')){ - log "Adding run-userdata scheduled task (user NT AUTHORITY\SYSTEM)" - Invoke-WebRequest -Uri "https://s3.amazonaws.com/cap-public/run-userdata_scheduledtask.xml" -OutFile $tmp/run-userdata_scheduledtask.xml - Register-ScheduledTask -Xml (Get-Content "$tmp/run-userdata_scheduledtask.xml" | out-string) -TaskName 'run-userdata' -Force -User "NT AUTHORITY\SYSTEM" - } +If (!(Test-Path $tmp/PSWindowsUpdate.zip)){ + If (!(Test-Path c:/Users/$admin_username/Documents/WindowsPowerShell/Modules)){ + mkdir c:/Users/$admin_username/Documents/WindowsPowerShell/Modules } - <% end %> - $awsid=(New-Object System.Net.WebClient).DownloadString("http://169.254.169.254/latest/meta-data/instance-id") - If (!(Test-Path $tmp/PSWindowsUpdate.zip)){ - If (!(Test-Path c:/Users/$admin_username/Documents/WindowsPowerShell/Modules)){ - mkdir c:/Users/$admin_username/Documents/WindowsPowerShell/Modules - } + $WebClient.DownloadFile("https://s3.amazonaws.com/cap-public/PSWindowsUpdate.zip","$tmp/PSWindowsUpdate.zip") + Add-Type -A 'System.IO.Compression.FileSystem' - $WebClient.DownloadFile("https://s3.amazonaws.com/cap-public/PSWindowsUpdate.zip","$tmp/PSWindowsUpdate.zip") - Add-Type -A 'System.IO.Compression.FileSystem' - - If (!(Test-Path c:/windows/System32/WindowsPowerShell/v1.0/Modules/PSWindowsUpdate)){ - log "Extracting PSWindowsUpdate module to c:/windows/System32/WindowsPowerShell/v1.0/Modules" - [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/PSWindowsUpdate.zip", "c:/windows/System32/WindowsPowerShell/v1.0/Modules") - } - If (!(Test-Path c:/Users/$admin_username/Documents/WindowsPowerShell/Modules/PSWindowsUpdate)){ - log "Extracting PSWindowsUpdate module to c:/Users/$admin_username/Documents/WindowsPowerShell" - [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/PSWindowsUpdate.zip", "c:/Users/$admin_username/Documents/WindowsPowerShell/Modules") - } + If (!(Test-Path c:/windows/System32/WindowsPowerShell/v1.0/Modules/PSWindowsUpdate)){ + log "Extracting PSWindowsUpdate module to c:/windows/System32/WindowsPowerShell/v1.0/Modules" + [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/PSWindowsUpdate.zip", "c:/windows/System32/WindowsPowerShell/v1.0/Modules") } - - log "Setting Windows Update parameters in registry" - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name AUOptions -Value 3 - - If (!(Test-Path "$cygwin_dir/Cygwin.bat")){ - If (!(Test-Path $tmp/setup-x86_64.exe)){ - $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$tmp/setup-x86_64.exe") - } - - If (!(Test-Path $tmp/cygwin.zip)){ - log "Downloading Cygwin packages" - $WebClient.DownloadFile("https://s3.amazonaws.com/mu-stuff/cygwin_20161022.zip","$tmp/cygwin.zip") - } - - Add-Type -A 'System.IO.Compression.FileSystem' - If (!(Test-Path $tmp/cygwin)){ - [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/cygwin.zip", "$tmp/cygwin") - } - - log "Running Cygwin installer" - Start-Process -wait -FilePath "$tmp/setup-x86_64.exe" -ArgumentList "-q -n -l $tmp -l $tmp\cygwin -L -R $cygwin_dir -P openssh,mintty,vim,curl,openssl" + If (!(Test-Path c:/Users/$admin_username/Documents/WindowsPowerShell/Modules/PSWindowsUpdate)){ + log "Extracting PSWindowsUpdate module to c:/Users/$admin_username/Documents/WindowsPowerShell" + [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/PSWindowsUpdate.zip", "c:/Users/$admin_username/Documents/WindowsPowerShell/Modules") } +} + +log "Setting Windows Update parameters in registry" +Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name AUOptions -Value 3 - if (!(Get-Service "sshd" -ErrorAction SilentlyContinue)){ - log "Invoking ssh-host-config to enable sshd as $sshdUser (I am $admin_username)" - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "ssh-host-config -y -c ntsec -w ''$password'' -u $sshdUser" > $cygwin_dir/sshd_setup_log.txt' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*StrictModes.*yes/StrictModes no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*PasswordAuthentication.*yes/PasswordAuthentication no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' - New-Item $cygwin_dir/sshd_installed_by.txt -type file -force -value $admin_username - log "Creating c:/$awsid (<%= $mu.muID %>)" - New-Item c:/$awsid -type file -force -value "<%= $mu.muID %>" - log "Value in that file: $(Get-Content c:/$awsid)" +If (!(Test-Path "$cygwin_dir/Cygwin.bat")){ + If (!(Test-Path $tmp/setup-x86_64.exe)){ + $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$tmp/setup-x86_64.exe") } - log "Ensuring domain or local users are in /etc/passwd for sshd" - if((Get-WmiObject win32_computersystem).partofdomain){ - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -d > /etc/passwd"' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l -d > /etc/group"' - } else { - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -l > /etc/passwd"' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l > /etc/group"' + If (!(Test-Path $tmp/cygwin.zip)){ + log "Downloading Cygwin packages" + $WebClient.DownloadFile("https://s3.amazonaws.com/mu-stuff/cygwin_20161022.zip","$tmp/cygwin.zip") } - if (!(Get-WmiObject win32_computersystem).partofdomain){ - If (!(Test-Path "c:/mu-configure-initial-ssh-user")){ - log "making sure the ssh user is configured correctly" - (([adsi]("WinNT://./$sshdUser, user")).psbase.invoke('SetPassword', "$password")) - $sshd_service = Get-WmiObject Win32_Service -Filter "Name='sshd'" - $sshd_service.Change($Null,$Null,$Null,$Null,$Null,$Null,".\$sshdUser",$password,$Null,$Null,$Null) - - $editrights="$cygwin_dir/bin/editrights" - &$editrights -a SeAssignPrimaryTokenPrivilege -u $sshdUser - &$editrights -a SeCreateTokenPrivilege -u $sshdUser - &$editrights -a SeTcbPrivilege -u $sshdUser - &$editrights -a SeServiceLogonRight -u $sshdUser - Add-Content c:/mu-configure-initial-ssh-user "done" - } + Add-Type -A 'System.IO.Compression.FileSystem' + If (!(Test-Path $tmp/cygwin)){ + [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/cygwin.zip", "$tmp/cygwin") } - $sshd_svc_user = (Get-WmiObject -Query "SELECT * FROM win32_service WHERE name='sshd'").StartName - if ( $sshd_svc_user.contains("\") ){ - $sshd_svc_user = $sshd_svc_user.substring($sshd_svc_user.LastIndexOf("\")+1) + log "Running Cygwin installer" + Start-Process -wait -FilePath "$tmp/setup-x86_64.exe" -ArgumentList "-q -n -l $tmp -l $tmp\cygwin -L -R $cygwin_dir -P openssh,mintty,vim,curl,openssl" +} + +if (!(Get-Service "sshd" -ErrorAction SilentlyContinue)){ + log "Invoking ssh-host-config to enable sshd as $sshdUser (I am $admin_username)" + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "ssh-host-config -y -c ntsec -w ''$password'' -u $sshdUser" > $cygwin_dir/sshd_setup_log.txt' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*StrictModes.*yes/StrictModes no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*PasswordAuthentication.*yes/PasswordAuthentication no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' + New-Item $cygwin_dir/sshd_installed_by.txt -type file -force -value $admin_username + log "Creating c:/$awsid (<%= $mu.muID %>)" + New-Item c:/$awsid -type file -force -value "<%= $mu.muID %>" + log "Value in that file: $(Get-Content c:/$awsid)" +} + +log "Ensuring domain or local users are in /etc/passwd for sshd" +if((Get-WmiObject win32_computersystem).partofdomain){ + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -d > /etc/passwd"' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l -d > /etc/group"' +} else { + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -l > /etc/passwd"' + Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l > /etc/group"' +} + +if (!(Get-WmiObject win32_computersystem).partofdomain){ + If (!(Test-Path "c:/mu-configure-initial-ssh-user")){ + log "making sure the ssh user is configured correctly" + (([adsi]("WinNT://./$sshdUser, user")).psbase.invoke('SetPassword', "$password")) + $sshd_service = Get-WmiObject Win32_Service -Filter "Name='sshd'" + $sshd_service.Change($Null,$Null,$Null,$Null,$Null,$Null,".\$sshdUser",$password,$Null,$Null,$Null) + + $editrights="$cygwin_dir/bin/editrights" + &$editrights -a SeAssignPrimaryTokenPrivilege -u $sshdUser + &$editrights -a SeCreateTokenPrivilege -u $sshdUser + &$editrights -a SeTcbPrivilege -u $sshdUser + &$editrights -a SeServiceLogonRight -u $sshdUser + Add-Content c:/mu-configure-initial-ssh-user "done" } - log "Chowning /var/empty, /var/log/sshd.log, and /etc/ssh* to $sshd_svc_user" - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "chown $sshd_svc_user /var/empty /var/log/sshd.log /etc/ssh*; chmod 755 /var/empty"' - - If (!((Get-ItemProperty HKLM:/SYSTEM/CurrentControlSet/Control/Lsa)."Authentication Packages" | Select-String -pattern "cyglsa64.dll")){ - If ((Test-Path "$cygwin_dir/bin/cyglsa-config")){ - log "Setting Cygwin LSA support, will reboot" - Invoke-Expression '& $cygwin_dir/bin/bash --login -c "echo yes | /bin/cyglsa-config"' - $need_reboot = $TRUE - } else { - log "Need to set Cygwin LSA support, but I don't see $cygwin_dir/bin/cyglsa-config!" - } +} + +$sshd_svc_user = (Get-WmiObject -Query "SELECT * FROM win32_service WHERE name='sshd'").StartName +if ( $sshd_svc_user.contains("\") ){ + $sshd_svc_user = $sshd_svc_user.substring($sshd_svc_user.LastIndexOf("\")+1) +} +log "Chowning /var/empty, /var/log/sshd.log, and /etc/ssh* to $sshd_svc_user" +Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "chown $sshd_svc_user /var/empty /var/log/sshd.log /etc/ssh*; chmod 755 /var/empty"' + +If (!((Get-ItemProperty HKLM:/SYSTEM/CurrentControlSet/Control/Lsa)."Authentication Packages" | Select-String -pattern "cyglsa64.dll")){ + If ((Test-Path "$cygwin_dir/bin/cyglsa-config")){ + log "Setting Cygwin LSA support, will reboot" + Invoke-Expression '& $cygwin_dir/bin/bash --login -c "echo yes | /bin/cyglsa-config"' + $need_reboot = $TRUE + } else { + log "Need to set Cygwin LSA support, but I don't see $cygwin_dir/bin/cyglsa-config!" } +} - If (!(Test-Path "$python_path\python.exe")){ - If (!(Test-Path $tmp/python-2.7.9.msi)){ - log "Downloading Python installer" - $WebClient.DownloadFile("https://www.python.org/ftp/python/2.7.9/python-2.7.9.msi","$tmp/python-2.7.9.msi") - } - log "Running Python installer" - (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\python-2.7.9.msi /qn ALLUSERS=1 TARGETDIR=$python_path" -Wait -Passthru).ExitCode +If (!(Test-Path "$pydir\python.exe")){ + If (!(Test-Path $tmp/python-2.7.9.msi)){ + log "Downloading Python installer" + $WebClient.DownloadFile("https://www.python.org/ftp/python/2.7.9/python-2.7.9.msi","$tmp/python-2.7.9.msi") } - - If (!(Test-Path "$python_path\Scripts\aws.cmd")){ - If (!(Test-Path $tmp/get-pip.py)){ - log "Downloading get-pip.py" - $WebClient.DownloadFile("https://bootstrap.pypa.io/get-pip.py","$tmp/get-pip.py") - } - python $tmp/get-pip.py - log "Running pip install awscli" - pip install awscli + log "Running Python installer" + (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\python-2.7.9.msi /qn ALLUSERS=1 TARGETDIR=$pydir" -Wait -Passthru).ExitCode +} + +If (!(Test-Path "$pydir\Scripts\aws.cmd")){ + If (!(Test-Path $tmp/get-pip.py)){ + log "Downloading get-pip.py" + $WebClient.DownloadFile("https://bootstrap.pypa.io/get-pip.py","$tmp/get-pip.py") } - - function removeChef($location){ - $install_chef = $false - $my_chef = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).DisplayName - if ($my_chef) { - if ($my_chef -match '<%= MU.chefVersion %>'.split('-')[0]) { - $install_chef = $false - } else{ - log "Uninstalling Chef" - $uninstall_string = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).UninstallString - $uninstall_string = ($uninstall_string -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","").Trim() - $($uninstall_string -Replace '[\s\t]+', ' ').Split() | ForEach { - log "msiexec.exe /X $_ /gn" - start-process "msiexec.exe" -arg "/X $_ /qn" -Wait - } - $install_chef = $true + python $tmp/get-pip.py + log "Running pip install awscli" + pip install awscli +} + +function removeChef($location){ + $install_chef = $false + $my_chef = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).DisplayName + if ($my_chef) { + if ($my_chef -match '<%= MU.chefVersion %>'.split('-')[0]) { + $install_chef = $false + } else{ + log "Uninstalling Chef" + $uninstall_string = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).UninstallString + $uninstall_string = ($uninstall_string -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","").Trim() + $($uninstall_string -Replace '[\s\t]+', ' ').Split() | ForEach { + log "msiexec.exe /X $_ /gn" + start-process "msiexec.exe" -arg "/X $_ /qn" -Wait } + $install_chef = $true } - - return $install_chef } - - If (!(Test-Path "c:\opscode\chef\embedded\bin\ruby.exe")){ + + return $install_chef +} + +$key = "Microsoft\Windows\CurrentVersion\Uninstall\*" +If (!(Test-Path "c:\opscode\chef\embedded\bin\ruby.exe")){ + $install_chef = $true +} else { + if (removeChef("HKLM:\Software\Wow6432Node\$key")){ + $install_chef = $true + } elseif (removeChef("HKLM:\Software\$key")) { $install_chef = $true } else { - if (removeChef("HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*")){ - $install_chef = $true - } elseif (removeChef("HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*")) { - $install_chef = $true - } else { - $install_chef = $false - } + $install_chef = $false } +} - If ($install_chef){ - log "Installing Chef" - If (!(Test-Path $tmp/chef-installer-<%= MU.chefVersion %>.msi)){ - log "Downloading Chef installer" - $WebClient.DownloadFile("https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=<%= MU.chefVersion %>","$tmp/chef-installer-<%= MU.chefVersion %>.msi") - } - log "Running Chef installer" - (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\chef-installer-<%= MU.chefVersion %>.msi ALLUSERS=1 /le $tmp\chef-client-install.log /qn" -Wait -Passthru).ExitCode - Set-Content "c:/mu_installed_chef" "yup" +If ($install_chef){ + log "Installing Chef" + If (!(Test-Path $tmp/chef-installer-<%= MU.chefVersion %>.msi)){ + log "Downloading Chef installer" + $WebClient.DownloadFile("https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=<%= MU.chefVersion %>","$tmp/chef-installer-<%= MU.chefVersion %>.msi") } + log "Running Chef installer" + (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\chef-installer-<%= MU.chefVersion %>.msi ALLUSERS=1 /le $tmp\chef-client-install.log /qn" -Wait -Passthru).ExitCode + Set-Content "c:/mu_installed_chef" "yup" +} <% if !$mu.skipApplyUpdates %> - If (!(Test-Path "c:/mu-installer-ran-updates")){ - log "Applying Windows updates" - Import-Module PSWindowsUpdate - Get-WUInstall -AcceptAll -IgnoreReboot - Start-Sleep -s 60 - If (Test-Path "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired"){ - log "Registry fiddling says I need a reboot" - $need_reboot = $TRUE - } +If (!(Test-Path "c:/mu-installer-ran-updates")){ + log "Applying Windows updates" + Import-Module PSWindowsUpdate + Get-WUInstall -AcceptAll -IgnoreReboot + Start-Sleep -s 60 + If (Test-Path "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired"){ + log "Registry fiddling says I need a reboot" + $need_reboot = $TRUE } +} <% end %> - fetchSecret("<%= $mu.muID %>-secret") - log "Encrypting Mu deploy secret" - $deploy_secret = & "c:\opscode\chef\embedded\bin\ruby" -ropenssl -rbase64 -e "key = OpenSSL::PKey::RSA.new(Base64.urlsafe_decode64('<%= $mu.deployKey %>'))" -e "print Base64.urlsafe_encode64(key.public_encrypt(File.read('$tmp/<%= $mu.muID %>-secret')))" - - function callMomma([string]$act) - { - $params = @{mu_id='<%= $mu.muID %>';mu_resource_name='<%= $mu.resourceName %>';mu_resource_type='<%= $mu.resourceType %>';mu_instance_id="$awsid";mu_user='<%= $mu.muUser %>';mu_deploy_secret="$deploy_secret";$act="1"} - log "Calling Momma Cat at https://52.0.111.223:2260 with $act" - [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # XXX - $resp = Invoke-WebRequest -Uri https://52.0.111.223:2260 -Method POST -Body $params - return $resp.Content - } +fetchSecret("<%= $mu.muID %>-secret") +log "Encrypting Mu deploy secret" +$deploy_secret = & "c:\opscode\chef\embedded\bin\ruby" -ropenssl -rbase64 -e "key = OpenSSL::PKey::RSA.new(Base64.urlsafe_decode64('<%= $mu.deployKey %>'))" -e "print Base64.urlsafe_encode64(key.public_encrypt(File.read('$tmp/<%= $mu.muID %>-secret')))" + +function callMomma([string]$act) +{ + $params = @{mu_id='<%= $mu.muID %>';mu_resource_name='<%= $mu.resourceName %>';mu_resource_type='<%= $mu.resourceType %>';mu_instance_id="$awsid";mu_user='<%= $mu.muUser %>';mu_deploy_secret="$deploy_secret";$act="1"} + log "Calling Momma Cat at https://52.0.111.223:2260 with $act" + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # XXX + $resp = Invoke-WebRequest -Uri https://52.0.111.223:2260 -Method POST -Body $params + return $resp.Content +} <% if $mu.windowsAdminName %> - if ((Get-WmiObject win32_computersystem).partofdomain -ne $true){ - if ("$admin_username" -ne "<%= $mu.windowsAdminName %>"){ - log "Changing local admin account from $admin_username to <%= $mu.windowsAdminName %>" - ([adsi]("WinNT://./$admin_username, user")).psbase.rename("<%= $mu.windowsAdminName %>") - $need_reboot = $TRUE - } +if ((Get-WmiObject win32_computersystem).partofdomain -ne $true){ + if ("$admin_username" -ne "<%= $mu.windowsAdminName %>"){ + log "Changing local admin account from $admin_username to <%= $mu.windowsAdminName %>" + ([adsi]("WinNT://./$admin_username, user")).psbase.rename("<%= $mu.windowsAdminName %>") + $need_reboot = $TRUE } +} <% end %> - $muca = importCert "Mu_CA.pem" "Root" +$muca = importCert "Mu_CA.pem" "Root" - $myname = "FEMABASE-DEV-2017082915-RD-WINDOWS" +$myname = "FEMABASE-DEV-2017082915-RD-WINDOWS" - $nodecert = importCert "$myname.pfx" "My" - $thumb = $nodecert.Thumbprint - winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$myname`";CertificateThumbprint=`"$thumb`"}" +$nodecert = importCert "$myname.pfx" "My" +$thumb = $nodecert.Thumbprint +winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$myname`";CertificateThumbprint=`"$thumb`"}" - $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" - Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true - $credstr = callMomma "mu_windows_admin_creds" - if($credstr){ - $credparts = $pass.Split(";", 2) - $creds = New-Object System.Management.Automation.PSCredential($credparts[0], (ConvertTo-SecureString $credparts[1] -AsPlainText -Force)) - if($creds){ - New-Item -Path WSMan:\localhost\ClientCertificate -Subject <%= $mu.windowsAdminName %> -URI * -Issuer $muca.Thumbprint -Force -Credential $creds - } +$winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" +Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true +$credstr = callMomma "mu_windows_admin_creds" +if($credstr){ + $credparts = $pass.Split(";", 2) + $creds = New-Object System.Management.Automation.PSCredential($credparts[0], (ConvertTo-SecureString $credparts[1] -AsPlainText -Force)) + if($creds){ + New-Item -Path WSMan:\localhost\ClientCertificate -Subject <%= $mu.windowsAdminName %> -URI * -Issuer $muca.Thumbprint -Force -Credential $creds } +} - winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' - winrm set winrm/config '@{MaxTimeoutms="1800000"}' +winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' +winrm set winrm/config '@{MaxTimeoutms="1800000"}' -# XXX winrm - if (!(Get-NetFirewallRule -DisplayName "Allow SSH" -ErrorAction SilentlyContinue)){ - log "Opening port 22 in Windows Firewall" - New-NetFirewallRule -DisplayName "Allow SSH" -Direction Inbound -LocalPort 22 -Protocol TCP -Action Allow - } +if (!(Get-NetFirewallRule -DisplayName "Allow SSH" -ErrorAction SilentlyContinue)){ + log "Opening port 22 in Windows Firewall" + New-NetFirewallRule -DisplayName "Allow SSH" -Direction Inbound -LocalPort 22 -Protocol TCP -Action Allow +} +if (!(Get-NetFirewallRule -DisplayName "Allow WinRM" -ErrorAction SilentlyContinue)){ + New-NetFirewallRule -DisplayName "Allow WinRM" -Direction Inbound -LocalPort 5985 -Protocol TCP -Action Allow +} <% if $mu.windowsAdminName %> - log "Creating $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys" - New-Item $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" +log "Creating $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys" +New-Item $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" <% else %> - log "Creating $cygwin_dir/home/$admin_username/.ssh/authorized_keys" - New-Item $cygwin_dir/home/$admin_username/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" +log "Creating $cygwin_dir/home/$admin_username/.ssh/authorized_keys" +New-Item $cygwin_dir/home/$admin_username/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" <% end %> - if((Get-WURebootStatus -Silent) -eq $true){ - log "Get-WURebootStatus says to reboot" - $need_reboot = $TRUE - } - - if ($need_reboot){ - log "- REBOOT -" - Restart-Computer -Force - exit - } else { - Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" - - log "Enabling sshd service" - sleep 30; Start-Service sshd - Set-Service sshd -startuptype "Automatic" - #Get-WUInstall -AcceptAll -AutoReboot - - callMomma "mu_bootstrap" - log $(Get-Content $cygwin_dir/var/log/sshd.log) - } - - Set-Content "c:/mu_userdata_complete" "yup" - Remove-Item -Recurse $tmp +if((Get-WURebootStatus -Silent) -eq $true){ + log "Get-WURebootStatus says to reboot" + $need_reboot = $TRUE +} + +if ($need_reboot){ + log "- REBOOT -" + Restart-Computer -Force + exit +} else { + Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" + + log "Enabling sshd service" + sleep 30; Start-Service sshd + Set-Service sshd -startuptype "Automatic" + #Get-WUInstall -AcceptAll -AutoReboot + + callMomma "mu_bootstrap" + log $(Get-Content $cygwin_dir/var/log/sshd.log) +} + +Set-Content "c:/mu_userdata_complete" "yup" +Remove-Item -Recurse $tmp # XXX - Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Undefined +Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Undefined true From 7f7086c60fe9ddd764947005504383e00627505c Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 31 Aug 2017 16:50:20 -0400 Subject: [PATCH 07/63] mu-node-manage: don't touch unrelated autoscale groups when manipulating cloud metadata on a pattern match; deploy updates to node profile IAM policies --- bin/mu-node-manage | 27 +++++++++++--- modules/mu/clouds/aws/server.rb | 66 ++++++++++++++++++--------------- modules/mu/userdata/windows.erb | 2 +- 3 files changed, 59 insertions(+), 36 deletions(-) diff --git a/bin/mu-node-manage b/bin/mu-node-manage index f610c68cd..a5ddd948f 100755 --- a/bin/mu-node-manage +++ b/bin/mu-node-manage @@ -31,11 +31,11 @@ Usage: opt :environment, "Operate exclusively on one nodes with a particular environment (e.g. dev, prod). Can be used in conjunction with -a or -d.", :require => false, :type => :string opt :override_chef_runlist, "An alternate runlist to pass to Chef, in chefrun mode.", :require => false, :type => :string opt :xecute, "Run a shell command on matching nodes. Overrides --mode and suppresses some informational output in favor of scriptability.", :require => false, :type => :string - opt :mode, "Action to perform on matching nodes. Valid actions: groom, chefrun, userdata, vaults, certs", :require => false, :default => "chefrun", :type => :string + opt :mode, "Action to perform on matching nodes. Valid actions: groom, chefrun, awsmeta, vaults, certs", :require => false, :default => "chefrun", :type => :string end -if !["groom", "chefrun", "vaults", "userdata", "certs"].include?($opts[:mode]) - Trollop::die(:mode, "--mode must be one of: groom, chefrun, userdata, vaults, certs") +if !["groom", "chefrun", "vaults", "userdata", "awsmeta", "certs"].include?($opts[:mode]) + Trollop::die(:mode, "--mode must be one of: groom, chefrun, awsmeta, vaults, certs") end if $opts[:platform] and !["linux", "windows"].include?($opts[:platform]) Trollop::die(:platform, "--platform must be one of: linux, windows") @@ -258,7 +258,7 @@ def runCommand(deploys = MU::MommaCat.listDeploys, nodes = [], cmd = "( chef-cli end end -def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) +def updateAWSMetaData(deploys = MU::MommaCat.listDeploys, nodes = []) deploys.each { |muid| mommacat = MU::MommaCat.new(muid) @@ -275,6 +275,17 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) server["platform"] = "linux" if !server.has_key?("platform") pool_name = mommacat.getResourceName(svr_class) + if nodes.size > 0 + matched = false + nodes.each { |n| + if n.match(/^#{Regexp.quote(pool_name)}-[a-z0-9]{3}$/) + matched = true + end + } + next if !matched + end + + MU::Cloud::AWS::Server.createIAMProfile(pool_name, base_profile: server['iam_role'], extra_policies: server['iam_policies']) resp = MU::Cloud::AWS.autoscale.describe_auto_scaling_groups( auto_scaling_group_names: [pool_name] @@ -425,6 +436,9 @@ def updateUserdata(deploys = MU::MommaCat.listDeploys, nodes = []) server['conf']["platform"] = "linux" if !server['conf'].has_key?("platform") next if nodes.size > 0 and !nodes.include?(nodename) + rolename, cfm_role_name, cfm_prof_name, arn = MU::Cloud::AWS::Server.createIAMProfile(nodename, base_profile: server["conf"]['iam_role'], extra_policies: server["conf"]['iam_policies']) + MU::Cloud::AWS::Server.addStdPoliciesToIAMProfile(rolename) + mytype = "server" mytype = "server_pool" if server['conf'].has_key?("basis") or server['conf']['#TYPENAME'] == "ServerPool" or server['conf']["#MU_CLASS"] == "MU::Cloud::AWS::ServerPool" olduserdata = Base64.decode64(MU::Cloud::AWS.ec2(server['region']).describe_instance_attribute( @@ -514,6 +528,7 @@ elsif $opts[:mode] == "chefrun" else runCommand(do_deploys, do_nodes) end -elsif $opts[:mode] == "userdata" - updateUserdata(do_deploys, do_nodes) +elsif $opts[:mode] == "userdata" or $opts[:mode] == "awsmeta" +# Need Google equiv and to select nodes correctly based on what cloud they're in + updateAWSMetaData(do_deploys, do_nodes) end diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index 2edfcb205..d50adab49 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -312,12 +312,8 @@ def self.removeIAMProfile(rolename) # Insert a Server's standard IAM role needs into an arbitrary IAM profile def self.addStdPoliciesToIAMProfile(rolename, cloudformation_data: {}, cfm_role_name: nil) policies = Hash.new - policies['Mu_Bootstrap_Secret_'+MU.deploy_id] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{MU.deploy_id}-secret"+'"}]}' - policies['Mu_Node_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.pfx"+'"}]}' - policies['Mu_Node_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.crt"+'"}]}' - policies['Mu_Node_Key'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}.key"+'"}]}' - policies['Mu_WinRM_Client_Certificate'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}-winrm.crt"+'"}]}' - policies['Mu_WinRM_Client_Key'] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":"arn:aws:s3:::'+MU.adminBucketName+'/'+"#{rolename}-winrm.key"+'"}]}' + objs = ["#{MU.deploy_id}-secret", "#{rolename}.pfx", "#{rolename}.crt", "#{rolename}.key", "#{rolename}-winrm.crt", "#{rolename}-winrm.key"] + policies['Mu_Secrets_'+MU.deploy_id] ='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject"],"Resource":['+objs.map { |m| '"arn:aws:s3:::'+MU.adminBucketName+'/'+m+'"' }.join(",")+']}]}' policies.each_pair { |name, doc| if cloudformation_data.size > 0 if !cfm_role_name.nil? @@ -325,11 +321,11 @@ def self.addStdPoliciesToIAMProfile(rolename, cloudformation_data: {}, cfm_role_ end next end - MU.log "Merging policy #{name} into #{rolename}", MU::NOTICE, details: doc + MU.log "Merging policy #{name} into #{rolename}", details: JSON.pretty_generate(JSON.parse(doc)) MU::Cloud::AWS.iam.put_role_policy( - role_name: rolename, - policy_name: name, - policy_document: doc + role_name: rolename, + policy_name: name, + policy_document: doc ) } if cloudformation_data.size > 0 @@ -352,19 +348,16 @@ def self.createIAMProfile(rolename, base_profile: nil, extra_policies: nil, clou cfm_prof_name, prof_cfm_template = MU::Cloud::CloudFormation.cloudFormationBase("iamprofile", name: rolename) cloudformation_data.merge!(role_cfm_template) cloudformation_data.merge!(prof_cfm_template) - else - MU.log "Creating IAM role and policies for '#{name}' nodes" end if base_profile - MU.log "Incorporating policies from existing IAM profile '#{base_profile}'" resp = MU::Cloud::AWS.iam.get_instance_profile(instance_profile_name: base_profile) resp.instance_profile.roles.each { |baserole| role_policies = MU::Cloud::AWS.iam.list_role_policies(role_name: baserole.role_name).policy_names role_policies.each { |name| resp = MU::Cloud::AWS.iam.get_role_policy( - role_name: baserole.role_name, - policy_name: name + role_name: baserole.role_name, + policy_name: name ) policies[name] = URI.unescape(resp.policy_document) } @@ -383,10 +376,15 @@ def self.createIAMProfile(rolename, base_profile: nil, extra_policies: nil, clou } end if !cloudformation_data.nil? and cloudformation_data.size == 0 - resp = MU::Cloud::AWS.iam.create_role( - role_name: rolename, - assume_role_policy_document: '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":["ec2.amazonaws.com"]},"Action":["sts:AssumeRole"]}]}' - ) + begin + resp = MU::Cloud::AWS.iam.create_role( + role_name: rolename, + assume_role_policy_document: '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":["ec2.amazonaws.com"]},"Action":["sts:AssumeRole"]}]}' + ) + MU.log "Creating IAM role and policies for '#{rolename}' nodes" + rescue Aws::IAM::Errors::EntityAlreadyExists => e + MU.log "IAM role #{rolename} already exists, updating" + end end begin name=doc=nil @@ -395,11 +393,11 @@ def self.createIAMProfile(rolename, base_profile: nil, extra_policies: nil, clou MU::Cloud::CloudFormation.setCloudFormationProp(cloudformation_data[cfm_role_name], "Policies", { "PolicyName" => name, "PolicyDocument" => JSON.parse(doc) }) next end - MU.log "Merging policy #{name} into #{rolename}", MU::NOTICE, details: doc + MU.log "Merging policy #{name} into #{rolename}", details: JSON.pretty_generate(JSON.parse(doc)) MU::Cloud::AWS.iam.put_role_policy( - role_name: rolename, - policy_name: name, - policy_document: doc + role_name: rolename, + policy_name: name, + policy_document: doc ) } rescue Aws::IAM::Errors::MalformedPolicyDocument => e @@ -412,14 +410,24 @@ def self.createIAMProfile(rolename, base_profile: nil, extra_policies: nil, clou return [rolename, cfm_role_name, cfm_prof_name] end - resp = MU::Cloud::AWS.iam.create_instance_profile( + begin + resp = MU::Cloud::AWS.iam.create_instance_profile( instance_profile_name: rolename - ) + ) + rescue Aws::IAM::Errors::EntityAlreadyExists => e + resp = MU::Cloud::AWS.iam.get_instance_profile( + instance_profile_name: rolename + ) + end - MU::Cloud::AWS.iam.add_role_to_instance_profile( + begin + MU::Cloud::AWS.iam.add_role_to_instance_profile( instance_profile_name: rolename, role_name: rolename - ) + ) + rescue Aws::IAM::Errors::LimitExceeded => e + # also ok + end begin MU::Cloud::AWS.iam.get_instance_profile(instance_profile_name: rolename) @@ -1011,9 +1019,9 @@ def postBoot(instance_id = nil) if windows? # kick off certificate generation early; WinRM will need it cert, key = @deploy.nodeSSLCerts(self) -# session = getWinRMSession(50, 60) + session = getWinRMSession(50, 60) # XXX account for machines behind bastion hosts that we can't tunnel through; -# maybe then it's ok to fall back to sshd +# maybe then it's ok to fall back to sshd? session = getSSHSession(50, 60) initialSSHTasks(session) else diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 70b4310b6..1b6250957 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -289,7 +289,7 @@ if ((Get-WmiObject win32_computersystem).partofdomain -ne $true){ $muca = importCert "Mu_CA.pem" "Root" -$myname = "FEMABASE-DEV-2017082915-RD-WINDOWS" +$myname = "<%= $mu.muID %>-<%= $mu.resourceName.upcase %>" $nodecert = importCert "$myname.pfx" "My" $thumb = $nodecert.Thumbprint From dcad9e6741a27581118dc1198c36a95e0141b678 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 31 Aug 2017 17:30:54 -0400 Subject: [PATCH 08/63] better debugging logging around WinRM bits --- modules/mommacat.ru | 9 ++++++++- modules/mu/cloud.rb | 10 +++++++++- modules/mu/clouds/aws/server.rb | 2 +- modules/mu/userdata/windows.erb | 4 ++-- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/modules/mommacat.ru b/modules/mommacat.ru index ac792be30..6e6a3b3e9 100644 --- a/modules/mommacat.ru +++ b/modules/mommacat.ru @@ -325,8 +325,15 @@ app = proc do |env| if req["mu_user"].nil? req["mu_user"] = "mu" end + requesttype = nil + ["mu_ssl_sign", "mu_bootstrap", "mu_windows_admin_creds", "add_volume"].each { |rt| + if req[rt] + requesttype = rt + break + end + } - MU.log "Processing request from #{env["REMOTE_ADDR"]} (MU-ID #{req["mu_id"]}, #{req["mu_resource_type"]}: #{req["mu_resource_name"]}, instance: #{req["mu_instance_id"]}, mu_ssl_sign: #{req["mu_ssl_sign"]}, mu_user #{req['mu_user']}, path #{env['REQUEST_PATH']})" + MU.log "Processing #{requesttype} request from #{env["REMOTE_ADDR"]} (MU-ID #{req["mu_id"]}, #{req["mu_resource_type"]}: #{req["mu_resource_name"]}, instance: #{req["mu_instance_id"]}, mu_user #{req['mu_user']}, path #{env['REQUEST_PATH']})" kittenpile = getKittenPile(req) if kittenpile.nil? or kittenpile.original_config.nil? or kittenpile.original_config[req["mu_resource_type"]+"s"].nil? returnval = throw500 "Couldn't find config data for #{req["mu_resource_type"]} in deploy_id #{req["mu_id"]}" diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index 456890b16..d29f1c938 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -817,7 +817,7 @@ def initialSSHTasks(ssh) def getWinRMSession(max_retries = 40, retry_interval = 60) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig opts = { - endpoint: 'http://'+canonical_ip+':5985/wsman', + endpoint: 'https://'+canonical_ip+':5986/wsman', transport: :ssl, ca_trust_path: "#{MU.mySSLDir}/#{@mu_name}.crt", client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt", @@ -829,9 +829,17 @@ def getWinRMSession(max_retries = 40, retry_interval = 60) conn = nil begin conn = WinRM::Connection.new(opts) + MU.log "WinRM connection to #{canonical_ip} created", MU::NOTICE, details: conn + shell = conn.shell(:powershell) + pp shell.run('ipconfig') +rescue HTTPClient::ConnectTimeoutError => e + # XXX crazy-ass retry logic goes here + sleep 20 + retry rescue Exception => e MU.log e.inspect, MU::ERR sleep 20 + retry end pp conn conn diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index d50adab49..1375225dd 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -1019,7 +1019,7 @@ def postBoot(instance_id = nil) if windows? # kick off certificate generation early; WinRM will need it cert, key = @deploy.nodeSSLCerts(self) - session = getWinRMSession(50, 60) +# session = getWinRMSession(50, 60) # XXX account for machines behind bastion hosts that we can't tunnel through; # maybe then it's ok to fall back to sshd? session = getSSHSession(50, 60) diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 1b6250957..a495e114d 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -299,7 +299,7 @@ $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true $credstr = callMomma "mu_windows_admin_creds" if($credstr){ - $credparts = $pass.Split(";", 2) + $credparts = $credstr.Split(";", 2) $creds = New-Object System.Management.Automation.PSCredential($credparts[0], (ConvertTo-SecureString $credparts[1] -AsPlainText -Force)) if($creds){ New-Item -Path WSMan:\localhost\ClientCertificate -Subject <%= $mu.windowsAdminName %> -URI * -Issuer $muca.Thumbprint -Force -Credential $creds @@ -314,7 +314,7 @@ if (!(Get-NetFirewallRule -DisplayName "Allow SSH" -ErrorAction SilentlyContinue New-NetFirewallRule -DisplayName "Allow SSH" -Direction Inbound -LocalPort 22 -Protocol TCP -Action Allow } if (!(Get-NetFirewallRule -DisplayName "Allow WinRM" -ErrorAction SilentlyContinue)){ - New-NetFirewallRule -DisplayName "Allow WinRM" -Direction Inbound -LocalPort 5985 -Protocol TCP -Action Allow + New-NetFirewallRule -DisplayName "Allow WinRM" -Direction Inbound -LocalPort 5986 -Protocol TCP -Action Allow } <% if $mu.windowsAdminName %> From a13f9aae380fd681f1eafda3a667d0c9398fd615 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Mon, 11 Sep 2017 13:19:47 -0400 Subject: [PATCH 09/63] scrub extraneous logic from Windows userdata; fix some edge cases and error messages --- modules/mommacat.ru | 8 ++++++-- modules/mu/cloud.rb | 9 ++++++++- modules/mu/clouds/aws/server.rb | 4 ++-- modules/mu/mommacat.rb | 17 ++++++++++++----- modules/mu/userdata/windows.erb | 26 +------------------------- 5 files changed, 29 insertions(+), 35 deletions(-) diff --git a/modules/mommacat.ru b/modules/mommacat.ru index 6e6a3b3e9..85ff86a86 100644 --- a/modules/mommacat.ru +++ b/modules/mommacat.ru @@ -362,8 +362,12 @@ app = proc do |env| if instance.nil? # Now we're just checking for existence in the cloud provider, really MU.log "No existing groomed server found, verifying that a server with this cloud id exists" - instance = MU::Cloud::Server.find(cloud_id: req["mu_instance_id"], region: server_cfg["region"]) -# XXX barf if this comes back empty + instance = MU::MommaCat.findStray("AWS", "server", cloud_id: req["mu_instance_id"], region: server_cfg["region"], deploy_id: req["mu_id"], name: req["mu_resource_name"], dummy_ok: true).first +# instance = MU::Cloud::Server.find(cloud_id: req["mu_instance_id"], region: server_cfg["region"]) + if instance.nil? or instance.size == 0 + returnval = throw500 "Failed to find an instance with cloud id #{req["mu_instance_id"]}" + end +# XXX barf if this comes back with nonsense else mu_name = instance.mu_name MU.log "Found an existing node named #{mu_name}" diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index d29f1c938..fd6804347 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -722,7 +722,14 @@ def self.createRecordsFromConfig(*flags) if shortname == "Server" def windows? - %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 windows}.include?(@config['platform']) + return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 windows}.include?(@config['platform']) + begin + return true if cloud_desc.respond_to?(:platform) and cloud_desc.platform == "Windows" +# XXX ^ that's AWS-speak, doesn't cover GCP or anything else; maybe we should require cloud layers to implement this so we can just call @cloudobj.windows? + rescue MU::MuError + return false + end + false end # Basic setup tasks performed on a new node during its first initial ssh diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index 1375225dd..a047f9ad8 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -884,7 +884,7 @@ def postBoot(instance_id = nil) cloud_id: instance.subnet_id ) if subnet.nil? - raise MuError, "Got null subnet id out of #{@config['vpc']}/#{instance.subnet_id}" + raise MuError, "Got null subnet id out of #{@config['vpc']} when asking for #{instance.subnet_id}" end end @@ -1029,7 +1029,7 @@ def postBoot(instance_id = nil) initialSSHTasks(session) end rescue BootstrapTempFail - sleep ssh_wait + sleep 45 retry ensure session.close if !session.nil? and !windows? diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 362d8a9b7..318e43452 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1919,12 +1919,15 @@ def listNodes # we can assume have been authenticated with the deploy secret. # @param server [MU::Cloud::Server]: The Server object whose credentials we're fetching. def retrieveWindowsAdminCreds(server) - if server.nil? or server.class.name != "MU::Cloud::Server" + if server.nil? raise MuError, "retrieveWindowsAdminCreds must be called with a Server object" + elsif !server.is_a?(MU::Cloud::Server) + raise MuError, "retrieveWindowsAdminCreds must be called with a Server object (got #{server.class.name})" end - if !server.windows? - raise MuError, "#{server} is not a Windows node" - end +# if !server.windows? +# raise MuError, "#{server} is not a Windows node" +# end +#MU.log "retrieveWindowsAdminCreds called on a thing", MU::NOTICE, details: server.config if server.config['use_cloud_provider_windows_password'] return [server.config["windows_admin_username"], getWindowsAdminPassword] elsif server.config['windows_auth_vault'] && !server.config['windows_auth_vault'].empty? @@ -1939,6 +1942,7 @@ def retrieveWindowsAdminCreds(server) return [server.config["windows_admin_username"], server.getWindowsAdminPassword] end end + [] end # Given a Certificate Signing Request, sign it with our internal CA and @@ -2103,7 +2107,10 @@ def nodeSSLCerts(server) if File.exists?("#{MU.mySSLDir}/#{server.mu_name}.crt") and File.exists?("#{MU.mySSLDir}/#{server.mu_name}.key") - results[server.mu_name] = [File.read("#{MU.mySSLDir}/#{server.mu_name}.crt"), File.read("#{MU.mySSLDir}/#{server.mu_name}.key")] + results[server.mu_name] = [ + OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{server.mu_name}.crt")), + OpenSSL::PKey::RSA.new(File.read("#{MU.mySSLDir}/#{server.mu_name}.key")) + ] else certs[server.mu_name] = { "sans" => ["IP:#{canonical_ip}"], diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index a495e114d..04cf92bc2 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -227,30 +227,6 @@ function removeChef($location){ return $install_chef } -$key = "Microsoft\Windows\CurrentVersion\Uninstall\*" -If (!(Test-Path "c:\opscode\chef\embedded\bin\ruby.exe")){ - $install_chef = $true -} else { - if (removeChef("HKLM:\Software\Wow6432Node\$key")){ - $install_chef = $true - } elseif (removeChef("HKLM:\Software\$key")) { - $install_chef = $true - } else { - $install_chef = $false - } -} - -If ($install_chef){ - log "Installing Chef" - If (!(Test-Path $tmp/chef-installer-<%= MU.chefVersion %>.msi)){ - log "Downloading Chef installer" - $WebClient.DownloadFile("https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=<%= MU.chefVersion %>","$tmp/chef-installer-<%= MU.chefVersion %>.msi") - } - log "Running Chef installer" - (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\chef-installer-<%= MU.chefVersion %>.msi ALLUSERS=1 /le $tmp\chef-client-install.log /qn" -Wait -Passthru).ExitCode - Set-Content "c:/mu_installed_chef" "yup" -} - <% if !$mu.skipApplyUpdates %> If (!(Test-Path "c:/mu-installer-ran-updates")){ log "Applying Windows updates" @@ -258,7 +234,7 @@ If (!(Test-Path "c:/mu-installer-ran-updates")){ Get-WUInstall -AcceptAll -IgnoreReboot Start-Sleep -s 60 If (Test-Path "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired"){ - log "Registry fiddling says I need a reboot" + log "Windows Update reboot" $need_reboot = $TRUE } } From ce28980bff7a3fe240a89538a0a8da1254194092 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Mon, 18 Sep 2017 16:38:17 -0400 Subject: [PATCH 10/63] WinRM: workaround for hostname resolution (/etc/hosts, ew); cert auth still being denied --- modules/mu/cloud.rb | 50 +++++++++++++++++++++------------ modules/mu/master/ldap.rb | 6 +++- modules/mu/mommacat.rb | 2 +- modules/mu/userdata/windows.erb | 4 +++ 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index fd6804347..8538cc297 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -823,22 +823,34 @@ def initialSSHTasks(ssh) def getWinRMSession(max_retries = 40, retry_interval = 60) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig - opts = { - endpoint: 'https://'+canonical_ip+':5986/wsman', - transport: :ssl, - ca_trust_path: "#{MU.mySSLDir}/#{@mu_name}.crt", - client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt", - client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key" -# user: ssh_user, -# password: 'Pass@word1' - } -MU.log "Tryin' a call WinRM on #{canonical_ip}", MU::WARN, details: opts - conn = nil -begin - conn = WinRM::Connection.new(opts) - MU.log "WinRM connection to #{canonical_ip} created", MU::NOTICE, details: conn - shell = conn.shell(:powershell) - pp shell.run('ipconfig') + + conn = opts = nil + # and now, a thing I really don't want to do + MU::MommaCat.addInstanceToEtcHosts(canonical_ip, @mu_name) + + begin + MU.log "Tryin' a call WinRM on #{@mu_name}", MU::WARN, details: opts + + opts = { + endpoint: 'https://'+@mu_name+':5986/wsman', + transport: :ssl, + no_ssl_peer_verification: true, # XXX this should not be necessary; we get 'hostname "foo" does not match the server certificate' even when it clearly does match + ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem", + client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt", + client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key" + } + conn = WinRM::Connection.new(opts) + MU.log "WinRM connection to #{@mu_name} created", MU::NOTICE, details: conn + shell = conn.shell(:powershell) + pp shell.run('ipconfig') + rescue OpenSSL::SSL::SSLError, SocketError => e + if e.message.match(/does not match the server certificate|Name or service not known/) + MU.log "WinRM failed to connect to #{@mu_name}: #{e.message}. Retrying in 10s.", MU::WARN + sleep 10 + retry + else + raise e + end rescue HTTPClient::ConnectTimeoutError => e # XXX crazy-ass retry logic goes here sleep 20 @@ -847,8 +859,10 @@ def getWinRMSession(max_retries = 40, retry_interval = 60) MU.log e.inspect, MU::ERR sleep 20 retry -end -pp conn + ensure + MU::MommaCat.removeInstanceFromEtcHosts(@mu_name) + end + conn end diff --git a/modules/mu/master/ldap.rb b/modules/mu/master/ldap.rb index 4957bc238..1d32e58b0 100755 --- a/modules/mu/master/ldap.rb +++ b/modules/mu/master/ldap.rb @@ -484,8 +484,9 @@ def self.findGroups(search = [], exact: false, searchbase: $MU_CFG['ldap']['base # @param search [Array]: Strings to search for. # @param exact [Boolean]: Return only exact matches for whole fields. # @param searchbase [String]: The DN under which to search. + # @param groups [Array]: An array of groups. If supplied, a user must be a member of one of these in order to match. # @return [Array] - def self.findUsers(search = [], exact: false, searchbase: $MU_CFG['ldap']['base_dn'], extra_attrs: []) + def self.findUsers(search = [], exact: false, searchbase: $MU_CFG['ldap']['base_dn'], extra_attrs: [], matchgroups: []) # We want to search groups, but can't search on memberOf with wildcards. # So search groups independently, build a list of full CNs, and use # those. @@ -555,6 +556,9 @@ def self.findUsers(search = [], exact: false, searchbase: $MU_CFG['ldap']['base_ rescue NoMethodError next end + if matchgroups and matchgroups.size > 0 + next if (acct[:memberOf] & matchgroups).size < 1 + end users[acct[@uid_attr].first] = {} users[acct[@uid_attr].first]['dn'] = acct.dn getattrs.each { |attr| diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 318e43452..a65fc47e2 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1634,7 +1634,7 @@ def self.removeInstanceFromEtcHosts(node) # @param system_name [String]: The node's local system name # @return [void] def self.addInstanceToEtcHosts(public_ip, chef_name = nil, system_name = nil) - return if MU.mu_user != "mu" + return if !["mu", "root"].include?(MU.mu_user) # XXX cover ipv6 case if public_ip.nil? or !public_ip.match(/^\d+\.\d+\.\d+\.\d+$/) or (chef_name.nil? and system_name.nil?) diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 04cf92bc2..cd96104ea 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -269,7 +269,11 @@ $myname = "<%= $mu.muID %>-<%= $mu.resourceName.upcase %>" $nodecert = importCert "$myname.pfx" "My" $thumb = $nodecert.Thumbprint +# XXX Clumsy- should guard removal for mismatched hostnames/thumbprints +# actually, should remove janky certificates outright +winrm delete winrm/config/Listener?Address=*+Transport=HTTPS winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$myname`";CertificateThumbprint=`"$thumb`"}" +net localgroup WinRMRemoteWMIUsers__ /add <%= $mu.windowsAdminName %> $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true From 5d13fe077f412ec46debe2541ea4c3c42c352a09 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Mon, 25 Sep 2017 09:14:07 -0400 Subject: [PATCH 11/63] suspect mu-tools windows_users resource has been meddling with sshd domain password; quash that --- cookbooks/mu-tools/resources/windows_users.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cookbooks/mu-tools/resources/windows_users.rb b/cookbooks/mu-tools/resources/windows_users.rb index f48fb00c9..6efd1ecdc 100644 --- a/cookbooks/mu-tools/resources/windows_users.rb +++ b/cookbooks/mu-tools/resources/windows_users.rb @@ -51,10 +51,13 @@ end } - # This is a workaround because user data might re-install cygwin and use a random password that we don't know about. This is not idempotent, it just doesn't throw and error. + # This is a workaround because user data might re-install cygwin and use a random password that we don't know about. This is not idempotent, it just doesn't throw an error. + # XXX I think this has been resetting the domain sshd user's password, which + # is bad. Either that, or Cygwin has been, and this is the thing trying to + # solve that problem. script =<<-EOH Add-ADGroupMember 'Domain Admins' -Members #{new_resource.ssh_user} -PassThru - Set-ADAccountPassword -Identity #{new_resource.ssh_user} -NewPassword (ConvertTo-SecureString -AsPlainText '#{new_resource.ssh_password}' -Force) -PassThru +# Set-ADAccountPassword -Identity #{new_resource.ssh_user} -NewPassword (ConvertTo-SecureString -AsPlainText '#{new_resource.ssh_password}' -Force) -PassThru EOH converge_by("Added #{new_resource.ssh_user} to Domain Admin group and reset its password") do From f84bf912b8797be6a713fd931459f2920f048125 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Mon, 25 Sep 2017 12:26:46 -0400 Subject: [PATCH 12/63] now do it right, chowderhead --- cookbooks/mu-tools/libraries/helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbooks/mu-tools/libraries/helper.rb b/cookbooks/mu-tools/libraries/helper.rb index 5e1a3e8d8..d08df4394 100644 --- a/cookbooks/mu-tools/libraries/helper.rb +++ b/cookbooks/mu-tools/libraries/helper.rb @@ -15,7 +15,7 @@ def set_aws_cfg_params @region = JSON.parse(instance_identity)["region"] ENV['AWS_DEFAULT_REGION'] = @region - if !$MU_CFG['aws'] or !$MU_CFG['aws']['access_key'] or $MU_CFG['aws']['access_key'].empty? + if !$MU_CFG or !$MU_CFG['aws'] or !$MU_CFG['aws']['access_key'] or $MU_CFG['aws']['access_key'].empty? ENV.delete('AWS_ACCESS_KEY_ID') ENV.delete('AWS_SECRET_ACCESS_KEY') Aws.config = {region: @region} From 1408326f78a54ba1df5eeb632c072fd940be9290 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 3 Oct 2017 16:37:18 -0400 Subject: [PATCH 13/63] WinRM breakthrough: successfully getting a shell with certificate auth --- Berksfile | 1 + modules/mommacat.ru | 6 +++--- modules/mu.rb | 2 +- modules/mu/clouds/aws/server.rb | 2 +- modules/mu/mommacat.rb | 35 ++++++++++++++++++++++----------- modules/mu/userdata/windows.erb | 5 +++-- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/Berksfile b/Berksfile index 7daf25561..fb23e425e 100644 --- a/Berksfile +++ b/Berksfile @@ -42,3 +42,4 @@ cookbook 'runit', '~> 1.7' cookbook 's3fs', path: "#{cookbookPath}/s3fs" cookbook 'zipfile', '~> 0.1.0' #cookbook 'hashicorp-vault', '~> 2.5.0', git: "https://github.com/johnbellone/vault-cookbook" +cookbook 'demo', path: "#{siteCookbookPath}/demo" diff --git a/modules/mommacat.ru b/modules/mommacat.ru index 85ff86a86..8bac344f5 100644 --- a/modules/mommacat.ru +++ b/modules/mommacat.ru @@ -357,13 +357,12 @@ app = proc do |env| # XXX We can't assume AWS anymore. What does this look like otherwise? # If this is an already-groomed instance, try to get a real object for it - instance = MU::MommaCat.findStray("AWS", "server", cloud_id: req["mu_instance_id"], region: server_cfg["region"], deploy_id: req["mu_id"], name: req["mu_resource_name"], dummy_ok: false).first + instance = MU::MommaCat.findStray("AWS", "server", cloud_id: req["mu_instance_id"], region: server_cfg["region"], deploy_id: req["mu_id"], name: req["mu_resource_name"], dummy_ok: false, calling_deploy: kittenpile).first mu_name = nil if instance.nil? # Now we're just checking for existence in the cloud provider, really MU.log "No existing groomed server found, verifying that a server with this cloud id exists" - instance = MU::MommaCat.findStray("AWS", "server", cloud_id: req["mu_instance_id"], region: server_cfg["region"], deploy_id: req["mu_id"], name: req["mu_resource_name"], dummy_ok: true).first -# instance = MU::Cloud::Server.find(cloud_id: req["mu_instance_id"], region: server_cfg["region"]) + instance = MU::MommaCat.findStray("AWS", "server", cloud_id: req["mu_instance_id"], region: server_cfg["region"], deploy_id: req["mu_id"], name: req["mu_resource_name"], dummy_ok: true, calling_deploy: kittenpile).first if instance.nil? or instance.size == 0 returnval = throw500 "Failed to find an instance with cloud id #{req["mu_instance_id"]}" end @@ -372,6 +371,7 @@ app = proc do |env| mu_name = instance.mu_name MU.log "Found an existing node named #{mu_name}" end + if !req["mu_windows_admin_creds"].nil? returnval[2] = [kittenpile.retrieveWindowsAdminCreds(instance).join(";")] elsif !req["mu_ssl_sign"].nil? diff --git a/modules/mu.rb b/modules/mu.rb index f069718bf..00e498403 100644 --- a/modules/mu.rb +++ b/modules/mu.rb @@ -36,7 +36,7 @@ class << self; end end -if $MU_CFG['aws']['access_key'] == nil or $MU_CFG['aws']['access_key'].empty? +if !$MU_CFG or !$MU_CFG['aws'] or !$MU_CFG['aws']['access_key'] or $MU_CFG['aws']['access_key'].empty? ENV.delete('AWS_ACCESS_KEY_ID') ENV.delete('AWS_SECRET_ACCESS_KEY') Aws.config = {region: ENV['EC2_REGION']} diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index a047f9ad8..30b923981 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -1019,7 +1019,7 @@ def postBoot(instance_id = nil) if windows? # kick off certificate generation early; WinRM will need it cert, key = @deploy.nodeSSLCerts(self) -# session = getWinRMSession(50, 60) + session = getWinRMSession(50, 60) # XXX account for machines behind bastion hosts that we can't tunnel through; # maybe then it's ok to fall back to sshd? session = getSSHSession(50, 60) diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 0933bfc60..cdfa0a3fe 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1147,20 +1147,33 @@ def self.findStray(cloud, elsif kittens.size == 0 # If we don't have a MU::Cloud object, manufacture a dummy one. # Give it a fake name if we have to and have decided that's ok. - if (name.nil? or name.empty?) and !dummy_ok - MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return", MU::DEBUG, details: caller - next - else - if !mu_name.nil? - name = mu_name - elsif !tag_value.nil? - name = tag_value + if (name.nil? or name.empty?) + if !dummy_ok + MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return", MU::DEBUG, details: caller + next else - name = kitten_cloud_id + if !mu_name.nil? + name = mu_name + elsif !tag_value.nil? + name = tag_value + else + name = kitten_cloud_id + end end end cfg = {"name" => name, "cloud" => cloud, "region" => r} - if !calling_deploy.nil? + # If we can at least find the config from the deploy this will + # belong with, use that, even if it's an ungroomed resource. + if !calling_deploy.nil? and + !calling_deploy.original_config.nil? and + !calling_deploy.original_config[type+"s"].nil? + calling_deploy.original_config[type+"s"].each { |s| + if s["name"] == name + cfg = s.dup + break + end + } + matches << resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id) else matches << resourceclass.new(mu_name: name, kitten_cfg: cfg, cloud_id: kitten_cloud_id) @@ -2124,7 +2137,7 @@ def nodeSSLCerts(server) results[server.mu_name+"-winrm"] = [File.read("#{MU.mySSLDir}/#{server.mu_name}-winrm.crt"), File.read("#{MU.mySSLDir}/#{server.mu_name}-winrm.key")] else certs[server.mu_name+"-winrm"] = { - "sans" => ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{$MU_CFG['mu_admin_email']}"], + "sans" => ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{server.config['windows_admin_username']}@localhost"], "cn" => server.config['windows_admin_username'] } end diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index cd96104ea..9ad83500b 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -277,15 +277,16 @@ net localgroup WinRMRemoteWMIUsers__ /add <%= $mu.windowsAdminName %> $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true +Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name LocalAccountTokenFilterPolicy -Value 1 + $credstr = callMomma "mu_windows_admin_creds" if($credstr){ $credparts = $credstr.Split(";", 2) $creds = New-Object System.Management.Automation.PSCredential($credparts[0], (ConvertTo-SecureString $credparts[1] -AsPlainText -Force)) if($creds){ - New-Item -Path WSMan:\localhost\ClientCertificate -Subject <%= $mu.windowsAdminName %> -URI * -Issuer $muca.Thumbprint -Force -Credential $creds + New-Item -Path WSMan:\localhost\ClientCertificate -Subject '<%= $mu.windowsAdminName %>@localhost' -URI * -Issuer $muca.Thumbprint -Force -Credential $creds } } - winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' winrm set winrm/config '@{MaxTimeoutms="1800000"}' From 85ebeea6bad6a0bc9f7589e0603b54060dc80741 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 5 Oct 2017 16:41:16 -0400 Subject: [PATCH 14/63] knife bootstrap working over winrm; Chef runs invoke correctly (but core recipes fail without Cygwin) --- bin/mu-node-manage | 4 + modules/Gemfile | 2 +- modules/Gemfile.lock | 29 +++---- modules/mu/cloud.rb | 99 ++++++++++++++++----- modules/mu/clouds/aws/server.rb | 3 +- modules/mu/groomers/chef.rb | 149 ++++++++++++++++++++++---------- modules/mu/logger.rb | 14 ++- modules/mu/mommacat.rb | 4 +- modules/mu/userdata/windows.erb | 146 +++++++++++-------------------- 9 files changed, 263 insertions(+), 187 deletions(-) diff --git a/bin/mu-node-manage b/bin/mu-node-manage index a5ddd948f..12e89e34d 100755 --- a/bin/mu-node-manage +++ b/bin/mu-node-manage @@ -214,6 +214,10 @@ def runCommand(deploys = MU::MommaCat.listDeploys, nodes = [], cmd = "( chef-cli done = false begin MU.log "Running #{cmd} on #{nodename} (##{count})" if !print_output + serverobj = mommacat.findLitterMate(type: "server", mu_name: nodename) + if serverobj.windows? + serverobj.groomer.run(max_retries: 0, output: print_output) + end output=`ssh -q #{nodename} "#{cmd}" 2>&1 < /dev/null` if !$?.success? if server['conf']["platform"] == "windows" and output.match(/NoMethodError: unknown property or method: `ConnectServer'/) diff --git a/modules/Gemfile b/modules/Gemfile index a588aaffd..651f2707c 100644 --- a/modules/Gemfile +++ b/modules/Gemfile @@ -20,7 +20,7 @@ gem 'chef-zero', "< 13" gem "aws-sdk-core", "~> 2.6.42" gem 'simple-password-gen' gem 'trollop', "~> 2.1.2" -gem 'knife-windows', '1.8.0' +gem 'knife-windows', '1.9.0' gem 'json-schema' gem 'colorize' gem 'color' diff --git a/modules/Gemfile.lock b/modules/Gemfile.lock index 1c85b8d75..d194e438c 100644 --- a/modules/Gemfile.lock +++ b/modules/Gemfile.lock @@ -1,12 +1,12 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.5.1) - public_suffix (~> 2.0, >= 2.0.2) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) aws-sdk-core (2.6.50) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sigv4 (1.0.1) + aws-sigv4 (1.0.2) berkshelf (5.6.5) addressable (~> 2.3, >= 2.3.4) berkshelf-api-client (>= 2.0.2, < 4.0) @@ -89,7 +89,7 @@ GEM diff-lcs (1.3) erubis (2.7.0) eventmachine (1.2.5) - faraday (0.12.2) + faraday (0.13.1) multipart-post (>= 1.2, < 3) ffi (1.9.18) ffi-yajl (2.3.1) @@ -101,7 +101,7 @@ GEM builder (>= 2.1.2) hashie (3.5.6) highline (1.7.8) - hitimes (1.2.5) + hitimes (1.2.6) httpclient (2.8.3) iniparse (1.4.4) ipaddress (0.8.3) @@ -109,7 +109,7 @@ GEM json (2.1.0) json-schema (2.8.0) addressable (>= 2.4) - knife-windows (1.8.0) + knife-windows (1.9.0) winrm (~> 2.1) winrm-elevated (~> 1.0) libyajl2 (1.2.0) @@ -121,14 +121,13 @@ GEM minitar (0.6.1) mixlib-archive (0.4.1) mixlib-log - mixlib-authentication (1.4.1) - mixlib-log + mixlib-authentication (1.4.2) mixlib-cli (1.7.0) mixlib-config (2.2.4) mixlib-log (1.7.1) mixlib-shellout (2.3.2) molinillo (0.5.7) - multi_json (1.12.1) + multi_json (1.12.2) multipart-post (2.0.0) mysql (2.9.1) net-ldap (0.16.0) @@ -165,7 +164,7 @@ GEM pg (0.21.0) plist (3.3.0) proxifier (1.0.3) - public_suffix (2.0.5) + public_suffix (3.0.0) rack (2.0.3) retryable (2.0.4) ridley (5.1.1) @@ -213,17 +212,17 @@ GEM addressable (>= 2.3.5, < 2.6) faraday (~> 0.8, < 1.0) semverse (2.0.0) - serverspec (2.39.1) + serverspec (2.41.0) multi_json rspec (~> 3.0) rspec-its - specinfra (~> 2.68) + specinfra (~> 2.72) sfl (2.3) simple-password-gen (0.1.5) solve (3.1.1) molinillo (>= 0.5) semverse (>= 1.1, < 3.0) - specinfra (2.70.1) + specinfra (2.72.0) net-scp net-ssh (>= 2.7, < 5.0) net-telnet @@ -254,7 +253,7 @@ GEM winrm-elevated (1.1.0) winrm (~> 2.0) winrm-fs (~> 1.0) - winrm-fs (1.0.1) + winrm-fs (1.0.2) erubis (~> 2.7) logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) @@ -274,7 +273,7 @@ DEPENDENCIES color colorize json-schema - knife-windows (= 1.8.0) + knife-windows (= 1.9.0) molinillo (< 0.6.0) mysql net-ldap diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index 8538cc297..705ca55b9 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -732,8 +732,52 @@ def windows? false end - # Basic setup tasks performed on a new node during its first initial ssh + # Basic setup tasks performed on a new node during its first WinRM # connection. Most of this is terrible Windows glue. + # @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node. + def initialWinRMTasks(shell) + if !@config['use_cloud_provider_windows_password'] + pw = @groomer.getSecret( + vault: @config['mu_name'], + item: "windows_credentials", + field: "password" + ) + win_check_for_pw = %Q{Add-Type -AssemblyName System.DirectoryServices.AccountManagement; $Creds = (New-Object System.Management.Automation.PSCredential("#{@config["windows_admin_username"]}", (ConvertTo-SecureString "#{pw}" -AsPlainText -Force)));$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine); $DS.ValidateCredentials($Creds.GetNetworkCredential().UserName, $Creds.GetNetworkCredential().password); echo $Result} + resp = shell.run(win_check_for_pw) + if resp.stdout.chomp != "True" + win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))} + resp = shell.run(win_set_pw) + puts resp.stdout + MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout + end + end + + set_hostname = true + hostname = nil + if !@config['active_directory'].nil? + if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname'] + hostname = @config['active_directory']['domain_controller_hostname'] + @mu_windows_name = hostname + set_hostname = true + else + # Do we have an AD specific hostname? + hostname = @mu_windows_name + set_hostname = true + end + else + hostname = @mu_windows_name + end + resp = shell.run(%Q{hostname}) + + if resp.stdout.chomp != hostname + resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force}) + MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout + end + end + + + # Basic setup tasks performed on a new node during its first initial + # ssh connection. Most of this is terrible Windows glue. # @param ssh [Net::SSH::Connection::Session]: The active SSH session to the new node. def initialSSHTasks(ssh) win_env_fix = %q{echo 'export PATH="$PATH:/cygdrive/c/opscode/chef/embedded/bin"' > "$HOME/chef-client"; echo 'prev_dir="`pwd`"; for __dir in /proc/registry/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session\ Manager/Environment;do cd "$__dir"; for __var in `ls * | grep -v TEMP | grep -v TMP`;do __var=`echo $__var | tr "[a-z]" "[A-Z]"`; test -z "${!__var}" && export $__var="`cat $__var`" >/dev/null 2>&1; done; done; cd "$prev_dir"; /cygdrive/c/opscode/chef/bin/chef-client.bat $@' >> "$HOME/chef-client"; chmod 700 "$HOME/chef-client"; ( grep "^alias chef-client=" "$HOME/.bashrc" || echo 'alias chef-client="$HOME/chef-client"' >> "$HOME/.bashrc" ) ; ( grep "^alias mu-groom=" "$HOME/.bashrc" || echo 'alias mu-groom="powershell -File \"c:/Program Files/Amazon/Ec2ConfigService/Scripts/UserScript.ps1\""' >> "$HOME/.bashrc" )} @@ -824,13 +868,28 @@ def initialSSHTasks(ssh) def getWinRMSession(max_retries = 40, retry_interval = 60) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig - conn = opts = nil + conn = nil + shell = nil + opts = nil # and now, a thing I really don't want to do MU::MommaCat.addInstanceToEtcHosts(canonical_ip, @mu_name) - begin - MU.log "Tryin' a call WinRM on #{@mu_name}", MU::WARN, details: opts + # catch exceptions that circumvent our regular call stack + Thread.abort_on_exception = false + Thread.handle_interrupt(WinRM::WinRMWSManFault => :never) { + begin + Thread.handle_interrupt(WinRM::WinRMWSManFault => :immediate) { + MU.log "(Probably harmless) Caught a WinRM::WinRMWSManFault in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace + } + ensure + # Reraise something useful +# raise MU::Groomer::RunError, "Ugly stupid WinRM exception hack" + end + } + retries = 0 + begin + MU.log "Calling WinRM on #{@mu_name}", MU::DEBUG, details: opts opts = { endpoint: 'https://'+@mu_name+':5986/wsman', transport: :ssl, @@ -840,30 +899,28 @@ def getWinRMSession(max_retries = 40, retry_interval = 60) client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key" } conn = WinRM::Connection.new(opts) - MU.log "WinRM connection to #{@mu_name} created", MU::NOTICE, details: conn + MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn shell = conn.shell(:powershell) - pp shell.run('ipconfig') - rescue OpenSSL::SSL::SSLError, SocketError => e - if e.message.match(/does not match the server certificate|Name or service not known/) - MU.log "WinRM failed to connect to #{@mu_name}: #{e.message}. Retrying in 10s.", MU::WARN - sleep 10 + shell.run('ipconfig') # verify that we can something + rescue Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError => e + msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})", MU::WARN + if retries < max_retries + if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0) + MU.log msg, MU::NOTICE + elsif retries/max_retries > 0.5 + MU.log msg, MU::WARN, details: e.inspect + end + sleep retry_interval + retries = retries + 1 retry else - raise e + raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace end -rescue HTTPClient::ConnectTimeoutError => e - # XXX crazy-ass retry logic goes here - sleep 20 - retry -rescue Exception => e -MU.log e.inspect, MU::ERR - sleep 20 - retry ensure MU::MommaCat.removeInstanceFromEtcHosts(@mu_name) end - conn + shell end # @param max_retries [Integer]: Number of connection attempts to make before giving up @@ -874,7 +931,7 @@ def getSSHSession(max_retries = 12, retry_interval = 30) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig session = nil retries = 0 - +pp caller # XXX catch a weird bug in Net::SSH where its exceptions circumvent # our regular call stack and we can't catch them. Thread.handle_interrupt(Net::SSH::Disconnect => :never) { diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index 30b923981..19d5435ec 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -1020,10 +1020,9 @@ def postBoot(instance_id = nil) # kick off certificate generation early; WinRM will need it cert, key = @deploy.nodeSSLCerts(self) session = getWinRMSession(50, 60) + initialWinRMTasks(session) # XXX account for machines behind bastion hosts that we can't tunnel through; # maybe then it's ok to fall back to sshd? - session = getSSHSession(50, 60) - initialSSHTasks(session) else session = getSSHSession(40, 30) initialSSHTasks(session) diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index 9e5aac86e..d7e4b3a66 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -238,9 +238,11 @@ def deleteSecret(vault: nil) # Invoke the Chef client on the node at the other end of a provided SSH # session. - # @param purpose [String] = A string describing the purpose of this client run. - # @param max_retries [Integer] = The maximum number of attempts at a successful run to make before giving up. - def run(purpose: "Chef run", update_runlist: true, max_retries: 5) + # @param purpose [String]: A string describing the purpose of this client run. + # @param max_retries [Integer]: The maximum number of attempts at a successful run to make before giving up. + # @param output [Boolean]: Display Chef's regular (non-error) output to the console + # @param override_runlist [String]: Use the specified run list instead of the node's configured list + def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, override_runlist: nil) self.class.loadChefLib if update_runlist and !@config['run_list'].nil? knifeAddToRunList(multiple: @config['run_list']) @@ -260,10 +262,13 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5) output = [] error_signal = "CHEF EXITED BADLY: "+(0...25).map { ('a'..'z').to_a[rand(26)] }.join runstart = nil + cmd = nil + ssh = nil + winrm = nil begin - ssh = @server.getSSHSession(max_retries) - cmd = nil + runstart = Time.new if !@server.windows? + ssh = @server.getSSHSession(max_retries) if !@config["ssh_user"].nil? and !@config["ssh_user"].empty? and @config["ssh_user"] != "root" upgrade_cmd = try_upgrade ? "sudo curl -L https://chef.io/chef/install.sh | sudo version=#{MU.chefVersion} sh &&" : "" cmd = "#{upgrade_cmd} sudo chef-client --color || echo #{error_signal}" @@ -271,17 +276,39 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5) upgrade_cmd = try_upgrade ? "curl -L https://chef.io/chef/install.sh | version=#{MU.chefVersion} sh &&" : "" cmd = "#{upgrade_cmd} chef-client --color || echo #{error_signal}" end + retval = ssh.exec!(cmd) { |ch, stream, data| + puts data + output << data + raise MU::Groomer::RunError, output.grep(/ ERROR: /).last if data.match(/#{error_signal}/) + } else - upgrade_cmd = try_upgrade ? "powershell \". { Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; Install-Project -version:#{MU.chefVersion} -download_directory:$HOME \" &&" : "" - cmd = "#{upgrade_cmd} $HOME/chef-client --color || echo #{error_signal}" + winrm = @server.getWinRMSession(max_retries) +# upgrade_cmd = try_upgrade ? "powershell \". { Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; Install-Project -version:#{MU.chefVersion} -download_directory:$HOME \" &&" : "" +# cmd = "#{upgrade_cmd} $HOME/chef-client --color || echo #{error_signal}" + if try_upgrade + pp winrm.run("Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; Install-Project -version:#{MU.chefVersion} -download_directory:$HOME") + else + output = [] + cmd = "c:/opscode/chef/bin/chef-client.bat --color" + if override_runlist + cmd = cmd + " -o '#{override_runlist}'" + end + resp = winrm.run(cmd) do |stdout, stderr| + if stdout + print stdout if output + output << stdout + end + if stderr + MU.log stderr, MU::ERR + output << stderr + end + end + if resp.exitcode != 0 + raise MU::Groomer::RunError, output.slice(output.length-50, output.length).join("") + end + end end - runstart = Time.new - retval = ssh.exec!(cmd) { |ch, stream, data| - puts data - output << data - raise MU::Groomer::RunError, output.grep(/ ERROR: /).last if data.match(/#{error_signal}/) - } - rescue RuntimeError, SystemCallError, Timeout::Error, SocketError, Errno::ECONNRESET, IOError, Net::SSH::Exception, MU::Groomer::RunError => e + rescue RuntimeError, SystemCallError, Timeout::Error, SocketError, Errno::ECONNRESET, IOError, Net::SSH::Exception, MU::Groomer::RunError, WinRM::WinRMError => e begin ssh.close if !ssh.nil? rescue Net::SSH::Exception, IOError => e @@ -293,7 +320,7 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5) sleep 10 end - if e.instance_of?(MU::Groomer::RunError) and retries == 0 + if e.instance_of?(MU::Groomer::RunError) and retries == 0 and max_retries > 1 MU.log "Got a run error, will attempt to install/update Chef Client on next attempt", MU::NOTICE try_upgrade = true else @@ -345,28 +372,51 @@ def preClean(leave_ours = false) remove_cmd = "sudo rpm -e erase chef ; sudo rm -rf /var/chef/ /etc/chef /opt/chef/ /usr/bin/chef-* ; sudo apt-get -y remove chef ; sudo touch /opt/mu_installed_chef" end guardfile = "/opt/mu_installed_chef" - else - remove_cmd = "( rm -rf /cygdrive/c/chef/*.pem ; /cygdrive/c/Windows/system32/reg query HKLM\\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ /f 'Chef Client' /s /t REG_SZ | grep '}$' | cut -d{ -f2 | cut -d} -f1 | xargs msiexec /qn /x ) ; /cygdrive/c/Windows/system32/reg query HKLM\\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ /f 'Chef Client' /s /t REG_SZ | grep '}$' | cut -d{ -f2 | cut -d} -f1 | xargs msiexec /qn /x )" - guardfile = "/cygdrive/c/mu_installed_chef" - end - - ssh = @server.getSSHSession(15) - if leave_ours - MU.log "Expunging pre-existing Chef install on #{@server.mu_name}, if we didn't create it", MU::NOTICE - begin - ssh.exec!(%Q{test -f #{guardfile} || (#{remove_cmd}) ; touch #{guardfile}}) - rescue IOError => e - # TO DO - retry this in a cleaner way - MU.log "Got #{e.inspect} while trying to clean up chef, retrying", MU::NOTICE - ssh = @server.getSSHSession(15) - ssh.exec!(%Q{test -f #{guardfile} || (#{remove_cmd}) ; touch #{guardfile}}) + + ssh = @server.getSSHSession(15) + if leave_ours + MU.log "Expunging pre-existing Chef install on #{@server.mu_name}, if we didn't create it", MU::NOTICE + begin + ssh.exec!(%Q{test -f #{guardfile} || (#{remove_cmd}) ; touch #{guardfile}}) + rescue IOError => e + # TO DO - retry this in a cleaner way + MU.log "Got #{e.inspect} while trying to clean up chef, retrying", MU::NOTICE + ssh = @server.getSSHSession(15) + ssh.exec!(%Q{test -f #{guardfile} || (#{remove_cmd}) ; touch #{guardfile}}) + end + else + MU.log "Expunging pre-existing Chef install on #{@server.mu_name}", MU::NOTICE + ssh.exec!(remove_cmd) end + + ssh.close else - MU.log "Expunging pre-existing Chef install on #{@server.mu_name}", MU::NOTICE - ssh.exec!(remove_cmd) - end + # XXX this hasn't been tested + remove_cmd = %Q{ + $uninstall_string = (Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | Where-Object {$_.DisplayName -like "chef client*"}).UninstallString + if($uninstall_string){ + $uninstall_string = ($uninstall_string -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","").Trim() + $($uninstall_string -Replace '[\\s\\t]+', ' ').Split() | ForEach { + start-process "msiexec.exe" -arg "/X $_ /qn" -Wait + } + } + } + shell = @server.getWinRMSession(15) + removechef = true + if leave_ours + resp = shell.run("Test-Path c:/mu_installed_chef") + if resp.stdout.chomp == "True" + MU.log "Found existing Chef installation created by Mu, leaving it alone" + removechef = false + end + end - ssh.close +# remove_cmd = %Q{$my_chef = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).DisplayName + if removechef + MU.log "Expunging pre-existing Chef install on #{@server.mu_name}", MU::NOTICE, details: remove_cmd + pp shell.run(remove_cmd) + end + end end # Bootstrap our server with Chef @@ -385,6 +435,7 @@ def bootstrap end nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_addr, ssh_user, ssh_key_name = @server.getSSHConfig + MU.log "Bootstrapping #{@server.mu_name} (#{canonical_addr}) with knife" run_list = ["recipe[mu-tools::newclient]"] @@ -404,18 +455,28 @@ def bootstrap if !@server.windows? kb = ::Chef::Knife::Bootstrap.new([canonical_addr]) kb.config[:use_sudo] = true + kb.name_args = "#{canonical_addr}" kb.config[:distro] = 'chef-full' + kb.config[:ssh_user] = ssh_user + kb.config[:forward_agent] = ssh_user + kb.config[:identity_file] = "#{Etc.getpwuid(Process.uid).dir}/.ssh/#{ssh_key_name}" else - kb = ::Chef::Knife::BootstrapWindowsSsh.new([canonical_addr]) - kb.config[:cygwin] = true - # kb.config[:distro] = 'windows-chef-client-msi' - #Trying to solve random createprocess errors - knife-windows always installs 32bit and architecture/bootstrap_architecture don't seem to work + kb = ::Chef::Knife::BootstrapWindowsWinrm.new([@server.mu_name]) + kb.name_args = [@server.mu_name] + kb.config[:manual] = true + kb.config[:winrm_transport] = :ssl + kb.config[:host] = @server.mu_name + kb.config[:winrm_port] = 5986 + kb.config[:winrm_authentication_protocol] = :cert + kb.config[:winrm_client_cert] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.crt" + kb.config[:winrm_client_key] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.key" +# kb.config[:ca_trust_file] = "#{MU.mySSLDir}/Mu_CA.pem" + # XXX ca_trust_file doesn't work for some reason, so we have to set the below for now + kb.config[:winrm_ssl_verify_mode] = :verify_none kb.config[:msi_url] = "https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=#{MU.chefVersion}" - # kb.config[:node_ssl_verify_mode] = 'none' - # kb.config[:node_verify_api_cert] = false end - # XXX this seems to break Knife Bootstrap for the moment + # XXX this seems to break Knife Bootstrap # if vault_access.size > 0 # v = {} # vault_access.each { |vault| @@ -427,14 +488,10 @@ def bootstrap kb.config[:json_attribs] = JSON.generate(json_attribs) if json_attribs.size > 1 kb.config[:run_list] = run_list - kb.config[:ssh_user] = ssh_user - kb.config[:forward_agent] = ssh_user - kb.name_args = "#{canonical_addr}" kb.config[:chef_node_name] = @server.mu_name kb.config[:bootstrap_version] = MU.chefVersion # XXX key off of MU verbosity level kb.config[:log_level] = :debug - kb.config[:identity_file] = "#{Etc.getpwuid(Process.uid).dir}/.ssh/#{ssh_key_name}" # kb.config[:ssh_gateway] = "#{nat_ssh_user}@#{nat_ssh_host}" if !nat_ssh_host.nil? # Breaking bootsrap # This defaults to localhost for some reason sometimes. Brute-force it. @@ -450,7 +507,7 @@ def bootstrap } # throws Net::HTTPServerException if we haven't really bootstrapped ::Chef::Node.load(@server.mu_name) - rescue Net::SSH::Disconnect, SystemCallError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError, SocketError, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, IOError, Net::HTTPServerException, SystemExit, Errno::ECONNREFUSED, Errno::EPIPE => e + rescue Net::SSH::Disconnect, SystemCallError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError, SocketError, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, IOError, Net::HTTPServerException, SystemExit, Errno::ECONNREFUSED, Errno::EPIPE, WinRM::WinRMError => e if retries < max_retries retries += 1 MU.log "#{@server.mu_name}: Knife Bootstrap failed #{e.inspect}, retrying (#{retries} of #{max_retries})", MU::WARN, details: e.backtrace diff --git a/modules/mu/logger.rb b/modules/mu/logger.rb index ae1ebb270..0784a8ee5 100644 --- a/modules/mu/logger.rb +++ b/modules/mu/logger.rb @@ -79,12 +79,18 @@ def log(msg, Syslog.open("Mu/"+caller_name, Syslog::LOG_PID, Syslog::LOG_DAEMON | Syslog::LOG_LOCAL3) if !Syslog.opened? if !details.nil? - details = details[:details] if details.has_key?(:details) - details = PP.pp(details, '') + if details.is_a?(Hash) and details.has_key?(:details) + details = details[:details] + end + details = PP.pp(details, '') if !details.is_a?(String) end details = "
"+details+"
" if @html - # We get passed literal quoted newlines sometimes, fix 'em - details.gsub!(/\\n/, "\n") if !details.nil? + # We get passed literal quoted newlines sometimes, fix 'em. Get Windows' + # ugly line feeds too. + if !details.nil? + details.gsub!(/\\n/, "\n") + details.gsub!(/(\\r|\r)/, "") + end msg = msg.first if msg.is_a?(Array) msg = "" if msg == nil diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index cdfa0a3fe..04ee9ed34 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1658,7 +1658,7 @@ def self.addInstanceToEtcHosts(public_ip, chef_name = nil, system_name = nil) end File.readlines("/etc/hosts").each { |line| if line.match(/^#{public_ip} /) or (chef_name != nil and line.match(/ #{chef_name}(\s|$)/)) or (system_name != nil and line.match(/ #{system_name}(\s|$)/)) - MU.log("Attempt to add duplicate /etc/hosts entry: #{public_ip} #{chef_name} #{system_name}", MU::WARN) + MU.log "Ignoring attempt to add duplicate /etc/hosts entry: #{public_ip} #{chef_name} #{system_name}", MU::DEBUG return end } @@ -1942,7 +1942,7 @@ def retrieveWindowsAdminCreds(server) # end #MU.log "retrieveWindowsAdminCreds called on a thing", MU::NOTICE, details: server.config if server.config['use_cloud_provider_windows_password'] - return [server.config["windows_admin_username"], getWindowsAdminPassword] + return [server.config["windows_admin_username"], server.getWindowsAdminPassword] elsif server.config['windows_auth_vault'] && !server.config['windows_auth_vault'].empty? if server.config["windows_auth_vault"].has_key?("password_field") return [server.config["windows_admin_username"], diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 9ad83500b..f1a4c08d8 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -2,7 +2,7 @@ Set-ExecutionPolicy Unrestricted -Force -Scope CurrentUser $sshdUser = "sshd_service" -$tmp = "$env:Temp/mu-userdata" +$tmp = "$env:Temp\mu-userdata" mkdir $tmp $logfile = "c:/Mu-Bootstrap-$([Environment]::UserName).log" $basedir = 'c:/bin' @@ -10,7 +10,8 @@ $cygwin_dir = "$basedir/cygwin" $username = (whoami).Split('\')[1] $WebClient = New-Object System.Net.WebClient $awsmeta = "http://169.254.169.254/latest" -$pydir = 'c:/bin/python/python27' +$pydir = 'c:\bin\python\python27' +$pyv = '2.7.14' $env:Path += ";$pydir\Scripts;$pydir" function log @@ -34,17 +35,6 @@ function importCert([string]$cert, [string]$store){ } } -function Disable-SSHD -{ - if ((Get-Service "sshd" -ErrorAction SilentlyContinue) -and (Test-Path "$cygwin_dir/bin/bash.exe")) { - log "Disabling pre-existing sshd" - - Stop-Service -ErrorAction SilentlyContinue sshd - Stop-Process -ProcessName sshd -force -ErrorAction SilentlyContinue - Invoke-Expression '& $cygwin_dir/bin/bash --login -c "cygrunsrv --stop sshd; cygrunsrv --remove sshd; net user sshd /delete; net user sshd_service /delete; mkpasswd > /etc/passwd"' - } -} - log "- Invoked as $([Environment]::UserName) (system started at $(Get-CimInstance -ClassName win32_operatingsystem | select lastbootuptime)) -" <% if !$mu.skipApplyUpdates %> @@ -115,85 +105,13 @@ If (!(Test-Path $tmp/PSWindowsUpdate.zip)){ log "Setting Windows Update parameters in registry" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name AUOptions -Value 3 -If (!(Test-Path "$cygwin_dir/Cygwin.bat")){ - If (!(Test-Path $tmp/setup-x86_64.exe)){ - $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$tmp/setup-x86_64.exe") - } - - If (!(Test-Path $tmp/cygwin.zip)){ - log "Downloading Cygwin packages" - $WebClient.DownloadFile("https://s3.amazonaws.com/mu-stuff/cygwin_20161022.zip","$tmp/cygwin.zip") - } - - Add-Type -A 'System.IO.Compression.FileSystem' - If (!(Test-Path $tmp/cygwin)){ - [IO.Compression.ZipFile]::ExtractToDirectory("$tmp/cygwin.zip", "$tmp/cygwin") - } - - log "Running Cygwin installer" - Start-Process -wait -FilePath "$tmp/setup-x86_64.exe" -ArgumentList "-q -n -l $tmp -l $tmp\cygwin -L -R $cygwin_dir -P openssh,mintty,vim,curl,openssl" -} - -if (!(Get-Service "sshd" -ErrorAction SilentlyContinue)){ - log "Invoking ssh-host-config to enable sshd as $sshdUser (I am $admin_username)" - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "ssh-host-config -y -c ntsec -w ''$password'' -u $sshdUser" > $cygwin_dir/sshd_setup_log.txt' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*StrictModes.*yes/StrictModes no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "sed -i.bak ''s/#.*PasswordAuthentication.*yes/PasswordAuthentication no/'' /etc/sshd_config" >> $cygwin_dir/sshd_setup_log.txt' - New-Item $cygwin_dir/sshd_installed_by.txt -type file -force -value $admin_username - log "Creating c:/$awsid (<%= $mu.muID %>)" - New-Item c:/$awsid -type file -force -value "<%= $mu.muID %>" - log "Value in that file: $(Get-Content c:/$awsid)" -} - -log "Ensuring domain or local users are in /etc/passwd for sshd" -if((Get-WmiObject win32_computersystem).partofdomain){ - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -d > /etc/passwd"' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l -d > /etc/group"' -} else { - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -l > /etc/passwd"' - Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l > /etc/group"' -} - -if (!(Get-WmiObject win32_computersystem).partofdomain){ - If (!(Test-Path "c:/mu-configure-initial-ssh-user")){ - log "making sure the ssh user is configured correctly" - (([adsi]("WinNT://./$sshdUser, user")).psbase.invoke('SetPassword', "$password")) - $sshd_service = Get-WmiObject Win32_Service -Filter "Name='sshd'" - $sshd_service.Change($Null,$Null,$Null,$Null,$Null,$Null,".\$sshdUser",$password,$Null,$Null,$Null) - - $editrights="$cygwin_dir/bin/editrights" - &$editrights -a SeAssignPrimaryTokenPrivilege -u $sshdUser - &$editrights -a SeCreateTokenPrivilege -u $sshdUser - &$editrights -a SeTcbPrivilege -u $sshdUser - &$editrights -a SeServiceLogonRight -u $sshdUser - Add-Content c:/mu-configure-initial-ssh-user "done" - } -} - -$sshd_svc_user = (Get-WmiObject -Query "SELECT * FROM win32_service WHERE name='sshd'").StartName -if ( $sshd_svc_user.contains("\") ){ - $sshd_svc_user = $sshd_svc_user.substring($sshd_svc_user.LastIndexOf("\")+1) -} -log "Chowning /var/empty, /var/log/sshd.log, and /etc/ssh* to $sshd_svc_user" -Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "chown $sshd_svc_user /var/empty /var/log/sshd.log /etc/ssh*; chmod 755 /var/empty"' - -If (!((Get-ItemProperty HKLM:/SYSTEM/CurrentControlSet/Control/Lsa)."Authentication Packages" | Select-String -pattern "cyglsa64.dll")){ - If ((Test-Path "$cygwin_dir/bin/cyglsa-config")){ - log "Setting Cygwin LSA support, will reboot" - Invoke-Expression '& $cygwin_dir/bin/bash --login -c "echo yes | /bin/cyglsa-config"' - $need_reboot = $TRUE - } else { - log "Need to set Cygwin LSA support, but I don't see $cygwin_dir/bin/cyglsa-config!" - } -} - If (!(Test-Path "$pydir\python.exe")){ - If (!(Test-Path $tmp/python-2.7.9.msi)){ + If (!(Test-Path $tmp\python-$pyv.msi)){ log "Downloading Python installer" - $WebClient.DownloadFile("https://www.python.org/ftp/python/2.7.9/python-2.7.9.msi","$tmp/python-2.7.9.msi") + $WebClient.DownloadFile("https://www.python.org/ftp/python/$pyv/python-$pyv.msi","$tmp/python-$pyv.msi") } log "Running Python installer" - (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\python-2.7.9.msi /qn ALLUSERS=1 TARGETDIR=$pydir" -Wait -Passthru).ExitCode + (Start-Process -FilePath msiexec -ArgumentList "/i $tmp\python-$pyv.msi /qn ALLUSERS=1 TARGETDIR=$pydir" -Wait -Passthru).ExitCode } If (!(Test-Path "$pydir\Scripts\aws.cmd")){ @@ -227,6 +145,29 @@ function removeChef($location){ return $install_chef } +If (!(Test-Path "c:\opscode\chef\embedded\bin\ruby.exe")){ + $install_chef = $true +} else { + if (removeChef("HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*")){ + $install_chef = $true + } elseif (removeChef("HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*")) { + $install_chef = $true + } else { + $install_chef = $false + } +} + +If ($install_chef){ + log "Installing Chef <%= MU.chefVersion %>" + If (!(Test-Path $env:Temp/chef-installer-<%= MU.chefVersion %>.msi)){ + log "Downloading Chef installer" + $WebClient.DownloadFile("https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=<%= MU.chefVersion %>","$env:Temp/chef-installer-<%= MU.chefVersion %>.msi") + } + log "Running Chef installer" + (Start-Process -FilePath msiexec -ArgumentList "/i $env:Temp\chef-installer-<%= MU.chefVersion %>.msi ALLUSERS=1 /le $env:Temp\chef-client-install.log /qn" -Wait -Passthru).ExitCode + Set-Content "c:/mu_installed_chef" "yup" +} + <% if !$mu.skipApplyUpdates %> If (!(Test-Path "c:/mu-installer-ran-updates")){ log "Applying Windows updates" @@ -242,7 +183,7 @@ If (!(Test-Path "c:/mu-installer-ran-updates")){ fetchSecret("<%= $mu.muID %>-secret") log "Encrypting Mu deploy secret" -$deploy_secret = & "c:\opscode\chef\embedded\bin\ruby" -ropenssl -rbase64 -e "key = OpenSSL::PKey::RSA.new(Base64.urlsafe_decode64('<%= $mu.deployKey %>'))" -e "print Base64.urlsafe_encode64(key.public_encrypt(File.read('$tmp/<%= $mu.muID %>-secret')))" +$deploy_secret = & "c:\opscode\chef\embedded\bin\ruby" -ropenssl -rbase64 -e "key = OpenSSL::PKey::RSA.new(Base64.urlsafe_decode64('<%= $mu.deployKey %>'))" -e "print Base64.urlsafe_encode64(key.public_encrypt(File.read('$tmp\<%= $mu.muID %>-secret')))" function callMomma([string]$act) { @@ -253,6 +194,17 @@ function callMomma([string]$act) return $resp.Content } +$credstr = callMomma "mu_windows_admin_creds" +$creds = $false +if($credstr){ + $credparts = $credstr.Split(";", 2) + $creds = New-Object System.Management.Automation.PSCredential($credparts[0], (ConvertTo-SecureString $credparts[1] -AsPlainText -Force)) + if($creds){ + log "Setting $admin_username password" + (([adsi]("WinNT://./$admin_username, user")).psbase.invoke("SetPassword", $credparts[1])) + } +} + <% if $mu.windowsAdminName %> if ((Get-WmiObject win32_computersystem).partofdomain -ne $true){ if ("$admin_username" -ne "<%= $mu.windowsAdminName %>"){ @@ -279,13 +231,8 @@ $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name LocalAccountTokenFilterPolicy -Value 1 -$credstr = callMomma "mu_windows_admin_creds" -if($credstr){ - $credparts = $credstr.Split(";", 2) - $creds = New-Object System.Management.Automation.PSCredential($credparts[0], (ConvertTo-SecureString $credparts[1] -AsPlainText -Force)) - if($creds){ - New-Item -Path WSMan:\localhost\ClientCertificate -Subject '<%= $mu.windowsAdminName %>@localhost' -URI * -Issuer $muca.Thumbprint -Force -Credential $creds - } +if($creds){ + New-Item -Path WSMan:\localhost\ClientCertificate -Subject '<%= $mu.windowsAdminName %>@localhost' -URI * -Issuer $muca.Thumbprint -Force -Credential $creds } winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' winrm set winrm/config '@{MaxTimeoutms="1800000"}' @@ -298,6 +245,13 @@ if (!(Get-NetFirewallRule -DisplayName "Allow WinRM" -ErrorAction SilentlyContin New-NetFirewallRule -DisplayName "Allow WinRM" -Direction Inbound -LocalPort 5986 -Protocol TCP -Action Allow } +# XXX stick this installer script somewhere we control +iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +choco feature enable -n useFipsCompliantChecksums +refreshenv +choco install openssh -y choco upgrade openssh -y + + <% if $mu.windowsAdminName %> log "Creating $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys" New-Item $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" From 848a9ae73ee05cea2a85975794a57a2c64326995 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 6 Oct 2017 15:41:22 -0400 Subject: [PATCH 15/63] bump Chef version; start on a non-cygwin opensshd for Windows --- Berksfile.lock | 60 +++++++++++++++++--- cookbooks/mu-master/recipes/init.rb | 2 +- cookbooks/mu-tools/recipes/windows-client.rb | 2 + install/installer | 2 +- modules/Gemfile | 2 +- modules/mu.rb | 2 +- modules/mu/cloud.rb | 5 +- modules/mu/groomer.rb | 1 + modules/mu/groomers/chef.rb | 25 ++++++-- modules/mu/userdata/windows.erb | 18 ++---- 10 files changed, 86 insertions(+), 33 deletions(-) diff --git a/Berksfile.lock b/Berksfile.lock index 50f899212..8841e2b7f 100644 --- a/Berksfile.lock +++ b/Berksfile.lock @@ -6,6 +6,8 @@ DEPENDENCIES build-essential (~> 8.0) chef-vault (< 3.0.0) chef_nginx (~> 6.1.1) + demo + path: site_cookbooks/demo freebsd (~> 0.1.9) gunicorn (~> 1.1.2) logrotate (~> 1.9.2) @@ -47,7 +49,24 @@ DEPENDENCIES GRAPH apache2 (3.3.1) - apt (6.1.3) + application (5.2.0) + poise (~> 2.4) + poise-service (~> 1.0) + application_python (4.0.0) + application (~> 5.0) + poise (~> 2.0) + poise-python (~> 1.0) + poise-service (~> 1.0) + application_ruby (4.1.0) + application (~> 5.0) + poise (~> 2.0) + poise-ruby (~> 2.1) + poise-service (~> 1.0) + apt (6.1.4) + ark (3.1.0) + build-essential (>= 0.0.0) + seven_zip (>= 0.0.0) + windows (>= 0.0.0) aws (2.9.3) ohai (>= 2.1.0) awscli (0.2.1) @@ -79,15 +98,24 @@ GRAPH cpan (0.0.37) database (6.1.1) postgresql (>= 1.0.0) - dmg (4.0.0) + demo (0.3.0) + application (>= 0.0.0) + application_python (>= 0.0.0) + application_ruby (>= 0.0.0) + chef-vault (>= 0.0.0) + chef_nginx (>= 0.0.0) + git (>= 0.0.0) + mysql (>= 0.0.0) + nodejs (>= 0.0.0) + php (>= 0.0.0) + ruby_build (>= 0.0.0) dpkg_autostart (0.2.0) firewall (2.6.2) chef-sugar (>= 0.0.0) freebsd (0.1.10) - git (6.1.0) + git (8.0.0) build-essential (>= 0.0.0) - dmg (>= 0.0.0) - yum-epel (>= 0.0.0) + homebrew (>= 0.0.0) golang (1.7.0) gunicorn (1.1.6) python (>= 0.0.0) @@ -103,7 +131,7 @@ GRAPH apt (>= 0.0.0) homebrew (>= 0.0.0) windows (>= 0.0.0) - jenkins (5.0.3) + jenkins (5.0.4) compat_resource (>= 12.16.3) dpkg_autostart (>= 0.0.0) runit (>= 1.7) @@ -204,6 +232,10 @@ GRAPH perl (>= 0.0.0) runit (>= 0.0.0) yum-epel (>= 0.0.0) + nodejs (4.0.0) + ark (>= 2.0.2) + build-essential (>= 0.0.0) + compat_resource (>= 12.16) nrpe (2.0.2) build-essential (>= 0.0.0) yum-epel (>= 0.0.0) @@ -216,7 +248,7 @@ GRAPH cpan (>= 0.0.0) php (>= 0.0.0) packagecloud (0.3.0) - perl (5.2.0) + perl (5.2.1) windows (>= 3.0) php (4.5.0) build-essential (>= 0.0.0) @@ -225,6 +257,15 @@ GRAPH poise (2.8.1) poise-archive (1.5.0) poise (~> 2.6) + poise-languages (2.1.1) + poise (~> 2.5) + poise-archive (~> 1.0) + poise-python (1.6.0) + poise (~> 2.7) + poise-languages (~> 2.0) + poise-ruby (2.3.0) + poise (~> 2.0) + poise-languages (~> 2.0) poise-service (1.5.2) poise (~> 2.0) postfix (5.1.1) @@ -235,6 +276,9 @@ GRAPH python (1.4.7) build-essential (>= 0.0.0) yum-epel (>= 0.0.0) + ruby_build (1.1.0) + git (>= 0.0.0) + yum-epel (>= 0.0.0) rubyzip (1.3.1) poise (~> 2.2) runit (1.8.0) @@ -253,7 +297,7 @@ GRAPH consul-cluster (~> 2.0) hashicorp-vault (~> 2.1) ssl_certificate (~> 1.11) - windows (3.1.2) + windows (3.1.3) ohai (>= 4.0.0) yum (3.13.0) yum-epel (2.1.2) diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index 6fa2ff84b..e40dc6f52 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -29,7 +29,7 @@ # XXX We want to be able to override these things when invoked from chef-apply, # but, like, how? CHEF_SERVER_VERSION="12.15.7-1" -CHEF_CLIENT_VERSION="12.20.3-1" +CHEF_CLIENT_VERSION="12.21.14-1" KNIFE_WINDOWS="1.8.0" MU_BRANCH="master" MU_BASE="/opt/mu" diff --git a/cookbooks/mu-tools/recipes/windows-client.rb b/cookbooks/mu-tools/recipes/windows-client.rb index 6631bdd4e..b6f6161ec 100644 --- a/cookbooks/mu-tools/recipes/windows-client.rb +++ b/cookbooks/mu-tools/recipes/windows-client.rb @@ -69,6 +69,7 @@ service_username "#{node['ad']['netbios_name']}\\#{sshd_user}" username sshd_user password sshd_password +ignore_failure true end else windows_users node['hostname'] do @@ -95,6 +96,7 @@ username sshd_user service_username ".\\#{sshd_user}" password sshd_password +ignore_failure true end end# rescue NoMethodError diff --git a/install/installer b/install/installer index 9477611f1..885fad979 100755 --- a/install/installer +++ b/install/installer @@ -1,6 +1,6 @@ #!/bin/sh -CHEF_CLIENT_VERSION="12.20.3-1" +CHEF_CLIENT_VERSION="12.21.14-1" MU_BRANCH="master" # XXX All RHEL family. We can at least cover Debian-flavored hosts too, I bet. diff --git a/modules/Gemfile b/modules/Gemfile index 651f2707c..9f1e4ae44 100644 --- a/modules/Gemfile +++ b/modules/Gemfile @@ -15,7 +15,7 @@ source "https://rubygems.org" gem 'yard' gem 'ruby-graphviz', "~> 1.2.2" -gem 'chef', "~> 12.20.3" +gem 'chef', "~> 12.21.14" gem 'chef-zero', "< 13" gem "aws-sdk-core", "~> 2.6.42" gem 'simple-password-gen' diff --git a/modules/mu.rb b/modules/mu.rb index 00e498403..1459a64e3 100644 --- a/modules/mu.rb +++ b/modules/mu.rb @@ -515,7 +515,7 @@ def self.mySubnets end # The version of Chef we will install on nodes. - @@chefVersion = "12.20.3-1" + @@chefVersion = "12.21.14-1" # The version of Chef we will install on nodes. # @return [String] def self.chefVersion; diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index 705ca55b9..8ee68e7d6 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -893,6 +893,7 @@ def getWinRMSession(max_retries = 40, retry_interval = 60) opts = { endpoint: 'https://'+@mu_name+':5986/wsman', transport: :ssl, + retry_limit: 5, no_ssl_peer_verification: true, # XXX this should not be necessary; we get 'hostname "foo" does not match the server certificate' even when it clearly does match ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem", client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt", @@ -902,10 +903,10 @@ def getWinRMSession(max_retries = 40, retry_interval = 60) MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn shell = conn.shell(:powershell) shell.run('ipconfig') # verify that we can something - rescue Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError => e + rescue Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})", MU::WARN if retries < max_retries - if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0) + if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0) MU.log msg, MU::NOTICE elsif retries/max_retries > 0.5 MU.log msg, MU::WARN, details: e.inspect diff --git a/modules/mu/groomer.rb b/modules/mu/groomer.rb index 7013ba3f7..4b7ef9fd9 100644 --- a/modules/mu/groomer.rb +++ b/modules/mu/groomer.rb @@ -110,6 +110,7 @@ def cleanup retval = @groomer_obj.method(method).call end rescue Exception => e + pp e.backtrace raise MU::Groomer::RunError, e.message, e.backtrace end @groom_semaphore.synchronize { diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index d7e4b3a66..f8b60e261 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -452,6 +452,8 @@ def bootstrap vault_access = [] end + @server.windows? ? max_retries = 25 : max_retries = 10 + @server.windows? ? timeout = 720 : timeout = 300 if !@server.windows? kb = ::Chef::Knife::Bootstrap.new([canonical_addr]) kb.config[:use_sudo] = true @@ -467,6 +469,8 @@ def bootstrap kb.config[:winrm_transport] = :ssl kb.config[:host] = @server.mu_name kb.config[:winrm_port] = 5986 + kb.config[:session_timeout] = timeout + kb.config[:operation_timeout] = timeout kb.config[:winrm_authentication_protocol] = :cert kb.config[:winrm_client_cert] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.crt" kb.config[:winrm_client_key] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.key" @@ -493,21 +497,26 @@ def bootstrap # XXX key off of MU verbosity level kb.config[:log_level] = :debug # kb.config[:ssh_gateway] = "#{nat_ssh_user}@#{nat_ssh_host}" if !nat_ssh_host.nil? # Breaking bootsrap - # This defaults to localhost for some reason sometimes. Brute-force it. - - MU.log "Knife Bootstrap settings for #{@server.mu_name} (#{canonical_addr})", MU::NOTICE, details: kb.config retries = 0 - @server.windows? ? max_retries = 25 : max_retries = 10 - @server.windows? ? timeout = 720 : timeout = 300 + MU.log "Knife Bootstrap settings for #{@server.mu_name} (#{canonical_addr}), timeout set to #{timeout.to_s}", MU::NOTICE, details: kb.config begin +# Thread.handle_interrupt(Timeout::Error => :never) { +# begin +# Thread.handle_interrupt(Timeout::Error => :immediate) { +# MU.log "Caught a Timeout::Error in #{Thread.current.inspect}", MU::NOTICE, details: Thread.current.backtrace +# } +# ensure +# raise MU::Cloud::BootstrapTempFail +# end +# } Timeout::timeout(timeout) { require 'chef' kb.run } # throws Net::HTTPServerException if we haven't really bootstrapped ::Chef::Node.load(@server.mu_name) - rescue Net::SSH::Disconnect, SystemCallError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError, SocketError, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, IOError, Net::HTTPServerException, SystemExit, Errno::ECONNREFUSED, Errno::EPIPE, WinRM::WinRMError => e + rescue Net::SSH::Disconnect, SystemCallError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError, SocketError, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, IOError, Net::HTTPServerException, SystemExit, Errno::ECONNREFUSED, Errno::EPIPE, WinRM::WinRMError, HTTPClient::ConnectTimeoutError, RuntimeError, MU::Cloud::BootstrapTempFail => e if retries < max_retries retries += 1 MU.log "#{@server.mu_name}: Knife Bootstrap failed #{e.inspect}, retrying (#{retries} of #{max_retries})", MU::WARN, details: e.backtrace @@ -522,6 +531,10 @@ def bootstrap else raise MuError, "#{@server.mu_name}: Knife Bootstrap failed too many times with #{e.inspect}" end + rescue Exception => e +MU.log e.inspect, MU::ERR, details: e.backtrace +sleep 10*retries +retry end # Now that we're done, remove one-shot bootstrap recipes from the diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index f1a4c08d8..4964d8c32 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -249,15 +249,13 @@ if (!(Get-NetFirewallRule -DisplayName "Allow WinRM" -ErrorAction SilentlyContin iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) choco feature enable -n useFipsCompliantChecksums refreshenv -choco install openssh -y choco upgrade openssh -y - +choco install openssh -y -params "/SSHServerFeature /SSHLogLevel:VERBOSE" +choco upgrade openssh -y -params "/SSHServerFeature /SSHLogLevel:VERBOSE" +Start-Service sshd +Set-Service sshd -startuptype "Automatic" <% if $mu.windowsAdminName %> -log "Creating $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys" -New-Item $cygwin_dir/home/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" -<% else %> -log "Creating $cygwin_dir/home/$admin_username/.ssh/authorized_keys" -New-Item $cygwin_dir/home/$admin_username/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey %>" +#New-Item c:/Users/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey.chomp %>" <% end %> if((Get-WURebootStatus -Silent) -eq $true){ @@ -272,13 +270,7 @@ if ($need_reboot){ } else { Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" - log "Enabling sshd service" - sleep 30; Start-Service sshd - Set-Service sshd -startuptype "Automatic" - #Get-WUInstall -AcceptAll -AutoReboot - callMomma "mu_bootstrap" - log $(Get-Content $cygwin_dir/var/log/sshd.log) } Set-Content "c:/mu_userdata_complete" "yup" From d9c3e39a8b4581115cc746fd025ad9094c844909 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 10 Oct 2017 11:15:36 -0400 Subject: [PATCH 16/63] pull knife-windows from our fork until the Chef people take my pull request --- cookbooks/mu-master/recipes/init.rb | 16 ++++++++-------- modules/Gemfile | 2 +- modules/mu/mommacat.rb | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index e40dc6f52..2d5b1adba 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -30,7 +30,7 @@ # but, like, how? CHEF_SERVER_VERSION="12.15.7-1" CHEF_CLIENT_VERSION="12.21.14-1" -KNIFE_WINDOWS="1.8.0" +KNIFE_WINDOWS="1.9.0" MU_BRANCH="master" MU_BASE="/opt/mu" if File.read("/etc/ssh/sshd_config").match(/^AllowUsers\s+([^\s]+)(?:\s|$)/) @@ -350,14 +350,14 @@ # XXX notify mommacat if we're *not* in chef-apply... RUNNING_STANDALONE end - execute "Patch #{rubydir}'s knife-windows for Cygwin SSH bootstraps" do - cwd "#{gemdir}/knife-windows-#{KNIFE_WINDOWS}" - command "patch -p1 < #{MU_BASE}/lib/install/knife-windows-cygwin-#{KNIFE_WINDOWS}.patch" - not_if "grep -i 'locate_config_value(:cygwin)' #{gemdir}/knife-windows-#{KNIFE_WINDOWS}/lib/chef/knife/bootstrap_windows_base.rb" - notifies :restart, "service[chef-server]", :delayed if rubydir == "/opt/opscode/embedded" - only_if { ::Dir.exists?(gemdir) } +# execute "Patch #{rubydir}'s knife-windows for Cygwin SSH bootstraps" do +# cwd "#{gemdir}/knife-windows-#{KNIFE_WINDOWS}" +# command "patch -p1 < #{MU_BASE}/lib/install/knife-windows-cygwin-#{KNIFE_WINDOWS}.patch" +# not_if "grep -i 'locate_config_value(:cygwin)' #{gemdir}/knife-windows-#{KNIFE_WINDOWS}/lib/chef/knife/bootstrap_windows_base.rb" +# notifies :restart, "service[chef-server]", :delayed if rubydir == "/opt/opscode/embedded" +# only_if { ::Dir.exists?(gemdir) } # XXX notify mommacat if we're *not* in chef-apply... RUNNING_STANDALONE - end +# end end } diff --git a/modules/Gemfile b/modules/Gemfile index 9f1e4ae44..ad68d669c 100644 --- a/modules/Gemfile +++ b/modules/Gemfile @@ -20,7 +20,7 @@ gem 'chef-zero', "< 13" gem "aws-sdk-core", "~> 2.6.42" gem 'simple-password-gen' gem 'trollop', "~> 2.1.2" -gem 'knife-windows', '1.9.0' +gem 'knife-windows', '1.9.0', :git => "https://github.com/eGT-Labs/knife-windows.git", :branch => "winrm_cert_auth" gem 'json-schema' gem 'colorize' gem 'color' diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 04ee9ed34..b9d453a44 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1994,7 +1994,7 @@ def signSSLCert(csr_path, sans = []) cert.serial = cur_serial cert.version = 3 cert.not_before = Time.now - cert.not_after = Time.now + 1800000 # 500 days + cert.not_after = Time.now + 180000000 cert.subject = csr.subject cert.public_key = csr.public_key cert.issuer = cacert.subject @@ -2019,6 +2019,7 @@ def signSSLCert(csr_path, sans = []) owner_uid = Etc.getpwnam(MU.mu_user).uid File.chown(owner_uid, nil, "#{certdir}/#{certname}.crt") end + end # Make sure deployment data is synchronized to/from each node in the From 6875467b58606a5f88342b9c8ac2240199a7de9e Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 10 Oct 2017 16:07:25 -0400 Subject: [PATCH 17/63] Cygwin install back in play, but not in the critical path (much) --- cookbooks/mu-tools/recipes/windows-client.rb | 89 ++++++++++++++++++-- modules/mu/cloud.rb | 27 +++++- modules/mu/userdata/windows.erb | 44 ++++------ 3 files changed, 125 insertions(+), 35 deletions(-) diff --git a/cookbooks/mu-tools/recipes/windows-client.rb b/cookbooks/mu-tools/recipes/windows-client.rb index b6f6161ec..9187281fd 100644 --- a/cookbooks/mu-tools/recipes/windows-client.rb +++ b/cookbooks/mu-tools/recipes/windows-client.rb @@ -19,6 +19,88 @@ case node['platform'] when "windows" include_recipe 'chef-vault' + ::Chef::Recipe.send(:include, Chef::Mixin::PowershellOut) + +# remote_file "cygwin-x86_64.exe" do +# path "#{Chef::Config[:file_cache_path]}/cygwin-x86_64.exe" +# source "http://cygwin.com/setup-x86_64.exe" +# XXX guard with a version check +# end + +# XXX keep a local cache of packages... really our own damn mirror + cygwindir = "c:/bin/cygwin" +# pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"] + +# powershell_script "install Cygwin" do +# code <<-EOH +# Start-Process -wait -FilePath "#{Chef::Config[:file_cache_path]}/cygwin-x86_64.exe" -ArgumentList "-q -n -l #{Chef::Config[:file_cache_path]} -L -R c:/bin/cygwin -s http://mirror.cs.vt.edu/pub/cygwin/cygwin/ -P #{pkgs.join(",")}" +# EOH +# not_if { ::File.exists?("#{cygwindir}/Cygwin.bat") } +# end + + # Be prepared to reinit installs that are missing key utilities +# file "#{cygwindir}/etc/setup/installed.db" do +# action :delete +# not_if { ::File.exists?("#{cygwindir}/bin/cygcheck.exe") } +# end + +# pkgs.each { |pkg| +# execute "install Cygwin package: #{pkg}" do +# cwd Chef::Config[:file_cache_path] +# command "#{Chef::Config[:file_cache_path]}/cygwin-x86_64.exe -f -A -q -R #{cygwindir} -s http://mirror.cs.vt.edu/pub/cygwin/cygwin/ -P #{pkg}" +# not_if "#{cygwindir}/bin/cygcheck -c #{pkg}".include? "OK" +# end +# } + + reboot "Cygwin LSA" do + action :nothing + reason "Enabling Cygwin LSA support" + end + + powershell_script "Configuring Cygwin LSA support" do + code <<-EOH + Invoke-Expression '& #{cygwindir}/bin/bash.exe --login -c "echo yes | /bin/cyglsa-config"' + EOH + not_if { + lsa_found = false + if registry_key_exists?("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa") + registry_get_values("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa").each { |val| + if val[:name] == "Authentication Packages" + lsa_found = true if val[:data].grep(/cyglsa64\.dll/) + break + end + } + end + lsa_found + } + notifies :reboot_now, "reboot[Cygwin LSA]", :immediately + end + + windows_vault = chef_vault_item(node['windows_auth_vault'], node['windows_auth_item']) + sshd_user = windows_vault[node['windows_sshd_username_field']] + sshd_password = windows_vault[node['windows_sshd_password_field']] + powershell_script "enable Cygwin sshd" do + code <<-EOH + Invoke-Expression -Debug '& #{cygwindir}/bin/bash.exe --login -c "ssh-host-config -y -c ntsec -w ''#{sshd_password}'' -u #{sshd_user}"' + Invoke-Expression -Debug '& #{cygwindir}/bin/bash.exe --login -c "sed -i.bak ''s/#.*StrictModes.*yes/StrictModes no/'' /etc/sshd_config"' + Invoke-Expression -Debug '& #{cygwindir}/bin/bash.exe --login -c "sed -i.bak ''s/#.*PasswordAuthentication.*yes/PasswordAuthentication no/'' /etc/sshd_config"' + EOH + sensitive true + not_if %Q{Get-Service "sshd"} + end + +# We probably don't have to do these things, but leaving them in a comment just +# in case. +# if((Get-WmiObject win32_computersystem).partofdomain){ +# Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -d > /etc/passwd"' +# Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l -d > /etc/group"' +# } else { +# Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkpasswd -l > /etc/passwd"' +# Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "mkgroup -l > /etc/group"' +# } +# Invoke-Expression -Debug '& $cygwin_dir/bin/bash --login -c "chown $sshd_svc_user /var/empty /var/log/sshd.log /etc/ssh*; chmod 755 /var/empty"' + + include_recipe 'mu-activedirectory' ::Chef::Recipe.send(:include, Chef::Mixin::PowershellOut) @@ -30,11 +112,8 @@ ignore_failure true end - windows_vault = chef_vault_item(node['windows_auth_vault'], node['windows_auth_item']) ec2config_user= windows_vault[node['windows_ec2config_username_field']] ec2config_password = windows_vault[node['windows_ec2config_password_field']] - sshd_user = windows_vault[node['windows_sshd_username_field']] - sshd_password = windows_vault[node['windows_sshd_password_field']] login_dom = "." if in_domain? @@ -96,9 +175,8 @@ username sshd_user service_username ".\\#{sshd_user}" password sshd_password -ignore_failure true end - end# rescue NoMethodError + end# begin resources('service[sshd]') @@ -107,6 +185,7 @@ run_as_user "#{login_dom}\\#{sshd_user}" run_as_password sshd_password action [:enable, :start] + sensitive true end end diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index 8ee68e7d6..26515d379 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -752,6 +752,31 @@ def initialWinRMTasks(shell) end end + # Install Cygwin here, because for some reason it breaks inside Chef + # XXX would love to not do this here + pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"] + admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}" + install_cygwin = %Q{ + If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){ + $WebClient = New-Object System.Net.WebClient + $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe") + Start-Process -wait -FilePath $env:Temp/setup-x86_64.exe -ArgumentList "-q -n -l $env:Temp/cygwin -R c:/bin/cygwin -s http://mirror.cs.vt.edu/pub/cygwin/cygwin/ -P #{pkgs.join(',')}" + } + if(!(Test-Path #{admin_home})){ + New-Item -type directory -path #{admin_home} + } + if(!(Test-Path #{admin_home}/.ssh)){ + New-Item -type directory -path #{admin_home}/.ssh + } + if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){ + New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}" + } + } + resp = shell.run(install_cygwin) + if resp.exitcode != 0 + MU.log "Failed at installing Cygwin", MU::ERR, details: resp + end + set_hostname = true hostname = nil if !@config['active_directory'].nil? @@ -892,10 +917,10 @@ def getWinRMSession(max_retries = 40, retry_interval = 60) MU.log "Calling WinRM on #{@mu_name}", MU::DEBUG, details: opts opts = { endpoint: 'https://'+@mu_name+':5986/wsman', - transport: :ssl, retry_limit: 5, no_ssl_peer_verification: true, # XXX this should not be necessary; we get 'hostname "foo" does not match the server certificate' even when it clearly does match ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem", + transport: :ssl, client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt", client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key" } diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 4964d8c32..6355d424d 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -215,6 +215,21 @@ if ((Get-WmiObject win32_computersystem).partofdomain -ne $true){ } <% end %> +if((Get-WURebootStatus -Silent) -eq $true){ + log "Get-WURebootStatus says to reboot" + $need_reboot = $TRUE +} + +if ($need_reboot){ + log "- REBOOT -" + Restart-Computer -Force + exit +} else { + Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" + + callMomma "mu_bootstrap" +} + $muca = importCert "Mu_CA.pem" "Root" $myname = "<%= $mu.muID %>-<%= $mu.resourceName.upcase %>" @@ -245,37 +260,8 @@ if (!(Get-NetFirewallRule -DisplayName "Allow WinRM" -ErrorAction SilentlyContin New-NetFirewallRule -DisplayName "Allow WinRM" -Direction Inbound -LocalPort 5986 -Protocol TCP -Action Allow } -# XXX stick this installer script somewhere we control -iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) -choco feature enable -n useFipsCompliantChecksums -refreshenv -choco install openssh -y -params "/SSHServerFeature /SSHLogLevel:VERBOSE" -choco upgrade openssh -y -params "/SSHServerFeature /SSHLogLevel:VERBOSE" -Start-Service sshd -Set-Service sshd -startuptype "Automatic" - -<% if $mu.windowsAdminName %> -#New-Item c:/Users/<%= $mu.windowsAdminName %>/.ssh/authorized_keys -type file -force -value "<%= $mu.deploySSHKey.chomp %>" -<% end %> - -if((Get-WURebootStatus -Silent) -eq $true){ - log "Get-WURebootStatus says to reboot" - $need_reboot = $TRUE -} - -if ($need_reboot){ - log "- REBOOT -" - Restart-Computer -Force - exit -} else { - Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" - - callMomma "mu_bootstrap" -} - Set-Content "c:/mu_userdata_complete" "yup" Remove-Item -Recurse $tmp -# XXX Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Undefined
true From f75e0a6a99d69ef15642bfbc9ac4690ab81395ab Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 11 Oct 2017 09:12:04 -0400 Subject: [PATCH 18/63] Chef Server has *problems* --- bin/mu-configure | 4 ++-- cookbooks/mu-master/recipes/firewall-holes.rb | 2 +- cookbooks/mu-master/recipes/init.rb | 2 +- modules/Gemfile.lock | 24 ++++++++++++------- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/bin/mu-configure b/bin/mu-configure index 09f277b41..a3efdf1ed 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -226,8 +226,8 @@ KNIFE_TEMPLATE = "log_level :info log_location STDOUT node_name '<%= chefuser %>' client_key '<%= MU_BASE %>/var/users/<%= user %>/<%= chefuser %>.user.key' -validation_client_name 'mu-validator' -validation_key '<%= MU_BASE %>/var/orgs/<%= user %>/<%= chefuser %>.org.key' +#validation_client_name 'mu-validator' +#validation_key '<%= MU_BASE %>/var/orgs/<%= user %>/<%= chefuser %>.org.key' chef_server_url 'https://<%= MU.mu_public_addr %>:7443/organizations/<%= chefuser %>' chef_server_root 'https://<%= MU.mu_public_addr %>:7443/organizations/<%= chefuser %>' syntax_check_cache_path '<%= HOMEDIR %>/.chef/syntax_check_cache' diff --git a/cookbooks/mu-master/recipes/firewall-holes.rb b/cookbooks/mu-master/recipes/firewall-holes.rb index a0a1d45a6..98739c0b7 100644 --- a/cookbooks/mu-master/recipes/firewall-holes.rb +++ b/cookbooks/mu-master/recipes/firewall-holes.rb @@ -23,7 +23,7 @@ port [2260, 7443, 8443, 9443, 10514, 443, 80, 25] end -local_chef_ports = [4321, 9463, 9583, 16379, 8983, 8000, 9680, 9683, 9090, 5432, 5672] +local_chef_ports = [4321, 9463, 9583, 16379, 8983, 8000, 9680, 9683, 9090, 5432, 5672, 9999, 15672, 25672, 81, 111, 4369, 9463] firewall_rule "Chef Server ports on 127.0.0.1" do port local_chef_ports source "127.0.0.1/32" diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index 2d5b1adba..7116ae056 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -28,7 +28,7 @@ # XXX We want to be able to override these things when invoked from chef-apply, # but, like, how? -CHEF_SERVER_VERSION="12.15.7-1" +CHEF_SERVER_VERSION="12.16.14-1" CHEF_CLIENT_VERSION="12.21.14-1" KNIFE_WINDOWS="1.9.0" MU_BRANCH="master" diff --git a/modules/Gemfile.lock b/modules/Gemfile.lock index d194e438c..2eeb37c25 100644 --- a/modules/Gemfile.lock +++ b/modules/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/eGT-Labs/knife-windows.git + revision: 824063601c59a81702e31f6a69223e68e96e4e06 + branch: winrm_cert_auth + specs: + knife-windows (1.9.0) + winrm (~> 2.1) + winrm-elevated (~> 1.0) + GEM remote: https://rubygems.org/ specs: @@ -41,11 +50,11 @@ GEM celluloid-io (0.16.2) celluloid (>= 0.16.0) nio4r (>= 1.1.0) - chef (12.20.3) + chef (12.21.14) addressable bundler (>= 1.10) - chef-config (= 12.20.3) - chef-zero (>= 4.8) + chef-config (= 12.21.14) + chef-zero (>= 4.8, < 13) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) ffi-yajl (~> 2.2) @@ -70,7 +79,7 @@ GEM specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) - chef-config (12.20.3) + chef-config (12.21.14) addressable fuzzyurl mixlib-config (~> 2.0) @@ -109,9 +118,6 @@ GEM json (2.1.0) json-schema (2.8.0) addressable (>= 2.4) - knife-windows (1.9.0) - winrm (~> 2.1) - winrm-elevated (~> 1.0) libyajl2 (1.2.0) little-plugger (1.1.4) logging (2.2.2) @@ -267,13 +273,13 @@ PLATFORMS DEPENDENCIES aws-sdk-core (~> 2.6.42) berkshelf (< 6) - chef (~> 12.20.3) + chef (~> 12.21.14) chef-vault (< 3) chef-zero (< 13) color colorize json-schema - knife-windows (= 1.9.0) + knife-windows (= 1.9.0)! molinillo (< 0.6.0) mysql net-ldap From dd6135cef589f23f9b40da630670a1ee8c42f110 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 11 Oct 2017 09:43:16 -0400 Subject: [PATCH 19/63] split list of localhost firewall holes for Chef server --- cookbooks/mu-master/recipes/firewall-holes.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cookbooks/mu-master/recipes/firewall-holes.rb b/cookbooks/mu-master/recipes/firewall-holes.rb index 98739c0b7..a30c7724c 100644 --- a/cookbooks/mu-master/recipes/firewall-holes.rb +++ b/cookbooks/mu-master/recipes/firewall-holes.rb @@ -23,11 +23,16 @@ port [2260, 7443, 8443, 9443, 10514, 443, 80, 25] end -local_chef_ports = [4321, 9463, 9583, 16379, 8983, 8000, 9680, 9683, 9090, 5432, 5672, 9999, 15672, 25672, 81, 111, 4369, 9463] +local_chef_ports = [4321, 9463, 9583, 16379, 8983, 8000, 9680, 9683, 9090, 5432] firewall_rule "Chef Server ports on 127.0.0.1" do port local_chef_ports source "127.0.0.1/32" end +local_chef_ports_2 = [5672, 9999, 15672, 25672, 81, 111, 4369, 9463] +firewall_rule "Chef Server ports on 127.0.0.1 (2)" do + port local_chef_ports_2 + source "127.0.0.1/32" +end if node.has_key?(:local_ipv4) firewall_rule "Chef Server ports on #{node[:local_ipv4]}" do port local_chef_ports From 626b25a37a20e9f1b54fd119c4fbd5f662c696e6 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 11 Oct 2017 13:35:38 -0400 Subject: [PATCH 20/63] shuffle windows userdata a bit --- modules/Gemfile | 2 +- modules/mu/groomers/chef.rb | 5 ++++- modules/mu/userdata/windows.erb | 19 ++++++++++--------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/modules/Gemfile b/modules/Gemfile index ad68d669c..de7e20973 100644 --- a/modules/Gemfile +++ b/modules/Gemfile @@ -20,7 +20,7 @@ gem 'chef-zero', "< 13" gem "aws-sdk-core", "~> 2.6.42" gem 'simple-password-gen' gem 'trollop', "~> 2.1.2" -gem 'knife-windows', '1.9.0', :git => "https://github.com/eGT-Labs/knife-windows.git", :branch => "winrm_cert_auth" +gem 'knife-windows', :git => "https://github.com/eGT-Labs/knife-windows.git", :branch => "winrm_cert_auth" gem 'json-schema' gem 'colorize' gem 'color' diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index f8b60e261..41cc15134 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -522,7 +522,10 @@ def bootstrap MU.log "#{@server.mu_name}: Knife Bootstrap failed #{e.inspect}, retrying (#{retries} of #{max_retries})", MU::WARN, details: e.backtrace # bad Chef installs are possible culprits of bootstrap failures if !@config['forced_preclean'] - preClean(false) + begin + preClean(false) # it's ok for this to fail + rescue Exception => e + end MU::Groomer::Chef.cleanup(@server.mu_name, nodeonly: true) @config['forced_preclean'] = true end diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 6355d424d..e20eccdb3 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -220,15 +220,6 @@ if((Get-WURebootStatus -Silent) -eq $true){ $need_reboot = $TRUE } -if ($need_reboot){ - log "- REBOOT -" - Restart-Computer -Force - exit -} else { - Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" - - callMomma "mu_bootstrap" -} $muca = importCert "Mu_CA.pem" "Root" @@ -260,6 +251,16 @@ if (!(Get-NetFirewallRule -DisplayName "Allow WinRM" -ErrorAction SilentlyContin New-NetFirewallRule -DisplayName "Allow WinRM" -Direction Inbound -LocalPort 5986 -Protocol TCP -Action Allow } +if ($need_reboot){ + log "- REBOOT -" + Restart-Computer -Force + exit +} else { + Add-Content c:/mu-installer-ran-updates "$(Get-Date -f MM-dd-yyyy_HH:mm:ss)" + + callMomma "mu_bootstrap" +} + Set-Content "c:/mu_userdata_complete" "yup" Remove-Item -Recurse $tmp Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Undefined From 90565df89dfd50da8664ccef56962162e5a9aa9d Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 11 Oct 2017 15:35:02 -0400 Subject: [PATCH 21/63] weird workarounds for bundling a gem from git --- bin/mu-configure | 4 ++-- modules/Gemfile | 2 +- modules/Gemfile.lock | 5 ++--- modules/mu/groomers/chef.rb | 13 ++++++++++++- modules/mu/mommacat.rb | 1 - 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/bin/mu-configure b/bin/mu-configure index a3efdf1ed..09f277b41 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -226,8 +226,8 @@ KNIFE_TEMPLATE = "log_level :info log_location STDOUT node_name '<%= chefuser %>' client_key '<%= MU_BASE %>/var/users/<%= user %>/<%= chefuser %>.user.key' -#validation_client_name 'mu-validator' -#validation_key '<%= MU_BASE %>/var/orgs/<%= user %>/<%= chefuser %>.org.key' +validation_client_name 'mu-validator' +validation_key '<%= MU_BASE %>/var/orgs/<%= user %>/<%= chefuser %>.org.key' chef_server_url 'https://<%= MU.mu_public_addr %>:7443/organizations/<%= chefuser %>' chef_server_root 'https://<%= MU.mu_public_addr %>:7443/organizations/<%= chefuser %>' syntax_check_cache_path '<%= HOMEDIR %>/.chef/syntax_check_cache' diff --git a/modules/Gemfile b/modules/Gemfile index de7e20973..77733ce8e 100644 --- a/modules/Gemfile +++ b/modules/Gemfile @@ -20,7 +20,6 @@ gem 'chef-zero', "< 13" gem "aws-sdk-core", "~> 2.6.42" gem 'simple-password-gen' gem 'trollop', "~> 2.1.2" -gem 'knife-windows', :git => "https://github.com/eGT-Labs/knife-windows.git", :branch => "winrm_cert_auth" gem 'json-schema' gem 'colorize' gem 'color' @@ -38,3 +37,4 @@ gem 'molinillo', '< 0.6.0' gem 'solve', '>= 3.1.1' gem 'net-ldap' gem 'winrm', '= 2.2.3' +gem 'knife-windows', :git => "https://github.com/eGT-Labs/knife-windows.git" diff --git a/modules/Gemfile.lock b/modules/Gemfile.lock index 2eeb37c25..163872980 100644 --- a/modules/Gemfile.lock +++ b/modules/Gemfile.lock @@ -1,7 +1,6 @@ GIT remote: https://github.com/eGT-Labs/knife-windows.git - revision: 824063601c59a81702e31f6a69223e68e96e4e06 - branch: winrm_cert_auth + revision: fa27b7746062d56ba08f3c7834bd1089aa23b8ee specs: knife-windows (1.9.0) winrm (~> 2.1) @@ -279,7 +278,7 @@ DEPENDENCIES color colorize json-schema - knife-windows (= 1.9.0)! + knife-windows! molinillo (< 0.6.0) mysql net-ldap diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index 41cc15134..44c834006 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -55,7 +55,6 @@ def self.loadChefLib(user = MU.chef_user, env = "dev", mu_user = MU.mu_user) require 'chef/knife' require 'chef/knife/ssh' require 'chef/knife/bootstrap' - require 'chef/knife/bootstrap_windows_ssh' require 'chef/knife/node_delete' require 'chef/knife/client_delete' require 'chef/knife/data_bag_delete' @@ -64,6 +63,18 @@ def self.loadChefLib(user = MU.chef_user, env = "dev", mu_user = MU.mu_user) require 'chef/file_access_control/unix' require 'chef-vault' require 'chef-vault/item' + # XXX kludge to get at knife-windows when it's installed from + # a git repo and bundler sticks it somewhere in a corner + $LOAD_PATH.each { |path| + if path.match(/\/gems\/aws\-sdk\-core\-\d+\.\d+\.\d+\/lib$/) + addpath = path.sub(/\/gems\/aws\-sdk\-core\-\d+\.\d+\.\d+\/lib$/, "")+"/bundler/gems" + Dir.glob(addpath+"/knife-windows-*").each { |version| + $LOAD_PATH << version+"/lib" + } + end + } + require 'chef/knife/bootstrap_windows_winrm' + require 'chef/knife/bootstrap_windows_ssh' ::Chef::Config[:chef_server_url] = "https://#{MU.mu_public_addr}:7443/organizations/#{user}" if File.exists?("#{Etc.getpwnam(mu_user).dir}/.chef/knife.rb") MU.log "Loading Chef configuration from #{Etc.getpwnam(mu_user).dir}/.chef/knife.rb", MU::DEBUG diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index b9d453a44..936eac2d2 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -21,7 +21,6 @@ autoload :Chef, 'chef' gem "chef-vault" autoload :ChefVault, 'chef-vault' -gem "knife-windows" require 'timeout' module MU From 88f62d351e17e79e9d20a965841528bfc3b1bc8c Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 12 Oct 2017 11:05:20 -0400 Subject: [PATCH 22/63] mu-node-manage: gracefully try WinRM to do things, then fail to ssh if it doesn't work --- bin/mu-node-manage | 29 ++++++++++++++++++++++++----- modules/Gemfile | 2 +- modules/Gemfile.lock | 3 ++- modules/mu/cloud.rb | 6 ++++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/bin/mu-node-manage b/bin/mu-node-manage index 12e89e34d..0aeac41f9 100755 --- a/bin/mu-node-manage +++ b/bin/mu-node-manage @@ -215,12 +215,30 @@ def runCommand(deploys = MU::MommaCat.listDeploys, nodes = [], cmd = "( chef-cli begin MU.log "Running #{cmd} on #{nodename} (##{count})" if !print_output serverobj = mommacat.findLitterMate(type: "server", mu_name: nodename) + output = nil + exitcode = -1 if serverobj.windows? - serverobj.groomer.run(max_retries: 0, output: print_output) + resp = nil + begin + shell = serverobj.getWinRMSession(0, timeout: 10, winrm_retries: 1) + resp = shell.run("ipconfig /all") + pp resp + rescue MU::MuError => e + end + if !resp or resp.exitcode != 0 + MU.log "WinRM connection to #{nodename} failed, trying SSH", MU::WARN + else + output = resp.stdout + exitcode = resp.exitcode + end + end + if !output + # maybe this should use getSSHSession, for the sake of symmetry + output = `ssh -q #{nodename} "#{cmd}" 2>&1 < /dev/null` + exitcode = $?.exitstatus end - output=`ssh -q #{nodename} "#{cmd}" 2>&1 < /dev/null` - if !$?.success? - if server['conf']["platform"] == "windows" and output.match(/NoMethodError: unknown property or method: `ConnectServer'/) + if exitcode != 0 + if serverobj.windows? and output.match(/NoMethodError: unknown property or method: `ConnectServer'/) MU.log "#{nodename} encountered transient Windows/Chef ConnectServer error, retrying", MU::WARN elsif print_output done = true @@ -230,8 +248,9 @@ def runCommand(deploys = MU::MommaCat.listDeploys, nodes = [], cmd = "( chef-cli done = true MU.log "#{nodename} did not exit cleanly", MU::WARN, details: output.slice(-2000, 2000) end - exit $?.exitstatus if done + exit exitcode if done else + MU.log "#{nodename} complete" done = true end end until done diff --git a/modules/Gemfile b/modules/Gemfile index 77733ce8e..beba83a20 100644 --- a/modules/Gemfile +++ b/modules/Gemfile @@ -37,4 +37,4 @@ gem 'molinillo', '< 0.6.0' gem 'solve', '>= 3.1.1' gem 'net-ldap' gem 'winrm', '= 2.2.3' -gem 'knife-windows', :git => "https://github.com/eGT-Labs/knife-windows.git" +gem 'knife-windows', :git => "https://github.com/eGT-Labs/knife-windows.git", :branch => "winrm_cert_auth" diff --git a/modules/Gemfile.lock b/modules/Gemfile.lock index 163872980..a05a10c3f 100644 --- a/modules/Gemfile.lock +++ b/modules/Gemfile.lock @@ -1,6 +1,7 @@ GIT remote: https://github.com/eGT-Labs/knife-windows.git - revision: fa27b7746062d56ba08f3c7834bd1089aa23b8ee + revision: 824063601c59a81702e31f6a69223e68e96e4e06 + branch: winrm_cert_auth specs: knife-windows (1.9.0) winrm (~> 2.1) diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index 26515d379..a159c941f 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -890,8 +890,9 @@ def initialSSHTasks(ssh) end - def getWinRMSession(max_retries = 40, retry_interval = 60) + def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 60, winrm_retries: 5) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig + @mu_name ||= @config['mu_name'] conn = nil shell = nil @@ -917,10 +918,11 @@ def getWinRMSession(max_retries = 40, retry_interval = 60) MU.log "Calling WinRM on #{@mu_name}", MU::DEBUG, details: opts opts = { endpoint: 'https://'+@mu_name+':5986/wsman', - retry_limit: 5, + retry_limit: winrm_retries, no_ssl_peer_verification: true, # XXX this should not be necessary; we get 'hostname "foo" does not match the server certificate' even when it clearly does match ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem", transport: :ssl, + operation_timeout: timeout, client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt", client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key" } From 23896c43e7303194b5656c42095c0aac015fca5d Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 17 Oct 2017 17:37:58 -0400 Subject: [PATCH 23/63] mu-master cookbook fixlets --- Berksfile | 1 + Berksfile.lock | 3 ++- cookbooks/mu-master/attributes/default.rb | 1 + cookbooks/mu-master/recipes/init.rb | 4 ++-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Berksfile b/Berksfile index fb23e425e..de4cfda6d 100644 --- a/Berksfile +++ b/Berksfile @@ -43,3 +43,4 @@ cookbook 's3fs', path: "#{cookbookPath}/s3fs" cookbook 'zipfile', '~> 0.1.0' #cookbook 'hashicorp-vault', '~> 2.5.0', git: "https://github.com/johnbellone/vault-cookbook" cookbook 'demo', path: "#{siteCookbookPath}/demo" +cookbook 'windows', '= 3.1.2' diff --git a/Berksfile.lock b/Berksfile.lock index 8841e2b7f..0775da2e5 100644 --- a/Berksfile.lock +++ b/Berksfile.lock @@ -45,6 +45,7 @@ DEPENDENCIES runit (~> 1.7) s3fs path: cookbooks/s3fs + windows (= 3.1.2) zipfile (~> 0.1.0) GRAPH @@ -297,7 +298,7 @@ GRAPH consul-cluster (~> 2.0) hashicorp-vault (~> 2.1) ssl_certificate (~> 1.11) - windows (3.1.3) + windows (3.1.2) ohai (>= 4.0.0) yum (3.13.0) yum-epel (2.1.2) diff --git a/cookbooks/mu-master/attributes/default.rb b/cookbooks/mu-master/attributes/default.rb index ae3a15c32..f358c8672 100644 --- a/cookbooks/mu-master/attributes/default.rb +++ b/cookbooks/mu-master/attributes/default.rb @@ -92,3 +92,4 @@ default['application_attributes']['sshd_allow_groups'] = "#{ssh_user} mu-users" default['application_attributes']['sshd_allow_password_auth'] = true default['update_nagios_only'] = false +default['apache']['listen'] = [80, 443, 8443] diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index 7116ae056..1894ef24a 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -306,8 +306,8 @@ bundler_path = gembin.sub(/gem$/, "bundle") bash "fix #{rubydir} gem permissions" do code <<-EOH - find -P #{rubydir}/lib/ruby/gems/?.?.?/gems/ -type d -exec chmod go+rx {} \\; - find -P #{rubydir}/lib/ruby/gems/?.?.?/gems/ -type f -exec chmod go+r {} \\; + find -P #{rubydir}/lib/ruby/gems/?.?.?/ #{rubydir}/lib/ruby/site_ruby/ -type d -exec chmod go+rx {} \\; + find -P #{rubydir}/lib/ruby/gems/?.?.?/ #{rubydir}/lib/ruby/site_ruby/ -type f -exec chmod go+r {} \\; find -P #{rubydir}/bin -type f -exec chmod go+rx {} \\; EOH action :nothing From 7a20f5af2576192a0a37a060d17506022c3f0b1a Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 18 Oct 2017 15:59:39 -0400 Subject: [PATCH 24/63] further Windows bootstrap hokey-pokey --- Berksfile | 2 +- Berksfile.lock | 6 +- bin/mu-node-manage | 3 +- modules/mommacat.ru | 1 + modules/mu/cloud.rb | 214 +++++++++++++++++++++----------- modules/mu/clouds/aws/server.rb | 11 +- modules/mu/groomers/chef.rb | 177 ++++++++++++++------------ modules/mu/userdata/windows.erb | 7 +- 8 files changed, 258 insertions(+), 163 deletions(-) diff --git a/Berksfile b/Berksfile index de4cfda6d..67f73b91b 100644 --- a/Berksfile +++ b/Berksfile @@ -43,4 +43,4 @@ cookbook 's3fs', path: "#{cookbookPath}/s3fs" cookbook 'zipfile', '~> 0.1.0' #cookbook 'hashicorp-vault', '~> 2.5.0', git: "https://github.com/johnbellone/vault-cookbook" cookbook 'demo', path: "#{siteCookbookPath}/demo" -cookbook 'windows', '= 3.1.2' +cookbook 'windows', '= 3.2.0' diff --git a/Berksfile.lock b/Berksfile.lock index 0775da2e5..77dbbe5e3 100644 --- a/Berksfile.lock +++ b/Berksfile.lock @@ -45,7 +45,7 @@ DEPENDENCIES runit (~> 1.7) s3fs path: cookbooks/s3fs - windows (= 3.1.2) + windows (= 3.2.0) zipfile (~> 0.1.0) GRAPH @@ -298,11 +298,11 @@ GRAPH consul-cluster (~> 2.0) hashicorp-vault (~> 2.1) ssl_certificate (~> 1.11) - windows (3.1.2) + windows (3.2.0) ohai (>= 4.0.0) yum (3.13.0) yum-epel (2.1.2) compat_resource (>= 12.16.3) - zap (0.15.1) + zap (1.0.2) zipfile (0.1.0) zypper (0.4.0) diff --git a/bin/mu-node-manage b/bin/mu-node-manage index 0aeac41f9..c0317b583 100755 --- a/bin/mu-node-manage +++ b/bin/mu-node-manage @@ -221,8 +221,7 @@ def runCommand(deploys = MU::MommaCat.listDeploys, nodes = [], cmd = "( chef-cli resp = nil begin shell = serverobj.getWinRMSession(0, timeout: 10, winrm_retries: 1) - resp = shell.run("ipconfig /all") - pp resp + resp = shell.run(cmd) rescue MU::MuError => e end if !resp or resp.exitcode != 0 diff --git a/modules/mommacat.ru b/modules/mommacat.ru index 8bac344f5..c68ec569f 100644 --- a/modules/mommacat.ru +++ b/modules/mommacat.ru @@ -374,6 +374,7 @@ app = proc do |env| if !req["mu_windows_admin_creds"].nil? returnval[2] = [kittenpile.retrieveWindowsAdminCreds(instance).join(";")] + MU.log returnval[2].sub(/;.*/, ";*********"), MU::NOTICE elsif !req["mu_ssl_sign"].nil? kittenpile.signSSLCert(req["mu_ssl_sign"], req["mu_ssl_sans"].split(/,/)) kittenpile.signSSLCert(req["mu_ssl_sign"], req["mu_ssl_sans"].split(/,/)) diff --git a/modules/mu/cloud.rb b/modules/mu/cloud.rb index a159c941f..b5a3ce5e8 100644 --- a/modules/mu/cloud.rb +++ b/modules/mu/cloud.rb @@ -732,71 +732,148 @@ def windows? false end - # Basic setup tasks performed on a new node during its first WinRM - # connection. Most of this is terrible Windows glue. - # @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node. - def initialWinRMTasks(shell) - if !@config['use_cloud_provider_windows_password'] - pw = @groomer.getSecret( - vault: @config['mu_name'], - item: "windows_credentials", - field: "password" - ) - win_check_for_pw = %Q{Add-Type -AssemblyName System.DirectoryServices.AccountManagement; $Creds = (New-Object System.Management.Automation.PSCredential("#{@config["windows_admin_username"]}", (ConvertTo-SecureString "#{pw}" -AsPlainText -Force)));$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine); $DS.ValidateCredentials($Creds.GetNetworkCredential().UserName, $Creds.GetNetworkCredential().password); echo $Result} - resp = shell.run(win_check_for_pw) - if resp.stdout.chomp != "True" - win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))} - resp = shell.run(win_set_pw) - puts resp.stdout - MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout + # Gracefully message and attempt to accommodate the common transient errors peculiar to Windows nodes + # @param e [Exception]: The exception that we're handling + # @param retries [Integer]: The current number of retries, which we'll increment and pass back to the caller + # @param rebootable_fails [Integer]: The current number of reboot-worthy failures, which we'll increment and pass back to the caller + # @param max_retries [Integer]: Maximum number of retries to attempt; we'll raise an exception if this is exceeded + # @param reboot_on_problems [Boolean]: Whether we should try to reboot a "stuck" machine + # @param retry_interval [Integer]: How many seconds to wait before returning for another attempt + def handleWindowsFail(e, retries, rebootable_fails, max_retries: 30, reboot_on_problems: false, retry_interval: 45) + msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})", MU::WARN + if e.message.match(/execution expired/) and reboot_on_problems + if rebootable_fails >= 5 + MU.log "#{@mu_name} still misbehaving, forcing Stop and Start from API", MU::WARN + reboot(true) # vicious API stop/start + sleep retry_interval + rebootable_fails = 0 + else + if rebootable_fails >= 3 + MU.log "#{@mu_name} misbehaving, attempting to reboot from API", MU::WARN + reboot # graceful API restart + sleep retry_interval + end + rebootable_fails = rebootable_fails + 1 + end + end + if retries < max_retries + if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0) + MU.log msg, MU::NOTICE + elsif retries/max_retries > 0.5 + MU.log msg, MU::WARN, details: e.inspect end + sleep retry_interval + retries = retries + 1 + else + raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace end + return [retries, rebootable_fails] + end - # Install Cygwin here, because for some reason it breaks inside Chef - # XXX would love to not do this here - pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"] - admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}" - install_cygwin = %Q{ - If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){ - $WebClient = New-Object System.Net.WebClient - $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe") - Start-Process -wait -FilePath $env:Temp/setup-x86_64.exe -ArgumentList "-q -n -l $env:Temp/cygwin -R c:/bin/cygwin -s http://mirror.cs.vt.edu/pub/cygwin/cygwin/ -P #{pkgs.join(',')}" - } - if(!(Test-Path #{admin_home})){ - New-Item -type directory -path #{admin_home} + def windowsRebootPending?(shell = nil) + if shell.nil? + shell = getWinRMSession(1, 30) + end +# if (Get-Item "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired" -EA Ignore) { exit 1 } + cmd = %Q{ + if (Get-ChildItem "HKLM:/Software/Microsoft/Windows/CurrentVersion/Component Based Servicing/RebootPending" -EA Ignore) { + echo "Component Based Servicing/RebootPending is true" + exit 1 } - if(!(Test-Path #{admin_home}/.ssh)){ - New-Item -type directory -path #{admin_home}/.ssh + if (Get-ItemProperty "HKLM:/SYSTEM/CurrentControlSet/Control/Session Manager" -Name PendingFileRenameOperations -EA Ignore) { + echo "Control/Session Manager/PendingFileRenameOperations is true" + exit 1 } - if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){ - New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}" + try { + $util = [wmiclass]"\\\\.\\root\\ccm\\clientsdk:CCM_ClientUtilities" + $status = $util.DetermineIfRebootPending() + if(($status -ne $null) -and $status.RebootPending){ + echo "WMI says RebootPending is true" + exit 1 + } + } catch { + exit 0 } + exit 0 } - resp = shell.run(install_cygwin) - if resp.exitcode != 0 - MU.log "Failed at installing Cygwin", MU::ERR, details: resp - end + resp = shell.run(cmd) + returnval = resp.exitcode == 0 ? false : true + shell.close + returnval + end - set_hostname = true - hostname = nil - if !@config['active_directory'].nil? - if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname'] - hostname = @config['active_directory']['domain_controller_hostname'] - @mu_windows_name = hostname - set_hostname = true + # Basic setup tasks performed on a new node during its first WinRM + # connection. Most of this is terrible Windows glue. + # @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node. + def initialWinRMTasks(shell) + retries = 0 + rebootable_fails = 0 + begin + if !@config['use_cloud_provider_windows_password'] + pw = @groomer.getSecret( + vault: @config['mu_name'], + item: "windows_credentials", + field: "password" + ) + win_check_for_pw = %Q{Add-Type -AssemblyName System.DirectoryServices.AccountManagement; $Creds = (New-Object System.Management.Automation.PSCredential("#{@config["windows_admin_username"]}", (ConvertTo-SecureString "#{pw}" -AsPlainText -Force)));$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine); $DS.ValidateCredentials($Creds.GetNetworkCredential().UserName, $Creds.GetNetworkCredential().password); echo $Result} + resp = shell.run(win_check_for_pw) + if resp.stdout.chomp != "True" + win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))} + resp = shell.run(win_set_pw) + puts resp.stdout + MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout + end + end + + # Install Cygwin here, because for some reason it breaks inside Chef + # XXX would love to not do this here + pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"] + admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}" + install_cygwin = %Q{ + If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){ + $WebClient = New-Object System.Net.WebClient + $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe") + Start-Process -wait -FilePath $env:Temp/setup-x86_64.exe -ArgumentList "-q -n -l $env:Temp/cygwin -R c:/bin/cygwin -s http://mirror.cs.vt.edu/pub/cygwin/cygwin/ -P #{pkgs.join(',')}" + } + if(!(Test-Path #{admin_home})){ + New-Item -type directory -path #{admin_home} + } + if(!(Test-Path #{admin_home}/.ssh)){ + New-Item -type directory -path #{admin_home}/.ssh + } + if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){ + New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}" + } + } + resp = shell.run(install_cygwin) + if resp.exitcode != 0 + MU.log "Failed at installing Cygwin", MU::ERR, details: resp + end + + set_hostname = true + hostname = nil + if !@config['active_directory'].nil? + if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname'] + hostname = @config['active_directory']['domain_controller_hostname'] + @mu_windows_name = hostname + set_hostname = true + else + # Do we have an AD specific hostname? + hostname = @mu_windows_name + set_hostname = true + end else - # Do we have an AD specific hostname? hostname = @mu_windows_name - set_hostname = true end - else - hostname = @mu_windows_name - end - resp = shell.run(%Q{hostname}) + resp = shell.run(%Q{hostname}) - if resp.stdout.chomp != hostname - resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force}) - MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout + if resp.stdout.chomp != hostname + resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force}) + MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout + end + rescue WinRM::WinRMError => e + retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: 10, reboot_on_problems: true, retry_interval: 30) + retry end end @@ -890,7 +967,13 @@ def initialSSHTasks(ssh) end - def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 60, winrm_retries: 5) + # Get a privileged Powershell session on the server in question, using SSL-encrypted WinRM with certificate authentication. + # @param max_retries [Integer]: + # @param retry_interval [Integer]: + # @param timeout [Integer]: + # @param winrm_retries [Integer]: + # @param reboot_on_problems [Boolean]: + def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 30, winrm_retries: 5, reboot_on_problems: false) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig @mu_name ||= @config['mu_name'] @@ -909,11 +992,11 @@ def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 60, winrm_re } ensure # Reraise something useful -# raise MU::Groomer::RunError, "Ugly stupid WinRM exception hack" end } retries = 0 + rebootable_fails = 0 begin MU.log "Calling WinRM on #{@mu_name}", MU::DEBUG, details: opts opts = { @@ -929,21 +1012,10 @@ def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 60, winrm_re conn = WinRM::Connection.new(opts) MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn shell = conn.shell(:powershell) - shell.run('ipconfig') # verify that we can something - rescue Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e - msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})", MU::WARN - if retries < max_retries - if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0) - MU.log msg, MU::NOTICE - elsif retries/max_retries > 0.5 - MU.log msg, MU::WARN, details: e.inspect - end - sleep retry_interval - retries = retries + 1 - retry - else - raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace - end + shell.run('ipconfig') # verify that we can do something + rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e + retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: max_retries, reboot_on_problems: reboot_on_problems, retry_interval: retry_interval) + retry ensure MU::MommaCat.removeInstanceFromEtcHosts(@mu_name) end @@ -959,7 +1031,7 @@ def getSSHSession(max_retries = 12, retry_interval = 30) nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig session = nil retries = 0 -pp caller + # XXX catch a weird bug in Net::SSH where its exceptions circumvent # our regular call stack and we can't catch them. Thread.handle_interrupt(Net::SSH::Disconnect => :never) { diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index 19d5435ec..a86a1862f 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -1019,8 +1019,9 @@ def postBoot(instance_id = nil) if windows? # kick off certificate generation early; WinRM will need it cert, key = @deploy.nodeSSLCerts(self) - session = getWinRMSession(50, 60) + session = getWinRMSession(50, 60, reboot_on_problems: true) initialWinRMTasks(session) + session.close # XXX account for machines behind bastion hosts that we can't tunnel through; # maybe then it's ok to fall back to sshd? else @@ -1332,9 +1333,11 @@ def groom @groomer.saveDeployData begin - @groomer.run(purpose: "Full Initial Run", max_retries: 15) - rescue MU::Groomer::RunError - MU.log "Proceeding after failed initial Groomer run, but #{node} may not behave as expected!", MU::WARN + @groomer.run(purpose: "Full Initial Run", max_retries: 15, reboot_first_fail: windows?) + rescue MU::Groomer::RunError => e + MU.log "Proceeding after failed initial Groomer run, but #{node} may not behave as expected!", MU::WARN, details: e.message + rescue Exception => e + MU.log "Caught #{e.inspect} on #{node} in an unexpected place (after @groomer.run on Full Initial Run)", MU::ERR end if !@config['create_image'].nil? and !@config['image_created'] diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index 44c834006..5fb36b175 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -253,7 +253,7 @@ def deleteSecret(vault: nil) # @param max_retries [Integer]: The maximum number of attempts at a successful run to make before giving up. # @param output [Boolean]: Display Chef's regular (non-error) output to the console # @param override_runlist [String]: Use the specified run list instead of the node's configured list - def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, override_runlist: nil) + def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, override_runlist: nil, reboot_first_fail: false) self.class.loadChefLib if update_runlist and !@config['run_list'].nil? knifeAddToRunList(multiple: @config['run_list']) @@ -294,30 +294,30 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, } else winrm = @server.getWinRMSession(max_retries) -# upgrade_cmd = try_upgrade ? "powershell \". { Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; Install-Project -version:#{MU.chefVersion} -download_directory:$HOME \" &&" : "" -# cmd = "#{upgrade_cmd} $HOME/chef-client --color || echo #{error_signal}" + if @server.windows? and @server.windowsRebootPending?(winrm) + raise MU::Groomer::RunError, "#{@server.mu_name} has a pending reboot" + end if try_upgrade - pp winrm.run("Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; Install-Project -version:#{MU.chefVersion} -download_directory:$HOME") - else - output = [] - cmd = "c:/opscode/chef/bin/chef-client.bat --color" - if override_runlist - cmd = cmd + " -o '#{override_runlist}'" - end - resp = winrm.run(cmd) do |stdout, stderr| - if stdout - print stdout if output - output << stdout - end - if stderr - MU.log stderr, MU::ERR - output << stderr - end + pp winrm.run("Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 | Invoke-Expression; Install-Project -version:#{MU.chefVersion} -download_directory:$HOME") + end + output = [] + cmd = "c:/opscode/chef/bin/chef-client.bat --color" + if override_runlist + cmd = cmd + " -o '#{override_runlist}'" + end + resp = winrm.run(cmd) do |stdout, stderr| + if stdout + print stdout if output + output << stdout end - if resp.exitcode != 0 - raise MU::Groomer::RunError, output.slice(output.length-50, output.length).join("") + if stderr + MU.log stderr, MU::ERR + output << stderr end end + if resp.exitcode != 0 + raise MU::Groomer::RunError, output.slice(output.length-50, output.length).join("") + end end rescue RuntimeError, SystemCallError, Timeout::Error, SocketError, Errno::ECONNRESET, IOError, Net::SSH::Exception, MU::Groomer::RunError, WinRM::WinRMError => e begin @@ -337,6 +337,16 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, else try_upgrade = false end + if reboot_first_fail and !e.is_a?(WinRM::WinRMError) + try_upgrade = true + begin + preClean(true) # drop any Chef install that's not ours + @server.reboot # try gently rebooting the thing + rescue Exception => e # it's ok to fail here (and to ignore failure) +MU.log "preclean err #{e.inspect}", MU::ERR + end + reboot_first_fail = false + end if retries < max_retries retries += 1 @@ -346,6 +356,9 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, else raise MU::Groomer::RunError, "#{@server.mu_name}: Chef run '#{purpose}' failed #{max_retries} times, last error was: #{e.message}" end + rescue Exception => e + raise MU::Groomer::RunError, "Caught unexpected #{e.inspect} on #{@server.mu_name} in @groomer.run" + end saveDeployData @@ -402,7 +415,6 @@ def preClean(leave_ours = false) ssh.close else - # XXX this hasn't been tested remove_cmd = %Q{ $uninstall_string = (Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | Where-Object {$_.DisplayName -like "chef client*"}).UninstallString if($uninstall_string){ @@ -411,6 +423,10 @@ def preClean(leave_ours = false) start-process "msiexec.exe" -arg "/X $_ /qn" -Wait } } + Remove-Item c:/chef/ -Force -Recurse -ErrorAction Continue + Remove-Item c:/opscode/ -Force -Recurse -ErrorAction Continue + Remove-Item C:/Users/ADMINI~1/AppData/Local/Temp/bootstrap*.bat -Force -Recurse -ErrorAction Continue + Remove-Item C:/Users/ADMINI~1/AppData/Local/Temp/chef-* -Force -Recurse -ErrorAction Continue } shell = @server.getWinRMSession(15) removechef = true @@ -425,7 +441,7 @@ def preClean(leave_ours = false) # remove_cmd = %Q{$my_chef = (Get-ItemProperty $location | Where-Object {$_.DisplayName -like "chef client*"}).DisplayName if removechef MU.log "Expunging pre-existing Chef install on #{@server.mu_name}", MU::NOTICE, details: remove_cmd - pp shell.run(remove_cmd) +# pp shell.run(remove_cmd) end end end @@ -465,62 +481,56 @@ def bootstrap @server.windows? ? max_retries = 25 : max_retries = 10 @server.windows? ? timeout = 720 : timeout = 300 - if !@server.windows? - kb = ::Chef::Knife::Bootstrap.new([canonical_addr]) - kb.config[:use_sudo] = true - kb.name_args = "#{canonical_addr}" - kb.config[:distro] = 'chef-full' - kb.config[:ssh_user] = ssh_user - kb.config[:forward_agent] = ssh_user - kb.config[:identity_file] = "#{Etc.getpwuid(Process.uid).dir}/.ssh/#{ssh_key_name}" - else - kb = ::Chef::Knife::BootstrapWindowsWinrm.new([@server.mu_name]) - kb.name_args = [@server.mu_name] - kb.config[:manual] = true - kb.config[:winrm_transport] = :ssl - kb.config[:host] = @server.mu_name - kb.config[:winrm_port] = 5986 - kb.config[:session_timeout] = timeout - kb.config[:operation_timeout] = timeout - kb.config[:winrm_authentication_protocol] = :cert - kb.config[:winrm_client_cert] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.crt" - kb.config[:winrm_client_key] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.key" -# kb.config[:ca_trust_file] = "#{MU.mySSLDir}/Mu_CA.pem" - # XXX ca_trust_file doesn't work for some reason, so we have to set the below for now - kb.config[:winrm_ssl_verify_mode] = :verify_none - kb.config[:msi_url] = "https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=#{MU.chefVersion}" - end - - # XXX this seems to break Knife Bootstrap - # if vault_access.size > 0 - # v = {} - # vault_access.each { |vault| - # v[vault['vault']] = [] if v[vault['vault']].nil? - # v[vault['vault']] << vault['item'] - # } - # kb.config[:bootstrap_vault_json] = JSON.generate(v) - # end - - kb.config[:json_attribs] = JSON.generate(json_attribs) if json_attribs.size > 1 - kb.config[:run_list] = run_list - kb.config[:chef_node_name] = @server.mu_name - kb.config[:bootstrap_version] = MU.chefVersion - # XXX key off of MU verbosity level - kb.config[:log_level] = :debug - # kb.config[:ssh_gateway] = "#{nat_ssh_user}@#{nat_ssh_host}" if !nat_ssh_host.nil? # Breaking bootsrap - retries = 0 - MU.log "Knife Bootstrap settings for #{@server.mu_name} (#{canonical_addr}), timeout set to #{timeout.to_s}", MU::NOTICE, details: kb.config begin -# Thread.handle_interrupt(Timeout::Error => :never) { -# begin -# Thread.handle_interrupt(Timeout::Error => :immediate) { -# MU.log "Caught a Timeout::Error in #{Thread.current.inspect}", MU::NOTICE, details: Thread.current.backtrace -# } -# ensure -# raise MU::Cloud::BootstrapTempFail -# end -# } + if !@server.windows? + kb = ::Chef::Knife::Bootstrap.new([canonical_addr]) + kb.config[:use_sudo] = true + kb.name_args = "#{canonical_addr}" + kb.config[:distro] = 'chef-full' + kb.config[:ssh_user] = ssh_user + kb.config[:forward_agent] = ssh_user + kb.config[:identity_file] = "#{Etc.getpwuid(Process.uid).dir}/.ssh/#{ssh_key_name}" + else + kb = ::Chef::Knife::BootstrapWindowsWinrm.new([@server.mu_name]) + kb.name_args = [@server.mu_name] + kb.config[:manual] = true + kb.config[:winrm_transport] = :ssl + kb.config[:host] = @server.mu_name + kb.config[:winrm_port] = 5986 + kb.config[:session_timeout] = timeout + kb.config[:operation_timeout] = timeout + kb.config[:winrm_authentication_protocol] = :cert + kb.config[:winrm_client_cert] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.crt" + kb.config[:winrm_client_key] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.key" +# kb.config[:ca_trust_file] = "#{MU.mySSLDir}/Mu_CA.pem" + # XXX ca_trust_file doesn't work for some reason, so we have to set the below for now + kb.config[:winrm_ssl_verify_mode] = :verify_none + kb.config[:msi_url] = "https://www.chef.io/chef/download?p=windows&pv=2012&m=x86_64&v=#{MU.chefVersion}" + end + + # XXX this seems to break Knife Bootstrap + # if vault_access.size > 0 + # v = {} + # vault_access.each { |vault| + # v[vault['vault']] = [] if v[vault['vault']].nil? + # v[vault['vault']] << vault['item'] + # } + # kb.config[:bootstrap_vault_json] = JSON.generate(v) + # end + + kb.config[:json_attribs] = JSON.generate(json_attribs) if json_attribs.size > 1 + kb.config[:run_list] = run_list + kb.config[:chef_node_name] = @server.mu_name + kb.config[:bootstrap_version] = MU.chefVersion + # XXX key off of MU verbosity level + kb.config[:log_level] = :debug + # kb.config[:ssh_gateway] = "#{nat_ssh_user}@#{nat_ssh_host}" if !nat_ssh_host.nil? # Breaking bootsrap + + MU.log "Knife Bootstrap settings for #{@server.mu_name} (#{canonical_addr}), timeout set to #{timeout.to_s}", MU::NOTICE, details: kb.config + if @server.windows? and @server.windowsRebootPending? + raise MU::Cloud::BootstrapTempFail, "#{@server.mu_name} has a pending reboot" + end Timeout::timeout(timeout) { require 'chef' kb.run @@ -530,16 +540,23 @@ def bootstrap rescue Net::SSH::Disconnect, SystemCallError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError, SocketError, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, IOError, Net::HTTPServerException, SystemExit, Errno::ECONNREFUSED, Errno::EPIPE, WinRM::WinRMError, HTTPClient::ConnectTimeoutError, RuntimeError, MU::Cloud::BootstrapTempFail => e if retries < max_retries retries += 1 - MU.log "#{@server.mu_name}: Knife Bootstrap failed #{e.inspect}, retrying (#{retries} of #{max_retries})", MU::WARN, details: e.backtrace - # bad Chef installs are possible culprits of bootstrap failures - if !@config['forced_preclean'] + # Bad Chef installs are possible culprits of bootstrap failures, so + # try scrubbing them when that happens. + # On Windows, even a fresh install comes up screwy disturbingly + # often, so we let it start over from scratch if needed. Except for + # the first attempt, which usually fails due to WinRM funk. + if !e.is_a?(MU::Cloud::BootstrapTempFail) and + !(e.is_a?(WinRM::WinRMError) and @config['forced_preclean']) and + !@config['forced_preclean'] begin preClean(false) # it's ok for this to fail rescue Exception => e end MU::Groomer::Chef.cleanup(@server.mu_name, nodeonly: true) @config['forced_preclean'] = true + @server.reboot if @server.windows? # *sigh* end + MU.log "#{@server.mu_name}: Knife Bootstrap failed #{e.inspect}, retrying in #{(10*retries).to_s}s (#{retries} of #{max_retries})", MU::WARN, details: e.backtrace sleep 10*retries retry else diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index e20eccdb3..76763794b 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -191,6 +191,7 @@ function callMomma([string]$act) log "Calling Momma Cat at https://52.0.111.223:2260 with $act" [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # XXX $resp = Invoke-WebRequest -Uri https://52.0.111.223:2260 -Method POST -Body $params + log $resp.Content return $resp.Content } @@ -231,16 +232,18 @@ $thumb = $nodecert.Thumbprint # actually, should remove janky certificates outright winrm delete winrm/config/Listener?Address=*+Transport=HTTPS winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$myname`";CertificateThumbprint=`"$thumb`"}" +# XXX guard this net localgroup WinRMRemoteWMIUsers__ /add <%= $mu.windowsAdminName %> $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name LocalAccountTokenFilterPolicy -Value 1 - +log $creds if($creds){ + log "Enabling WinRM cert auth for <%= $mu.windowsAdminName %>" New-Item -Path WSMan:\localhost\ClientCertificate -Subject '<%= $mu.windowsAdminName %>@localhost' -URI * -Issuer $muca.Thumbprint -Force -Credential $creds } -winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' +winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="8192"}' winrm set winrm/config '@{MaxTimeoutms="1800000"}' if (!(Get-NetFirewallRule -DisplayName "Allow SSH" -ErrorAction SilentlyContinue)){ From c533710f8abb1cc590c9e86fa64abb0ad19a966a Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 11:27:47 -0400 Subject: [PATCH 25/63] comment extraneous install of knife-windows gem --- cookbooks/mu-master/recipes/init.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index 1894ef24a..2dc75f365 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -342,13 +342,14 @@ execute "rm -rf #{gemdir}/knife-windows-#{Regexp.last_match[1]}" } - gem_package "#{rubydir} knife-windows #{KNIFE_WINDOWS} #{gembin}" do - gem_binary gembin - package_name "knife-windows" - version KNIFE_WINDOWS - notifies :restart, "service[chef-server]", :delayed if rubydir == "/opt/opscode/embedded" - # XXX notify mommacat if we're *not* in chef-apply... RUNNING_STANDALONE - end +# XXX rely on bundler to get this right for us +# gem_package "#{rubydir} knife-windows #{KNIFE_WINDOWS} #{gembin}" do +# gem_binary gembin +# package_name "knife-windows" +# version KNIFE_WINDOWS +# notifies :restart, "service[chef-server]", :delayed if rubydir == "/opt/opscode/embedded" +# # XXX notify mommacat if we're *not* in chef-apply... RUNNING_STANDALONE +# end # execute "Patch #{rubydir}'s knife-windows for Cygwin SSH bootstraps" do # cwd "#{gemdir}/knife-windows-#{KNIFE_WINDOWS}" From ee35a6479c82122353324b567b06d5ea0cd7b04a Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 11:56:32 -0400 Subject: [PATCH 26/63] we're going to try *not* bundling our gems into /opt/opscode and see how that goes --- cookbooks/mu-master/recipes/init.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index 2dc75f365..ed6a62393 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -300,7 +300,7 @@ mode 0755 end -["/usr/local/ruby-current", "/opt/chef/embedded", "/opt/opscode/embedded"].each { |rubydir| +["/usr/local/ruby-current", "/opt/chef/embedded"].each { |rubydir| gembin = rubydir+"/bin/gem" gemdir = Dir.glob("#{rubydir}/lib/ruby/gems/?.?.?/gems").last bundler_path = gembin.sub(/gem$/, "bundle") @@ -458,7 +458,7 @@ # Community cookbooks keep touching gems, and none of them are smart about our # default umask. We have to clean up after them every time. -["/usr/local/ruby-current", "/opt/chef/embedded", "/opt/opscode/embedded"].each { |rubydir| +["/usr/local/ruby-current", "/opt/chef/embedded"].each { |rubydir| execute "trigger permission fix in #{rubydir}" do command "ls /etc/motd > /dev/null" notifies :run, "bash[fix #{rubydir} gem permissions]", :delayed From f1012b9e8bd691a9049758069ce0689e5335f621 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 12:32:24 -0400 Subject: [PATCH 27/63] minor YARD doc updates --- bin/mu-gen-docs | 3 ++- modules/mu-load-config.rb | 3 +++ modules/mu/master/ldap.rb | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/mu-gen-docs b/bin/mu-gen-docs index ef28e89da..06e54afcf 100755 --- a/bin/mu-gen-docs +++ b/bin/mu-gen-docs @@ -24,10 +24,11 @@ require 'json' require 'erb' require 'trollop' require 'json-schema' +require File.realpath(File.expand_path(File.dirname(__FILE__)+"/mu-load-config.rb")) require 'mu' require 'yard' MU::Config.emitSchemaAsRuby MU.log "Generating YARD documentation in /var/www/html/docs (see http://#{ENV['CHEF_PUBLIC_IP']}/docs/frames.html)" File.umask(0022) -exec "cd #{MU.myRoot} && umask 0022 && env -i PATH=#{ENV['PATH']} HOME=#{ENV['HOME']} /usr/local/ruby-current/bin/yard doc modules -m markdown -o /var/www/html/docs && chcon -R -h -t httpd_sys_script_exec_t /var/www/html/" +exec "cd #{MU.myRoot} && umask 0022 && env -i PATH=#{ENV['PATH']} HOME=#{ENV['HOME']} /usr/local/ruby-current/bin/yard doc modules -m markdown -o /var/www/html/docs && chcon -R -h -t httpd_sys_script_exec_t /var/www/html/ ; /usr/local/ruby-current/bin/yard stats --list-undoc modules" diff --git a/modules/mu-load-config.rb b/modules/mu-load-config.rb index a6c843e36..00fde10b4 100755 --- a/modules/mu-load-config.rb +++ b/modules/mu-load-config.rb @@ -112,6 +112,9 @@ def loadMuConfig(default_cfg_overrides = nil) return default_cfg.merge(global_cfg).freeze end +# Output an in-memory configuration hash to the standard config file location, +# in YAML. +# @param cfg [Hash]: The configuration to dump def saveMuConfig(cfg) home = Etc.getpwuid(Process.uid).dir username = Etc.getpwuid(Process.uid).name diff --git a/modules/mu/master/ldap.rb b/modules/mu/master/ldap.rb index 1d32e58b0..a6d7fce97 100755 --- a/modules/mu/master/ldap.rb +++ b/modules/mu/master/ldap.rb @@ -484,7 +484,8 @@ def self.findGroups(search = [], exact: false, searchbase: $MU_CFG['ldap']['base # @param search [Array]: Strings to search for. # @param exact [Boolean]: Return only exact matches for whole fields. # @param searchbase [String]: The DN under which to search. - # @param groups [Array]: An array of groups. If supplied, a user must be a member of one of these in order to match. + # @param extra_attrs [Array]: Other LDAP attributes to search + # @param matchgroups [Array]: An array of groups. If supplied, a user must be a member of one of these in order to match. # @return [Array] def self.findUsers(search = [], exact: false, searchbase: $MU_CFG['ldap']['base_dn'], extra_attrs: [], matchgroups: []) # We want to search groups, but can't search on memberOf with wildcards. From 2b280f42bf557005bdfa8310c1cd935846d09453 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 13:41:56 -0400 Subject: [PATCH 28/63] remove accidental hardcoded mommacat IP from userdata; tweaks to Windows image generation --- cookbooks/mu-tools/recipes/updates.rb | 2 +- demo/ami-generators/windows.yaml | 5 ++++- demo/simple-windows.yaml | 2 +- modules/mommacat.ru | 3 ++- modules/mu/mommacat.rb | 4 ---- modules/mu/userdata/windows.erb | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cookbooks/mu-tools/recipes/updates.rb b/cookbooks/mu-tools/recipes/updates.rb index 460630dbf..4ea67e524 100644 --- a/cookbooks/mu-tools/recipes/updates.rb +++ b/cookbooks/mu-tools/recipes/updates.rb @@ -48,7 +48,7 @@ recursive true end - if node[:os_updates_using_chef] + if node[:os_updates_using_chef] or node[:application_attributes][:os_updates_using_chef] powershell_script "Install Windows Updates" do # XXX Something in here throws a security error now. Whee. # Set-ExecutionPolicy RemoteSigned -Force diff --git a/demo/ami-generators/windows.yaml b/demo/ami-generators/windows.yaml index da1d40186..efae6232f 100644 --- a/demo/ami-generators/windows.yaml +++ b/demo/ami-generators/windows.yaml @@ -4,9 +4,12 @@ - name: win2k12 platform: windows - size: m3.large + size: t2.large run_list: + - recipe[mu-tools::updates] - recipe[mu-utility::cleanup_image_helper] + application_attributes: + os_updates_using_chef: true create_image: image_then_destroy: true public: true diff --git a/demo/simple-windows.yaml b/demo/simple-windows.yaml index 0b9fa1b5d..4d9441920 100644 --- a/demo/simple-windows.yaml +++ b/demo/simple-windows.yaml @@ -6,7 +6,7 @@ appname: demo servers: - name: windows platform: windows - size: m4.large + size: t2.large static_ip: assign_ip: true storage: diff --git a/modules/mommacat.ru b/modules/mommacat.ru index c68ec569f..3ca6f9ea1 100644 --- a/modules/mommacat.ru +++ b/modules/mommacat.ru @@ -374,7 +374,8 @@ app = proc do |env| if !req["mu_windows_admin_creds"].nil? returnval[2] = [kittenpile.retrieveWindowsAdminCreds(instance).join(";")] - MU.log returnval[2].sub(/;.*/, ";*********"), MU::NOTICE + logstr = returnval[2].is_a?(Array) ? returnval[2].first.sub(/;.*/, ";*********") : returnval[2].sub(/;.*/, ";*********") + MU.log logstr, MU::NOTICE elsif !req["mu_ssl_sign"].nil? kittenpile.signSSLCert(req["mu_ssl_sign"], req["mu_ssl_sans"].split(/,/)) kittenpile.signSSLCert(req["mu_ssl_sign"], req["mu_ssl_sans"].split(/,/)) diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 936eac2d2..710f5372f 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1936,10 +1936,6 @@ def retrieveWindowsAdminCreds(server) elsif !server.is_a?(MU::Cloud::Server) raise MuError, "retrieveWindowsAdminCreds must be called with a Server object (got #{server.class.name})" end -# if !server.windows? -# raise MuError, "#{server} is not a Windows node" -# end -#MU.log "retrieveWindowsAdminCreds called on a thing", MU::NOTICE, details: server.config if server.config['use_cloud_provider_windows_password'] return [server.config["windows_admin_username"], server.getWindowsAdminPassword] elsif server.config['windows_auth_vault'] && !server.config['windows_auth_vault'].empty? diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 76763794b..a7d11680b 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -188,9 +188,9 @@ $deploy_secret = & "c:\opscode\chef\embedded\bin\ruby" -ropenssl -rbase64 -e "ke function callMomma([string]$act) { $params = @{mu_id='<%= $mu.muID %>';mu_resource_name='<%= $mu.resourceName %>';mu_resource_type='<%= $mu.resourceType %>';mu_instance_id="$awsid";mu_user='<%= $mu.muUser %>';mu_deploy_secret="$deploy_secret";$act="1"} - log "Calling Momma Cat at https://52.0.111.223:2260 with $act" + log "Calling Momma Cat at https://<%= $mu.publicIP %>:2260 with $act" [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # XXX - $resp = Invoke-WebRequest -Uri https://52.0.111.223:2260 -Method POST -Body $params + $resp = Invoke-WebRequest -Uri https://<%= $mu.publicIP %>:2260 -Method POST -Body $params log $resp.Content return $resp.Content } From 83b0b29e508127932ccf00b998e3bb20374697ad Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 14:16:58 -0400 Subject: [PATCH 29/63] more reboot assistance for stuck windows nodes --- modules/mu/groomers/chef.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index 5fb36b175..38a017934 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -295,6 +295,9 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, else winrm = @server.getWinRMSession(max_retries) if @server.windows? and @server.windowsRebootPending?(winrm) + if retries > 3 + @server.reboot # sometimes it needs help + end raise MU::Groomer::RunError, "#{@server.mu_name} has a pending reboot" end if try_upgrade @@ -350,7 +353,7 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, if retries < max_retries retries += 1 - MU.log "#{@server.mu_name}: Chef run '#{purpose}' failed after #{Time.new - runstart} seconds, retrying (#{retries}/#{max_retries})", MU::WARN, details: e.inspect + MU.log "#{@server.mu_name}: Chef run '#{purpose}' failed after #{Time.new - runstart} seconds, retrying (#{retries}/#{max_retries})", MU::WARN, details: e.message sleep 30 retry else From 4d01bb6d4850879919f38cbbbff03af696001846 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 14:53:59 -0400 Subject: [PATCH 30/63] handle windows' stuff sshd service differently if not in a domain --- cookbooks/mu-tools/recipes/windows-client.rb | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/cookbooks/mu-tools/recipes/windows-client.rb b/cookbooks/mu-tools/recipes/windows-client.rb index 9187281fd..681cf9958 100644 --- a/cookbooks/mu-tools/recipes/windows-client.rb +++ b/cookbooks/mu-tools/recipes/windows-client.rb @@ -150,6 +150,17 @@ password sshd_password ignore_failure true end + + begin + resources('service[sshd]') + rescue Chef::Exceptions::ResourceNotFound + service "sshd" do + run_as_user login_dom+'\\'+sshd_user + run_as_password sshd_password + action [:enable, :start] + sensitive true + end + end else windows_users node['hostname'] do username node['windows_admin_username'] @@ -176,16 +187,15 @@ service_username ".\\#{sshd_user}" password sshd_password end - end# - - begin - resources('service[sshd]') - rescue Chef::Exceptions::ResourceNotFound - service "sshd" do - run_as_user "#{login_dom}\\#{sshd_user}" - run_as_password sshd_password - action [:enable, :start] - sensitive true + begin + resources('service[sshd]') + rescue Chef::Exceptions::ResourceNotFound + service "sshd" do + run_as_user sshd_user + run_as_password sshd_password + action [:enable, :start] + sensitive true + end end end From b453299e6d12625bfb525614ef86ff7ffca772a5 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 16:28:51 -0400 Subject: [PATCH 31/63] catch some aggravating errors --- modules/mu/clouds/aws/server_pool.rb | 4 +++- modules/mu/mommacat.rb | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/mu/clouds/aws/server_pool.rb b/modules/mu/clouds/aws/server_pool.rb index b3aff03c1..c7444a659 100644 --- a/modules/mu/clouds/aws/server_pool.rb +++ b/modules/mu/clouds/aws/server_pool.rb @@ -436,7 +436,9 @@ def create kitten = MU::Cloud::Server.new(mommacat: @deploy, kitten_cfg: @config, cloud_id: member.instance_id) MU::MommaCat.lock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies") MU::MommaCat.unlock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies") - kitten.postBoot(member.instance_id) + if !kitten.postBoot(member.instance_id) + raise MU::Groomer::RunError, "Failure grooming #{member.instance_id}" + end kitten.groom MU::MommaCat.unlockAll } diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 710f5372f..fe0b1e67b 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1966,7 +1966,12 @@ def signSSLCert(csr_path, sans = []) end MU.log "Signing SSL certificate request #{csr_path} with #{MU.mySSLDir}/Mu_CA.pem" - csr = OpenSSL::X509::Request.new File.read csr_path + begin + csr = OpenSSL::X509::Request.new File.read csr_path + rescue Exception => e + MU.log e.message, MU::ERR, details: File.read csr_path + raise e + end key = OpenSSL::PKey::RSA.new File.read "#{certdir}/#{certname}.key" # Load up the Mu Certificate Authority From 2b39c113fede8182283a5a6791d440f9d04e6f06 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 17:17:51 -0400 Subject: [PATCH 32/63] don't accept dumb values like 'localhost' for public_addr --- bin/mu-configure | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bin/mu-configure b/bin/mu-configure index 09f277b41..cbcf6dd0b 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -38,6 +38,8 @@ $CONFIGURABLES = { "desc" => "IP address or hostname", "required" => true, "rootonly" => true, + "pattern" => /^(localhost|127\.0\.0\.1)$/, + "negate_pattern" => true, "changes" => ["389ds", "chef-server", "chefrun", "chefcerts"] }, "mu_admin_email" => { @@ -613,6 +615,11 @@ def validate(newval, reqs) if newval.nil? puts "\nSupplied value did not pass validation".light_red.on_black+"\n\n" ok = false + elsif reqs['negate_pattern'] + if newval.match(reqs['pattern']) + puts "\nInvalid value '#{newval.bold}' (must NOT match #{reqs['pattern']})".light_red.on_black+"\n\n" + ok = false + end elsif !newval.match(reqs['pattern']) puts "\nInvalid value '#{newval.bold}' (must match #{reqs['pattern']})".light_red.on_black+"\n\n" ok = false From 97371c2634868ae44d8235835f9a22662f0d0367 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 17:47:41 -0400 Subject: [PATCH 33/63] signing a CSR: scene missing? --- modules/mu/mommacat.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index fe0b1e67b..8de7903aa 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -2161,6 +2161,7 @@ def nodeSSLCerts(server) csr.version = 3 csr.subject = OpenSSL::X509::Name.parse "CN=#{data['cn']}/O=Mu/C=US" csr.public_key = key.public_key + csr.sign key, OpenSSL::Digest::SHA256.new open("#{MU.mySSLDir}/#{certname}.csr", 'w', 0644) { |io| io.write csr.to_pem } From f8f0d8d25014482fc25d1989b08ee2a10819ada7 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 19 Oct 2017 17:49:46 -0400 Subject: [PATCH 34/63] parents around args to MU.log so it doesn't think spaces mean more args --- modules/mu/mommacat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 8de7903aa..9ef02b782 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -1969,7 +1969,7 @@ def signSSLCert(csr_path, sans = []) begin csr = OpenSSL::X509::Request.new File.read csr_path rescue Exception => e - MU.log e.message, MU::ERR, details: File.read csr_path + MU.log e.message, MU::ERR, details: File.read(csr_path) raise e end key = OpenSSL::PKey::RSA.new File.read "#{certdir}/#{certname}.key" From 8a38ebbc12be023d2531a24206e44b31925b9688 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 24 Oct 2017 10:40:19 -0400 Subject: [PATCH 35/63] new win2k12 stock AMIs; cleaner script for old base images --- cookbooks/mu-tools/recipes/windows-client.rb | 6 +-- cookbooks/mu-tools/resources/sshd_service.rb | 16 +++---- cookbooks/mu-tools/resources/windows_users.rb | 19 ++++++-- .../{windows.yaml => win2k12.yaml} | 4 +- demo/ami-generators/win2k16.yaml | 15 ++++++ extras/clean-stock-amis | 48 +++++++++++++++++++ modules/Gemfile.lock | 36 +++++++------- modules/mu/defaults/amazon_images.yaml | 26 +++++----- 8 files changed, 122 insertions(+), 48 deletions(-) rename demo/ami-generators/{windows.yaml => win2k12.yaml} (77%) create mode 100644 demo/ami-generators/win2k16.yaml create mode 100755 extras/clean-stock-amis diff --git a/cookbooks/mu-tools/recipes/windows-client.rb b/cookbooks/mu-tools/recipes/windows-client.rb index 681cf9958..3c690313a 100644 --- a/cookbooks/mu-tools/recipes/windows-client.rb +++ b/cookbooks/mu-tools/recipes/windows-client.rb @@ -148,7 +148,6 @@ service_username "#{node['ad']['netbios_name']}\\#{sshd_user}" username sshd_user password sshd_password -ignore_failure true end begin @@ -190,8 +189,9 @@ begin resources('service[sshd]') rescue Chef::Exceptions::ResourceNotFound - service "sshd" do - run_as_user sshd_user + service "Cygwin sshd as '#{sshd_user}'" do + service_name "sshd" + run_as_user ".\\"+sshd_user run_as_password sshd_password action [:enable, :start] sensitive true diff --git a/cookbooks/mu-tools/resources/sshd_service.rb b/cookbooks/mu-tools/resources/sshd_service.rb index f2bf70a18..b305c32d5 100644 --- a/cookbooks/mu-tools/resources/sshd_service.rb +++ b/cookbooks/mu-tools/resources/sshd_service.rb @@ -29,17 +29,17 @@ # cmd = powershell_out("c:/bin/cygwin/bin/bash --login -c 'chown -R #{new_resource.username} /var/empty && chown #{new_resource.username} /var/log/sshd.log /etc/ssh*\'; Stop-Process -ProcessName #{new_resource.name} -force; Stop-Service #{new_resource.name} -Force; Start-Service #{new_resource.name}; sleep 5; Start-Service #{new_resource.name}") # We would much prefer to use the above because that wouldn't require another reboot, but in some cases the session dosen't get terminated from Mu. Throwing Chef::Application.fatal seems to work more reliably cmd = powershell_out("c:/bin/cygwin/bin/bash --login -c 'chown -R #{new_resource.username} /var/empty && chown #{new_resource.username} /var/log/sshd.log /etc/ssh*\'") - execute "kill ssh for reboot" do - command "Taskkill /im sshd.exe /f /t" - returns [0, 128, 1115] - action :nothing - end +# execute "kill ssh for reboot" do +# command "Taskkill /im sshd.exe /f /t" +# returns [0, 128, 1115] +# action :nothing +# end reboot "Setting Cygwin ssh user to #{new_resource.username}" do - action :reboot_now + action :request_reboot reason "Setting Cygwin ssh user to #{new_resource.username}" - notifies :run, "execute[kill ssh for reboot]", :immediately +# notifies :run, "execute[kill ssh for reboot]", :immediately end - kill_ssh +# kill_ssh end end end diff --git a/cookbooks/mu-tools/resources/windows_users.rb b/cookbooks/mu-tools/resources/windows_users.rb index 6efd1ecdc..802e67e7f 100644 --- a/cookbooks/mu-tools/resources/windows_users.rb +++ b/cookbooks/mu-tools/resources/windows_users.rb @@ -198,11 +198,15 @@ cookbook_file "c:\\Windows\\SysWOW64\\ntrights.exe" do source "ntrights" end - [new_resource.ssh_user, new_resource.ec2config_user].each { |usr| + pass = if usr == new_resource.ec2config_user + new_resource.ec2config_password + elsif usr == new_resource.ssh_user + new_resource.ssh_password + end + user usr do - password new_resource.ec2config_password if usr == new_resource.ec2config_user - password new_resource.ssh_password if usr == new_resource.ssh_user + password pass end group "Administrators" do @@ -217,12 +221,21 @@ end } + # XXX user resource seems not to really be setting password, or is setting # in such a way that the user is being required to change it. Workaround. + powershell_script "Adjust local account params for #{new_resource.ssh_user}" do + code <<-EOH + (([adsi]('WinNT://./#{usr}, user')).psbase.invoke('SetPassword', '#{pass}')) + EOH + end + if usr == new_resource.ssh_user + %w{SeCreateTokenPrivilege SeTcbPrivilege SeAssignPrimaryTokenPrivilege}.each { |privilege| batch "Grant local user #{usr} logon as service right" do code "C:\\Windows\\SysWOW64\\ntrights +r #{privilege} -u #{usr}" end } + end } end diff --git a/demo/ami-generators/windows.yaml b/demo/ami-generators/win2k12.yaml similarity index 77% rename from demo/ami-generators/windows.yaml rename to demo/ami-generators/win2k12.yaml index efae6232f..fb7c6155d 100644 --- a/demo/ami-generators/windows.yaml +++ b/demo/ami-generators/win2k12.yaml @@ -4,12 +4,10 @@ - name: win2k12 platform: windows - size: t2.large + size: m4.large run_list: - recipe[mu-tools::updates] - recipe[mu-utility::cleanup_image_helper] - application_attributes: - os_updates_using_chef: true create_image: image_then_destroy: true public: true diff --git a/demo/ami-generators/win2k16.yaml b/demo/ami-generators/win2k16.yaml new file mode 100644 index 000000000..d0995c567 --- /dev/null +++ b/demo/ami-generators/win2k16.yaml @@ -0,0 +1,15 @@ +--- + appname: mu + servers: + - + name: win2k16 + platform: windows + size: m4.large + run_list: + - recipe[mu-tools::updates] + - recipe[mu-utility::cleanup_image_helper] + create_image: + image_then_destroy: true + public: true + copy_to_regions: + - "#ALL" diff --git a/extras/clean-stock-amis b/extras/clean-stock-amis new file mode 100755 index 000000000..c3f908eec --- /dev/null +++ b/extras/clean-stock-amis @@ -0,0 +1,48 @@ +#!/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 'trollop' +require 'json' +require File.realpath(File.expand_path(File.dirname(__FILE__)+"/../bin/mu-load-config.rb")) +require 'mu' + +filters = [ + { + name: "owner-id", + values: [MU.account_number] + } +] + + +MU::Cloud::AWS.listRegions.each { | r| + images = MU::Cloud::AWS.ec2(r).describe_images( + filters: filters + [{ "name" => "state", "values" => ["available"]}] + ).images + images.each { |ami| + if (DateTime.now.to_time - DateTime.parse(ami.creation_date).to_time) > 15552000 and ami.name.match(/^MU-(PROD|DEV)/) + snaps = [] + ami.block_device_mappings.each { |dev| + if !dev.ebs.nil? + snaps << dev.ebs.snapshot_id + end + } + MU.log "Deregistering #{ami.name} (#{ami.creation_date})", MU::WARN, details: snaps + MU::Cloud::AWS.ec2(r).deregister_image(image_id: ami.image_id) + snaps.each { |snap_id| + MU::Cloud::AWS.ec2(r).delete_snapshot(snapshot_id: snap_id) + } + end + } +} diff --git a/modules/Gemfile.lock b/modules/Gemfile.lock index a05a10c3f..d2f94606f 100644 --- a/modules/Gemfile.lock +++ b/modules/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/eGT-Labs/knife-windows.git - revision: 824063601c59a81702e31f6a69223e68e96e4e06 + revision: 0deb5ce9bba6718ebac6bb6c48a1a4b7bdc4b15b branch: winrm_cert_auth specs: knife-windows (1.9.0) @@ -50,10 +50,10 @@ GEM celluloid-io (0.16.2) celluloid (>= 0.16.0) nio4r (>= 1.1.0) - chef (12.21.14) + chef (12.21.20) addressable bundler (>= 1.10) - chef-config (= 12.21.14) + chef-config (= 12.21.20) chef-zero (>= 4.8, < 13) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -79,7 +79,7 @@ GEM specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) - chef-config (12.21.14) + chef-config (12.21.20) addressable fuzzyurl mixlib-config (~> 2.0) @@ -155,7 +155,7 @@ GEM nori (2.6.0) octokit (4.7.0) sawyer (~> 0.8.0, >= 0.5.3) - ohai (8.24.1) + ohai (8.25.0) chef-config (>= 12.5.0.alpha.1, < 14) ffi (~> 1.9) ffi-yajl (~> 2.2) @@ -191,22 +191,22 @@ GEM retryable (~> 2.0) semverse (~> 2.0) varia_model (~> 0.6) - rspec (3.6.0) - rspec-core (~> 3.6.0) - rspec-expectations (~> 3.6.0) - rspec-mocks (~> 3.6.0) - rspec-core (3.6.0) - rspec-support (~> 3.6.0) - rspec-expectations (3.6.0) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.0) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) + rspec-support (~> 3.7.0) rspec-its (1.2.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) - rspec-mocks (3.6.0) + rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-support (3.6.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.0) rspec_junit_formatter (0.2.3) builder (< 4) rspec-core (>= 2, < 4, != 2.12.0) @@ -218,7 +218,7 @@ GEM addressable (>= 2.3.5, < 2.6) faraday (~> 0.8, < 1.0) semverse (2.0.0) - serverspec (2.41.0) + serverspec (2.41.1) multi_json rspec (~> 3.0) rspec-its @@ -259,7 +259,7 @@ GEM winrm-elevated (1.1.0) winrm (~> 2.0) winrm-fs (~> 1.0) - winrm-fs (1.0.2) + winrm-fs (1.1.0) erubis (~> 2.7) logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) diff --git a/modules/mu/defaults/amazon_images.yaml b/modules/mu/defaults/amazon_images.yaml index d769bf584..69cb2cc31 100644 --- a/modules/mu/defaults/amazon_images.yaml +++ b/modules/mu/defaults/amazon_images.yaml @@ -67,19 +67,19 @@ ubuntu14: &ubuntu14 ap-southeast-1: ami-2855964b ap-southeast-2: ami-d19fc4b2 win2k12r2: &win2k12r2 - us-east-1: ami-d5651cc3 - us-east-2: ami-51b39434 - us-west-1: ami-80e9c8e0 - us-west-2: ami-942147f4 - eu-central-1: ami-a14f96ce - eu-west-1: ami-e9171c8f - sa-east-1: ami-bad8b7d6 - ap-northeast-1: ami-8edfe1e9 - ap-northeast-2: ami-0b21fc65 - ap-southeast-1: ami-adec6bce - ap-southeast-2: ami-f0061393 - ap-south-1: ami-69aad706 - ca-central-1: ami-906ed2f4 + us-east-1: ami-d4409aae + us-east-2: ami-fbbe929e + us-west-1: ami-ec91ac8c + us-west-2: ami-106ca068 + eu-central-1: ami-59e15a36 + eu-west-1: ami-65b16b1c + sa-east-1: ami-93d6afff + ap-northeast-1: ami-dcd375ba + ap-northeast-2: ami-fa2e8b94 + ap-southeast-1: ami-b61657d5 + ap-southeast-2: ami-9a7b97f8 + ap-south-1: ami-99a8eaf6 + ca-central-1: ami-608b3304 win2k16: &win2k16 us-east-1: ami-d2cb25a8 us-east-2: ami-2db59748 From 74051b505cdeaf2f2061a9b3c32f8b78687202d4 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 24 Oct 2017 11:58:37 -0400 Subject: [PATCH 36/63] mu-tools windows_users resource: name the temporary password setter resource properly --- cookbooks/mu-tools/resources/windows_users.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbooks/mu-tools/resources/windows_users.rb b/cookbooks/mu-tools/resources/windows_users.rb index 802e67e7f..50e032a59 100644 --- a/cookbooks/mu-tools/resources/windows_users.rb +++ b/cookbooks/mu-tools/resources/windows_users.rb @@ -222,7 +222,7 @@ } # XXX user resource seems not to really be setting password, or is setting # in such a way that the user is being required to change it. Workaround. - powershell_script "Adjust local account params for #{new_resource.ssh_user}" do + powershell_script "Adjust local account params for #{usr}" do code <<-EOH (([adsi]('WinNT://./#{usr}, user')).psbase.invoke('SetPassword', '#{pass}')) EOH From db05d6b9a1a9ae93f23b8f678f1cf0cde8d6ca03 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 25 Oct 2017 10:28:41 -0400 Subject: [PATCH 37/63] don't log passwords to plain text in windows userdata, fool --- modules/mu/userdata/windows.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index a7d11680b..3350bf372 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -191,7 +191,6 @@ function callMomma([string]$act) log "Calling Momma Cat at https://<%= $mu.publicIP %>:2260 with $act" [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # XXX $resp = Invoke-WebRequest -Uri https://<%= $mu.publicIP %>:2260 -Method POST -Body $params - log $resp.Content return $resp.Content } From cd03e8b6442d89907280d24992bc844af1c321c0 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 26 Oct 2017 13:02:53 -0400 Subject: [PATCH 38/63] installer: take a branch argument --- install/installer | 3 +++ 1 file changed, 3 insertions(+) diff --git a/install/installer b/install/installer index 885fad979..f067ff38b 100755 --- a/install/installer +++ b/install/installer @@ -2,6 +2,9 @@ CHEF_CLIENT_VERSION="12.21.14-1" MU_BRANCH="master" +if [ "$1" != "" ];then + MU_BRANCH="$1" +fi # XXX All RHEL family. We can at least cover Debian-flavored hosts too, I bet. DIST_VERSION=`rpm -qa \*-release\* | grep -Ei "redhat|centos" | cut -d"-" -f3` From 22d8de8563f3511cd652d304bd8a6ebe571dd3f4 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 26 Oct 2017 13:12:30 -0400 Subject: [PATCH 39/63] mu-configure: drop out quicker if LDAP setup fails --- bin/mu-configure | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/mu-configure b/bin/mu-configure index cbcf6dd0b..2f88bc645 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -938,8 +938,10 @@ if $MU_CFG['ldap']['type'] == "389 Directory Services" MU.log "Configuring 389 Directory Services", MU::NOTICE set389DSCreds system("chef-client -o 'recipe[mu-master::389ds]'") + exit 1 if $? != 0 MU::Master::LDAP.initLocalLDAP system("chef-client -o 'recipe[mu-master::sssd]'") + exit 1 if $? != 0 end end From 61c7306c9f68700e82228aacd2edd67c7772c4ea Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 26 Oct 2017 13:18:31 -0400 Subject: [PATCH 40/63] 389ds setup script bug workaroud --- cookbooks/mu-master/recipes/389ds.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/cookbooks/mu-master/recipes/389ds.rb b/cookbooks/mu-master/recipes/389ds.rb index 230bdb7c6..b37aa253e 100644 --- a/cookbooks/mu-master/recipes/389ds.rb +++ b/cookbooks/mu-master/recipes/389ds.rb @@ -68,6 +68,7 @@ # %x{/usr/sbin/setenforce 0} execute "initialize 389 Directory Services" do command "/usr/sbin/setup-ds-admin.pl -s -f /root/389ds.tmp/389-directory-setup.inf --continue --debug #{Dir.exists?("/etc/dirsrv/slapd-#{$MU_CFG["hostname"]}") ? "--update" : ""}" + ignore_failure true # XXX this is a bug in the package; we're currently getting spurious exit(1)s from this thing even when it works fine action :nothing end From 9017f2700fb5dd74b97646785796223ef3c95881 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 26 Oct 2017 13:58:08 -0400 Subject: [PATCH 41/63] more workarounds for new bugs in SSSD and 389DS vendor packages (thanks guys) --- cookbooks/mu-master/recipes/389ds.rb | 7 ++++++- cookbooks/mu-master/recipes/sssd.rb | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cookbooks/mu-master/recipes/389ds.rb b/cookbooks/mu-master/recipes/389ds.rb index b37aa253e..989e13784 100644 --- a/cookbooks/mu-master/recipes/389ds.rb +++ b/cookbooks/mu-master/recipes/389ds.rb @@ -64,11 +64,16 @@ $CREDS[creds]['user'] = user if !$CREDS[creds]['user'] $CREDS[creds]['pw'] = pw if !$CREDS[creds]['pw'] } +directory "/var/log/dirsrv/admin-serv" do + user "nobody" + group "nobody" + mode 0770 + recursive true +end # %x{/usr/sbin/setenforce 0} execute "initialize 389 Directory Services" do command "/usr/sbin/setup-ds-admin.pl -s -f /root/389ds.tmp/389-directory-setup.inf --continue --debug #{Dir.exists?("/etc/dirsrv/slapd-#{$MU_CFG["hostname"]}") ? "--update" : ""}" - ignore_failure true # XXX this is a bug in the package; we're currently getting spurious exit(1)s from this thing even when it works fine action :nothing end diff --git a/cookbooks/mu-master/recipes/sssd.rb b/cookbooks/mu-master/recipes/sssd.rb index dc2024826..d3695b0a3 100644 --- a/cookbooks/mu-master/recipes/sssd.rb +++ b/cookbooks/mu-master/recipes/sssd.rb @@ -63,6 +63,10 @@ notifies :reload, "service[sshd]", :delayed not_if "grep pam_sss.so /etc/pam.d/password-auth" end +directory "/var/log/sssd" do + mode 0750 + recursive true +end service "sssd" do action :nothing notifies :restart, "service[sshd]", :immediately @@ -70,6 +74,8 @@ template "/etc/sssd/sssd.conf" do source "sssd.conf.erb" mode 0600 + owner "root" + group "root" notifies :restart, "service[sssd]", :immediately variables( :base_dn => $MU_CFG['ldap']['base_dn'], From ea0d5d6ca84da25c1bef88403ac09eeb672f7e21 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 27 Oct 2017 12:02:52 -0400 Subject: [PATCH 42/63] auto-regenerate expired node SSL certs --- modules/mu/mommacat.rb | 21 ++++++++++++++++----- modules/mu/userdata/windows.erb | 6 ++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index 9ef02b782..a9153c577 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -2121,11 +2121,22 @@ def nodeSSLCerts(server) if File.exists?("#{MU.mySSLDir}/#{server.mu_name}.crt") and File.exists?("#{MU.mySSLDir}/#{server.mu_name}.key") - results[server.mu_name] = [ - OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{server.mu_name}.crt")), - OpenSSL::PKey::RSA.new(File.read("#{MU.mySSLDir}/#{server.mu_name}.key")) - ] - else + ext_cert = OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{server.mu_name}.crt")) + if ext_cert.not_after < Time.now + MU.log "Node certificate for #{server.mu_name} is expired, regenerating", MU::WARN + ["crt", "key", "csr"].each { |suffix| + if File.exists?("#{MU.mySSLDir}/#{server.mu_name}.#{suffix}") + File.unlink("#{MU.mySSLDir}/#{server.mu_name}.#{suffix}") + end + } + else + results[server.mu_name] = [ + OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{server.mu_name}.crt")), + OpenSSL::PKey::RSA.new(File.read("#{MU.mySSLDir}/#{server.mu_name}.key")) + ] + end + end + if results.size == 0 certs[server.mu_name] = { "sans" => ["IP:#{canonical_ip}"], "cn" => server.mu_name diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 3350bf372..10124db3d 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -231,8 +231,10 @@ $thumb = $nodecert.Thumbprint # actually, should remove janky certificates outright winrm delete winrm/config/Listener?Address=*+Transport=HTTPS winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$myname`";CertificateThumbprint=`"$thumb`"}" -# XXX guard this -net localgroup WinRMRemoteWMIUsers__ /add <%= $mu.windowsAdminName %> +ngroup = net localgroup WinRMRemoteWMIUsers__ | Where-Object {$_ -eq "<%= $mu.windowsAdminName %>"} +if($ingroup -ne "<%= $mu.windowsAdminName %>"){ + net localgroup WinRMRemoteWMIUsers__ /add <%= $mu.windowsAdminName %> +} $winrmcert = importCert "$myname-winrm.crt" "TrustedPeople" Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true From 4a22367d99b13bb63ac8895c1ed71bb1acbd7915 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Mon, 30 Oct 2017 14:54:07 -0400 Subject: [PATCH 43/63] windows: existing nodes can try ssh if winrm grooms fail; installer: take an env var for MU_BRANCH instead of an arg --- Berksfile.lock | 2 +- install/installer | 5 ++--- modules/mu/clouds/aws/server.rb | 17 ++++++++++++----- modules/mu/groomers/chef.rb | 29 ++++++++++++++++++----------- modules/mu/userdata/windows.erb | 2 +- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/Berksfile.lock b/Berksfile.lock index 77dbbe5e3..d930a3db4 100644 --- a/Berksfile.lock +++ b/Berksfile.lock @@ -303,6 +303,6 @@ GRAPH yum (3.13.0) yum-epel (2.1.2) compat_resource (>= 12.16.3) - zap (1.0.2) + zap (1.1.0) zipfile (0.1.0) zypper (0.4.0) diff --git a/install/installer b/install/installer index f067ff38b..1254e8db5 100755 --- a/install/installer +++ b/install/installer @@ -1,9 +1,8 @@ #!/bin/sh CHEF_CLIENT_VERSION="12.21.14-1" -MU_BRANCH="master" -if [ "$1" != "" ];then - MU_BRANCH="$1" +if [ "$MU_BRANCH" == "" ];then + MU_BRANCH="master" fi # XXX All RHEL family. We can at least cover Debian-flavored hosts too, I bet. diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index a86a1862f..cb341bdf5 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -1019,11 +1019,18 @@ def postBoot(instance_id = nil) if windows? # kick off certificate generation early; WinRM will need it cert, key = @deploy.nodeSSLCerts(self) - session = getWinRMSession(50, 60, reboot_on_problems: true) - initialWinRMTasks(session) - session.close -# XXX account for machines behind bastion hosts that we can't tunnel through; -# maybe then it's ok to fall back to sshd? + if !@groomer.haveBootstrapped? + session = getWinRMSession(50, 60, reboot_on_problems: true) + initialWinRMTasks(session) + session.close + else # for an existing Windows node: WinRM, then SSH if it fails + begin + session = getWinRMSession(1, 60) + rescue Exception # yeah, yeah + session = getSSHSession(1, 60) + # XXX maybe loop at least once if this also fails? + end + end else session = getSSHSession(40, 30) initialSSHTasks(session) diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index 38a017934..bbf0a0532 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -276,9 +276,11 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, cmd = nil ssh = nil winrm = nil + windows_try_ssh = false begin runstart = Time.new - if !@server.windows? + if !@server.windows? or windows_try_ssh + windows_try_ssh = false ssh = @server.getSSHSession(max_retries) if !@config["ssh_user"].nil? and !@config["ssh_user"].empty? and @config["ssh_user"] != "root" upgrade_cmd = try_upgrade ? "sudo curl -L https://chef.io/chef/install.sh | sudo version=#{MU.chefVersion} sh &&" : "" @@ -293,7 +295,7 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, raise MU::Groomer::RunError, output.grep(/ ERROR: /).last if data.match(/#{error_signal}/) } else - winrm = @server.getWinRMSession(max_retries) + winrm = @server.getWinRMSession(haveBootstrapped? ? 1 : max_retries) if @server.windows? and @server.windowsRebootPending?(winrm) if retries > 3 @server.reboot # sometimes it needs help @@ -322,7 +324,7 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, raise MU::Groomer::RunError, output.slice(output.length-50, output.length).join("") end end - rescue RuntimeError, SystemCallError, Timeout::Error, SocketError, Errno::ECONNRESET, IOError, Net::SSH::Exception, MU::Groomer::RunError, WinRM::WinRMError => e + rescue RuntimeError, SystemCallError, Timeout::Error, SocketError, Errno::ECONNRESET, IOError, Net::SSH::Exception, MU::Groomer::RunError, WinRM::WinRMError, MU::MuError => e begin ssh.close if !ssh.nil? rescue Net::SSH::Exception, IOError => e @@ -340,15 +342,20 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, else try_upgrade = false end - if reboot_first_fail and !e.is_a?(WinRM::WinRMError) - try_upgrade = true - begin - preClean(true) # drop any Chef install that's not ours - @server.reboot # try gently rebooting the thing - rescue Exception => e # it's ok to fail here (and to ignore failure) -MU.log "preclean err #{e.inspect}", MU::ERR + + if !e.is_a?(WinRM::WinRMError) + if reboot_first_fail + try_upgrade = true + begin + preClean(true) # drop any Chef install that's not ours + @server.reboot # try gently rebooting the thing + rescue Exception => e # it's ok to fail here (and to ignore failure) + MU.log "preclean err #{e.inspect}", MU::ERR + end + reboot_first_fail = false end - reboot_first_fail = false + elsif haveBootstrapped? + windows_try_ssh = true end if retries < max_retries diff --git a/modules/mu/userdata/windows.erb b/modules/mu/userdata/windows.erb index 10124db3d..d71a4de84 100644 --- a/modules/mu/userdata/windows.erb +++ b/modules/mu/userdata/windows.erb @@ -231,7 +231,7 @@ $thumb = $nodecert.Thumbprint # actually, should remove janky certificates outright winrm delete winrm/config/Listener?Address=*+Transport=HTTPS winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=`"$myname`";CertificateThumbprint=`"$thumb`"}" -ngroup = net localgroup WinRMRemoteWMIUsers__ | Where-Object {$_ -eq "<%= $mu.windowsAdminName %>"} +$ingroup = net localgroup WinRMRemoteWMIUsers__ | Where-Object {$_ -eq "<%= $mu.windowsAdminName %>"} if($ingroup -ne "<%= $mu.windowsAdminName %>"){ net localgroup WinRMRemoteWMIUsers__ /add <%= $mu.windowsAdminName %> } From a544bf2f9dc549a4f3420f665bb21617910eb7de Mon Sep 17 00:00:00 2001 From: Mu Master Date: Mon, 30 Oct 2017 15:48:17 -0400 Subject: [PATCH 44/63] tighter retry logic for windows in Chef groomer's run method --- modules/mu/groomers/chef.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/mu/groomers/chef.rb b/modules/mu/groomers/chef.rb index bbf0a0532..e5240360b 100644 --- a/modules/mu/groomers/chef.rb +++ b/modules/mu/groomers/chef.rb @@ -267,7 +267,6 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, end saveDeployData - MU.log "Invoking Chef on #{@server.mu_name}: #{purpose}" retries = 0 try_upgrade = false output = [] @@ -280,9 +279,12 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, begin runstart = Time.new if !@server.windows? or windows_try_ssh + MU.log "Invoking Chef over ssh on #{@server.mu_name}: #{purpose}" windows_try_ssh = false ssh = @server.getSSHSession(max_retries) - if !@config["ssh_user"].nil? and !@config["ssh_user"].empty? and @config["ssh_user"] != "root" + if @server.windows? + cmd = "chef-client.bat --color || echo #{error_signal}" + elsif !@config["ssh_user"].nil? and !@config["ssh_user"].empty? and @config["ssh_user"] != "root" upgrade_cmd = try_upgrade ? "sudo curl -L https://chef.io/chef/install.sh | sudo version=#{MU.chefVersion} sh &&" : "" cmd = "#{upgrade_cmd} sudo chef-client --color || echo #{error_signal}" else @@ -295,7 +297,9 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, raise MU::Groomer::RunError, output.grep(/ ERROR: /).last if data.match(/#{error_signal}/) } else + MU.log "Invoking Chef over WinRM on #{@server.mu_name}: #{purpose}" winrm = @server.getWinRMSession(haveBootstrapped? ? 1 : max_retries) +MU.log "wtfsauce", MU::WARN if @server.windows? and @server.windowsRebootPending?(winrm) if retries > 3 @server.reboot # sometimes it needs help @@ -335,7 +339,6 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, end sleep 10 end - if e.instance_of?(MU::Groomer::RunError) and retries == 0 and max_retries > 1 MU.log "Got a run error, will attempt to install/update Chef Client on next attempt", MU::NOTICE try_upgrade = true @@ -343,7 +346,7 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, try_upgrade = false end - if !e.is_a?(WinRM::WinRMError) + if e.is_a?(MU::Groomer::RunError) if reboot_first_fail try_upgrade = true begin @@ -354,7 +357,11 @@ def run(purpose: "Chef run", update_runlist: true, max_retries: 5, output: true, end reboot_first_fail = false end - elsif haveBootstrapped? + end + + # Effectively alternate between WinRM and ssh on Windows. Something + # will probably work eventually. Right? + if @server.windows? and haveBootstrapped? windows_try_ssh = true end From 5e7230ceb865fb79ec68ce2dfd925bf3911c328e Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 31 Oct 2017 11:53:22 -0400 Subject: [PATCH 45/63] init.rb: put the right branch name in this testing branch, eh wot --- cookbooks/mu-master/recipes/init.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index ed6a62393..15bee755e 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -31,7 +31,7 @@ CHEF_SERVER_VERSION="12.16.14-1" CHEF_CLIENT_VERSION="12.21.14-1" KNIFE_WINDOWS="1.9.0" -MU_BRANCH="master" +MU_BRANCH="winrm_more_like_rm_windows" MU_BASE="/opt/mu" if File.read("/etc/ssh/sshd_config").match(/^AllowUsers\s+([^\s]+)(?:\s|$)/) SSH_USER = Regexp.last_match[1].chomp From 28f6bb8419fead46be6b40308b52957b16423418 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 1 Nov 2017 09:58:57 -0400 Subject: [PATCH 46/63] make manual VPC peering connection acceptance a warning so it jumps out more; add explanatory details --- modules/mu/clouds/aws/vpc.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mu/clouds/aws/vpc.rb b/modules/mu/clouds/aws/vpc.rb index e43e1e152..d3df0b85d 100644 --- a/modules/mu/clouds/aws/vpc.rb +++ b/modules/mu/clouds/aws/vpc.rb @@ -671,7 +671,7 @@ def groom end } else - MU.log "VPC #{peer_id} is not managed by this Mu server or is not configured to auto-accept peering requests. You must accept the peering request for '#{@config['name']}' (#{@config['vpc_id']}) by hand.", MU::NOTICE + MU.log "VPC #{peer_id} is not managed by this Mu server or is not configured to auto-accept peering requests. You must accept the peering request for '#{@config['name']}' (#{@config['vpc_id']}) by hand.", MU::WARN, details: "In the AWS Console, go to VPC => Peering Connections and look in the Actions drop-down. You can also set 'Invade Foreign VPCs' to 'true' using mu-configure to auto-accept all peering connections within this account, regardless of whether this Mu server owns the VPCs." end end From e7da04e2247bb9da2d733efe602207b8085afd8a Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 1 Nov 2017 10:56:44 -0400 Subject: [PATCH 47/63] we don't need these old knife-windows diffs anymore --- ...n => cfn_create_mu_master.json.NEEDUPDATE} | 0 install/knife-windows-chef12-0.8.2.patch | 44 - install/knife-windows-cygwin-0.8.2.patch | 71 -- install/knife-windows-cygwin-1.1.4.patch | 142 --- install/knife-windows-cygwin-1.4.0.patch | 130 -- install/knife-windows-cygwin-1.8.0.patch | 1088 ----------------- 6 files changed, 1475 deletions(-) rename install/{cfn_create_mu_master.json => cfn_create_mu_master.json.NEEDUPDATE} (100%) delete mode 100644 install/knife-windows-chef12-0.8.2.patch delete mode 100644 install/knife-windows-cygwin-0.8.2.patch delete mode 100644 install/knife-windows-cygwin-1.1.4.patch delete mode 100644 install/knife-windows-cygwin-1.4.0.patch delete mode 100644 install/knife-windows-cygwin-1.8.0.patch diff --git a/install/cfn_create_mu_master.json b/install/cfn_create_mu_master.json.NEEDUPDATE similarity index 100% rename from install/cfn_create_mu_master.json rename to install/cfn_create_mu_master.json.NEEDUPDATE diff --git a/install/knife-windows-chef12-0.8.2.patch b/install/knife-windows-chef12-0.8.2.patch deleted file mode 100644 index 5d2d3bfb3..000000000 --- a/install/knife-windows-chef12-0.8.2.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff -bBruPN knife-windows-0.8.2/lib/chef/knife/core/windows_bootstrap_context.rb /root/knife-windows-0.8.2-patched/lib/chef/knife/core/windows_bootstrap_context.rb ---- knife-windows-0.8.2/lib/chef/knife/core/windows_bootstrap_context.rb 2015-01-27 15:50:51.147154392 +0000 -+++ /root/knife-windows-0.8.2-patched/lib/chef/knife/core/windows_bootstrap_context.rb 2015-01-27 15:13:07.619991818 +0000 -@@ -76,6 +76,40 @@ - client_rb << %Q{encrypted_data_bag_secret "c:/chef/encrypted_data_bag_secret"\n} - end - -+ # We configure :verify_api_cert only when it's overridden on the CLI -+ # or when specified in the knife config. -+ if !@config[:node_verify_api_cert].nil? || knife_config.has_key?(:verify_api_cert) -+ value = @config[:node_verify_api_cert].nil? ? knife_config[:verify_api_cert] : @config[:node_verify_api_cert] -+ client_rb << %Q{verify_api_cert #{value}\n} -+ end -+ -+ # We configure :ssl_verify_mode only when it's overridden on the CLI -+ # or when specified in the knife config. -+ if @config[:node_ssl_verify_mode] || knife_config.has_key?(:ssl_verify_mode) -+ value = case @config[:node_ssl_verify_mode] -+ when "peer" -+ :verify_peer -+ when "none" -+ :verify_none -+ when nil -+ knife_config[:ssl_verify_mode] -+ else -+ nil -+ end -+ -+ if value -+ client_rb << %Q{ssl_verify_mode :#{value}\n} -+ end -+ end -+ -+ if @config[:ssl_verify_mode] -+ client_rb << %Q{ssl_verify_mode :#{knife_config[:ssl_verify_mode]}\n} -+ end -+ -+ unless trusted_certs.empty? -+ client_rb << %Q{trusted_certs_dir "c:/chef/trusted_certs"\n} -+ end -+ - escape_and_echo(client_rb) - end - diff --git a/install/knife-windows-cygwin-0.8.2.patch b/install/knife-windows-cygwin-0.8.2.patch deleted file mode 100644 index 2369ca5d2..000000000 --- a/install/knife-windows-cygwin-0.8.2.patch +++ /dev/null @@ -1,71 +0,0 @@ -diff -BbruPN knife-windows-0.8.2/lib/chef/knife/bootstrap_windows_base.rb knife-windows-0.8.2-patched/lib/chef/knife/bootstrap_windows_base.rb ---- knife-windows-0.8.2/lib/chef/knife/bootstrap_windows_base.rb 2015-01-27 01:34:57.345453199 +0000 -+++ knife-windows-0.8.2-patched/lib/chef/knife/bootstrap_windows_base.rb 2015-01-27 01:32:30.582940660 +0000 -@@ -153,7 +153,12 @@ - # we have to run the remote commands in 2047 char chunks - create_bootstrap_bat_command do |command_chunk, chunk_num| - begin -- render_command_result = run_command(%Q!cmd.exe /C echo "Rendering #{bootstrap_bat_file} chunk #{chunk_num}" && #{command_chunk}!) -+ if locate_config_value(:cygwin) -+ render_command = %q!cd $TEMP && cmd.exe /C 'echo "Rendering !+bootstrap_bat_file+%q! chunk !+chunk_num.to_s+%q!" && !+command_chunk+%q!'! -+ else -+ render_command = %q!cmd.exe /C echo "Rendering !+bootstrap_bat_file+%q! chunk !+chunk_num.to_s+%q!" && !+command_chunk -+ end -+ render_command_result = run_command(render_command) - ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0 - render_command_result - rescue SystemExit => e -@@ -174,8 +179,12 @@ - end - - def bootstrap_command -+ if locate_config_value(:cygwin) -+ @bootstrap_command ||= "cd $TEMP && cmd.exe /C #{bootstrap_bat_file}" -+ else - @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}" - end -+ end - - def create_bootstrap_bat_command(&block) - bootstrap_bat = [] -@@ -194,8 +203,12 @@ - end - - def bootstrap_bat_file -+ if locate_config_value(:cygwin) -+ @bootstrap_bat_file ||= "\"bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" -+ else - @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" - end -+ end - - def locate_config_value(key) - key = key.to_sym -diff -BbruPN knife-windows-0.8.2/lib/chef/knife/bootstrap_windows_ssh.rb knife-windows-0.8.2-patched/lib/chef/knife/bootstrap_windows_ssh.rb ---- knife-windows-0.8.2/lib/chef/knife/bootstrap_windows_ssh.rb 2015-01-27 01:34:57.346453175 +0000 -+++ knife-windows-0.8.2-patched/lib/chef/knife/bootstrap_windows_ssh.rb 2015-01-27 01:32:30.582940660 +0000 -@@ -71,12 +71,24 @@ - :boolean => true, - :default => true - -+ option :cygwin, -+ :long => "--[no-]cygwin", -+ :short => "-c", -+ :description => "Assume that we have Cygwin (and a bash shell) at the client end.", -+ :boolean => true, -+ :default => false -+ - def run - bootstrap - end - - def run_command(command = '') - ssh = Chef::Knife::Ssh.new -+ if locate_config_value(:cygwin) -+ # Harvest crucial env variables that don't exist by default in -+ # Cygwin shells. -+ command = %q{export CYGWIN=nodosfilewarning && for __dir in /proc/registry/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session\ Manager/Environment;do cd "$__dir";for __var in *;do __var=`echo $__var | tr "[a-z]" "[A-Z]"` ; test -z "${!__var}" && export $__var="`cat $__var`" >/dev/null 2>&1;done;/bin/true;done && export TEMP="$SYSTEMROOT/TEMP" && export TMP="$TEMP"} + " && cd && " + command -+ end - ssh.name_args = [ server_name, command ] - ssh.config[:ssh_user] = locate_config_value(:ssh_user) - ssh.config[:ssh_password] = locate_config_value(:ssh_password) diff --git a/install/knife-windows-cygwin-1.1.4.patch b/install/knife-windows-cygwin-1.1.4.patch deleted file mode 100644 index 54712baa8..000000000 --- a/install/knife-windows-cygwin-1.1.4.patch +++ /dev/null @@ -1,142 +0,0 @@ -diff -BbruPN knife-windows-1.1.4/lib/chef/knife/bootstrap/windows-chef-client-msi.erb knife-windows-1.1.4-morepatched/lib/chef/knife/bootstrap/windows-chef-client-msi.erb ---- knife-windows-1.1.4/lib/chef/knife/bootstrap/windows-chef-client-msi.erb 2016-01-17 09:56:09.290955029 -0500 -+++ knife-windows-1.1.4-morepatched/lib/chef/knife/bootstrap/windows-chef-client-msi.erb 2016-01-21 12:35:30.051270076 -0500 -@@ -181,6 +181,17 @@ - - <%= install_chef %> - -+SET LookForFile="c:\opscode\chef\bin\chef-client.bat" -+@echo off -+ -+:CheckForFile -+IF EXIST %LookForFile% GOTO FoundIt -+c:\Windows\System32\timeout.exe /t 30 -+GOTO CheckForFile -+ -+:FoundIt -+@echo on -+ - @if ERRORLEVEL 1 ( - echo Chef-client package failed to install with status code !ERRORLEVEL!. > "&2" - echo See installation log for additional detail: %CHEF_CLIENT_MSI_LOG_PATH%. > "&2" -@@ -245,3 +256,4 @@ - @echo Starting chef to bootstrap the node... - <%= start_chef %> - -+ -diff -BbruPN knife-windows-1.1.4/lib/chef/knife/bootstrap_windows_base.rb knife-windows-1.1.4-morepatched/lib/chef/knife/bootstrap_windows_base.rb ---- knife-windows-1.1.4/lib/chef/knife/bootstrap_windows_base.rb 2016-01-17 09:56:09.290955029 -0500 -+++ knife-windows-1.1.4-morepatched/lib/chef/knife/bootstrap_windows_base.rb 2016-01-17 19:30:53.722165146 -0500 -@@ -324,7 +324,11 @@ - # we have to run the remote commands in 2047 char chunks - create_bootstrap_bat_command do |command_chunk| - begin -- render_command_result = run_command(command_chunk) -+ render_command = command_chunk -+ if locate_config_value(:cygwin) -+ render_command = %q!cd $TEMP && !+command_chunk -+ end -+ render_command_result = run_command(render_command) - ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0 - render_command_result - rescue SystemExit => e -@@ -346,11 +350,20 @@ - end - - def bootstrap_command -+ if locate_config_value(:cygwin) -+ @bootstrap_command ||= "cd $TEMP && cmd.exe /C #{bootstrap_bat_file}" -+ else - @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}" - end -+ @bootstrap_command -+ end - - def bootstrap_render_banner_command(chunk_num) -- "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}" -+ if locate_config_value(:cygwin) -+ return "echo 'Rendering #{bootstrap_bat_file} chunk #{chunk_num}'" -+ else -+ return "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}" -+ end - end - - def escape_windows_batch_characters(line) -@@ -363,11 +376,18 @@ - bootstrap_bat = "" - banner = bootstrap_render_banner_command(chunk_num += 1) - render_template(load_template(config[:bootstrap_template])).each_line do |line| -- escape_windows_batch_characters(line) - # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can - # confidently prefix every actual command with &&. - # TODO: Why does ^\n&& work directly through the commandline but not through SOAP? -+ if locate_config_value(:cygwin) -+ render_line = "" -+ if !line.nil? and !line.chomp.strip.nil? -+ render_line = " && echo '#{line.chomp.strip.gsub(/'/, '\'\\\\\1\'\'')}' >> #{bootstrap_bat_file}" -+ end -+ else -+ escape_windows_batch_characters(line) - render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})" -+ end - # Windows commands are limited to 8191 characters for machines running XP or higher but - # this includes the length of environment variables after they have been expanded. - # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner -@@ -394,8 +414,12 @@ - end - - def bootstrap_bat_file -+ if locate_config_value(:cygwin) -+ @bootstrap_bat_file ||= "\"bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" -+ else - @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" - end -+ end - - def warn_chef_config_secret_key - ui.info "* " * 40 -diff -BbruPN knife-windows-1.1.4/lib/chef/knife/bootstrap_windows_ssh.rb knife-windows-1.1.4-morepatched/lib/chef/knife/bootstrap_windows_ssh.rb ---- knife-windows-1.1.4/lib/chef/knife/bootstrap_windows_ssh.rb 2016-01-17 09:56:09.290955029 -0500 -+++ knife-windows-1.1.4-morepatched/lib/chef/knife/bootstrap_windows_ssh.rb 2016-01-17 17:32:33.916538468 -0500 -@@ -91,12 +91,25 @@ - :boolean => true, - :default => true - -+ option :cygwin, -+ :long => "--[no-]cygwin", -+ :short => "-c", -+ :description => "Assume that we have Cygwin (and a bash shell) at the client end.", -+ :boolean => true, -+ :default => false -+ -+ - def run - bootstrap - end - - def run_command(command = '') - ssh = Chef::Knife::Ssh.new -+ if locate_config_value(:cygwin) -+ # Harvest crucial env variables that don't exist by default in -+ # Cygwin shells. -+ command = %q{export CYGWIN=nodosfilewarning && for __dir in /proc/registry/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session\ Manager/Environment;do cd "$__dir";for __var in *;do __var=`echo $__var | tr "[a-z]" "[A-Z]"` ; test -z "${!__var}" && export $__var="`cat $__var`" >/dev/null 2>&1;done;/bin/true;done && export TEMP="$SYSTEMROOT/TEMP" && export TMP="$TEMP"} + " && cd && " + command -+ end - ssh.name_args = [ server_name, command ] - ssh.config[:ssh_user] = locate_config_value(:ssh_user) - ssh.config[:ssh_password] = locate_config_value(:ssh_password) -diff -BbruPN knife-windows-1.1.4/lib/chef/knife/core/windows_bootstrap_context.rb knife-windows-1.1.4-morepatched/lib/chef/knife/core/windows_bootstrap_context.rb ---- knife-windows-1.1.4/lib/chef/knife/core/windows_bootstrap_context.rb 2016-01-17 09:56:09.291955055 -0500 -+++ knife-windows-1.1.4-morepatched/lib/chef/knife/core/windows_bootstrap_context.rb 2016-01-21 11:41:32.941202376 -0500 -@@ -275,7 +275,12 @@ - url += "&pv=#{machine_os}" unless machine_os.nil? - url += "&m=#{machine_arch}" unless machine_arch.nil? - url += "&DownloadContext=#{download_context}" unless download_context.nil? -+ if !@config[:bootstrap_version].nil? and @config[:bootstrap_version] -+ require 'uri' -+ url += "&v=#{URI.escape(@config[:bootstrap_version])}" -+ else - url += latest_current_windows_chef_version_query -+ end - else - @config[:msi_url] - end diff --git a/install/knife-windows-cygwin-1.4.0.patch b/install/knife-windows-cygwin-1.4.0.patch deleted file mode 100644 index 2bddfaefc..000000000 --- a/install/knife-windows-cygwin-1.4.0.patch +++ /dev/null @@ -1,130 +0,0 @@ -diff -rupN knife-windows-1.4.0.pristine/lib/chef/knife/bootstrap_windows_base.rb knife-windows-1.4.0/lib/chef/knife/bootstrap_windows_base.rb ---- knife-windows-1.4.0.pristine/lib/chef/knife/bootstrap_windows_base.rb 2016-08-16 12:25:22.000000000 -0400 -+++ knife-windows-1.4.0/lib/chef/knife/bootstrap_windows_base.rb 2016-04-12 20:18:37.579414376 -0400 -@@ -335,7 +335,11 @@ class Chef - # we have to run the remote commands in 2047 char chunks - create_bootstrap_bat_command do |command_chunk| - begin -- render_command_result = run_command(command_chunk) -+ render_command = command_chunk -+ if locate_config_value(:cygwin) -+ render_command = %q!cd $TEMP && !+command_chunk -+ end -+ render_command_result = run_command(render_command) - ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0 - render_command_result - rescue SystemExit => e -@@ -357,11 +361,20 @@ class Chef - end - - def bootstrap_command -+ if locate_config_value(:cygwin) -+ @bootstrap_command ||= "cd $TEMP && cmd.exe /C #{bootstrap_bat_file}" -+ else - @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}" - end -+ @bootstrap_command -+ end - - def bootstrap_render_banner_command(chunk_num) -- "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}" -+ if locate_config_value(:cygwin) -+ return "echo 'Rendering #{bootstrap_bat_file} chunk #{chunk_num}'" -+ else -+ return "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}" -+ end - end - - def escape_windows_batch_characters(line) -@@ -374,11 +387,18 @@ class Chef - bootstrap_bat = "" - banner = bootstrap_render_banner_command(chunk_num += 1) - render_template(load_template(config[:bootstrap_template])).each_line do |line| -- escape_windows_batch_characters(line) - # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can - # confidently prefix every actual command with &&. - # TODO: Why does ^\n&& work directly through the commandline but not through SOAP? -+ if locate_config_value(:cygwin) -+ render_line = "" -+ if !line.nil? and !line.chomp.strip.nil? -+ render_line = " && echo '#{line.chomp.strip.gsub(/'/, '\'\\\\\1\'\'')}' >> #{bootstrap_bat_file}" -+ end -+ else -+ escape_windows_batch_characters(line) - render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})" -+ end - # Windows commands are limited to 8191 characters for machines running XP or higher but - # this includes the length of environment variables after they have been expanded. - # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner -@@ -405,8 +425,12 @@ class Chef - end - - def bootstrap_bat_file -+ if locate_config_value(:cygwin) -+ @bootstrap_bat_file ||= "\"bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" -+ else - @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" - end -+ end - - def warn_chef_config_secret_key - ui.info "* " * 40 -@@ -426,11 +450,14 @@ behavior will be removed and any 'encryp - # to whatever the target system is. We assume that we are only bootstrapping 1 node at a time - # so we don't need to worry about multipe responses from this command. - def set_target_architecture(bootstrap_architecture) -+ if locate_config_value(:cygwin) -+ else - session_results = relay_winrm_command("echo %PROCESSOR_ARCHITECTURE%") - if session_results.empty? || session_results[0].stdout.strip.empty? - raise "Response to 'echo %PROCESSOR_ARCHITECTURE%' command was invalid: #{session_results}" - end - current_architecture = session_results[0].stdout.strip == "X86" ? :i386 : :x86_64 -+ end - - if bootstrap_architecture.nil? - architecture = current_architecture -diff -rupN knife-windows-1.4.0.pristine/lib/chef/knife/bootstrap_windows_ssh.rb knife-windows-1.4.0/lib/chef/knife/bootstrap_windows_ssh.rb ---- knife-windows-1.4.0.pristine/lib/chef/knife/bootstrap_windows_ssh.rb 2016-08-16 12:25:22.000000000 -0400 -+++ knife-windows-1.4.0/lib/chef/knife/bootstrap_windows_ssh.rb 2016-04-12 20:18:37.580414402 -0400 -@@ -91,12 +91,24 @@ class Chef - :boolean => true, - :default => true - -+ option :cygwin, -+ :long => "--[no-]cygwin", -+ :short => "-c", -+ :description => "Assume that we have Cygwin (and a bash shell) at the client end.", -+ :boolean => true, -+ :default => false -+ - def run - bootstrap - end - - def run_command(command = '') - ssh = Chef::Knife::Ssh.new -+ if locate_config_value(:cygwin) -+ # Harvest crucial env variables that don't exist by default in -+ # Cygwin shells. -+ command = %q{export CYGWIN=nodosfilewarning && for __dir in /proc/registry/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session\ Manager/Environment;do cd "$__dir";for __var in *;do __var=`echo $__var | tr "[a-z]" "[A-Z]"` ; test -z "${!__var}" && export $__var="`cat $__var`" >/dev/null 2>&1;done;/bin/true;done && export TEMP="$SYSTEMROOT/TEMP" && export TMP="$TEMP"} + " && cd && " + command -+ end - ssh.name_args = [ server_name, command ] - ssh.config[:ssh_user] = locate_config_value(:ssh_user) - ssh.config[:ssh_password] = locate_config_value(:ssh_password) -diff -rupN knife-windows-1.4.0.pristine/lib/chef/knife/core/windows_bootstrap_context.rb knife-windows-1.4.0/lib/chef/knife/core/windows_bootstrap_context.rb ---- knife-windows-1.4.0.pristine/lib/chef/knife/core/windows_bootstrap_context.rb 2016-08-16 12:25:22.000000000 -0400 -+++ knife-windows-1.4.0/lib/chef/knife/core/windows_bootstrap_context.rb 2016-04-12 20:18:37.580414402 -0400 -@@ -285,7 +285,12 @@ WGET_PS - url += "&pv=#{machine_os}" unless machine_os.nil? - url += "&m=#{machine_arch}" unless machine_arch.nil? - url += "&DownloadContext=#{download_context}" unless download_context.nil? -+ if !@config[:bootstrap_version].nil? and @config[:bootstrap_version] -+ require 'uri' -+ url += "&v=#{URI.escape(@config[:bootstrap_version])}" -+ else - url += latest_current_windows_chef_version_query -+ end - else - @config[:msi_url] - end diff --git a/install/knife-windows-cygwin-1.8.0.patch b/install/knife-windows-cygwin-1.8.0.patch deleted file mode 100644 index 62cbc635c..000000000 --- a/install/knife-windows-cygwin-1.8.0.patch +++ /dev/null @@ -1,1088 +0,0 @@ -diff -rupN knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_base.rb knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_base.rb ---- knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_base.rb 2017-01-23 14:20:03.993814602 -0500 -+++ knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_base.rb 2017-01-23 14:22:19.508463131 -0500 -@@ -331,7 +331,11 @@ class Chef - # we have to run the remote commands in 2047 char chunks - create_bootstrap_bat_command do |command_chunk| - begin -- render_command_result = run_command(command_chunk) -+ render_command = command_chunk -+ if locate_config_value(:cygwin) -+ render_command = %q!cd $TEMP && !+command_chunk -+ end -+ render_command_result = run_command(render_command) - ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0 - render_command_result - rescue SystemExit => e -@@ -353,11 +357,20 @@ class Chef - end - - def bootstrap_command -+ if locate_config_value(:cygwin) -+ @bootstrap_command ||= "cd $TEMP && cmd.exe /C #{bootstrap_bat_file}" -+ else - @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}" - end -+ @bootstrap_command -+ end - - def bootstrap_render_banner_command(chunk_num) -- "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}" -+ if locate_config_value(:cygwin) -+ return "echo 'Rendering #{bootstrap_bat_file} chunk #{chunk_num}'" -+ else -+ return "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}" -+ end - end - - def escape_windows_batch_characters(line) -@@ -370,11 +383,18 @@ class Chef - bootstrap_bat = "" - banner = bootstrap_render_banner_command(chunk_num += 1) - render_template(load_template(config[:bootstrap_template])).each_line do |line| -- escape_windows_batch_characters(line) - # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can - # confidently prefix every actual command with &&. - # TODO: Why does ^\n&& work directly through the commandline but not through SOAP? -+ if locate_config_value(:cygwin) -+ render_line = "" -+ if !line.nil? and !line.chomp.strip.nil? -+ render_line = " && echo '#{line.chomp.strip.gsub(/'/, '\'\\\\\1\'\'')}' >> #{bootstrap_bat_file}" -+ end -+ else -+ escape_windows_batch_characters(line) - render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})" -+ end - # Windows commands are limited to 8191 characters for machines running XP or higher but - # this includes the length of environment variables after they have been expanded. - # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner -@@ -401,8 +421,12 @@ class Chef - end - - def bootstrap_bat_file -+ if locate_config_value(:cygwin) -+ @bootstrap_bat_file ||= "\"bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" -+ else - @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" - end -+ end - - def warn_chef_config_secret_key - ui.info "* " * 40 -diff -rupN knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_base.rb.orig knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_base.rb.orig ---- knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_base.rb.orig 1969-12-31 19:00:00.000000000 -0500 -+++ knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_base.rb.orig 2017-01-23 14:20:03.993814602 -0500 -@@ -0,0 +1,443 @@ -+# -+# Author:: Seth Chisamore () -+# Copyright:: Copyright (c) 2011-2016 Chef Software, 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 'chef/knife' -+require 'chef/knife/bootstrap' -+require 'chef/encrypted_data_bag_item' -+require 'chef/knife/core/windows_bootstrap_context' -+require 'chef/knife/knife_windows_base' -+# Chef 11 PathHelper doesn't have #home -+#require 'chef/util/path_helper' -+ -+class Chef -+ class Knife -+ module BootstrapWindowsBase -+ -+ include Chef::Knife::KnifeWindowsBase -+ -+ # :nodoc: -+ # Would prefer to do this in a rational way, but can't be done b/c of -+ # Mixlib::CLI's design :( -+ def self.included(includer) -+ includer.class_eval do -+ -+ deps do -+ require 'readline' -+ require 'chef/json_compat' -+ end -+ -+ option :chef_node_name, -+ :short => "-N NAME", -+ :long => "--node-name NAME", -+ :description => "The Chef node name for your new node" -+ -+ option :prerelease, -+ :long => "--prerelease", -+ :description => "Install the pre-release chef gems" -+ -+ option :bootstrap_version, -+ :long => "--bootstrap-version VERSION", -+ :description => "The version of Chef to install", -+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v } -+ -+ option :bootstrap_proxy, -+ :long => "--bootstrap-proxy PROXY_URL", -+ :description => "The proxy server for the node being bootstrapped", -+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p } -+ -+ option :bootstrap_no_proxy, -+ :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]", -+ :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode", -+ :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np } -+ -+ option :bootstrap_install_command, -+ :long => "--bootstrap-install-command COMMANDS", -+ :description => "Custom command to install chef-client", -+ :proc => Proc.new { |ic| Chef::Config[:knife][:bootstrap_install_command] = ic } -+ -+ # DEPR: Remove this option in Chef 13 -+ option :distro, -+ :short => "-d DISTRO", -+ :long => "--distro DISTRO", -+ :description => "Bootstrap a distro using a template. [DEPRECATED] Use -t / --bootstrap-template option instead.", -+ :proc => Proc.new { |v| -+ Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use --bootstrap-template option instead.") -+ v -+ } -+ -+ option :bootstrap_template, -+ :short => "-t TEMPLATE", -+ :long => "--bootstrap-template TEMPLATE", -+ :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates." -+ -+ # DEPR: Remove this option in Chef 13 -+ option :template_file, -+ :long => "--template-file TEMPLATE", -+ :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.", -+ :proc => Proc.new { |v| -+ Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use --bootstrap-template option instead.") -+ v -+ } -+ -+ option :run_list, -+ :short => "-r RUN_LIST", -+ :long => "--run-list RUN_LIST", -+ :description => "Comma separated list of roles/recipes to apply", -+ :proc => lambda { |o| o.split(",") }, -+ :default => [] -+ -+ option :hint, -+ :long => "--hint HINT_NAME[=HINT_FILE]", -+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.", -+ :proc => Proc.new { |h| -+ Chef::Config[:knife][:hints] ||= Hash.new -+ name, path = h.split("=") -+ Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new -+ } -+ -+ option :first_boot_attributes, -+ :short => "-j JSON_ATTRIBS", -+ :long => "--json-attributes", -+ :description => "A JSON string to be added to the first run of chef-client", -+ :proc => lambda { |o| JSON.parse(o) }, -+ :default => nil -+ -+ option :first_boot_attributes_from_file, -+ :long => "--json-attribute-file FILE", -+ :description => "A JSON file to be used to the first run of chef-client", -+ :proc => lambda { |o| Chef::JSONCompat.parse(File.read(o)) }, -+ :default => nil -+ -+ # Mismatch between option 'encrypted_data_bag_secret' and it's long value '--secret' is by design for compatibility -+ option :encrypted_data_bag_secret, -+ :short => "-s SECRET", -+ :long => "--secret ", -+ :description => "The secret key to use to decrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config.", -+ :default => false -+ -+ # Mismatch between option 'encrypted_data_bag_secret_file' and it's long value '--secret-file' is by design for compatibility -+ option :encrypted_data_bag_secret_file, -+ :long => "--secret-file SECRET_FILE", -+ :description => "A file containing the secret key to use to encrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config." -+ -+ option :auth_timeout, -+ :long => "--auth-timeout MINUTES", -+ :description => "The maximum time in minutes to wait to for authentication over the transport to the node to succeed. The default value is 2 minutes.", -+ :default => 2 -+ -+ option :node_ssl_verify_mode, -+ :long => "--node-ssl-verify-mode [peer|none]", -+ :description => "Whether or not to verify the SSL cert for all HTTPS requests.", -+ :proc => Proc.new { |v| -+ valid_values = ["none", "peer"] -+ unless valid_values.include?(v) -+ raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}" -+ end -+ v -+ } -+ -+ option :node_verify_api_cert, -+ :long => "--[no-]node-verify-api-cert", -+ :description => "Verify the SSL cert for HTTPS requests to the Chef server API.", -+ :boolean => true -+ -+ option :msi_url, -+ :short => "-u URL", -+ :long => "--msi-url URL", -+ :description => "Location of the Chef Client MSI. The default templates will prefer to download from this location. The MSI will be downloaded from chef.io if not provided.", -+ :default => '' -+ -+ option :install_as_service, -+ :long => "--install-as-service", -+ :description => "Install chef-client as a Windows service", -+ :default => false -+ -+ option :bootstrap_vault_file, -+ :long => '--bootstrap-vault-file VAULT_FILE', -+ :description => 'A JSON file with a list of vault(s) and item(s) to be updated' -+ -+ option :bootstrap_vault_json, -+ :long => '--bootstrap-vault-json VAULT_JSON', -+ :description => 'A JSON string with the vault(s) and item(s) to be updated' -+ -+ option :bootstrap_vault_item, -+ :long => '--bootstrap-vault-item VAULT_ITEM', -+ :description => 'A single vault and item to update as "vault:item"', -+ :proc => Proc.new { |i| -+ (vault, item) = i.split(/:/) -+ Chef::Config[:knife][:bootstrap_vault_item] ||= {} -+ Chef::Config[:knife][:bootstrap_vault_item][vault] ||= [] -+ Chef::Config[:knife][:bootstrap_vault_item][vault].push(item) -+ Chef::Config[:knife][:bootstrap_vault_item] -+ } -+ -+ option :policy_name, -+ :long => "--policy-name POLICY_NAME", -+ :description => "Policyfile name to use (--policy-group must also be given)", -+ :default => nil -+ -+ option :policy_group, -+ :long => "--policy-group POLICY_GROUP", -+ :description => "Policy group name to use (--policy-name must also be given)", -+ :default => nil -+ -+ option :tags, -+ :long => "--tags TAGS", -+ :description => "Comma separated list of tags to apply to the node", -+ :proc => lambda { |o| o.split(/[\s,]+/) }, -+ :default => [] -+ end -+ end -+ -+ def default_bootstrap_template -+ "windows-chef-client-msi" -+ end -+ -+ def bootstrap_template -+ # The order here is important. We want to check if we have the new Chef 12 option is set first. -+ # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at -+ # the end. -+ config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template -+ end -+ -+ # TODO: This should go away when CHEF-2193 is fixed -+ def load_template(template=nil) -+ # Are we bootstrapping using an already shipped template? -+ -+ template = bootstrap_template -+ -+ # Use the template directly if it's a path to an actual file -+ if File.exists?(template) -+ Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}") -+ return IO.read(template).chomp -+ end -+ -+ # Otherwise search the template directories until we find the right one -+ bootstrap_files = [] -+ bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb") -+ bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir -+ ::Knife::Windows::PathHelper.all_homes('.chef', 'bootstrap', "#{template}.erb") { |p| bootstrap_files << p } -+ bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb")) -+ bootstrap_files.flatten! -+ -+ template = Array(bootstrap_files).find do |bootstrap_template| -+ Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}") -+ ::File.exists?(bootstrap_template) -+ end -+ -+ unless template -+ ui.info("Can not find bootstrap definition for #{config[:distro]}") -+ raise Errno::ENOENT -+ end -+ -+ Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}") -+ -+ IO.read(template).chomp -+ end -+ -+ def bootstrap_context -+ @bootstrap_context ||= Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config) -+ end -+ -+ def load_correct_secret -+ knife_secret_file = Chef::Config[:knife][:encrypted_data_bag_secret_file] -+ knife_secret = Chef::Config[:knife][:encrypted_data_bag_secret] -+ cli_secret_file = config[:encrypted_data_bag_secret_file] -+ cli_secret = config[:encrypted_data_bag_secret] -+ -+ cli_secret_file = nil if cli_secret_file == knife_secret_file -+ cli_secret = nil if cli_secret == knife_secret -+ -+ cli_secret_file = Chef::EncryptedDataBagItem.load_secret(cli_secret_file) if cli_secret_file != nil -+ knife_secret_file = Chef::EncryptedDataBagItem.load_secret(knife_secret_file) if knife_secret_file != nil -+ -+ cli_secret_file || cli_secret || knife_secret_file || knife_secret -+ end -+ -+ def first_boot_attributes -+ config[:first_boot_attributes] || config[:first_boot_attributes_from_file] || {} -+ end -+ -+ def render_template(template=nil) -+ config[:first_boot_attributes] = first_boot_attributes -+ config[:secret] = load_correct_secret -+ Erubis::Eruby.new(template).evaluate(bootstrap_context) -+ end -+ -+ def bootstrap(proto=nil) -+ if Chef::Config[:knife][:encrypted_data_bag_secret_file] || Chef::Config[:knife][:encrypted_data_bag_secret] -+ warn_chef_config_secret_key -+ end -+ -+ set_target_architecture -+ -+ # adding respond_to? so this works with pre 12.4 chef clients -+ validate_options! if respond_to?(:validate_options!) -+ -+ @node_name = Array(@name_args).first -+ # back compat--templates may use this setting: -+ config[:server_name] = @node_name -+ -+ STDOUT.sync = STDERR.sync = true -+ -+ if Chef::VERSION.split('.').first.to_i == 11 && Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])) -+ ui.error("Unable to find validation key. Please verify your configuration file for validation_key config value.") -+ exit 1 -+ end -+ -+ if (defined?(chef_vault_handler) && chef_vault_handler.doing_chef_vault?) || -+ (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key]))) -+ -+ unless locate_config_value(:chef_node_name) -+ ui.error("You must pass a node name with -N when bootstrapping with user credentials") -+ exit 1 -+ end -+ -+ client_builder.run -+ -+ if client_builder.respond_to?(:client) -+ chef_vault_handler.run(client_builder.client) -+ else -+ chef_vault_handler.run(node_name: config[:chef_node_name]) -+ end -+ -+ bootstrap_context.client_pem = client_builder.client_path -+ -+ else -+ ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...") -+ ui.info("Delete your validation key in order to use your user credentials instead") -+ ui.info("") -+ end -+ -+ wait_for_remote_response( config[:auth_timeout].to_i ) -+ -+ ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}") -+ # create a bootstrap.bat file on the node -+ # we have to run the remote commands in 2047 char chunks -+ create_bootstrap_bat_command do |command_chunk| -+ begin -+ render_command_result = run_command(command_chunk) -+ ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0 -+ render_command_result -+ rescue SystemExit => e -+ raise unless e.success? -+ end -+ end -+ -+ # execute the bootstrap.bat file -+ bootstrap_command_result = run_command(bootstrap_command) -+ ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0 -+ -+ bootstrap_command_result -+ end -+ -+ protected -+ -+ # Default implementation -- override only if required by the transport -+ def wait_for_remote_response(wait_max_minutes) -+ end -+ -+ def bootstrap_command -+ @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}" -+ end -+ -+ def bootstrap_render_banner_command(chunk_num) -+ "cmd.exe /C echo Rendering #{bootstrap_bat_file} chunk #{chunk_num}" -+ end -+ -+ def escape_windows_batch_characters(line) -+ # TODO: The commands are going to get redirected - do we need to escape &? -+ line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"} -+ end -+ -+ def create_bootstrap_bat_command() -+ chunk_num = 0 -+ bootstrap_bat = "" -+ banner = bootstrap_render_banner_command(chunk_num += 1) -+ render_template(load_template(config[:bootstrap_template])).each_line do |line| -+ escape_windows_batch_characters(line) -+ # We are guaranteed to have a prefix "banner" command that echo's chunk number. We can -+ # confidently prefix every actual command with &&. -+ # TODO: Why does ^\n&& work directly through the commandline but not through SOAP? -+ render_line = " && >> #{bootstrap_bat_file} (echo.#{line.chomp.strip})" -+ # Windows commands are limited to 8191 characters for machines running XP or higher but -+ # this includes the length of environment variables after they have been expanded. -+ # Since we don't actually know how long %TEMP% (and it's used twice - once in the banner -+ # and once in every command redirection), we simply guess and set the max to 5000. -+ # TODO: When a more accurate method is available, fix this. -+ if bootstrap_bat.length + render_line.length + banner.length > 5000 -+ # Can't fit it into this chunk? - flush (if necessary) and then try. -+ # Do this first because banner.length might change (e.g. due to an extra digit) and -+ # prevent a fit. -+ unless bootstrap_bat.empty? -+ yield banner + bootstrap_bat -+ bootstrap_bat = "" -+ banner = bootstrap_render_banner_command(chunk_num += 1) -+ end -+ # Will this ever fit? -+ if render_line.length + banner.length > 5000 -+ raise "Command in bootstrap template too long by #{render_line.length + banner.length - 5000} characters : #{line}" -+ end -+ end -+ bootstrap_bat << render_line -+ end -+ raise "Bootstrap template was empty! Check #{config[:bootstrap_template]}" if bootstrap_bat.empty? -+ yield banner + bootstrap_bat -+ end -+ -+ def bootstrap_bat_file -+ @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" -+ end -+ -+ def warn_chef_config_secret_key -+ ui.info "* " * 40 -+ ui.warn(<<-WARNING) -+\nSpecifying the encrypted data bag secret key using an 'encrypted_data_bag_secret' -+entry in 'knife.rb' is deprecated. Please use the '--secret' or '--secret-file' -+options of this command instead. -+ -+#{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this -+behavior will be removed and any 'encrypted_data_bag_secret' entries in -+'knife.rb' will be ignored completely. -+ WARNING -+ ui.info "* " * 40 -+ end -+ -+ # We allow the user to specify the desired architecture of Chef to install or we default -+ # to whatever the target system is. -+ # This is because a user might want to install a 32bit chef client on a 64bit machine -+ def set_target_architecture -+ if Chef::Config[:knife][:architecture] -+ raise "Do not set :architecture in your knife config, use :bootstrap_architecture." -+ end -+ -+ if Chef::Config[:knife][:bootstrap_architecture] -+ bootstrap_architecture = Chef::Config[:knife][:bootstrap_architecture] -+ -+ if ![:x86_64, :i386].include?(bootstrap_architecture.to_sym) -+ raise "Valid values for the knife config :bootstrap_architecture are i386 or x86_64. Supplied value is #{bootstrap_architecture}" -+ end -+ -+ # The windows install script wants i686, not i386 -+ bootstrap_architecture = :i686 if bootstrap_architecture == :i386 -+ Chef::Config[:knife][:architecture] = bootstrap_architecture -+ end -+ end -+ end -+ end -+end -diff -rupN knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_ssh.rb knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_ssh.rb ---- knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_ssh.rb 2017-01-23 14:20:03.993814602 -0500 -+++ knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_ssh.rb 2017-01-23 14:26:35.622357039 -0500 -@@ -91,6 +91,14 @@ class Chef - :boolean => true, - :default => true - -+ option :cygwin, -+ :long => "--[no-]cygwin", -+ :short => "-c", -+ :description => "Assume that we have Cygwin (and a bash shell) at the client end.", -+ :boolean => true, -+ :default => false -+ -+ - def run - validate_name_args! - bootstrap -@@ -98,6 +106,12 @@ class Chef - - def run_command(command = '') - ssh = Chef::Knife::Ssh.new -+ if locate_config_value(:cygwin) -+ # Harvest crucial env variables that don't exist by default in -+ # Cygwin shells. -+ command = %q{export CYGWIN=nodosfilewarning && for __dir in /proc/registry/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session\ Manager/Environment;do cd "$__dir";for __var in *;do __var=`echo $__var | tr "[a-z]" "[A-Z]"` ; test -z "${!__var}" && export $__var="`cat $__var`" >/dev/null 2>&1;done;/bin/true;done && export TEMP="$SYSTEMROOT/TEMP" && export TMP="$TEMP"} + " && cd && " + command -+ end -+ - ssh.name_args = [ server_name, command ] - ssh.config[:ssh_user] = locate_config_value(:ssh_user) - ssh.config[:ssh_password] = locate_config_value(:ssh_password) -diff -rupN knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_ssh.rb.orig knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_ssh.rb.orig ---- knife-windows-1.8.0/lib/chef/knife/bootstrap_windows_ssh.rb.orig 1969-12-31 19:00:00.000000000 -0500 -+++ knife-windows-1.8.0.patched/lib/chef/knife/bootstrap_windows_ssh.rb.orig 2017-01-23 14:20:03.000000000 -0500 -@@ -0,0 +1,116 @@ -+# -+# Author:: Seth Chisamore () -+# Copyright:: Copyright (c) 2011-2016 Chef Software, 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 'chef/knife/bootstrap_windows_base' -+ -+class Chef -+ class Knife -+ class BootstrapWindowsSsh < Bootstrap -+ -+ include Chef::Knife::BootstrapWindowsBase -+ -+ deps do -+ require 'chef/knife/core/windows_bootstrap_context' -+ require 'chef/json_compat' -+ require 'tempfile' -+ require 'highline' -+ require 'net/ssh' -+ require 'net/ssh/multi' -+ Chef::Knife::Ssh.load_deps -+ end -+ -+ banner "knife bootstrap windows ssh FQDN (options)" -+ -+ option :ssh_user, -+ :short => "-x USERNAME", -+ :long => "--ssh-user USERNAME", -+ :description => "The ssh username", -+ :default => "root" -+ -+ option :ssh_password, -+ :short => "-P PASSWORD", -+ :long => "--ssh-password PASSWORD", -+ :description => "The ssh password" -+ -+ option :ssh_port, -+ :short => "-p PORT", -+ :long => "--ssh-port PORT", -+ :description => "The ssh port", -+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key.strip } -+ -+ option :ssh_gateway, -+ :short => "-G GATEWAY", -+ :long => "--ssh-gateway GATEWAY", -+ :description => "The ssh gateway", -+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key } -+ -+ option :forward_agent, -+ :short => "-A", -+ :long => "--forward-agent", -+ :description => "Enable SSH agent forwarding", -+ :boolean => true -+ -+ option :identity_file, -+ :long => "--identity-file IDENTITY_FILE", -+ :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead." -+ -+ option :ssh_identity_file, -+ :short => "-i IDENTITY_FILE", -+ :long => "--ssh-identity-file IDENTITY_FILE", -+ :description => "The SSH identity file used for authentication" -+ -+ # DEPR: Remove this option for the next release. -+ option :host_key_verification, -+ :long => "--[no-]host-key-verify", -+ :description => "Verify host key, enabled by default. [DEPRECATED] Use --host-key-verify option instead.", -+ :boolean => true, -+ :default => true, -+ :proc => Proc.new { |key| -+ Chef::Log.warn("[DEPRECATED] --host-key-verification option is deprecated. Use --host-key-verify option instead.") -+ config[:host_key_verify] = key -+ } -+ -+ option :host_key_verify, -+ :long => "--[no-]host-key-verify", -+ :description => "Verify host key, enabled by default.", -+ :boolean => true, -+ :default => true -+ -+ def run -+ validate_name_args! -+ bootstrap -+ end -+ -+ def run_command(command = '') -+ ssh = Chef::Knife::Ssh.new -+ ssh.name_args = [ server_name, command ] -+ ssh.config[:ssh_user] = locate_config_value(:ssh_user) -+ ssh.config[:ssh_password] = locate_config_value(:ssh_password) -+ ssh.config[:ssh_port] = locate_config_value(:ssh_port) -+ ssh.config[:ssh_gateway] = locate_config_value(:ssh_gateway) -+ ssh.config[:identity_file] = config[:identity_file] -+ ssh.config[:ssh_identity_file] = config[:ssh_identity_file] || config[:identity_file] -+ ssh.config[:forward_agent] = config[:forward_agent] -+ ssh.config[:manual] = true -+ ssh.config[:host_key_verify] = config[:host_key_verify] -+ ssh.run -+ end -+ -+ end -+ end -+end -Binary files knife-windows-1.8.0/lib/chef/knife/.bootstrap_windows_ssh.rb.swp and knife-windows-1.8.0.patched/lib/chef/knife/.bootstrap_windows_ssh.rb.swp differ -diff -rupN knife-windows-1.8.0/lib/chef/knife/core/windows_bootstrap_context.rb knife-windows-1.8.0.patched/lib/chef/knife/core/windows_bootstrap_context.rb ---- knife-windows-1.8.0/lib/chef/knife/core/windows_bootstrap_context.rb 2017-01-23 14:20:03.994814629 -0500 -+++ knife-windows-1.8.0.patched/lib/chef/knife/core/windows_bootstrap_context.rb 2017-01-23 14:22:19.508463131 -0500 -@@ -310,7 +310,12 @@ WGET_PS - url += "&pv=#{machine_os}" unless machine_os.nil? - url += "&m=#{machine_arch}" unless machine_arch.nil? - url += "&DownloadContext=#{download_context}" unless download_context.nil? -+ if !@config[:bootstrap_version].nil? and @config[:bootstrap_version] -+ require 'uri' -+ url += "&v=#{URI.escape(@config[:bootstrap_version])}" -+ else - url += latest_current_windows_chef_version_query -+ end - else - @config[:msi_url] - end -diff -rupN knife-windows-1.8.0/lib/chef/knife/core/windows_bootstrap_context.rb.orig knife-windows-1.8.0.patched/lib/chef/knife/core/windows_bootstrap_context.rb.orig ---- knife-windows-1.8.0/lib/chef/knife/core/windows_bootstrap_context.rb.orig 1969-12-31 19:00:00.000000000 -0500 -+++ knife-windows-1.8.0.patched/lib/chef/knife/core/windows_bootstrap_context.rb.orig 2017-01-23 14:20:03.994814629 -0500 -@@ -0,0 +1,397 @@ -+# -+# Author:: Seth Chisamore () -+# Copyright:: Copyright (c) 2011-2016 Chef Software, 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 'chef/knife/core/bootstrap_context' -+# Chef::Util::PathHelper in Chef 11 is a bit juvenile still -+require 'knife-windows/path_helper' -+# require 'chef/util/path_helper' -+ -+class Chef -+ class Knife -+ module Core -+ # Instances of BootstrapContext are the context objects (i.e., +self+) for -+ # bootstrap templates. For backwards compatability, they +must+ set the -+ # following instance variables: -+ # * @config - a hash of knife's config values -+ # * @run_list - the run list for the node to boostrap -+ # -+ class WindowsBootstrapContext < BootstrapContext -+ PathHelper = ::Knife::Windows::PathHelper -+ -+ attr_accessor :client_pem -+ -+ def initialize(config, run_list, chef_config, secret=nil) -+ @config = config -+ @run_list = run_list -+ @chef_config = chef_config -+ @secret = secret -+ # Compatibility with Chef 12 and Chef 11 versions -+ begin -+ # Pass along the secret parameter for Chef 12 -+ super(config, run_list, chef_config, secret) -+ rescue ArgumentError -+ # The Chef 11 base class only has parameters for initialize -+ super(config, run_list, chef_config) -+ end -+ end -+ -+ def validation_key -+ if File.exist?(File.expand_path(@chef_config[:validation_key])) -+ IO.read(File.expand_path(@chef_config[:validation_key])) -+ else -+ false -+ end -+ end -+ -+ def secret -+ escape_and_echo(@config[:secret]) -+ end -+ -+ def trusted_certs_script -+ @trusted_certs_script ||= trusted_certs_content -+ end -+ -+ def config_content -+ client_rb = <<-CONFIG -+chef_server_url "#{@chef_config[:chef_server_url]}" -+validation_client_name "#{@chef_config[:validation_client_name]}" -+file_cache_path "c:/chef/cache" -+file_backup_path "c:/chef/backup" -+cache_options ({:path => "c:/chef/cache/checksums", :skip_expires => true}) -+ CONFIG -+ if @config[:chef_node_name] -+ client_rb << %Q{node_name "#{@config[:chef_node_name]}"\n} -+ else -+ client_rb << "# Using default node name (fqdn)\n" -+ end -+ -+ if @chef_config[:config_log_level] -+ client_rb << %Q{log_level :#{@chef_config[:config_log_level]}\n} -+ else -+ client_rb << "log_level :info\n" -+ end -+ -+ client_rb << "log_location #{get_log_location}" -+ -+ # We configure :verify_api_cert only when it's overridden on the CLI -+ # or when specified in the knife config. -+ if !@config[:node_verify_api_cert].nil? || knife_config.has_key?(:verify_api_cert) -+ value = @config[:node_verify_api_cert].nil? ? knife_config[:verify_api_cert] : @config[:node_verify_api_cert] -+ client_rb << %Q{verify_api_cert #{value}\n} -+ end -+ -+ # We configure :ssl_verify_mode only when it's overridden on the CLI -+ # or when specified in the knife config. -+ if @config[:node_ssl_verify_mode] || knife_config.has_key?(:ssl_verify_mode) -+ value = case @config[:node_ssl_verify_mode] -+ when "peer" -+ :verify_peer -+ when "none" -+ :verify_none -+ when nil -+ knife_config[:ssl_verify_mode] -+ else -+ nil -+ end -+ -+ if value -+ client_rb << %Q{ssl_verify_mode :#{value}\n} -+ end -+ end -+ -+ if @config[:ssl_verify_mode] -+ client_rb << %Q{ssl_verify_mode :#{knife_config[:ssl_verify_mode]}\n} -+ end -+ -+ if knife_config[:bootstrap_proxy] -+ client_rb << "\n" -+ client_rb << %Q{http_proxy "#{knife_config[:bootstrap_proxy]}"\n} -+ client_rb << %Q{https_proxy "#{knife_config[:bootstrap_proxy]}"\n} -+ client_rb << %Q{no_proxy "#{knife_config[:bootstrap_no_proxy]}"\n} if knife_config[:bootstrap_no_proxy] -+ end -+ -+ if knife_config[:bootstrap_no_proxy] -+ client_rb << %Q{no_proxy "#{knife_config[:bootstrap_no_proxy]}"\n} -+ end -+ -+ if @config[:secret] -+ client_rb << %Q{encrypted_data_bag_secret "c:/chef/encrypted_data_bag_secret"\n} -+ end -+ -+ unless trusted_certs_script.empty? -+ client_rb << %Q{trusted_certs_dir "c:/chef/trusted_certs"\n} -+ end -+ -+ if Chef::Config[:fips] -+ client_rb << <<-CONFIG -+fips true -+chef_version = ::Chef::VERSION.split(".") -+unless chef_version[0].to_i > 12 || (chef_version[0].to_i == 12 && chef_version[1].to_i >= 8) -+ raise "FIPS Mode requested but not supported by this client" -+end -+CONFIG -+ end -+ -+ escape_and_echo(client_rb) -+ end -+ -+ def get_log_location -+ if @chef_config[:config_log_location].equal?(:win_evt) -+ %Q{:#{@chef_config[:config_log_location]}\n} -+ elsif @chef_config[:config_log_location].equal?(:syslog) -+ raise "syslog is not supported for log_location on Windows OS\n" -+ elsif (@chef_config[:config_log_location].equal?(STDOUT)) -+ "STDOUT\n" -+ elsif (@chef_config[:config_log_location].equal?(STDERR)) -+ "STDERR\n" -+ elsif @chef_config[:config_log_location].nil? || @chef_config[:config_log_location].empty? -+ "STDOUT\n" -+ elsif @chef_config[:config_log_location] -+ %Q{"#{@chef_config[:config_log_location]}"\n} -+ else -+ "STDOUT\n" -+ end -+ end -+ -+ def start_chef -+ bootstrap_environment_option = bootstrap_environment.nil? ? '' : " -E #{bootstrap_environment}" -+ start_chef = "SET \"PATH=%PATH%;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin\"\n" -+ start_chef << "chef-client -c c:/chef/client.rb -j c:/chef/first-boot.json#{bootstrap_environment_option}\n" -+ end -+ -+ def latest_current_windows_chef_version_query -+ installer_version_string = nil -+ if @config[:prerelease] -+ installer_version_string = "&prerelease=true" -+ else -+ chef_version_string = if knife_config[:bootstrap_version] -+ knife_config[:bootstrap_version] -+ else -+ Chef::VERSION.split(".").first -+ end -+ -+ installer_version_string = "&v=#{chef_version_string}" -+ -+ # If bootstrapping a pre-release version add the prerelease query string -+ if chef_version_string.split(".").length > 3 -+ installer_version_string << "&prerelease=true" -+ end -+ end -+ -+ installer_version_string -+ end -+ -+ def win_wget -+ # I tried my best to figure out how to properly url decode and switch / to \ -+ # but this is VBScript - so I don't really care that badly. -+ win_wget = <<-WGET -+url = WScript.Arguments.Named("url") -+path = WScript.Arguments.Named("path") -+proxy = null -+'* Vaguely attempt to handle file:// scheme urls by url unescaping and switching all -+'* / into \. Also assume that file:/// is a local absolute path and that file:// -+'* is possibly a network file path. -+If InStr(url, "file://") = 1 Then -+url = Unescape(url) -+If InStr(url, "file:///") = 1 Then -+sourcePath = Mid(url, Len("file:///") + 1) -+Else -+sourcePath = Mid(url, Len("file:") + 1) -+End If -+sourcePath = Replace(sourcePath, "/", "\\") -+ -+Set objFSO = CreateObject("Scripting.FileSystemObject") -+If objFSO.Fileexists(path) Then objFSO.DeleteFile path -+objFSO.CopyFile sourcePath, path, true -+Set objFSO = Nothing -+ -+Else -+Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP") -+Set wshShell = CreateObject( "WScript.Shell" ) -+Set objUserVariables = wshShell.Environment("USER") -+ -+rem http proxy is optional -+rem attempt to read from HTTP_PROXY env var first -+On Error Resume Next -+ -+If NOT (objUserVariables("HTTP_PROXY") = "") Then -+proxy = objUserVariables("HTTP_PROXY") -+ -+rem fall back to named arg -+ElseIf NOT (WScript.Arguments.Named("proxy") = "") Then -+proxy = WScript.Arguments.Named("proxy") -+End If -+ -+If NOT isNull(proxy) Then -+rem setProxy method is only available on ServerXMLHTTP 6.0+ -+Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP.6.0") -+objXMLHTTP.setProxy 2, proxy -+End If -+ -+On Error Goto 0 -+ -+objXMLHTTP.open "GET", url, false -+objXMLHTTP.send() -+If objXMLHTTP.Status = 200 Then -+Set objADOStream = CreateObject("ADODB.Stream") -+objADOStream.Open -+objADOStream.Type = 1 -+objADOStream.Write objXMLHTTP.ResponseBody -+objADOStream.Position = 0 -+Set objFSO = Createobject("Scripting.FileSystemObject") -+If objFSO.Fileexists(path) Then objFSO.DeleteFile path -+Set objFSO = Nothing -+objADOStream.SaveToFile path -+objADOStream.Close -+Set objADOStream = Nothing -+End If -+Set objXMLHTTP = Nothing -+End If -+WGET -+ escape_and_echo(win_wget) -+ end -+ -+ def win_wget_ps -+ win_wget_ps = <<-WGET_PS -+param( -+ [String] $remoteUrl, -+ [String] $localPath -+) -+ -+$ProxyUrl = $env:http_proxy; -+$webClient = new-object System.Net.WebClient; -+ -+if ($ProxyUrl -ne '') { -+ $WebProxy = New-Object System.Net.WebProxy($ProxyUrl,$true) -+ $WebClient.Proxy = $WebProxy -+} -+ -+$webClient.DownloadFile($remoteUrl, $localPath); -+WGET_PS -+ -+ escape_and_echo(win_wget_ps) -+ end -+ -+ def install_chef -+ # The normal install command uses regular double quotes in -+ # the install command, so request such a string from install_command -+ install_chef = install_command('"') + "\n" + fallback_install_task_command -+ end -+ -+ def bootstrap_directory -+ bootstrap_directory = "C:\\chef" -+ end -+ -+ def local_download_path -+ local_download_path = "%TEMP%\\chef-client-latest.msi" -+ end -+ -+ def msi_url(machine_os=nil, machine_arch=nil, download_context=nil) -+ # The default msi path has a number of url query parameters - we attempt to substitute -+ # such parameters in as long as they are provided by the template. -+ -+ if @config[:msi_url].nil? || @config[:msi_url].empty? -+ url = "https://www.chef.io/chef/download?p=windows" -+ url += "&pv=#{machine_os}" unless machine_os.nil? -+ url += "&m=#{machine_arch}" unless machine_arch.nil? -+ url += "&DownloadContext=#{download_context}" unless download_context.nil? -+ url += latest_current_windows_chef_version_query -+ else -+ @config[:msi_url] -+ end -+ end -+ -+ def first_boot -+ escape_and_echo(super.to_json) -+ end -+ -+ # escape WIN BATCH special chars -+ # and prefixes each line with an -+ # echo -+ def escape_and_echo(file_contents) -+ file_contents.gsub(/^(.*)$/, 'echo.\1').gsub(/([(<|>)^])/, '^\1') -+ end -+ -+ private -+ -+ def install_command(executor_quote) -+ if @config[:install_as_service] -+ "msiexec /qn /log #{executor_quote}%CHEF_CLIENT_MSI_LOG_PATH%#{executor_quote} /i #{executor_quote}%LOCAL_DESTINATION_MSI_PATH%#{executor_quote} ADDLOCAL=#{executor_quote}ChefClientFeature,ChefServiceFeature#{executor_quote}" -+ else -+ "msiexec /qn /log #{executor_quote}%CHEF_CLIENT_MSI_LOG_PATH%#{executor_quote} /i #{executor_quote}%LOCAL_DESTINATION_MSI_PATH%#{executor_quote}" -+ end -+ end -+ -+ # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped -+ # This string should contain both the commands necessary to both create the files, as well as their content -+ def trusted_certs_content -+ content = "" -+ if @chef_config[:trusted_certs_dir] -+ Dir.glob(File.join(PathHelper.escape_glob_dir(@chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert| -+ content << "> #{bootstrap_directory}/trusted_certs/#{File.basename(cert)} (\n" + -+ escape_and_echo(IO.read(File.expand_path(cert))) + "\n)\n" -+ end -+ end -+ content -+ end -+ -+ def fallback_install_task_command -+ # This command will be executed by schtasks.exe in the batch -+ # code below. To handle tasks that contain arguments that -+ # need to be double quoted, schtasks allows the use of single -+ # quotes that will later be converted to double quotes -+ command = install_command('\'') -+<<-EOH -+ @set MSIERRORCODE=!ERRORLEVEL! -+ @if ERRORLEVEL 1 ( -+ @echo WARNING: Failed to install Chef Client MSI package in remote context with status code !MSIERRORCODE!. -+ @echo WARNING: This may be due to a defect in operating system update KB2918614: http://support.microsoft.com/kb/2918614 -+ @set OLDLOGLOCATION="%CHEF_CLIENT_MSI_LOG_PATH%-fail.log" -+ @move "%CHEF_CLIENT_MSI_LOG_PATH%" "!OLDLOGLOCATION!" > NUL -+ @echo WARNING: Saving installation log of failure at !OLDLOGLOCATION! -+ @echo WARNING: Retrying installation with local context... -+ @schtasks /create /f /sc once /st 00:00:00 /tn chefclientbootstraptask /ru SYSTEM /rl HIGHEST /tr \"cmd /c #{command} & sleep 2 & waitfor /s %computername% /si chefclientinstalldone\" -+ -+ @if ERRORLEVEL 1 ( -+ @echo ERROR: Failed to create Chef Client installation scheduled task with status code !ERRORLEVEL! > "&2" -+ ) else ( -+ @echo Successfully created scheduled task to install Chef Client. -+ @schtasks /run /tn chefclientbootstraptask -+ @if ERRORLEVEL 1 ( -+ @echo ERROR: Failed to execut Chef Client installation scheduled task with status code !ERRORLEVEL!. > "&2" -+ ) else ( -+ @echo Successfully started Chef Client installation scheduled task. -+ @echo Waiting for installation to complete -- this may take a few minutes... -+ waitfor chefclientinstalldone /t 600 -+ if ERRORLEVEL 1 ( -+ @echo ERROR: Timed out waiting for Chef Client package to install -+ ) else ( -+ @echo Finished waiting for Chef Client package to install. -+ ) -+ @schtasks /delete /f /tn chefclientbootstraptask > NUL -+ ) -+ ) -+ ) else ( -+ @echo Successfully installed Chef Client package. -+ ) -+EOH -+ end -+ end -+ end -+ end -+end -\ No newline at end of file From b9a4c724b2cd1b3060cb4e3c776ecdb60d80e6fb Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 3 Nov 2017 10:40:14 -0400 Subject: [PATCH 48/63] mu-configure: don't keel over if the user enters a wrong (stub) menu option --- bin/mu-configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/mu-configure b/bin/mu-configure index 2f88bc645..bf4a6f64c 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -660,7 +660,7 @@ if !$opts[:noninteractive] puts "" exit 0 end - if $MENU_MAP.has_key?(answer) + if $MENU_MAP.has_key?(answer) and $MENU_MAP[answer].has_key?("title") newval = ask($MENU_MAP[answer]) if !validate(newval, $MENU_MAP[answer]) sleep 1 From 9db930701156bce7664a17d490cff79fe0515285 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 3 Nov 2017 11:21:35 -0400 Subject: [PATCH 49/63] VPC manual peering warning: clarify that setting is per-accont --- modules/mu/clouds/aws/vpc.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mu/clouds/aws/vpc.rb b/modules/mu/clouds/aws/vpc.rb index d3df0b85d..ae1d0e454 100644 --- a/modules/mu/clouds/aws/vpc.rb +++ b/modules/mu/clouds/aws/vpc.rb @@ -671,7 +671,7 @@ def groom end } else - MU.log "VPC #{peer_id} is not managed by this Mu server or is not configured to auto-accept peering requests. You must accept the peering request for '#{@config['name']}' (#{@config['vpc_id']}) by hand.", MU::WARN, details: "In the AWS Console, go to VPC => Peering Connections and look in the Actions drop-down. You can also set 'Invade Foreign VPCs' to 'true' using mu-configure to auto-accept all peering connections within this account, regardless of whether this Mu server owns the VPCs." + MU.log "VPC #{peer_id} is not managed by this Mu server or is not configured to auto-accept peering requests. You must accept the peering request for '#{@config['name']}' (#{@config['vpc_id']}) by hand.", MU::WARN, details: "In the AWS Console, go to VPC => Peering Connections and look in the Actions drop-down. You can also set 'Invade Foreign VPCs' to 'true' using mu-configure to auto-accept all peering connections within this account, regardless of whether this Mu server owns the VPCs. This setting is per-user." end end From 4b837e4bc5f617c61ea15b17e94de4991edb3e03 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Sun, 5 Nov 2017 21:26:14 -0500 Subject: [PATCH 50/63] deploy nits for non-root users --- cookbooks/mu-tools/libraries/helper.rb | 2 +- modules/mu/deploy.rb | 3 ++- modules/mu/mommacat.rb | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cookbooks/mu-tools/libraries/helper.rb b/cookbooks/mu-tools/libraries/helper.rb index d08df4394..1d3b99268 100644 --- a/cookbooks/mu-tools/libraries/helper.rb +++ b/cookbooks/mu-tools/libraries/helper.rb @@ -116,7 +116,7 @@ def mommacat_request(action, arg) "mu_id" => mu_get_tag_value("MU-ID"), "mu_resource_name" => node[:service_name], "mu_resource_type" => res_type, - "mu_user" => node[:deployment][:chef_user], + "mu_user" => node[:deployment][:mu_user] || node[:deployment][:chef_user], "mu_deploy_secret" => get_deploy_secret, action => arg ) diff --git a/modules/mu/deploy.rb b/modules/mu/deploy.rb index 4f22c0892..307fa0141 100644 --- a/modules/mu/deploy.rb +++ b/modules/mu/deploy.rb @@ -202,7 +202,8 @@ def run "environment" => @environment, "seed" => MU.seed, "deployment_start_time" => @timestart, - "chef_user" => MU.chef_user + "chef_user" => MU.chef_user, + "mu_user" => MU.mu_user } @mommacat = MU::MommaCat.new( MU.deploy_id, diff --git a/modules/mu/mommacat.rb b/modules/mu/mommacat.rb index a9153c577..90493e62f 100644 --- a/modules/mu/mommacat.rb +++ b/modules/mu/mommacat.rb @@ -2190,7 +2190,7 @@ def nodeSSLCerts(server) "mu_resource_name" => server.config['name'], "mu_resource_type" => res_type, "mu_ssl_sign" => "#{MU.mySSLDir}/#{certname}.csr", - "mu_ssl_sans" => sans.join(","), + "mu_ssl_sans" => data["sans"].join(","), "mu_user" => MU.mu_user, "mu_deploy_secret" => deploysecret ) From 8b02939c0131c1f637f8051676f2ff31bfdeed72 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 7 Nov 2017 11:56:17 -0500 Subject: [PATCH 51/63] better check for bogus menu options in mu-configure --- bin/mu-configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/mu-configure b/bin/mu-configure index bf4a6f64c..331d49b36 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -660,7 +660,7 @@ if !$opts[:noninteractive] puts "" exit 0 end - if $MENU_MAP.has_key?(answer) and $MENU_MAP[answer].has_key?("title") + if $MENU_MAP.has_key?(answer) and !$MENU_MAP[answer].has_key?("subtree") newval = ask($MENU_MAP[answer]) if !validate(newval, $MENU_MAP[answer]) sleep 1 From cab9c1c273e6e7aa864dc88d177c6764349f9d55 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 8 Nov 2017 10:05:17 -0500 Subject: [PATCH 52/63] don't blow up when an ungroomed node calls to momma --- modules/mu/clouds/aws/server.rb | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/modules/mu/clouds/aws/server.rb b/modules/mu/clouds/aws/server.rb index cb341bdf5..e7e1c0141 100644 --- a/modules/mu/clouds/aws/server.rb +++ b/modules/mu/clouds/aws/server.rb @@ -95,22 +95,24 @@ def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil) @config = MU::Config.manxify(kitten_cfg) @cloud_id = cloud_id - @userdata = MU::Cloud::AWS::Server.fetchUserdata( - platform: @config["platform"], - template_variables: { - "deployKey" => Base64.urlsafe_encode64(@deploy.public_key), - "deploySSHKey" => @deploy.ssh_public_key, - "muID" => MU.deploy_id, - "muUser" => MU.mu_user, - "publicIP" => MU.mu_public_ip, - "skipApplyUpdates" => @config['skipinitialupdates'], - "windowsAdminName" => @config['windows_admin_username'], - "resourceName" => @config["name"], - "resourceType" => "server", - "platform" => @config["platform"] - }, - custom_append: @config['userdata_script'] - ) + if @deploy + @userdata = MU::Cloud::AWS::Server.fetchUserdata( + platform: @config["platform"], + template_variables: { + "deployKey" => Base64.urlsafe_encode64(@deploy.public_key), + "deploySSHKey" => @deploy.ssh_public_key, + "muID" => MU.deploy_id, + "muUser" => MU.mu_user, + "publicIP" => MU.mu_public_ip, + "skipApplyUpdates" => @config['skipinitialupdates'], + "windowsAdminName" => @config['windows_admin_username'], + "resourceName" => @config["name"], + "resourceType" => "server", + "platform" => @config["platform"] + }, + custom_append: @config['userdata_script'] + ) + end @disk_devices = MU::Cloud::AWS::Server.disk_devices @ephemeral_mappings = MU::Cloud::AWS::Server.ephemeral_mappings From 6153d206f771d94fee52d9e1418be0a0fe39cbdc Mon Sep 17 00:00:00 2001 From: Mu Master Date: Wed, 15 Nov 2017 15:43:00 +0000 Subject: [PATCH 53/63] AWS: always try to rig up ephemeral volumes, if the AMI didn't present them --- bin/mu-aws-setup | 15 +++++++++++++++ bin/mu-configure | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/bin/mu-aws-setup b/bin/mu-aws-setup index 2836cf3ec..87fd21268 100755 --- a/bin/mu-aws-setup +++ b/bin/mu-aws-setup @@ -43,6 +43,7 @@ Usage: opt :logs, "Ensure the presence of a cloud storage bucket for use with CloudTrails, syslog, deploy secrets, node SSL certificates, etc.", :require => false, :default => false, :type => :boolean opt :dns, "Ensure the presence of a private DNS Zone called for internal amongst Mu resources.", :require => false, :default => false, :type => :boolean opt :uploadlogs, "Push today's log files to the S3 bucket created by the -l option.", :require => false, :default => false, :type => :boolean + opt :ephemeral, "Make sure all of our instance store (ephemeral) block devices are mapped and available.", :require => false, :default => false, :type => :boolean end my_instance_id = MU::Cloud::AWS.getAWSMetaData("instance-id") @@ -52,6 +53,20 @@ instance = resp.reservations.first.instances.first preferred_ip = MU.mu_public_ip +if $opts[:ephemeral] + if instance.instance_type.match(/^(t2|m4)\./) + MU.log "t2 and m4 instance types do not have ephemeral volumes, skipping setup", MU::WARN + else +# instance.block_device_mappings.each { |dev| +# next if dev.ebs +# } + MU::Cloud::AWS.ec2.modify_instance_attribute( + instance_id: instance.instance_id, + block_device_mappings: MU::Cloud::AWS::Server.ephemeral_mappings + ) + end +end + # Create a security group, or manipulate an existing one, so that we have all # of the appropriate network holes. if $opts[:sg] diff --git a/bin/mu-configure b/bin/mu-configure index 331d49b36..03b13381b 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -865,7 +865,7 @@ else end if $IN_AWS and AMROOT - system("#{MU_BASE}/lib/bin/mu-aws-setup --dns --sg --logs") + system("#{MU_BASE}/lib/bin/mu-aws-setup --dns --sg --logs --ephemeral") # XXX --ip? Do we really care? end From d577f486fa6183d4d32782debad1463e979973ce Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 24 Nov 2017 11:18:12 -0500 Subject: [PATCH 54/63] see if we can avoid the Chef git resource leaving us on the 'deploy' non-branch --- cookbooks/mu-master/recipes/init.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index 15bee755e..a0ba10943 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -185,6 +185,8 @@ git "#{MU_BASE}/lib" do repository "git://github.com/cloudamatic/mu.git" revision MU_BRANCH + checkout_branch MU_BRANCH + enable_checkout false not_if { ::Dir.exists?("#{MU_BASE}/lib/.git") } notifies :run, "bash[set git default branch]", :immediately end From 0b96f4ffd8908e9afca7213709ce72318bb0e451 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 24 Nov 2017 13:18:47 -0500 Subject: [PATCH 55/63] testing pre-commit hook to auto-set branch name in installer --- cookbooks/mu-master/recipes/init.rb | 12 ++++++++++++ extras/git-fix-branch-hook | 25 +++++++++++++++++++++++++ extras/git-fix-permissions-hook | 2 +- install/installer | 8 ++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100755 extras/git-fix-branch-hook diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index a0ba10943..e5229e5c0 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -191,6 +191,18 @@ notifies :run, "bash[set git default branch]", :immediately end +# Enable some git hook weirdness for Mu developers +["post-merge", "post-checkout", "post-rewrite"].each { |hook| + remote_file "#{MU_BASE}/lib/.git/hooks/#{hook}" do + source "file://#{MU_BASE}/lib/extras/git-fix-permissions-hook" + mode 0755 + end +} +remote_file "#{MU_BASE}/lib/.git/hooks/pre-commit" do + source "file://#{MU_BASE}/lib/extras/git-fix-branch-hook" + mode 0755 +end + directory MU_BASE+"/var" do recursive true mode 0755 diff --git a/extras/git-fix-branch-hook b/extras/git-fix-branch-hook new file mode 100755 index 000000000..205e0d09a --- /dev/null +++ b/extras/git-fix-branch-hook @@ -0,0 +1,25 @@ +#!/bin/sh +# +# The name of the default branch of Mu is hardcoded in a couple of places for +# our installer to use. Mangle it to reflect the name of whatever branch is +# being committed, so that people don't have to think so hard when using dev +# branches. + +# XXX I don't like these non-qualified calls to executables, but OTOH this +# would behave in an incredibly annoying way on a system where someone stuck, +# say, git or GNU sed in a non-standard place. +if [ "`whoami`" == "root" ];then + # XXX dumbly assume we're in Mu's LIBDIR in .git/hooks + branch=`git branch | grep '^*' | cut -d' ' -f2` + if ! grep "^ MU_BRANCH=\"$branch\"" ../../install/installer ;then + sed -i "s/^ MU_BRANCH=\".*\"/ MU_BRANCH=\"$branch\"/" ../../install/installer + echo "Set default branch in install/installer to $branch" + git add ../../install/installer + fi + if ! grep "^MU_BRANCH=\"$branch\"" ../../cookbooks/mu-master/recipes/init.rb ;then + sed -i "s/^MU_BRANCH=\".*\"/MU_BRANCH=\"$branch\"/" ../../cookbooks/mu-master/recipes/init.rb + echo "Set default branch in cookbooks/mu-master/recipes/init.rb to $branch" + git add ../../cookbooks/mu-master/recipes/init.rb + fi + echo $scriptpath +fi diff --git a/extras/git-fix-permissions-hook b/extras/git-fix-permissions-hook index bd28dba9f..bd65356c5 100755 --- a/extras/git-fix-permissions-hook +++ b/extras/git-fix-permissions-hook @@ -4,8 +4,8 @@ if [ "`whoami`" == "root" ];then scriptpath="`dirname $0`" + # XXX dumbly assume we're in Mu's LIBDIR in .git/hooks library=1 - # assume we're in Mu's LIBDIR in .git/hooks source "`dirname $0`"/../../install/deprecated-bash-library.sh set_permissions "skip_rubies" fi diff --git a/install/installer b/install/installer index 1254e8db5..fff4ff5cc 100755 --- a/install/installer +++ b/install/installer @@ -1,5 +1,7 @@ #!/bin/sh +BOLD=`tput bold` +NORM=`tput sgr0` CHEF_CLIENT_VERSION="12.21.14-1" if [ "$MU_BRANCH" == "" ];then MU_BRANCH="master" @@ -44,11 +46,17 @@ if ! /bin/rpm -q $CHEF_CLIENT_PKG > /dev/null ;then /bin/sh /root/chef-install.sh -v $CHEF_CLIENT_VERSION fi + if [ -d /opt/mu/lib/cookbooks/mu-master/recipes ];then /opt/chef/bin/chef-apply /opt/mu/lib/cookbooks/mu-master/recipes/init.rb else + echo "****** Installing Mu from the ${BOLD}$MU_BRANCH${NORM} branch ******" + echo "***** Hit ^C now if that's not what you intended *****" + echo "*** Prepend ${BOLD}MU_BRANCH=some_branch_name${NORM} when calling me to use a different branch ***" + sleep 10 /usr/bin/curl https://raw.githubusercontent.com/cloudamatic/mu/$MU_BRANCH/cookbooks/mu-master/recipes/init.rb > /root/mu-master-init-recipe.rb /opt/chef/bin/chef-apply /root/mu-master-init-recipe.rb fi +echo "Launching ${BOLD}mu-configure${NORM}" /opt/mu/bin/mu-configure $@ From aa42907a4971ceaa49483be1e506094bd3b94fbb Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 24 Nov 2017 13:28:13 -0500 Subject: [PATCH 56/63] be better at paths in pre-commit hook --- extras/git-fix-branch-hook | 17 ++++++++++------- install/installer | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/extras/git-fix-branch-hook b/extras/git-fix-branch-hook index 205e0d09a..f95205371 100755 --- a/extras/git-fix-branch-hook +++ b/extras/git-fix-branch-hook @@ -9,17 +9,20 @@ # would behave in an incredibly annoying way on a system where someone stuck, # say, git or GNU sed in a non-standard place. if [ "`whoami`" == "root" ];then + if [ "$MU_LIBDIR" == "" ];then + MU_LIBDIR="/opt/mu/lib" + fi + cd $MU_LIBDIR # XXX dumbly assume we're in Mu's LIBDIR in .git/hooks branch=`git branch | grep '^*' | cut -d' ' -f2` - if ! grep "^ MU_BRANCH=\"$branch\"" ../../install/installer ;then - sed -i "s/^ MU_BRANCH=\".*\"/ MU_BRANCH=\"$branch\"/" ../../install/installer + if ! grep "^ MU_BRANCH=\"$branch\"" install/installer > /dev/null;then + sed -i "s/^ MU_BRANCH=\".*\"/ MU_BRANCH=\"$branch\"/" install/installer echo "Set default branch in install/installer to $branch" - git add ../../install/installer + git add install/installer fi - if ! grep "^MU_BRANCH=\"$branch\"" ../../cookbooks/mu-master/recipes/init.rb ;then - sed -i "s/^MU_BRANCH=\".*\"/MU_BRANCH=\"$branch\"/" ../../cookbooks/mu-master/recipes/init.rb + if ! grep "^MU_BRANCH=\"$branch\"" cookbooks/mu-master/recipes/init.rb > /dev/null;then + sed -i "s/^MU_BRANCH=\".*\"/MU_BRANCH=\"$branch\"/" cookbooks/mu-master/recipes/init.rb echo "Set default branch in cookbooks/mu-master/recipes/init.rb to $branch" - git add ../../cookbooks/mu-master/recipes/init.rb + git add cookbooks/mu-master/recipes/init.rb fi - echo $scriptpath fi diff --git a/install/installer b/install/installer index fff4ff5cc..d3ecfbfe3 100755 --- a/install/installer +++ b/install/installer @@ -4,7 +4,7 @@ BOLD=`tput bold` NORM=`tput sgr0` CHEF_CLIENT_VERSION="12.21.14-1" if [ "$MU_BRANCH" == "" ];then - MU_BRANCH="master" + MU_BRANCH="winrm_more_like_rm_windows" fi # XXX All RHEL family. We can at least cover Debian-flavored hosts too, I bet. From 7d016f1100dab34d624e1f39a7c8c634888fef23 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 24 Nov 2017 13:36:56 -0500 Subject: [PATCH 57/63] cleaner display of branch heads-up in installer stub --- install/installer | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/install/installer b/install/installer index d3ecfbfe3..ba803e4c6 100755 --- a/install/installer +++ b/install/installer @@ -50,10 +50,14 @@ fi if [ -d /opt/mu/lib/cookbooks/mu-master/recipes ];then /opt/chef/bin/chef-apply /opt/mu/lib/cookbooks/mu-master/recipes/init.rb else - echo "****** Installing Mu from the ${BOLD}$MU_BRANCH${NORM} branch ******" - echo "***** Hit ^C now if that's not what you intended *****" - echo "*** Prepend ${BOLD}MU_BRANCH=some_branch_name${NORM} when calling me to use a different branch ***" + set +x + echo "" + echo "*** Installing Mu from the ${BOLD}$MU_BRANCH${NORM} branch ***" + echo "*** Hit ^C now if that's not what you intended ***" + echo "*** Prepend ${BOLD}MU_BRANCH=some_branch_name${NORM} to use another branch ***" + echo "" sleep 10 + set -x /usr/bin/curl https://raw.githubusercontent.com/cloudamatic/mu/$MU_BRANCH/cookbooks/mu-master/recipes/init.rb > /root/mu-master-init-recipe.rb /opt/chef/bin/chef-apply /root/mu-master-init-recipe.rb fi From 3eb95926d3f2b8d49b8b825b29f2f44af094a915 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 24 Nov 2017 13:59:26 -0500 Subject: [PATCH 58/63] work around dumb Chef git resource checkout behavior --- bin/mu-configure | 2 ++ cookbooks/mu-master/recipes/init.rb | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/mu-configure b/bin/mu-configure index 03b13381b..fd2566cb0 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -1031,6 +1031,8 @@ end if $INITIALIZE + MU.log "Generating documentation in /var/www/html" + %x{#{MU_BASE}/lib/bin/mu-gen-docs} MU.log "Setting initial password for admin user 'mu', for logging into Nagios and other built-in services.", MU::NOTICE puts %x{#{MU_BASE}/lib/bin/mu-user-manage -g mu} MU.log "If Scratchpad web interface is not accessible, try the following:", MU::NOTICE diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index e5229e5c0..1a05d5b52 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -174,11 +174,12 @@ recursive true mode 0755 end -bash "set git default branch" do +bash "set git default branch to #{MU_BRANCH}" do cwd "#{MU_BASE}/lib" code <<-EOH git config branch.#{MU_BRANCH}.remote origin git config branch.#{MU_BRANCH}.merge refs/heads/#{MU_BRANCH} + git checkout #{MU_BRANCH} EOH action :nothing end @@ -188,7 +189,7 @@ checkout_branch MU_BRANCH enable_checkout false not_if { ::Dir.exists?("#{MU_BASE}/lib/.git") } - notifies :run, "bash[set git default branch]", :immediately + notifies :run, "bash[set git default branch to #{MU_BRANCH}]", :immediately end # Enable some git hook weirdness for Mu developers From c10b839547068ad2c26d5812988d80163def1385 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 28 Nov 2017 11:23:50 -0500 Subject: [PATCH 59/63] custom DNS records for ALBs don't alias correctly; work around with CNAMEs for now --- modules/mu/clouds/aws/dnszone.rb | 8 ++++---- modules/mu/clouds/aws/loadbalancer.rb | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/modules/mu/clouds/aws/dnszone.rb b/modules/mu/clouds/aws/dnszone.rb index ad46d4577..7fd698d0e 100644 --- a/modules/mu/clouds/aws/dnszone.rb +++ b/modules/mu/clouds/aws/dnszone.rb @@ -248,9 +248,9 @@ def self.createRecordsFromConfig(cfg, target: nil) } } - record_threads.each { |t| - t.join - } + record_threads.each { |t| + t.join + } end # Create a Route53 health check. @@ -610,7 +610,7 @@ def self.genericMuDNSEntry(name: nil, target: nil, cloudclass: nil, noop: false, MU.log "#{dns_name} already exists", MU::DEBUG, details: e.inspect end end - return dns_name + return "#{dns_name}.platform-mu" else return nil end diff --git a/modules/mu/clouds/aws/loadbalancer.rb b/modules/mu/clouds/aws/loadbalancer.rb index 8d824ca5c..be404a24c 100644 --- a/modules/mu/clouds/aws/loadbalancer.rb +++ b/modules/mu/clouds/aws/loadbalancer.rb @@ -164,9 +164,10 @@ def create MU.log "Load Balancer is at #{lb.dns_name}" parent_thread_id = Thread.current.object_id + generic_mu_dns = nil dnsthread = Thread.new { MU.dupGlobals(parent_thread_id) - MU::Cloud::AWS::DNSZone.genericMuDNSEntry(name: @mu_name, target: "#{lb.dns_name}.", cloudclass: MU::Cloud::LoadBalancer, sync_wait: @config['dns_sync_wait']) + generic_mu_dns = MU::Cloud::AWS::DNSZone.genericMuDNSEntry(name: @mu_name, target: "#{lb.dns_name}.", cloudclass: MU::Cloud::LoadBalancer, sync_wait: @config['dns_sync_wait']) } if zones_to_try.size < @config["zones"].size @@ -521,15 +522,20 @@ def create dnsthread.join # from genericMuDNS -# XXX fix for elb2 -# if !@config['dns_records'].nil? + if !@config['dns_records'].nil? # XXX this should be a call to @deploy.nameKitten -# @config['dns_records'].each { |dnsrec| -# dnsrec['name'] = @mu_name.downcase if !dnsrec.has_key?('name') -# dnsrec['name'] = "#{dnsrec['name']}.#{MU.environment.downcase}" if dnsrec["append_environment_name"] && !dnsrec['name'].match(/\.#{MU.environment.downcase}$/) -# } -# MU::Cloud::AWS::DNSZone.createRecordsFromConfig(@config['dns_records'], target: resp.dns_name) -# end + @config['dns_records'].each { |dnsrec| + dnsrec['name'] = @mu_name.downcase if !dnsrec.has_key?('name') + dnsrec['name'] = "#{dnsrec['name']}.#{MU.environment.downcase}" if dnsrec["append_environment_name"] && !dnsrec['name'].match(/\.#{MU.environment.downcase}$/) + } + if !@config['classic'] + # XXX should be R53ALIAS, but we get "the alias target name does not lie within the target zone" + @config['dns_records'].each { |r| + r['type'] = "CNAME" + } + end + MU::Cloud::AWS::DNSZone.createRecordsFromConfig(@config['dns_records'], target: cloud_desc.dns_name) + end notify end From 96c2054616e6a59bf0c99a8c69a69cc044a6e109 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 28 Nov 2017 12:03:54 -0500 Subject: [PATCH 60/63] more aggressive validation of bad inputs in mu-configure --- bin/mu-configure | 37 ++++++++++++++++++++++------- cookbooks/mu-master/recipes/init.rb | 9 +++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/bin/mu-configure b/bin/mu-configure index fd2566cb0..58ef45c85 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -510,7 +510,11 @@ def importCurrentValues end def printVal(data) - if !data["value"].nil? + valid = true + valid = validate(data["value"], data) if data["value"] + if !valid + print " - "+data["value"].to_s.red.on_black + elsif !data["value"].nil? print " - "+data["value"].to_s.green.on_black elsif data["required"] print " - "+"REQUIRED".red.on_black @@ -616,17 +620,18 @@ def validate(newval, reqs) puts "\nSupplied value did not pass validation".light_red.on_black+"\n\n" ok = false elsif reqs['negate_pattern'] - if newval.match(reqs['pattern']) + if newval.to_s.match(reqs['pattern']) puts "\nInvalid value '#{newval.bold}' (must NOT match #{reqs['pattern']})".light_red.on_black+"\n\n" ok = false end - elsif !newval.match(reqs['pattern']) + elsif !newval.to_s.match(reqs['pattern']) puts "\nInvalid value '#{newval.bold}' (must match #{reqs['pattern']})".light_red.on_black+"\n\n" ok = false end end ok end + if reqs['array'] if !newval.is_a?(Array) puts "\nInvalid value '#{newval.bold}' (should be an array)".light_red.on_black+"\n\n" @@ -683,7 +688,23 @@ if !$opts[:noninteractive] end end while answer != "0" and answer != "O" and answer != "o" end -# XXX validate overall input + +ok = true +$CONFIGURABLES.each_pair { |key, data| + next if !AMROOT and data['rootonly'] + if data.has_key?("subtree") + data["subtree"].each_pair { |subkey, subdata| + next if !AMROOT and subdata['rootonly'] + ok = false if !validate(data["value"], data) + } + else + ok = false if !validate(data["value"], data) + end +} +if !ok + puts "Configuration had validation errors, exiting.\nRe-invoke #{$0} to correct." + exit 1 +end def set389DSCreds require 'mu' @@ -741,8 +762,8 @@ def set389DSCreds end if AMROOT -cur_chef_version = `/bin/rpm -q chef`.sub(/^chef-(\d+\.\d+\.\d+-\d+)\..*/, '\1').chomp -pref_chef_version = File.read("#{MU_BASE}/var/mu-chef-client-version").chomp + cur_chef_version = `/bin/rpm -q chef`.sub(/^chef-(\d+\.\d+\.\d+-\d+)\..*/, '\1').chomp + pref_chef_version = File.read("#{MU_BASE}/var/mu-chef-client-version").chomp if cur_chef_version != pref_chef_version puts "Updating MU-MASTER's Chef Client to '#{pref_chef_version}'" chef_installer = open("https://www.chef.io/chef/install.sh").read @@ -1029,10 +1050,10 @@ if $?.exitstatus != 0 or output.match(/is not a chef-vault/) ) end +MU.log "Regenerating documentation in /var/www/html/docs" +puts %x{#{MU_BASE}/lib/bin/mu-gen-docs} if $INITIALIZE - MU.log "Generating documentation in /var/www/html" - %x{#{MU_BASE}/lib/bin/mu-gen-docs} MU.log "Setting initial password for admin user 'mu', for logging into Nagios and other built-in services.", MU::NOTICE puts %x{#{MU_BASE}/lib/bin/mu-user-manage -g mu} MU.log "If Scratchpad web interface is not accessible, try the following:", MU::NOTICE diff --git a/cookbooks/mu-master/recipes/init.rb b/cookbooks/mu-master/recipes/init.rb index 1a05d5b52..0448ab852 100644 --- a/cookbooks/mu-master/recipes/init.rb +++ b/cookbooks/mu-master/recipes/init.rb @@ -471,6 +471,15 @@ only_if { RUNNING_STANDALONE } end +file "#{MU_BASE}/etc/mu.rc" do + content %Q{export MU_INSTALLDIR="#{MU_BASE}" + export MU_DATADIR="#{MU_BASE}/var" + export PATH="#{MU_BASE}/bin:/usr/local/ruby-current/bin:${PATH}:/opt/opscode/embedded/bin" +} + mode 0644 + action :create_if_missing +end + # Community cookbooks keep touching gems, and none of them are smart about our # default umask. We have to clean up after them every time. ["/usr/local/ruby-current", "/opt/chef/embedded"].each { |rubydir| From d13277c212aa272e3aeea81db1177f05635b92d2 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Tue, 28 Nov 2017 12:43:48 -0500 Subject: [PATCH 61/63] polish for new mu-configure validation behavior --- bin/mu-configure | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/bin/mu-configure b/bin/mu-configure index 58ef45c85..e5cbd74bf 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -511,9 +511,10 @@ end def printVal(data) valid = true - valid = validate(data["value"], data) if data["value"] + valid = validate(data["value"], data, false) if data["value"] if !valid - print " - "+data["value"].to_s.red.on_black + print " "+data["value"].to_s.red.on_black + print " (consider default of #{data["default"].to_s.bold})" if data["default"] elsif !data["value"].nil? print " - "+data["value"].to_s.green.on_black elsif data["required"] @@ -608,24 +609,28 @@ def ask(desc) val end -def validate(newval, reqs) +def validate(newval, reqs, addnewline = true) ok = true - def validate_individual_value(newval, reqs) + def validate_individual_value(newval, reqs, addnewline) ok = true if reqs['boolean'] and newval != true and newval != false and newval != nil - puts "\nInvalid value '#{newval.bold}' (must be true or false)".light_red.on_black+"\n\n" + puts "\nInvalid value '#{newval.bold}' for #{reqs['title'].bold} (must be true or false)".light_red.on_black + puts "\n\n" if addnewline ok = false elsif reqs['pattern'] if newval.nil? - puts "\nSupplied value did not pass validation".light_red.on_black+"\n\n" + puts "\nSupplied value for #{reqs['title'].bold} did not pass validation".light_red.on_black + puts "\n\n" if addnewline ok = false elsif reqs['negate_pattern'] if newval.to_s.match(reqs['pattern']) - puts "\nInvalid value '#{newval.bold}' (must NOT match #{reqs['pattern']})".light_red.on_black+"\n\n" + puts "\nInvalid value '#{newval.bold}' for #{reqs['title'].bold} (must NOT match #{reqs['pattern']})".light_red.on_black + puts "\n\n" if addnewline ok = false end elsif !newval.to_s.match(reqs['pattern']) - puts "\nInvalid value '#{newval.bold}' (must match #{reqs['pattern']})".light_red.on_black+"\n\n" + puts "\nInvalid value '#{newval.bold}' #{reqs['title'].bold} (must match #{reqs['pattern']})".light_red.on_black + puts "\n\n" if addnewline ok = false end end @@ -634,15 +639,16 @@ def validate(newval, reqs) if reqs['array'] if !newval.is_a?(Array) - puts "\nInvalid value '#{newval.bold}' (should be an array)".light_red.on_black+"\n\n" + puts "\nInvalid value '#{newval.bold}' for #{reqs['title'].bold} (should be an array)".light_red.on_black + puts "\n\n" if addnewline ok = false else newval.each { |v| - ok = false if !validate_individual_value(v, reqs) + ok = false if !validate_individual_value(v, reqs, addnewline) } end else - ok = false if !validate_individual_value(newval, reqs) + ok = false if !validate_individual_value(newval, reqs, addnewline) end ok end @@ -695,10 +701,12 @@ $CONFIGURABLES.each_pair { |key, data| if data.has_key?("subtree") data["subtree"].each_pair { |subkey, subdata| next if !AMROOT and subdata['rootonly'] - ok = false if !validate(data["value"], data) + next if !data["value"] + ok = false if !validate(data["value"], data, false) } else - ok = false if !validate(data["value"], data) + next if !data["value"] + ok = false if !validate(data["value"], data, false) end } if !ok @@ -1051,7 +1059,7 @@ if $?.exitstatus != 0 or output.match(/is not a chef-vault/) end MU.log "Regenerating documentation in /var/www/html/docs" -puts %x{#{MU_BASE}/lib/bin/mu-gen-docs} +%x{#{MU_BASE}/lib/bin/mu-gen-docs} if $INITIALIZE MU.log "Setting initial password for admin user 'mu', for logging into Nagios and other built-in services.", MU::NOTICE From 6d6947797ca88c234cbe7241cc987696449ec8a2 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Thu, 30 Nov 2017 09:56:25 -0500 Subject: [PATCH 62/63] a little more tweaking for mu-configure's new validation pickiness --- bin/mu-configure | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/bin/mu-configure b/bin/mu-configure index e5cbd74bf..a5b049a6d 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -656,6 +656,24 @@ end answer = nil changed = false +def entireConfigValid? + ok = true + $CONFIGURABLES.each_pair { |key, data| + next if !AMROOT and data['rootonly'] + if data.has_key?("subtree") + data["subtree"].each_pair { |subkey, subdata| + next if !AMROOT and subdata['rootonly'] + next if !data["value"] + ok = false if !validate(data["value"], data, false) + } + else + next if !data["value"] + ok = false if !validate(data["value"], data, false) + end + } + ok +end + if !$opts[:noninteractive] begin optlist = displayCurrentOpts @@ -691,27 +709,15 @@ if !$opts[:noninteractive] elsif !["", "0", "O", "o"].include?(answer) puts "\nInvalid option '#{answer.bold}'".light_red.on_black+"\n\n" sleep 1 + else + answer = nil if !entireConfigValid? end - end while answer != "0" and answer != "O" and answer != "o" -end - -ok = true -$CONFIGURABLES.each_pair { |key, data| - next if !AMROOT and data['rootonly'] - if data.has_key?("subtree") - data["subtree"].each_pair { |subkey, subdata| - next if !AMROOT and subdata['rootonly'] - next if !data["value"] - ok = false if !validate(data["value"], data, false) - } - else - next if !data["value"] - ok = false if !validate(data["value"], data, false) + end while answer != "0" and answer != "O" and answer != "o" and !entireConfigValid? +else + if !entireConfigValid? + puts "Configuration had validation errors, exiting.\nRe-invoke #{$0} to correct." + exit 1 end -} -if !ok - puts "Configuration had validation errors, exiting.\nRe-invoke #{$0} to correct." - exit 1 end def set389DSCreds From 7f39312caed9737cf65885440fb904ef648f4783 Mon Sep 17 00:00:00 2001 From: Mu Master Date: Fri, 1 Dec 2017 08:49:16 -0500 Subject: [PATCH 63/63] mu-configure: don't just run as soon as we see a valid config --- bin/mu-configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/mu-configure b/bin/mu-configure index a5b049a6d..0370dc0b0 100755 --- a/bin/mu-configure +++ b/bin/mu-configure @@ -712,7 +712,7 @@ if !$opts[:noninteractive] else answer = nil if !entireConfigValid? end - end while answer != "0" and answer != "O" and answer != "o" and !entireConfigValid? + end while answer != "0" and answer != "O" and answer != "o" else if !entireConfigValid? puts "Configuration had validation errors, exiting.\nRe-invoke #{$0} to correct."