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



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

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



223
224
225
# File 'lib/chef/key.rb', line 223

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

.generate_fingerprint(public_key) ⇒ Object



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

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



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

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



232
233
234
235
# File 'lib/chef/key.rb', line 232

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



227
228
229
230
# File 'lib/chef/key.rb', line 227

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



242
243
244
245
# File 'lib/chef/key.rb', line 242

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



237
238
239
240
# File 'lib/chef/key.rb', line 237

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



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

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



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

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



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

def delete_create_key
  @create_key = nil
end

#delete_public_keyObject



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

def delete_public_key
  @public_key = nil
end

#destroyObject



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

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



165
166
167
# File 'lib/chef/key.rb', line 165

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

#saveObject



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

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



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

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



132
133
134
# File 'lib/chef/key.rb', line 132

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



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

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