Class: GoogleComputeWindowsPassword

Inherits:
Object
  • Object
show all
Defined in:
lib/gcewinpass/version.rb,
lib/gcewinpass.rb

Overview

Author

Chef Partner Engineering (<[email protected]>)

Copyright

Copyright © 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.

Constant Summary collapse

VERSION =
'1.1.0'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ GoogleComputeWindowsPassword

Returns a new instance of GoogleComputeWindowsPassword.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/gcewinpass.rb', line 30

def initialize(opts = {})
  validate_options!(opts)

  @api = Google::Apis::ComputeV1::ComputeService.new
  @api.authorization = authorization

  @project       = opts[:project]
  @zone          = opts[:zone]
  @instance_name = opts[:instance_name]
  @email         = opts[:email]
  @username      = opts.fetch(:username, 'Administrator')
  @debug         = opts.fetch(:debug, false)
  @timeout       = opts.fetch(:timeout, 120)
end

Instance Attribute Details

#apiObject (readonly)

Returns the value of attribute api.



28
29
30
# File 'lib/gcewinpass.rb', line 28

def api
  @api
end

#debugObject (readonly)

Returns the value of attribute debug.



28
29
30
# File 'lib/gcewinpass.rb', line 28

def debug
  @debug
end

#emailObject (readonly)

Returns the value of attribute email.



28
29
30
# File 'lib/gcewinpass.rb', line 28

def email
  @email
end

#instance_nameObject (readonly)

Returns the value of attribute instance_name.



28
29
30
# File 'lib/gcewinpass.rb', line 28

def instance_name
  @instance_name
end

#projectObject (readonly)

Returns the value of attribute project.



28
29
30
# File 'lib/gcewinpass.rb', line 28

def project
  @project
end

#usernameObject (readonly)

Returns the value of attribute username.



28
29
30
# File 'lib/gcewinpass.rb', line 28

def username
  @username
end

#zoneObject (readonly)

Returns the value of attribute zone.



28
29
30
# File 'lib/gcewinpass.rb', line 28

def zone
  @zone
end

Instance Method Details

#authorizationObject



59
60
61
62
63
64
65
66
# File 'lib/gcewinpass.rb', line 59

def authorization
  @authorization ||= Google::Auth.get_application_default(
    [
      'https://www.googleapis.com/auth/cloud-platform',
      'https://www.googleapis.com/auth/compute',
    ]
  )
end

#check_operation_for_errors!(operation_name) ⇒ Object



189
190
191
192
193
194
195
196
197
198
# File 'lib/gcewinpass.rb', line 189

def check_operation_for_errors!(operation_name)
  operation = operation(operation_name)
  unless operation.error.nil?
    errors = operation.error.errors.each_with_object([]) do |error, memo|
      memo << "#{error.code}: #{error.message}"
    end

    raise "Operation failed: #{errors.join(', ')}"
  end
end

#expiration_dateObject



129
130
131
# File 'lib/gcewinpass.rb', line 129

def expiration_date
  (Time.now + 300).to_datetime.rfc3339
end

#exponentObject



125
126
127
# File 'lib/gcewinpass.rb', line 125

def exponent
  Base64.strict_encode64(public_key.to_der[291, 3])
end

#instanceObject



68
69
70
# File 'lib/gcewinpass.rb', line 68

def instance
  @instance ||= api.get_instance(project, zone, instance_name)
end

#instance_exists?Boolean

Returns:

  • (Boolean)


72
73
74
75
76
77
78
# File 'lib/gcewinpass.rb', line 72

def instance_exists?
  instance
rescue Google::Apis::ClientError
  false
else
  true
end

#instance_metadataObject



80
81
82
# File 'lib/gcewinpass.rb', line 80

def 
  instance.
end

#log_debug(msg) ⇒ Object



204
205
206
# File 'lib/gcewinpass.rb', line 204

def log_debug(msg)
  $stderr.puts msg if debug
end

#modulusObject



121
122
123
# File 'lib/gcewinpass.rb', line 121

def modulus
  Base64.strict_encode64(public_key.to_der[33, 256])
end

#new_passwordObject



45
46
47
48
49
50
# File 'lib/gcewinpass.rb', line 45

def new_password
  raise "Unable to locate instance #{instance_name} in project #{project}, zone #{zone}" unless instance_exists?

  
  password_from_instance
end

#operation(operation_name) ⇒ Object



200
201
202
# File 'lib/gcewinpass.rb', line 200

def operation(operation_name)
  api.get_zone_operation(project, zone, operation_name)
end

#password_from_instanceObject



133
134
135
136
137
138
# File 'lib/gcewinpass.rb', line 133

def password_from_instance
  response = response_from_console_port
  raise 'Password agent attempted the reset but did not succeed' if response['passwordFound'] == false

  private_key.private_decrypt(Base64.strict_decode64(response['encryptedPassword']), OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
end

#password_requestObject



84
85
86
87
88
89
90
91
92
# File 'lib/gcewinpass.rb', line 84

def password_request
  {
    'userName' => username,
    'modulus'  => modulus,
    'exponent' => exponent,
    'email'    => email,
    'expireOn' => expiration_date,
  }
end

#password_request_metadataObject



94
95
96
97
98
99
# File 'lib/gcewinpass.rb', line 94

def 
  Google::Apis::ComputeV1::Metadata::Item.new.tap do |item|
    item.key = 'windows-keys'
    item.value = password_request.to_json
  end
end

#private_keyObject



113
114
115
# File 'lib/gcewinpass.rb', line 113

def private_key
  @private_key ||= OpenSSL::PKey::RSA.new(2048)
end

#public_keyObject



117
118
119
# File 'lib/gcewinpass.rb', line 117

def public_key
  @public_key ||= private_key.public_key
end

#response_from_console_portObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/gcewinpass.rb', line 140

def response_from_console_port
  log_debug('fetching password from console port')

  Timeout.timeout(timeout) do
    loop do
      api.get_instance_serial_port_output(project, zone, instance_name, port: 4).contents.lines.reverse_each do |line|
        line.strip!

        begin
          event = JSON.parse(line)
        rescue JSON::ParserError
          next
        end

        if event['modulus'] == modulus && event['exponent'] == exponent
          log_debug('modulus and exponent found - returning event')
          return event
        end
      end

      log_debug('event not found, sleeping...')
      sleep 5
    end
  end
rescue Timeout::Error
  raise Timeout::Error, 'Timeout while waiting for password agent to perform password reset'
end

#timeoutObject



208
209
210
# File 'lib/gcewinpass.rb', line 208

def timeout
  @timeout.to_i
end

#update_instance_metadataObject



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/gcewinpass.rb', line 101

def 
  .items = [] if .items.nil?
  .items = .items.select { |item| item.key != 'windows-keys' }
  .items << 

  log_debug("Updating instance #{instance_name} metadata with: #{.inspect}")

  wait_for_operation(api.(project, zone, instance_name, ))

  log_debug('Instance metadata updated.')
end

#validate_options!(opts) ⇒ Object



52
53
54
55
56
57
# File 'lib/gcewinpass.rb', line 52

def validate_options!(opts)
  raise 'Project not specified'                   unless opts[:project]
  raise 'Zone not specified'                      unless opts[:zone]
  raise 'Instance name not specified'             unless opts[:instance_name]
  raise 'Email address of GCE user not specified' unless opts[:email]
end

#wait_for_operation(operation_obj) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/gcewinpass.rb', line 168

def wait_for_operation(operation_obj)
  operation_name = operation_obj.name

  begin
    Timeout.timeout(timeout) do
      loop do
        operation = operation(operation_name)
        log_debug("Current operation status: #{operation.status}")
        break if operation.status == 'DONE'

        sleep 2
      end
    end
  rescue Timeout::Error
    raise Timeout::Error, 'Timeout while performing GCE API operation'
  end

  check_operation_for_errors!(operation_name)
  log_debug('Operation completed successfully.')
end