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.

[View source]

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

[View source]

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

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

[View source]

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

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

[View source]

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

def assert_destination_writable!
  abs_path = File.expand_path(destination)
  unless File.exist?(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.exist?(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

[View source]

105
106
107
108
109
# File 'lib/chef/api_client/registration.rb', line 105

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

#create_or_updateObject

[View source]

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

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

[View source]

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

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

[View source]

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

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

#generated_public_keyObject

[View source]

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

def generated_public_key
  generated_private_key.public_key.to_pem
end

#http_apiObject

[View source]

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

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

[View source]

153
154
155
156
157
# File 'lib/chef/api_client/registration.rb', line 153

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

[View source]

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

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

#put_dataObject

[View source]

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

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.

[View source]

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 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)
[View source]

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

def self_generate_keys?
  Chef::Config.local_key_generation
end

#updateObject

[View source]

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

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

[View source]

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

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