File: reset_windows_password.rb

package info (click to toggle)
ruby-fog-google 1.19.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,568 kB
  • sloc: ruby: 16,775; makefile: 3
file content (154 lines) | stat: -rw-r--r-- 5,650 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# Copyright 2015 Google Inc. All Rights Reserved.
#
# 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.
#
# Changes:
# March 2020: Modified example found here:
# https://github.com/GoogleCloudPlatform/compute-image-windows/blob/master/examples/windows_auth_python_sample.py
# to enable fog-google to change windows passwords.

require "openssl"
require "base64"
require "json"

module Fog
  module Compute
    class Google
      class Mock
        def reset_windows_password(_server:, _user:)
          Fog::Mock.not_implemented
        end
      end

      class Real
        ##
        # Resets Windows passwords for users on Google's Windows based images.  Code based on Google provided example.
        #
        # @param instance [String] the name of the instance
        # @param zone [String] the name of the zone of the instance
        # @param user [String] the user whose password should be reset
        #
        # @return [String] new password
        #
        # @see https://cloud.google.com/compute/docs/instances/windows/automate-pw-generation
        def reset_windows_password(server:, user:)
          # Pull the e-mail address of user authenticated to API
          email = @compute.request_options.authorization.issuer

          # Create a new key
          key = OpenSSL::PKey::RSA.new(2048)
          modulus, exponent = get_modulus_exponent_in_base64(key)

          # Get Old Metadata
          old_metadata = server.metadata

          # Create JSON Object with needed information
          metadata_entry = get_json_string(user, modulus, exponent, email)

          # Create new metadata object
          new_metadata = update_windows_keys(old_metadata, metadata_entry)

          # Set metadata on instance
          server.set_metadata(new_metadata, false)

          # Get encrypted password from Serial Port 4 Output

          # If machine is booting for the first time, there appears to be a
          # delay before the password appears on the serial port.
          sleep(1) until server.ready?
          serial_port_output = server.serial_port_output(:port => 4)
          loop_cnt = 0
          while serial_port_output.empty?
            if loop_cnt > 12
              Fog::Logger.warning("Encrypted password never found on Serial Output Port 4")
              raise "Could not reset password."
            end
            sleep(5)
            serial_port_output = server.serial_port_output(:port => 4)
            loop_cnt += 1
          end

          # Parse and decrypt password
          enc_password = get_encrypted_password_from_serial_port(serial_port_output, modulus)
          password = decrypt_password(enc_password, key)

          return password
        end

        def get_modulus_exponent_in_base64(key)
          mod = [key.n.to_s(16)].pack("H*").strip
          exp = [key.e.to_s(16)].pack("H*").strip
          modulus = Base64.strict_encode64(mod).strip
          exponent = Base64.strict_encode64(exp).strip
          return modulus, exponent
        end

        def get_expiration_time_string
          utc_now = Time.now.utc
          expire_time = utc_now + 5 * 60
          return expire_time.strftime("%Y-%m-%dT%H:%M:%SZ")
        end

        def get_json_string(user, modulus, exponent, email)
          expire = get_expiration_time_string
          data = { 'userName': user,
                   'modulus': modulus,
                   'exponent': exponent,
                   'email': email,
                   'expireOn': expire }
          return ::JSON.dump(data)
        end

        def update_windows_keys(old_metadata, metadata_entry)
          if old_metadata[:items]
            new_metadata = Hash[old_metadata[:items].map { |item| [item[:key], item[:value]] }]
          else
            new_metadata = {}
          end
          new_metadata["windows-keys"] = metadata_entry
          return new_metadata
        end

        def get_encrypted_password_from_serial_port(serial_port_output, modulus)
          output = serial_port_output.split("\n")
          output.reverse_each do |line|
            begin
              if line.include?("modulus") && line.include?("encryptedPassword")
                entry = ::JSON.parse(line)
                if modulus == entry["modulus"]
                  return entry["encryptedPassword"]
                end
              else
                next
              end
            rescue ::JSON::ParserError
              Fog::Logger.warning("Parsing encrypted password from serial output
                                  failed. Trying to parse next matching line.")
              next
            end
          end
        end

        def decrypt_password(enc_password, key)
          decoded_password = Base64.strict_decode64(enc_password)
          begin
            return key.private_decrypt(decoded_password, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
          rescue OpenSSL::PKey::RSAError
            Fog::Logger.warning("Error decrypting password received from Google.
                                Maybe check output on Serial Port 4 and Metadata key: windows-keys?")
          end
        end
      end
    end
  end
end