chef-vault 1.2.5 → 2.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Changelog.md +17 -0
- data/KNIFE_EXAMPLES.md +169 -0
- data/README.md +83 -106
- data/bin/chef-vault +25 -19
- data/lib/chef-vault.rb +17 -9
- data/lib/chef-vault/certificate.rb +7 -28
- data/lib/chef-vault/chef_patch/api_client.rb +40 -0
- data/lib/chef-vault/chef_patch/user.rb +33 -0
- data/lib/chef-vault/exceptions.rb +27 -0
- data/lib/chef-vault/item.rb +243 -0
- data/lib/chef-vault/item_keys.rb +121 -0
- data/lib/chef-vault/user.rb +7 -28
- data/lib/chef-vault/version.rb +1 -1
- data/lib/chef/knife/Decrypt.rb +64 -0
- data/lib/chef/knife/encrypt_create.rb +91 -0
- data/lib/chef/knife/encrypt_delete.rb +62 -0
- data/lib/chef/knife/encrypt_remove.rb +100 -0
- data/lib/chef/knife/encrypt_rotate_keys.rb +62 -0
- data/lib/chef/knife/encrypt_update.rb +90 -0
- data/lib/{chef-vault/chef/offline.rb → chef/knife/mixin/compat.rb} +15 -11
- data/lib/chef/knife/mixin/helper.rb +50 -0
- data/spec/chef-vault_spec.rb +19 -30
- data/spec/item_keys_spec.rb +29 -0
- data/spec/item_spec.rb +33 -0
- metadata +23 -21
- data/lib/chef/knife/DecryptCert.rb +0 -59
- data/lib/chef/knife/DecryptPassword.rb +0 -58
- data/lib/chef/knife/EncryptCert.rb +0 -185
- data/lib/chef/knife/EncryptPassword.rb +0 -182
- data/lib/chef/knife/compat.rb +0 -71
@@ -1,185 +0,0 @@
|
|
1
|
-
# Description: Chef-Vault EncryptCert class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
3
|
-
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
# you may not use this file except in compliance with the License.
|
6
|
-
# You may obtain a copy of the License at
|
7
|
-
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
# See the License for the specific language governing permissions and
|
14
|
-
# limitations under the License.
|
15
|
-
|
16
|
-
require 'chef/knife'
|
17
|
-
|
18
|
-
class EncryptCert < Chef::Knife
|
19
|
-
deps do
|
20
|
-
require 'chef/search/query'
|
21
|
-
require File.expand_path('../compat', __FILE__)
|
22
|
-
include ChefVault::Compat
|
23
|
-
end
|
24
|
-
|
25
|
-
banner "knife encrypt cert --search SEARCH --cert CERT --password PASSWORD --name NAME --admins ADMINS"
|
26
|
-
|
27
|
-
option :search,
|
28
|
-
:short => '-S SEARCH',
|
29
|
-
:long => '--search SEARCH',
|
30
|
-
:description => 'node search for nodes to encrypt to'
|
31
|
-
|
32
|
-
option :cert,
|
33
|
-
:short => '-C CERT',
|
34
|
-
:long => '--cert CERT',
|
35
|
-
:description => 'cert with contents to encrypt'
|
36
|
-
|
37
|
-
option :admins,
|
38
|
-
:short => '-A ADMINS',
|
39
|
-
:long => '--admins ADMINS',
|
40
|
-
:description => 'administrators who can decrypt certificate'
|
41
|
-
|
42
|
-
option :password,
|
43
|
-
:short => '-P PASSWORD',
|
44
|
-
:long => '--password PASSWORD',
|
45
|
-
:description => 'optional pfx password'
|
46
|
-
|
47
|
-
option :name,
|
48
|
-
:short => '-N NAME',
|
49
|
-
:long => '--name NAME',
|
50
|
-
:description => 'optional data bag name'
|
51
|
-
|
52
|
-
def run
|
53
|
-
unless config[:search]
|
54
|
-
puts("You must supply either -S or --search")
|
55
|
-
exit 1
|
56
|
-
end
|
57
|
-
unless config[:cert]
|
58
|
-
puts("You must supply either -C or --cert")
|
59
|
-
exit 1
|
60
|
-
end
|
61
|
-
unless config[:admins]
|
62
|
-
puts("You must supply either -A or --admins")
|
63
|
-
exit 1
|
64
|
-
end
|
65
|
-
extend_context_object(self)
|
66
|
-
|
67
|
-
data_bag = "certs"
|
68
|
-
data_bag_path = "./data_bags/#{data_bag}"
|
69
|
-
|
70
|
-
unless ::File.exists?(data_bag_path)
|
71
|
-
require 'fileutils'
|
72
|
-
|
73
|
-
puts("INFO: #{data_bag_path} does not exist, creating...")
|
74
|
-
FileUtils.mkdir_p(data_bag_path)
|
75
|
-
end
|
76
|
-
|
77
|
-
node_search = config[:search]
|
78
|
-
admins = config[:admins]
|
79
|
-
file_to_encrypt = config[:cert]
|
80
|
-
contents = open(file_to_encrypt, "rb").read
|
81
|
-
name = config[:name] ? config[:name].gsub(".", "_") : File.basename(file_to_encrypt, ".*").gsub(".", "_")
|
82
|
-
|
83
|
-
current_dbi = Hash.new
|
84
|
-
current_dbi_keys = Hash.new
|
85
|
-
if File.exists?("#{data_bag_path}/#{name}_keys.json") && File.exists?("#{data_bag_path}/#{name}.json")
|
86
|
-
current_dbi_keys = JSON.parse(File.open("#{data_bag_path}/#{name}_keys.json"){ |file| file.read() })
|
87
|
-
current_dbi = JSON.parse(File.open("#{data_bag_path}/#{name}_keys.json"){ |file| file.read() })
|
88
|
-
|
89
|
-
unless equal?(data_bag, name, "contents", contents)
|
90
|
-
puts("FATAL: Content in #{data_bag_path}/#{name}.json does not match content in file supplied!")
|
91
|
-
exit 1
|
92
|
-
end
|
93
|
-
else
|
94
|
-
puts("INFO: Existing data bag #{data_bag}/#{name} does not exist in #{data_bag_path}, continuing as fresh build...")
|
95
|
-
end
|
96
|
-
|
97
|
-
# Get the public keys for all of the nodes to encrypt for. Skipping the nodes that are already in
|
98
|
-
# the data bag
|
99
|
-
keyfob = Hash.new
|
100
|
-
public_keys = search(:node, node_search).map(&:name).map do |client|
|
101
|
-
begin
|
102
|
-
if current_dbi_keys[client]
|
103
|
-
puts("INFO: Skipping #{client} as it is already in the data bag...")
|
104
|
-
else
|
105
|
-
puts("INFO: Adding #{client} to public_key array...")
|
106
|
-
keyfob[client] = get_client_public_key(client)
|
107
|
-
end
|
108
|
-
rescue Exception => node_error
|
109
|
-
puts("WARNING: Caught exception: #{node_error.message} while processing #{client}, so skipping...")
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Get the public keys for the admin users, skipping users already in the data bag
|
114
|
-
public_keys << admins.split(/[\s,]+/).map do |user|
|
115
|
-
begin
|
116
|
-
if current_dbi_keys[user]
|
117
|
-
puts("INFO: Skipping #{user} as it is already in the data bag")
|
118
|
-
else
|
119
|
-
puts("INFO: Adding #{user} to public_key array...")
|
120
|
-
keyfob[user] = get_user_public_key(user)
|
121
|
-
end
|
122
|
-
rescue Exception => user_error
|
123
|
-
puts("WARNING: Caught exception: #{user_error.message} while processing #{user}, so skipping...")
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
if public_keys.length == 0
|
128
|
-
puts "A node search for #{node_search} returned no results"
|
129
|
-
exit 1
|
130
|
-
end
|
131
|
-
|
132
|
-
# Get the current secret, is nil if current secret does not exist yet
|
133
|
-
current_secret = get_shared_secret(data_bag, name)
|
134
|
-
data_bag_shared_key = current_secret ? current_secret : OpenSSL::PKey::RSA.new(245).to_pem.lines.to_a[1..-2].join
|
135
|
-
enc_db_key_dbi = current_dbi_keys.empty? ? Mash.new({id: "#{name}_keys"}) : current_dbi_keys
|
136
|
-
|
137
|
-
# Encrypt for every new node not already in the data bag
|
138
|
-
keyfob.each do |node,pkey|
|
139
|
-
puts("INFO: Encrypting for #{node}...")
|
140
|
-
enc_db_key_dbi[node] = Base64.encode64(pkey.public_encrypt(data_bag_shared_key))
|
141
|
-
end unless keyfob.empty?
|
142
|
-
|
143
|
-
# Delete existing keys data bag and rewrite the whole bag from memory
|
144
|
-
puts("INFO: Writing #{data_bag_path}/#{name}_keys.json...")
|
145
|
-
File.delete("#{data_bag_path}/#{name}_keys.json") if File.exists?("#{data_bag_path}/#{name}_keys.json")
|
146
|
-
File.open("#{data_bag_path}/#{name}_keys.json",'w'){ |file| file.write(JSON.pretty_generate(enc_db_key_dbi)) }
|
147
|
-
|
148
|
-
# If the existing certificate bag does not exist, write it out with the correct certificate
|
149
|
-
# Otherwise leave the existing bag alone
|
150
|
-
if current_dbi.empty?
|
151
|
-
dbi_mash = Mash.new({id: name, contents: contents})
|
152
|
-
dbi_mash.merge!({password: config[:password]}) if config[:password]
|
153
|
-
dbi = Chef::DataBagItem.from_hash(dbi_mash)
|
154
|
-
edbi = Chef::EncryptedDataBagItem.encrypt_data_bag_item(dbi, data_bag_shared_key)
|
155
|
-
|
156
|
-
puts("INFO: Writing #{data_bag_path}/#{name}.json...")
|
157
|
-
File.open("#{data_bag_path}/#{name}.json",'w'){ |file| file.write(JSON.pretty_generate(edbi)) }
|
158
|
-
end
|
159
|
-
|
160
|
-
puts("INFO: Successfully wrote #{data_bag_path}/#{name}.json & #{data_bag_path}/#{name}_keys.json!")
|
161
|
-
end
|
162
|
-
|
163
|
-
def equal?(db, dbi, key, value)
|
164
|
-
data_bag_path = "./data_bags/#{db}"
|
165
|
-
|
166
|
-
shared_secret = get_shared_secret(db, dbi)
|
167
|
-
dbi = JSON.parse(File.open("#{data_bag_path}/#{dbi}.json") { |file| file.read() })
|
168
|
-
dbi = Chef::EncryptedDataBagItem.new dbi, shared_secret
|
169
|
-
|
170
|
-
dbi[key] == value
|
171
|
-
end
|
172
|
-
|
173
|
-
def get_shared_secret(db, dbi)
|
174
|
-
data_bag_path = "./data_bags/#{db}"
|
175
|
-
|
176
|
-
private_key = OpenSSL::PKey::RSA.new(open(Chef::Config[:client_key]).read())
|
177
|
-
key = File.exists?("#{data_bag_path}/#{dbi}_keys.json") ? JSON.parse(File.open("#{data_bag_path}/#{dbi}_keys.json"){ |file| file.read() }) : nil
|
178
|
-
|
179
|
-
begin
|
180
|
-
private_key.private_decrypt(Base64.decode64(key[Chef::Config[:node_name]]))
|
181
|
-
rescue
|
182
|
-
nil
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
@@ -1,182 +0,0 @@
|
|
1
|
-
# Description: Chef-Vault EncryptPassword class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
3
|
-
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
# you may not use this file except in compliance with the License.
|
6
|
-
# You may obtain a copy of the License at
|
7
|
-
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
# See the License for the specific language governing permissions and
|
14
|
-
# limitations under the License.
|
15
|
-
|
16
|
-
require 'chef/knife'
|
17
|
-
|
18
|
-
class EncryptPassword < Chef::Knife
|
19
|
-
deps do
|
20
|
-
require 'chef/search/query'
|
21
|
-
require File.expand_path('../compat', __FILE__)
|
22
|
-
include ChefVault::Compat
|
23
|
-
end
|
24
|
-
|
25
|
-
banner "knife encrypt password --search SEARCH --username USERNAME --password PASSWORD --admins ADMINS"
|
26
|
-
|
27
|
-
option :search,
|
28
|
-
:short => '-S SEARCH',
|
29
|
-
:long => '--search SEARCH',
|
30
|
-
:description => 'node search for nodes to encrypt for'
|
31
|
-
|
32
|
-
option :username,
|
33
|
-
:short => '-U USERNAME',
|
34
|
-
:long => '--username USERNAME',
|
35
|
-
:description => 'username of account to encrypt'
|
36
|
-
|
37
|
-
option :password,
|
38
|
-
:short => '-P PASSWORD',
|
39
|
-
:long => '--password PASSWORD',
|
40
|
-
:description => 'password of account to encrypt'
|
41
|
-
|
42
|
-
option :admins,
|
43
|
-
:short => '-A ADMINS',
|
44
|
-
:long => '--admins ADMINS',
|
45
|
-
:description => 'administrators who can decrypt password'
|
46
|
-
|
47
|
-
def run
|
48
|
-
unless config[:search]
|
49
|
-
puts("You must supply either -S or --search")
|
50
|
-
exit 1
|
51
|
-
end
|
52
|
-
unless config[:username]
|
53
|
-
puts("You must supply either -U or --username")
|
54
|
-
exit 1
|
55
|
-
end
|
56
|
-
unless config[:password]
|
57
|
-
puts("You must supply either -P or --password")
|
58
|
-
exit 1
|
59
|
-
end
|
60
|
-
unless config[:admins]
|
61
|
-
puts("You must supply either -A or --admins")
|
62
|
-
exit 1
|
63
|
-
end
|
64
|
-
|
65
|
-
extend_context_object(self)
|
66
|
-
|
67
|
-
data_bag = "passwords"
|
68
|
-
data_bag_path = "./data_bags/#{data_bag}"
|
69
|
-
|
70
|
-
unless ::File.exists?(data_bag_path)
|
71
|
-
require 'fileutils'
|
72
|
-
|
73
|
-
puts("INFO: #{data_bag_path} does not exist, creating...")
|
74
|
-
FileUtils.mkdir_p(data_bag_path)
|
75
|
-
end
|
76
|
-
|
77
|
-
node_search = config[:search]
|
78
|
-
admins = config[:admins]
|
79
|
-
username = config[:username]
|
80
|
-
password = config[:password]
|
81
|
-
current_dbi = Hash.new
|
82
|
-
current_dbi_keys = Hash.new
|
83
|
-
if File.exists?("#{data_bag_path}/#{username}_keys.json") && File.exists?("#{data_bag_path}/#{username}.json")
|
84
|
-
current_dbi_keys = JSON.parse(File.open("#{data_bag_path}/#{username}_keys.json"){ |file| file.read() })
|
85
|
-
current_dbi = JSON.parse(File.open("#{data_bag_path}/#{username}.json"){ |file| file.read() })
|
86
|
-
|
87
|
-
unless equal?(data_bag, username, "password", password)
|
88
|
-
puts("FATAL: Password in #{data_bag_path}/#{username}.json does not match password supplied!")
|
89
|
-
exit 1
|
90
|
-
end
|
91
|
-
else
|
92
|
-
puts("INFO: Existing data bag #{data_bag}/#{username} does not exist in #{data_bag_path}, continuing as fresh build...")
|
93
|
-
end
|
94
|
-
|
95
|
-
# Get the public keys for all of the nodes to encrypt for. Skipping the nodes that are already in
|
96
|
-
# the data bag
|
97
|
-
keyfob = Hash.new
|
98
|
-
public_keys = search(:node, node_search).map(&:name).map do |client|
|
99
|
-
begin
|
100
|
-
if current_dbi_keys[client]
|
101
|
-
puts("INFO: Skipping #{client} as it is already in the data bag...")
|
102
|
-
else
|
103
|
-
puts("INFO: Adding #{client} to public_key array...")
|
104
|
-
keyfob[client] = get_client_public_key(client)
|
105
|
-
end
|
106
|
-
rescue Exception => node_error
|
107
|
-
puts("WARNING: Caught exception: #{node_error.message} while processing #{client}, so skipping...")
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Get the public keys for the admin users, skipping users already in the data bag
|
112
|
-
public_keys << admins.split(/[\s,]+/).map do |user|
|
113
|
-
begin
|
114
|
-
if current_dbi_keys[user]
|
115
|
-
puts("INFO: Skipping #{user} as it is already in the data bag")
|
116
|
-
else
|
117
|
-
puts("INFO: Adding #{user} to public_key array...")
|
118
|
-
keyfob[user] = get_user_public_key(user)
|
119
|
-
end
|
120
|
-
rescue Exception => user_error
|
121
|
-
puts("WARNING: Caught exception: #{user_error.message} while processing #{user}, so skipping...")
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
if public_keys.length == 0
|
126
|
-
puts "A node search for #{node_search} returned no results"
|
127
|
-
exit 1
|
128
|
-
end
|
129
|
-
|
130
|
-
# Get the current secret, is nil if current secret does not exist yet
|
131
|
-
current_secret = get_shared_secret(data_bag, username)
|
132
|
-
data_bag_shared_key = current_secret ? current_secret : OpenSSL::PKey::RSA.new(245).to_pem.lines.to_a[1..-2].join
|
133
|
-
enc_db_key_dbi = current_dbi_keys.empty? ? Mash.new({id: "#{username}_keys"}) : current_dbi_keys
|
134
|
-
|
135
|
-
# Encrypt for every new node not already in the data bag
|
136
|
-
keyfob.each do |node,pkey|
|
137
|
-
puts("INFO: Encrypting for #{node}...")
|
138
|
-
enc_db_key_dbi[node] = Base64.encode64(pkey.public_encrypt(data_bag_shared_key))
|
139
|
-
end unless keyfob.empty?
|
140
|
-
|
141
|
-
# Delete existing keys data bag and rewrite the whole bag from memory
|
142
|
-
puts("INFO: Writing #{data_bag_path}/#{username}_keys.json...")
|
143
|
-
File.delete("#{data_bag_path}/#{username}_keys.json") if File.exists?("#{data_bag_path}/#{username}_keys.json")
|
144
|
-
File.open("#{data_bag_path}/#{username}_keys.json",'w'){ |file| file.write(JSON.pretty_generate(enc_db_key_dbi)) }
|
145
|
-
|
146
|
-
# If the existing password bag does not exist, write it out with the correct password
|
147
|
-
# Otherwise leave the existing bag alone
|
148
|
-
if current_dbi.empty?
|
149
|
-
dbi_mash = Mash.new({id: username, username: username, password: password})
|
150
|
-
dbi = Chef::DataBagItem.from_hash(dbi_mash)
|
151
|
-
edbi = Chef::EncryptedDataBagItem.encrypt_data_bag_item(dbi, data_bag_shared_key)
|
152
|
-
|
153
|
-
puts("INFO: Writing #{data_bag_path}/#{username}.json...")
|
154
|
-
File.open("#{data_bag_path}/#{username}.json",'w'){ |file| file.write(JSON.pretty_generate(edbi)) }
|
155
|
-
end
|
156
|
-
|
157
|
-
puts("INFO: Successfully wrote #{data_bag_path}/#{username}.json & #{data_bag_path}/#{username}_keys.json!")
|
158
|
-
end
|
159
|
-
|
160
|
-
def equal?(db, dbi, key, value)
|
161
|
-
data_bag_path = "./data_bags/#{db}"
|
162
|
-
|
163
|
-
shared_secret = get_shared_secret(db, dbi)
|
164
|
-
dbi = JSON.parse(open("#{data_bag_path}/#{dbi}.json").read())
|
165
|
-
dbi = Chef::EncryptedDataBagItem.new dbi, shared_secret
|
166
|
-
|
167
|
-
dbi[key] == value
|
168
|
-
end
|
169
|
-
|
170
|
-
def get_shared_secret(db, dbi)
|
171
|
-
data_bag_path = "./data_bags/#{db}"
|
172
|
-
|
173
|
-
private_key = OpenSSL::PKey::RSA.new(open(Chef::Config[:client_key]).read())
|
174
|
-
key = File.exists?("#{data_bag_path}/#{dbi}_keys.json") ? JSON.parse(File.open("#{data_bag_path}/#{dbi}_keys.json"){ |file| file.read() }) : nil
|
175
|
-
|
176
|
-
begin
|
177
|
-
private_key.private_decrypt(Base64.decode64(key[Chef::Config[:node_name]]))
|
178
|
-
rescue
|
179
|
-
nil
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
data/lib/chef/knife/compat.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
# Description: ChefVault::Compat module
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
3
|
-
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
# you may not use this file except in compliance with the License.
|
6
|
-
# You may obtain a copy of the License at
|
7
|
-
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
# See the License for the specific language governing permissions and
|
14
|
-
# limitations under the License.
|
15
|
-
|
16
|
-
# Make a wraper to chef10/11 "shef/shell" changes
|
17
|
-
|
18
|
-
class ChefVault
|
19
|
-
module Compat
|
20
|
-
require 'chef/version'
|
21
|
-
def extend_context_object(obj)
|
22
|
-
if Chef::VERSION.to_i >= 11
|
23
|
-
require "chef/shell/ext"
|
24
|
-
Shell::Extensions.extend_context_object(obj)
|
25
|
-
else
|
26
|
-
require 'chef/shef/ext'
|
27
|
-
Shef::Extensions.extend_context_object(obj)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def get_client_public_key(client)
|
32
|
-
get_public_key(api.get("clients/#{client}"))
|
33
|
-
end
|
34
|
-
|
35
|
-
def get_user_public_key(user)
|
36
|
-
begin
|
37
|
-
user = api.get("users/#{user}")
|
38
|
-
rescue Exception
|
39
|
-
puts("INFO: Could not locate user #{user}, searching for client key instead")
|
40
|
-
user = api.get("clients/#{user}")
|
41
|
-
end
|
42
|
-
get_public_key(user)
|
43
|
-
end
|
44
|
-
|
45
|
-
def get_public_key(client)
|
46
|
-
# Check the response back from the api call to see if
|
47
|
-
# we get 'certificate' which is Chef 10 or just
|
48
|
-
# 'public_key' which is Chef 11
|
49
|
-
unless client.is_a?(Chef::ApiClient)
|
50
|
-
name = client['name']
|
51
|
-
certificate = client['certificate']
|
52
|
-
public_key = client['public_key']
|
53
|
-
|
54
|
-
client = Chef::ApiClient.new
|
55
|
-
client.name name
|
56
|
-
client.admin false
|
57
|
-
|
58
|
-
if certificate
|
59
|
-
cert_der = OpenSSL::X509::Certificate.new certificate
|
60
|
-
client.public_key cert_der.public_key.to_s
|
61
|
-
else
|
62
|
-
client.public_key public_key
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
public_key = OpenSSL::PKey::RSA.new client.public_key
|
67
|
-
|
68
|
-
public_key
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|