modules/mu/clouds/google/server.rb in cloud-mu-3.1.4 vs modules/mu/clouds/google/server.rb in cloud-mu-3.1.5
- old
+ new
@@ -162,23 +162,30 @@
# @param disk_as_url [Boolean]: Whether to declare the disk type as a short string or full URL, which can vary depending on the calling resource
# @return [Array]: The Compute :AttachedDisk objects describing disks that've been created
def self.diskConfig(config, create = true, disk_as_url = true, credentials: nil)
disks = []
if config['image_id'].nil? and config['basis'].nil?
- pp config.keys
raise MuError, "Can't generate disk configuration for server #{config['name']} without an image ID or basis specified"
end
img = fetchImage(config['image_id'] || config['basis']['launch_config']['image_id'], credentials: credentials)
-# XXX slurp settings from /dev/sda or w/e by convention?
+# if img.source_disk and img.source_disk.match(/projects\/([^\/]+)\/zones\/([^\/]+)\/disks\/(.*)/)
+# _junk, proj, az, name = Regexp.last_match
+# disk_desc = MU::Cloud::Google.compute(credentials: credentials).get_disk(proj, az, name)
+# pp disk_desc
+# raise "nah"
+# end
+
disktype = "projects/#{config['project']}/zones/#{config['availability_zone']}/diskTypes/pd-standard"
- disktype = "pd-standard" if !disk_as_url
-# disk_type: projects/project/zones/#{config['availability_zone']}/diskTypes/pd-standard Other values include pd-ssd and local-ssd
+
+
+ disktype.gsub!(/.*?\/([^\/])$/, '\1') if !disk_as_url
+
imageobj = MU::Cloud::Google.compute(:AttachedDiskInitializeParams).new(
source_image: img.self_link,
- disk_size_gb: 10, # this is binary? 2gb, that says
+ disk_size_gb: img.disk_size_gb,
disk_type: disktype,
)
disks << MU::Cloud::Google.compute(:AttachedDisk).new(
auto_delete: true,
boot: true,
@@ -327,11 +334,11 @@
[m["key"], m["value"]]
}]
end
metadata["startup-script"] = @userdata if @userdata and !@userdata.empty?
- deploykey = @config['ssh_user']+":"+@deploy.ssh_public_key
+ deploykey = @config["ssh_user"]+":"+@deploy.ssh_public_key
if metadata["ssh-keys"]
metadata["ssh-keys"] += "\n"+deploykey
else
metadata["ssh-keys"] = deploykey
end
@@ -500,11 +507,12 @@
end
end
if @config['ssh_user'].nil?
if windows?
- @config['ssh_user'] = "Administrator"
+ @config['ssh_user'] = @config['windows_admin_user']
+ @config['ssh_user'] ||= "Administrator"
else
@config['ssh_user'] = "root"
end
end
@@ -798,33 +806,10 @@
end
end
# punchAdminNAT
-# MU::Cloud::AWS::Server.tagVolumes(@cloud_id)
-
- # If we have a loadbalancer configured, attach us to it
-# if !@config['loadbalancers'].nil?
-# if @loadbalancers.nil?
-# raise MuError, "#{@mu_name} is configured to use LoadBalancers, but none have been loaded by dependencies()"
-# end
-# @loadbalancers.each { |lb|
-# lb.registerNode(@cloud_id)
-# }
-# end
-
- # Let us into any databases we depend on.
- # This is probelmtic with autscaling - old ips are not removed, and access to the database can easily be given at the BoK level
- # if @dependencies.has_key?("database")
- # @dependencies['database'].values.each { |db|
- # db.allowHost(@deploydata["private_ip_address"]+"/32")
- # if @deploydata["public_ip_address"]
- # db.allowHost(@deploydata["public_ip_address"]+"/32")
- # end
- # }
- # end
-
@groomer.saveDeployData
begin
@groomer.run(purpose: "Full Initial Run", max_retries: 15)
rescue MU::Groomer::RunError
@@ -1015,13 +1000,97 @@
return public_ips.first
end
end
# return [String]: A password string.
- def getWindowsAdminPassword
+ def getWindowsAdminPassword(use_cache: true)
+ @config['windows_auth_vault'] ||= {
+ "vault" => @mu_name,
+ "item" => "windows_credentials",
+ "password_field" => "password"
+ }
+
+ if use_cache
+ begin
+ win_admin_password = @groomer.getSecret(
+ vault: @config['windows_auth_vault']['vault'],
+ item: @config['windows_auth_vault']['item'],
+ field: @config["windows_auth_vault"]["password_field"]
+ )
+MU.log "RETURNINATING FROM CACHE", MU::WARN, details: win_admin_password
+ return win_admin_password if win_admin_password
+ rescue MU::Groomer::MuNoSuchSecret, MU::Groomer::RunError
+ end
+ end
+
+ require 'openssl/oaep'
+ timeout = 300
+
+ serial_out = nil
+ key = OpenSSL::PKey::RSA.generate 2048
+
+ missing_response = Proc.new {
+ !serial_out or !serial_out.contents or serial_out.contents.empty? or JSON.parse(serial_out.contents)["userName"] != @config['windows_admin_username']
+ }
+
+ did_metadata = false
+ MU.retrier(loop_if: missing_response, wait: 10, max: timeout/10) {
+ serial_out = MU::Cloud::Google.compute(credentials: @credentials).get_instance_serial_port_output(@project_id, @config['availability_zone'], @cloud_id, port: 4)
+
+ if missing_response.call and
+ !cloud_desc(use_cache: false).metadata.items.map { |i| i.key }.include?("windows-keys")
+ keybytes = Base64.decode64(key.public_key.export.gsub(/-----(?:BEGIN|END) PUBLIC KEY-----/, ''))
+ modulus = keybytes.byteslice(33,256)
+ exponent = keybytes.byteslice(291,3)
+ keydata = {
+ "userName" => @config['windows_admin_username'],
+ "modulus" => Base64.strict_encode64(modulus),
+ "exponent" => Base64.strict_encode64(exponent),
+ "email" => MU.muCfg['mu_admin_email'],
+ "expireOn" => (Time.now.utc+timeout).strftime('%Y-%m-%dT%H:%M:%SZ')
+ }
+
+ new_items = cloud_desc.metadata.items.map { |item|
+ MU::Cloud::Google.compute(:Metadata)::Item.new(
+ key: item.key,
+ value: item.value
+ )
+ }
+ new_items.reject! { |item| item.key == "windows-keys" }
+ new_items << MU::Cloud::Google.compute(:Metadata)::Item.new(
+ key: "windows-keys",
+ value: JSON.generate(keydata)
+ )
+ new_metadata = MU::Cloud::Google.compute(:Metadata).new(
+ fingerprint: cloud_desc(use_cache: false).metadata.fingerprint,
+ items: new_items
+ )
+
+ MU::Cloud::Google.compute(credentials: @credentials).set_instance_metadata(@project_id, @config['availability_zone'], @cloud_id, new_metadata)
+ end
+ }
+
+ return nil if missing_response.call
+
+ pwdata = JSON.parse(serial_out.contents)
+ if pwdata['encryptedPassword'] and pwdata['userName'] == @config['windows_admin_username']
+ decrypted_pw = key.private_decrypt_oaep(Base64.strict_decode64(pwdata['encryptedPassword']))
+ creds = {
+ "username" => @config['windows_admin_username'],
+ "password" => decrypted_pw,
+ "sshd_username" => "sshd_service",
+ "sshd_password" => decrypted_pw
+ }
+ @groomer.saveSecret(vault: @mu_name, item: "windows_credentials", data: creds, permissions: "name:#{@mu_name}")
+
+ return decrypted_pw
+ end
+
+ nil
end
+
# Add a volume to this instance
# @param dev [String]: Device name to use when attaching to instance
# @param size [String]: Size (in gb) of the new volume
# @param type [String]: Cloud storage type of the volume, if applicable
# @param delete_on_termination [Boolean]: Value of delete_on_termination flag to set
@@ -1270,11 +1339,11 @@
toplevel_required = []
schema = {
"roles" => MU::Cloud::Google::User.schema(config)[1]["roles"],
"windows_admin_username" => {
"type" => "string",
- "default" => "Administrator"
+ "default" => "muadmin"
},
"create_image" => {
"properties" => {
"family" => {
"type" => "string",
@@ -1436,9 +1505,10 @@
if !server['availability_zone']
server['availability_zone'] = MU::Cloud::Google.listAZs(server['region']).sample
end
if server['service_account']
+ server['service_account'] = server['service_account'].to_h
server['service_account']['cloud'] = "Google"
server['service_account']['habitat'] ||= server['project']
found = MU::Config::Ref.get(server['service_account'])
if found.id and !found.kitten
MU.log "GKE server #{server['name']} failed to locate service account #{server['service_account']} in project #{server['project']}", MU::ERR