#!/usr/local/ruby-current/bin/ruby # # Copyright:: Copyright (c) 2017 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. # Perform initial Mu setup tasks: # 1. Set up an appropriate Security Group # 2. Associate a specific Elastic IP address to this MU server, if required. # 3. Create an S3 bucket for Mu logs. require 'etc' require 'securerandom' require File.expand_path(File.dirname(__FILE__))+"/mu-load-config.rb" require 'rubygems' require 'bundler/setup' require 'json' require 'erb' require 'optimist' require 'json-schema' require 'mu' require 'mu/master/ssl' Dir.chdir(MU.installDir) $opts = Optimist::options do banner <<-EOS Usage: #{$0} [-i] [-s] [-l] [-u] [-d] 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 Cloud Storage bucket prefixed with 'Mu_Logs' for use with CloudTrails, syslog, 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 Cloud Storage bucket created by the -l option.", :require => false, :default => false, :type => :boolean opt :optdisk, "Create a block volume for /opt and slide our installation onto it", :require => false, :default => false, :type => :boolean end if MU::Cloud::Google.hosted? and !$MU_CFG['google'] new_cfg = $MU_CFG.dup cfg_blob = MU::Cloud::Google.hosted_config if cfg_blob cfg_blob['log_bucket_name'] ||= $MU_CFG['hostname'] new_cfg["google"] = { "default" => cfg_blob } MU.log "Adding auto-detected Google stanza to #{cfgPath}", MU::NOTICE if new_cfg != $MU_CFG or !cfgExists? MU.log "Generating #{cfgPath}" saveMuConfig(new_cfg) $MU_CFG = new_cfg end end end my_instance_id = MU::Cloud::AWS.getAWSMetaData("instance-id") if MU::Cloud::Google.hosted? instance = MU.myCloudDescriptor admin_sg_name = MU.myInstanceId+"-"+MU.myVPC+"-ingress-allow" if !instance.tags.items or !instance.tags.items.include?(admin_sg_name) newitems = instance.tags.items ? instance.tags.items.dup : [] newitems << admin_sg_name MU.log "Setting my instance tags", MU::NOTICE, details: newitems newtags = MU::Cloud::Google.compute(:Tags).new( fingerprint: instance.tags.fingerprint, items: newitems ) MU::Cloud::Google.compute.set_instance_tags( MU::Cloud::Google.myProject, MU.myAZ, MU.myInstanceId, newtags ) instance = MU.myCloudDescriptor end preferred_ip = MU.mu_public_ip end # Create a security group, or manipulate an existing one, so that we have all # of the appropriate network holes. if $opts[:sg] open_ports = [80, 443, MU.mommaCatPort, 7443, 8443, 9443, 8200] found = MU::MommaCat.findStray("Google", "firewall_rule", dummy_ok: true, cloud_id: admin_sg_name) found.reject! { |v| v.cloud_desc.network != MU.myVPC } admin_sg = found.first if !found.nil? and found.size > 0 rules = [] open_ports.each { |port| rules << { "proto" => "tcp", "port" => port.to_s, "hosts" => ["0.0.0.0/0"] } } # TODO this is getting subsumed in all the 0.0.0.0/0 above; what we really want is a separate rule for this rules << { "proto" => "tcp", "port" => 22, "hosts" => ["#{preferred_ip}/32"] } cfg = { "name" => admin_sg_name, "scrub_mu_isms" => true, "cloud" => "Google", "rules" => rules, "project" => MU::Cloud::Google.myProject, "target_tags" => [admin_sg_name], "vpc" => { "id" => MU.myVPC } } if !admin_sg admin_sg = MU::Cloud::FirewallRule.new(kitten_cfg: cfg, mu_name: admin_sg_name) begin admin_sg.create rescue ::Google::Apis::ClientError => e raise e if !e.message.match(/alreadyExists: /) ensure admin_sg.groom end else admin_sg.groom end end if $opts[:optdisk] and !File.open("/etc/mtab").read.match(/ \/opt[\s\/]/) myname = MU::Cloud::Google.getGoogleMetaData("instance/name") wd = Dir.getwd Dir.chdir("/") if File.exists?("/opt/opscode/bin/chef-server-ctl") system("/opt/opscode/bin/chef-server-ctl stop") end if !File.exists?("/sbin/mkfs.xfs") system("/usr/bin/yum -y install xfsprogs") end MU::Master.disk(myname+"-mu-opt", "/opt_tmp", 30) uuid = MU::Master.diskUUID(myname+"-mu-opt") if !uuid or uuid.empty? MU.log "Failed to retrieve UUID of block device #{myname}-mu-opt", MU::ERR, details: MU::Cloud::AWS.realDevicePath(myname+"-mu-opt") exit 1 end MU.log "Moving contents of /opt to /opt_tmp", MU::NOTICE system("/bin/mv /opt/* /opt_tmp/") exit 1 if $?.exitstatus != 0 MU.log "Remounting /opt_tmp /opt", MU::NOTICE system("/bin/umount /opt_tmp") exit 1 if $?.exitstatus != 0 system("echo '#{uuid} /opt xfs defaults 0 0' >> /etc/fstab") system("/bin/mount -a") exit 1 if $?.exitstatus != 0 if File.exists?("/opt/opscode/bin/chef-server-ctl") system("/opt/opscode/bin/chef-server-ctl start") end Dir.chdir(wd) end $bucketname = MU::Cloud::Google.adminBucketName if $opts[:logs] MU::Cloud::Google.listCredentials.each { |credset| bucketname = MU::Cloud::Google.adminBucketName(credset) exists = false MU.log "Configuring log and secret Google Cloud Storage bucket '#{bucketname}'" bucket = nil begin bucket = MU::Cloud::Google.storage(credentials: credset).get_bucket(bucketname) rescue ::Google::Apis::ClientError => e if e.message.match(/notFound:/) MU.log "Creating #{bucketname} bucket" bucketobj = MU::Cloud::Google.storage(:Bucket).new( name: bucketname, location: "US", # XXX why is this needed? versioning: MU::Cloud::Google.storage(:Bucket)::Versioning.new( enabled: true ), lifecycle: MU::Cloud::Google.storage(:Bucket)::Lifecycle.new( rule: [ MU::Cloud::Google.storage(:Bucket)::Lifecycle::Rule.new( action: MU::Cloud::Google.storage(:Bucket)::Lifecycle::Rule::Action.new( type: "SetStorageClass", storage_class: "DURABLE_REDUCED_AVAILABILITY" ), condition: MU::Cloud::Google.storage(:Bucket)::Lifecycle::Rule::Condition.new( age: 180 ) )] ) ) bucket = MU::Cloud::Google.storage(credentials: credset).insert_bucket( MU::Cloud::Google.defaultProject(credset), bucketobj ) else pp e.backtrace raise MU::MuError, e.inspect end end ebs_key = nil begin ebs_key = MU::Cloud::Google.storage(credentials: credset).get_object(bucketname, "log_vol_ebs_key") rescue ::Google::Apis::ClientError => e if e.message.match(/notFound:/) # XXX this may not be useful outside of AWS MU.log "Creating new key for encrypted log volume" key = SecureRandom.random_bytes(32) f = Tempfile.new("logvolkey") # XXX this is insecure and stupid f.write key f.close objectobj = MU::Cloud::Google.storage(:Object).new( bucket: bucketname, name: "log_vol_ebs_key" ) ebs_key = MU::Cloud::Google.storage(credentials: credset).insert_object( bucketname, objectobj, upload_source: f.path ) f.unlink else raise MuError, e.inspect end end myname = MU::Cloud::Google.getGoogleMetaData("instance/name") MU::Master.disk("/dev/"+myname+"-mu-logs", "/Mu_Logs", 50, "log_vol_ebs_key", "ram7") } end if $opts[:dns] end if $opts[:uploadlogs] today = Time.new.strftime("%Y%m%d").to_s ["master.log", "nodes.log"].each { |log| if File.exist?("/Mu_Logs/#{log}-#{today}") MU.log "Uploading /Mu_Logs/#{log}-#{today} to bucket #{$bucketname}" MU::Cloud::AWS.s3.put_object( bucket: $bucketname, key: "#{log}/#{today}", body: File.read("/Mu_Logs/#{log}-#{today}") ) else MU.log "No log /Mu_Logs/#{log}-#{today} was found", MU::WARN end } end