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.

Author:

  • Tyler Cloke

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

Returns a new instance of 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

Returns:

  • (String)

    the current value of actor


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

def actor
  @actor
end

#actor_field_nameString (readonly)

must be either 'client' or 'user'

Returns:

  • (String)

    the current value of actor_field_name


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

Returns:

  • (string)

    the current value of api_base


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

Returns:

  • (String)

    the current value of expiration_date


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

Returns:

  • (String)

    the current value of name


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

Returns:

  • (String)

    the current value of private_key


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

Returns:

  • (String)

    the current value of public_key

Raises:


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

Returns:

  • (String)

    the current value of rest


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