Class: Chef::Key

Inherits:
Object
  • Object
show all
Includes:
Mixin::ParamsValidate
Defined in:
lib/chef/key.rb

Overview

Class for interacting with a chef key object. Can be used to create new keys, save to server, load keys from server, list keys, delete keys, etc.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mixin::ParamsValidate

#lazy, #set_or_return, #validate

Constructor Details

#initialize(actor, actor_field_name) ⇒ Key


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/chef/key.rb', line 45

def initialize(actor, actor_field_name)
  # Actor that the key is for, either a client or a user.
  @actor = actor

  unless actor_field_name == "user" || actor_field_name == "client"
    raise Chef::Exceptions::InvalidKeyArgument, "the second argument to initialize must be either 'user' or 'client'"
  end

  @actor_field_name = actor_field_name

  @name = nil
  @public_key = nil
  @private_key = nil
  @expiration_date = nil
  @create_key = nil
end

Instance Attribute Details

#actor(arg = nil) ⇒ String

the name of the client or user that this key is for


39
40
41
# File 'lib/chef/key.rb', line 39

def actor
  @actor
end

#actor_field_nameString (readonly)

must be either 'client' or 'user'


39
40
41
# File 'lib/chef/key.rb', line 39

def actor_field_name
  @actor_field_name
end

#api_basestring

either “users” or “clients”, initialized and cached via api_base method


39
40
41
# File 'lib/chef/key.rb', line 39

def api_base
  @api_base
end

#expiration_date(arg = nil) ⇒ String

the ISO formatted string YYYY-MM-DDTHH:MM:SSZ, i.e. 2020-12-24T21:00:00Z


39
40
41
# File 'lib/chef/key.rb', line 39

def expiration_date
  @expiration_date
end

#name(arg = nil) ⇒ String

the name of the key


39
40
41
# File 'lib/chef/key.rb', line 39

def name
  @name
end

#private_key(arg = nil) ⇒ String

the RSA string of the private key if returned via a POST or PUT


39
40
41
# File 'lib/chef/key.rb', line 39

def private_key
  @private_key
end

#public_key(arg = nil) ⇒ String

the RSA string of this key


39
40
41
# File 'lib/chef/key.rb', line 39

def public_key
  @public_key
end

#restString

Chef::ServerAPI object, initialized and cached via chef_rest method


39
40
41
# File 'lib/chef/key.rb', line 39

def rest
  @rest
end

Class Method Details

.from_hash(key_hash) ⇒ Object


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/chef/key.rb', line 209

def from_hash(key_hash)
  if key_hash.key?("user")
    key = Chef::Key.new(key_hash["user"], "user")
  elsif key_hash.key?("client")
    key = Chef::Key.new(key_hash["client"], "client")
  else
    raise Chef::Exceptions::MissingKeyAttribute, "The hash passed to from_hash does not contain the key 'user' or 'client'. Please pass a hash that defines one of those keys."
  end
  key.name key_hash["name"] if key_hash.key?("name")
  key.public_key key_hash["public_key"] if key_hash.key?("public_key")
  key.private_key key_hash["private_key"] if key_hash.key?("private_key")
  key.create_key key_hash["create_key"] if key_hash.key?("create_key")
  key.expiration_date key_hash["expiration_date"] if key_hash.key?("expiration_date")
  key
end

.from_json(json) ⇒ Object


225
226
227
# File 'lib/chef/key.rb', line 225

def from_json(json)
  Chef::Key.from_hash(Chef::JSONCompat.from_json(json))
end

.generate_fingerprint(public_key) ⇒ Object


249
250
251
252
253
254
255
256
# File 'lib/chef/key.rb', line 249

def generate_fingerprint(public_key)
  openssl_key_object = OpenSSL::PKey::RSA.new(public_key)
  data_string = OpenSSL::ASN1::Sequence([
    OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.n),
    OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.e),
  ])
  OpenSSL::Digest::SHA1.hexdigest(data_string.to_der).scan(/../).join(":")
end

.list(keys, actor, load_method_symbol, inflate) ⇒ Object


258
259
260
261
262
263
264
265
266
267
268
# File 'lib/chef/key.rb', line 258

def list(keys, actor, load_method_symbol, inflate)
  if inflate
    keys.inject({}) do |key_map, result|
      name = result["name"]
      key_map[name] = Chef::Key.send(load_method_symbol, actor, name)
      key_map
    end
  else
    keys
  end
end

.list_by_client(actor, inflate = false) ⇒ Object


234
235
236
237
# File 'lib/chef/key.rb', line 234

def list_by_client(actor, inflate = false)
  keys = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("clients/#{actor}/keys")
  list(keys, actor, :load_by_client, inflate)
end

.list_by_user(actor, inflate = false) ⇒ Object


229
230
231
232
# File 'lib/chef/key.rb', line 229

def list_by_user(actor, inflate = false)
  keys = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys")
  list(keys, actor, :load_by_user, inflate)
end

.load_by_client(actor, key_name) ⇒ Object


244
245
246
247
# File 'lib/chef/key.rb', line 244

def load_by_client(actor, key_name)
  response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("clients/#{actor}/keys/#{key_name}")
  Chef::Key.from_hash(response.merge({ "client" => actor }))
end

.load_by_user(actor, key_name) ⇒ Object


239
240
241
242
# File 'lib/chef/key.rb', line 239

def load_by_user(actor, key_name)
  response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys/#{key_name}")
  Chef::Key.from_hash(response.merge({ "user" => actor }))
end

Instance Method Details

#chef_restObject


62
63
64
65
66
67
68
# File 'lib/chef/key.rb', line 62

def chef_rest
  @rest ||= if @actor_field_name == "user"
              Chef::ServerAPI.new(Chef::Config[:chef_server_root])
            else
              Chef::ServerAPI.new(Chef::Config[:chef_server_url])
            end
end

#createObject


138
139
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
# File 'lib/chef/key.rb', line 138

def create
  # if public_key is undefined and create_key is false, we cannot create
  if @public_key.nil? && !@create_key
    raise Chef::Exceptions::MissingKeyAttribute, "either public_key must be defined or create_key must be true"
  end

  # defaults the key name to the fingerprint of the key
  if @name.nil?
    # if they didn't pass a public_key,
    # then they must supply a name because we can't generate a fingerprint
    unless @public_key.nil?
      @name = fingerprint
    else
      raise Chef::Exceptions::MissingKeyAttribute, "a name cannot be auto-generated if no public key passed, either pass a public key or supply a name"
    end
  end

  payload = { "name" => @name }
  payload["public_key"] = @public_key unless @public_key.nil?
  payload["create_key"] = @create_key if @create_key
  payload["expiration_date"] = @expiration_date unless @expiration_date.nil?
  result = chef_rest.post("#{api_base}/#{@actor}/keys", payload)
  # append the private key to the current key if the server returned one,
  # since the POST endpoint just returns uri and private_key if needed.
  new_key = to_h
  new_key["private_key"] = result["private_key"] if result["private_key"]
  Chef::Key.from_hash(new_key)
end

#create_key(arg = nil) ⇒ Object


108
109
110
111
112
113
# File 'lib/chef/key.rb', line 108

def create_key(arg = nil)
  raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set create_key to true if the public_key field exists" if arg == true && !@public_key.nil?

  set_or_return(:create_key, arg,
    kind_of: [TrueClass, FalseClass])
end

#delete_create_keyObject


104
105
106
# File 'lib/chef/key.rb', line 104

def delete_create_key
  @create_key = nil
end

#delete_public_keyObject


100
101
102
# File 'lib/chef/key.rb', line 100

def delete_public_key
  @public_key = nil
end

#destroyObject


200
201
202
203
204
205
206
# File 'lib/chef/key.rb', line 200

def destroy
  if @name.nil?
    raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when delete is called"
  end

  chef_rest.delete("#{api_base}/#{@actor}/keys/#{@name}")
end

#fingerprintObject


167
168
169
# File 'lib/chef/key.rb', line 167

def fingerprint
  self.class.generate_fingerprint(@public_key)
end

#saveObject


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

def save
  create
rescue Net::HTTPClientException => e
  if e.response.code == "409"
    update
  else
    raise e
  end
end

#to_hObject Also known as: to_hash


120
121
122
123
124
125
126
127
128
129
130
# File 'lib/chef/key.rb', line 120

def to_h
  result = {
    @actor_field_name => @actor,
  }
  result["name"] = @name if @name
  result["public_key"] = @public_key if @public_key
  result["private_key"] = @private_key if @private_key
  result["expiration_date"] = @expiration_date if @expiration_date
  result["create_key"] = @create_key if @create_key
  result
end

#to_json(*a) ⇒ Object


134
135
136
# File 'lib/chef/key.rb', line 134

def to_json(*a)
  Chef::JSONCompat.to_json(to_h, *a)
end

#update(put_name = nil) ⇒ Object

set @name and pass put_name if you wish to update the name of an existing key put_name to @name


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/chef/key.rb', line 172

def update(put_name = nil)
  if @name.nil? && put_name.nil?
    raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated or you must pass a name to update when update is called"
  end

  # If no name was passed, fall back to using @name in the PUT URL, otherwise
  # use the put_name passed. This will update the a key by the name put_name
  # to @name.
  put_name = @name if put_name.nil?

  new_key = chef_rest.put("#{api_base}/#{@actor}/keys/#{put_name}", to_h)
  # if the server returned a public_key, remove the create_key field, as we now have a key
  if new_key["public_key"]
    delete_create_key
  end
  Chef::Key.from_hash(to_h.merge(new_key))
end