Class: Chef::ApiClient::Registration

Inherits:
Object
  • Object
show all
Defined in:
lib/chef/api_client/registration.rb

Overview

Chef::ApiClient::Registration

Manages the process of creating or updating a Chef::ApiClient on the server and writing the resulting private key to disk. Registration uses the validator credentials for its API calls. This allows it to bootstrap a new client/node identity by borrowing the validator client identity when creating a new client.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, destination, http_api: nil) ⇒ Registration

Returns a new instance of Registration



37
38
39
40
41
42
# File 'lib/chef/api_client/registration.rb', line 37

def initialize(name, destination, http_api: nil)
  @name                         = name
  @destination                  = destination
  @http_api                     = http_api
  @server_generated_private_key = nil
end

Instance Attribute Details

#destinationObject (readonly)

Returns the value of attribute destination



34
35
36
# File 'lib/chef/api_client/registration.rb', line 34

def destination
  @destination
end

#nameObject (readonly)

Returns the value of attribute name



35
36
37
# File 'lib/chef/api_client/registration.rb', line 35

def name
  @name
end

Instance Method Details

#api_client(response) ⇒ Object



119
120
121
122
123
124
125
126
127
# File 'lib/chef/api_client/registration.rb', line 119

def api_client(response)
  return response if response.is_a?(Chef::ApiClient)

  client = Chef::ApiClient.new
  client.name(name)
  client.public_key(api_client_key(response, "public_key"))
  client.private_key(api_client_key(response, "private_key"))
  client
end

#api_client_key(response, key_name) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/chef/api_client/registration.rb', line 129

def api_client_key(response, key_name)
  if response[key_name]
    if response[key_name].respond_to?(:to_pem)
      response[key_name].to_pem
    else
      response[key_name]
    end
  elsif response["chef_key"]
    response["chef_key"][key_name]
  end
end

#assert_destination_writable!Object



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/chef/api_client/registration.rb', line 72

def assert_destination_writable!
  abs_path = File.expand_path(destination)
  if !File.exists?(File.dirname(abs_path))
    begin
      FileUtils.mkdir_p(File.dirname(abs_path))
    rescue Errno::EACCES
      raise Chef::Exceptions::CannotWritePrivateKey, "I can't create the configuration directory at #{File.dirname(abs_path)} - check permissions?"
    end
  end
  if (File.exists?(abs_path) && !File.writable?(abs_path)) || !File.writable?(File.dirname(abs_path))
    raise Chef::Exceptions::CannotWritePrivateKey, "I can't write your private key to #{abs_path} - check permissions?"
  end
end

#createObject



103
104
105
106
107
# File 'lib/chef/api_client/registration.rb', line 103

def create
  response = http_api.post("clients", post_data)
  @server_generated_private_key = response["private_key"]
  response
end

#create_or_updateObject



94
95
96
97
98
99
100
101
# File 'lib/chef/api_client/registration.rb', line 94

def create_or_update
  create
rescue Net::HTTPClientException => e
  # If create fails because the client exists, attempt to update. This
  # requires admin privileges.
  raise unless e.response.code == "409"
  update
end

#file_flagsObject



190
191
192
193
194
195
196
197
# File 'lib/chef/api_client/registration.rb', line 190

def file_flags
  base_flags = File::CREAT | File::TRUNC | File::RDWR
  # Windows doesn't have symlinks, so it doesn't have NOFOLLOW
  if defined?(File::NOFOLLOW) && !Chef::Config[:follow_client_key_symlink]
    base_flags |= File::NOFOLLOW
  end
  base_flags
end

#generated_private_keyObject



182
183
184
# File 'lib/chef/api_client/registration.rb', line 182

def generated_private_key
  @generated_key ||= OpenSSL::PKey::RSA.generate(2048)
end

#generated_public_keyObject



186
187
188
# File 'lib/chef/api_client/registration.rb', line 186

def generated_public_key
  generated_private_key.public_key.to_pem
end

#http_apiObject



157
158
159
160
161
162
163
164
165
# File 'lib/chef/api_client/registration.rb', line 157

def http_api
  @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url],
                                    {
                                      api_version: "0",
                                      client_name: Chef::Config[:validation_client_name],
                                      signing_key_filename: Chef::Config[:validation_key],
                                    }
                                   )
end

#post_dataObject



151
152
153
154
155
# File 'lib/chef/api_client/registration.rb', line 151

def post_data
  post_data = { name: name, admin: false }
  post_data[:public_key] = generated_public_key if self_generate_keys?
  post_data
end

#private_keyObject



174
175
176
177
178
179
180
# File 'lib/chef/api_client/registration.rb', line 174

def private_key
  if self_generate_keys?
    generated_private_key.to_pem
  else
    @server_generated_private_key
  end
end

#put_dataObject



141
142
143
144
145
146
147
148
149
# File 'lib/chef/api_client/registration.rb', line 141

def put_data
  base_put_data = { name: name, admin: false }
  if self_generate_keys?
    base_put_data[:public_key] = generated_public_key
  else
    base_put_data[:private_key] = true
  end
  base_put_data
end

#runObject

Runs the client registration process, including creating the client on the chef-server and writing its private key to disk. – If client creation fails with a 5xx, it is retried up to 5 times. These retries are on top of the retries with randomized exponential backoff built in to Chef::ServerAPI. The retries here are a workaround for failures caused by resource contention in Hosted Chef when creating a very large number of clients simultaneously, (e.g., spinning up 100s of ec2 nodes at once). Future improvements to the affected component should make these retries unnecessary.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/chef/api_client/registration.rb', line 54

def run
  assert_destination_writable!
  retries = Config[:client_registration_retries] || 5
  client = nil
  begin
    client = api_client(create_or_update)
  rescue Net::HTTPFatalError => e
    # HTTPFatalError implies 5xx.
    raise if retries <= 0
    retries -= 1
    Chef::Log.warn("Failed to register new client, #{retries} tries remaining")
    Chef::Log.warn("Response: HTTP #{e.response.code} - #{e}")
    retry
  end
  write_key
  client
end

#self_generate_keys?Boolean

Whether or not to generate keys locally and post the public key to the server. Delegates to `Chef::Config.local_key_generation`. Servers before 11.0 do not support this feature.

Returns:

  • (Boolean)


170
171
172
# File 'lib/chef/api_client/registration.rb', line 170

def self_generate_keys?
  Chef::Config.local_key_generation
end

#updateObject



109
110
111
112
113
114
115
116
117
# File 'lib/chef/api_client/registration.rb', line 109

def update
  response = http_api.put("clients/#{name}", put_data)
  if response.respond_to?(:private_key) # Chef 11
    @server_generated_private_key = response.private_key
  else # Chef 10
    @server_generated_private_key = response["private_key"]
  end
  response
end

#write_keyObject



86
87
88
89
90
91
92
# File 'lib/chef/api_client/registration.rb', line 86

def write_key
  ::File.open(destination, file_flags, 0600) do |f|
    f.print(private_key)
  end
rescue IOError => e
  raise Chef::Exceptions::CannotWritePrivateKey, "Error writing private key to #{destination}: #{e}"
end