#!/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::Azure.hosted? and !$MU_CFG['azure'] new_cfg = $MU_CFG.dup cfg_blob = MU::Cloud::Azure.hosted_config if cfg_blob cfg_blob['log_bucket_name'] ||= $MU_CFG['hostname'] new_cfg["azure"] = { "default" => cfg_blob } MU.log "Adding auto-detected Azure 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 sgs_to_ifaces = {} ifaces_to_sgs = {} sgs = [] if MU::Cloud::Azure.hosted? instance = MU.myCloudDescriptor # Azure VMs can have exactly one security group per network interface, so if # there's already one, we use it. iface_num = 0 instance.network_profile.network_interfaces.each { |iface| iface_id = MU::Cloud::Azure::Id.new(iface.id) ifaces_to_sgs[iface_id] = false iface_desc = MU::Cloud::Azure.network.network_interfaces.get(MU.myInstanceId.resource_group, iface_id.to_s) if iface_desc.network_security_group sg_id = MU::Cloud::Azure::Id.new(iface_desc.network_security_group.id) sgs << sg_id sgs_to_ifaces[sg_id] = iface_id ifaces_to_sgs[iface_id] = sg_id else ifaces_to_sgs[iface_id] = "mu-master-"+MU.myInstanceId.name ifaces_to_sgs[iface_id] += "-"+iface_num.to_s if iface_num > 0 end if iface_desc.ip_configurations iface_desc.ip_configurations.each { |ipcfg| ipcfg.subnet.id.match(/resourceGroups\/([^\/]+)\/providers\/Microsoft.Network\/virtualNetworks\/([^\/]+)\/subnets\/(.*)/) rg = Regexp.last_match[1] vpc_id = Regexp.last_match[2] subnet_id = Regexp.last_match[3] subnet = MU::Cloud::Azure.network.subnets.get( rg, vpc_id, subnet_id ) if subnet.network_security_group sg_id = MU::Cloud::Azure::Id.new(subnet.network_security_group.id) sgs << sg_id end } end iface_num += 1 } sgs.uniq! # 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::Azure.compute(:Tags).new( # fingerprint: instance.tags.fingerprint, # items: newitems # ) # MU::Cloud::Azure.compute.set_instance_tags( # MU::Cloud::Azure.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] sgs.each { |sg_id| admin_sg_name = sg_id.is_a?(String) ? sg_id : sg_id.name found = MU::MommaCat.findStray("Azure", "firewall_rule", dummy_ok: true, cloud_id: admin_sg_name, region: instance.location) admin_sg = found.first if !found.nil? and found.size > 0 rules = [] open_ports.each { |port| rules << { "proto" => "tcp", "port" => port.to_s } } rules << { "proto" => "tcp", "port" => 22 # "hosts" => ["#{preferred_ip}/32"] } cfg = { "name" => admin_sg_name, "scrub_mu_isms" => true, "cloud" => "Azure", "rules" => rules, "region" => instance.location, "target_tags" => [admin_sg_name], "vpc" => { "vpc_id" => MU::Cloud::Azure::Id.new(instance.network_profile.network_interfaces.first.id) } } if !admin_sg admin_sg = MU::Cloud::FirewallRule.new(kitten_cfg: cfg, mu_name: admin_sg_name) admin_sg.create admin_sg.groom else rules.each { |rule| admin_sg.addRule(rule["hosts"], proto: rule["proto"], port: rule["port"].to_i) } end } end $bucketname = MU::Cloud::Azure.adminBucketName if $opts[:logs] MU::Cloud::Azure.listCredentials.each { |credset| bucketname = MU::Cloud::Azure.adminBucketName(credset) exists = false MU.log "Configuring log and secret Azure Cloud Storage bucket '#{bucketname}'" bucket = nil begin bucket = MU::Cloud::Azure.storage(credentials: credset).get_bucket(bucketname) rescue ::Azure::Apis::ClientError => e if e.message.match(/notFound:/) MU.log "Creating #{bucketname} bucket" bucketobj = MU::Cloud::Azure.storage(:Bucket).new( name: bucketname, location: "US", # XXX why is this needed? versioning: MU::Cloud::Azure.storage(:Bucket)::Versioning.new( enabled: true ), lifecycle: MU::Cloud::Azure.storage(:Bucket)::Lifecycle.new( rule: [ MU::Cloud::Azure.storage(:Bucket)::Lifecycle::Rule.new( action: MU::Cloud::Azure.storage(:Bucket)::Lifecycle::Rule::Action.new( type: "SetStorageClass", storage_class: "DURABLE_REDUCED_AVAILABILITY" ), condition: MU::Cloud::Azure.storage(:Bucket)::Lifecycle::Rule::Condition.new( age: 180 ) )] ) ) bucket = MU::Cloud::Azure.storage(credentials: credset).insert_bucket( MU::Cloud::Azure.defaultProject(credset), bucketobj ) else pp e.backtrace raise MU::MuError, e.inspect end end ebs_key = nil begin ebs_key = MU::Cloud::Azure.storage(credentials: credset).get_object(bucketname, "log_vol_ebs_key") rescue ::Azure::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::Azure.storage(:Object).new( bucket: bucketname, name: "log_vol_ebs_key" ) ebs_key = MU::Cloud::Azure.storage(credentials: credset).insert_object( bucketname, objectobj, upload_source: f.path ) f.unlink else raise MuError, e.inspect end end # XXX stop doing this per-bucket, chowderhead MU::Master.disk("/dev/xvdl", "/Mu_Logs", 50, "log_vol_ebs_key", "ram7") } end if $opts[:optdisk] and !File.open("/etc/mtab").read.match(/ \/opt[\s\/]/) puts "PLACEHOLDER" # 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 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