chef-ssl-client 1.0.0 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/chef-ssl +3 -1
- data/lib/chef-ssl/client.rb +13 -3
- data/lib/chef-ssl/client.rb~ +64 -0
- data/lib/chef-ssl/client/request.rb +1 -5
- data/lib/chef-ssl/client/request.rb~ +42 -0
- data/lib/chef-ssl/client/signing_authority.rb~ +83 -0
- data/lib/chef-ssl/client/version.rb +1 -1
- data/lib/chef-ssl/client/version.rb~ +5 -0
- data/lib/chef-ssl/command.rb +55 -55
- data/lib/chef-ssl/command.rb~ +260 -0
- metadata +96 -31
data/bin/chef-ssl
CHANGED
data/lib/chef-ssl/client.rb
CHANGED
@@ -4,6 +4,7 @@ require 'json'
|
|
4
4
|
require 'openssl'
|
5
5
|
require 'digest/sha2'
|
6
6
|
require 'active_support/hash_with_indifferent_access'
|
7
|
+
require 'chef/knife'
|
7
8
|
require 'chef/config'
|
8
9
|
require 'chef/node'
|
9
10
|
|
@@ -16,11 +17,20 @@ module ChefSSL
|
|
16
17
|
class Client
|
17
18
|
|
18
19
|
def initialize
|
19
|
-
|
20
|
-
|
20
|
+
Chef::Knife.new.tap do |knife|
|
21
|
+
# Set the log-level, knife style. This equals :error level
|
22
|
+
Chef::Config[:verbosity] = knife.config[:verbosity] ||= 0
|
23
|
+
knife.configure_chef
|
24
|
+
end
|
25
|
+
|
21
26
|
Spice.reset
|
27
|
+
|
28
|
+
# avoid Spice issue if chef_server_url has a trailing slash.
|
29
|
+
chef_server_url = Chef::Config.chef_server_url
|
30
|
+
chef_server_url.gsub!(/\/$/, '')
|
31
|
+
|
22
32
|
Spice.setup do |s|
|
23
|
-
s.server_url =
|
33
|
+
s.server_url = chef_server_url
|
24
34
|
s.client_name = Chef::Config.node_name
|
25
35
|
s.client_key = Spice.read_key_file(File.expand_path(Chef::Config.client_key))
|
26
36
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spice'
|
2
|
+
require 'eassl'
|
3
|
+
require 'json'
|
4
|
+
require 'openssl'
|
5
|
+
require 'digest/sha2'
|
6
|
+
require 'active_support/hash_with_indifferent_access'
|
7
|
+
require 'chef/config'
|
8
|
+
require 'chef/node'
|
9
|
+
|
10
|
+
require 'chef-ssl/client/version'
|
11
|
+
require 'chef-ssl/client/request'
|
12
|
+
require 'chef-ssl/client/signing_authority'
|
13
|
+
require 'chef-ssl/client/issued_certificate'
|
14
|
+
|
15
|
+
module ChefSSL
|
16
|
+
class Client
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
path = File.expand_path('knife.rb', '~/.chef')
|
20
|
+
Chef::Config.from_file(path)
|
21
|
+
Spice.reset
|
22
|
+
Spice.setup do |s|
|
23
|
+
s.server_url = Chef::Config.chef_server_url
|
24
|
+
s.client_name = Chef::Config.node_name
|
25
|
+
s.client_key = Spice.read_key_file(File.expand_path(Chef::Config.client_key))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.load_authority(options)
|
30
|
+
SigningAuthority.load(
|
31
|
+
:path => options[:path],
|
32
|
+
:password => options[:password]
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ca_search(ca=nil)
|
37
|
+
if ca
|
38
|
+
nodes = Spice.nodes("csr_outbox_*_ca:#{ca}")
|
39
|
+
else
|
40
|
+
nodes = Spice.nodes("csr_outbox_*")
|
41
|
+
end
|
42
|
+
nodes.each do |node|
|
43
|
+
node.normal['csr_outbox'].each do |id, data|
|
44
|
+
next if data['csr'].nil? # XXX warn, raise?
|
45
|
+
yield Request.new(node.name, data)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def common_name_search(name)
|
51
|
+
name_sha = Digest::SHA256.new << name
|
52
|
+
cert_id = name_sha.to_s
|
53
|
+
nodes = Spice.nodes("csr_outbox_*_id:#{cert_id}")
|
54
|
+
nodes.each do |node|
|
55
|
+
node.normal['csr_outbox'].each do |id, data|
|
56
|
+
next unless data['id'] == cert_id
|
57
|
+
next if data['csr'].nil? # XXX warn, raise?
|
58
|
+
yield Request.new(node.name, data)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -31,11 +31,7 @@ module ChefSSL
|
|
31
31
|
def self.create(key, type, options)
|
32
32
|
name = EaSSL::CertificateName.new(options)
|
33
33
|
csr = EaSSL::SigningRequest.new(:name => name, :key => key)
|
34
|
-
|
35
|
-
data = {
|
36
|
-
:type => type
|
37
|
-
}
|
38
|
-
self.new('localhost', data, csr)
|
34
|
+
self.new('localhost', { 'type' => type }, csr)
|
39
35
|
end
|
40
36
|
end
|
41
37
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ChefSSL
|
2
|
+
class Client
|
3
|
+
class Request
|
4
|
+
|
5
|
+
attr_reader :host, :csr, :type, :ca, :id, :name, :key, :days
|
6
|
+
|
7
|
+
def initialize(host, data, csr=nil)
|
8
|
+
@host = host
|
9
|
+
@csr = csr || EaSSL::SigningRequest.new.load(data['csr'])
|
10
|
+
@type = data['type']
|
11
|
+
@ca = data['ca']
|
12
|
+
@id = data['id']
|
13
|
+
@name = data['name']
|
14
|
+
@key = data['key']
|
15
|
+
@days = data['days'] || (365 * 5)
|
16
|
+
end
|
17
|
+
|
18
|
+
def subject
|
19
|
+
@csr.subject.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_pem
|
23
|
+
@csr.to_pem
|
24
|
+
end
|
25
|
+
|
26
|
+
def issue_certificate(cert_text)
|
27
|
+
cert = EaSSL::Certificate.new({}).load(cert_text)
|
28
|
+
IssuedCertificate.new(self, cert)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create(key, type, options)
|
32
|
+
name = EaSSL::CertificateName.new(options)
|
33
|
+
csr = EaSSL::SigningRequest.new(:name => name, :key => key)
|
34
|
+
|
35
|
+
data = {
|
36
|
+
'type' => type
|
37
|
+
}
|
38
|
+
self.new('localhost', data, csr)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ChefSSL
|
2
|
+
class Client
|
3
|
+
class SigningAuthority
|
4
|
+
|
5
|
+
def self.load(options)
|
6
|
+
ca = EaSSL::CertificateAuthority.load(
|
7
|
+
:ca_path => options[:path],
|
8
|
+
:ca_password => options[:password]
|
9
|
+
)
|
10
|
+
self.new(ca)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(ca)
|
14
|
+
@ca = ca
|
15
|
+
end
|
16
|
+
|
17
|
+
def dn
|
18
|
+
@ca.certificate.subject.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def sign(req)
|
22
|
+
cert = @ca.create_certificate(req.csr, req.type, req.days)
|
23
|
+
IssuedCertificate.new(req, cert, @ca)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create(name, path, passphrase)
|
27
|
+
config = {
|
28
|
+
:ca_dir => path,
|
29
|
+
:password => passphrase,
|
30
|
+
:ca_rsa_key_length => 1024,
|
31
|
+
:ca_cert_days => 3650,
|
32
|
+
:name => name
|
33
|
+
}
|
34
|
+
config[:serial_file] = File.join(config[:ca_dir], 'serial.txt')
|
35
|
+
config[:keypair_file] = File.join(config[:ca_dir], 'cakey.pem')
|
36
|
+
config[:cert_file] = File.join(config[:ca_dir], 'cacert.pem')
|
37
|
+
|
38
|
+
Dir.mkdir config[:ca_dir]
|
39
|
+
Dir.mkdir File.join(config[:ca_dir], 'private'), 0700
|
40
|
+
Dir.mkdir File.join(config[:ca_dir], 'newcerts')
|
41
|
+
Dir.mkdir File.join(config[:ca_dir], 'crl')
|
42
|
+
|
43
|
+
File.open config[:serial_file], 'w' do |f| f << '1' end
|
44
|
+
|
45
|
+
keypair = OpenSSL::PKey::RSA.new config[:ca_rsa_key_length]
|
46
|
+
|
47
|
+
cert = OpenSSL::X509::Certificate.new
|
48
|
+
cert.subject = cert.issuer = config[:name]
|
49
|
+
cert.not_before = Time.now
|
50
|
+
cert.not_after = Time.now + config[:ca_cert_days] * 24 * 60 * 60
|
51
|
+
cert.public_key = keypair.public_key
|
52
|
+
cert.serial = 0x0
|
53
|
+
cert.version = 2 # X509v3
|
54
|
+
|
55
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
56
|
+
ef.subject_certificate = cert
|
57
|
+
ef.issuer_certificate = cert
|
58
|
+
cert.extensions = [
|
59
|
+
ef.create_extension("basicConstraints", "CA:TRUE", true),
|
60
|
+
ef.create_extension("nsComment", "Ruby/OpenSSL/chef-ssl Generated Certificate"),
|
61
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
62
|
+
ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
|
63
|
+
]
|
64
|
+
cert.add_extension ef.create_extension("authorityKeyIdentifier",
|
65
|
+
"keyid:always,issuer:always")
|
66
|
+
cert.sign keypair, OpenSSL::Digest::SHA1.new
|
67
|
+
|
68
|
+
keypair_export = keypair.export OpenSSL::Cipher::DES.new(:EDE3, :CBC),
|
69
|
+
config[:password]
|
70
|
+
|
71
|
+
File.open config[:keypair_file], "w", 0400 do |fp|
|
72
|
+
fp << keypair_export
|
73
|
+
end
|
74
|
+
|
75
|
+
File.open config[:cert_file], "w", 0644 do |f|
|
76
|
+
f << cert.to_pem
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
data/lib/chef-ssl/command.rb
CHANGED
@@ -55,11 +55,11 @@ command :issue do |c|
|
|
55
55
|
req = ChefSSL::Client::Request.create(key, options.type, name)
|
56
56
|
cert = authority.sign(req)
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
say "#{'Key:'.cyan}"
|
59
|
+
say HighLine.color(key.private_key.to_s, :bright_black)
|
60
|
+
say ""
|
61
|
+
say "#{'Certificate:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
|
62
|
+
say HighLine.color(cert.to_pem, :bright_black)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
@@ -67,35 +67,35 @@ command :makeca do |c|
|
|
67
67
|
c.syntax = "chef-ssl makeca [options]"
|
68
68
|
c.description = "Creates a new CA"
|
69
69
|
c.example "Upload cert for CSR www.venda.com",
|
70
|
-
"chef-ssl makeca --
|
70
|
+
"chef-ssl makeca --dn '/CN=My New CA' --ca-path ./newCA"
|
71
71
|
|
72
72
|
c.option "--ca-path=STRING", String, "the path to the new CA"
|
73
|
-
c.option "--
|
73
|
+
c.option "--dn=STRING", String, "the distinguished name of the new CA"
|
74
74
|
|
75
75
|
c.action do |args, options|
|
76
76
|
begin
|
77
|
-
name = OpenSSL::X509::Name.parse(options.
|
77
|
+
name = OpenSSL::X509::Name.parse(options.dn)
|
78
78
|
rescue NoMethodError
|
79
|
-
raise "--
|
79
|
+
raise "--dn is required and must be a distinguished name"
|
80
80
|
rescue TypeError
|
81
|
-
raise "--
|
81
|
+
raise "--dn is required and must be a distinguished name"
|
82
82
|
rescue OpenSSL::X509::NameError => e
|
83
|
-
raise "--
|
83
|
+
raise "--dn must specify a valid DN: #{e.message}"
|
84
84
|
end
|
85
85
|
|
86
86
|
raise "CA path is required" unless options.ca_path
|
87
87
|
raise "CA path must not already exist" if Dir.glob(options.ca_path).length > 0
|
88
88
|
|
89
|
-
|
90
|
-
|
89
|
+
say "#{'New CA DN'.cyan}: #{name.to_s}"
|
90
|
+
say ""
|
91
91
|
|
92
92
|
passphrase = ask("Enter new CA passphrase: ") { |q| q.echo = false }
|
93
93
|
passphrase2 = ask("Re-enter new CA passphrase: ") { |q| q.echo = false }
|
94
94
|
raise "passphrases do not match" unless passphrase == passphrase2
|
95
95
|
|
96
|
-
|
96
|
+
say "\n#{'Creating new CA'.cyan}: "
|
97
97
|
ChefSSL::Client::SigningAuthority.create(name, options.ca_path, passphrase)
|
98
|
-
|
98
|
+
say "done"
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
@@ -110,17 +110,17 @@ command :search do |c|
|
|
110
110
|
c.action do |args, options|
|
111
111
|
client = ChefSSL::Client.new
|
112
112
|
|
113
|
-
|
113
|
+
say "#{'Search CA'.cyan}: #{options.ca_name}" if options.ca_name
|
114
114
|
|
115
115
|
client.ca_search(options.ca_name) do |req|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
116
|
+
say ""
|
117
|
+
say "#{' Node Hostname'.cyan}: #{req.host}"
|
118
|
+
say "#{' Certificate Type'.cyan}: #{req.type.bold}"
|
119
|
+
say "#{' Certificate DN'.cyan}: #{req.subject.bold}"
|
120
|
+
say "#{' Requested CA'.cyan}: #{req.ca.bold}"
|
121
|
+
say "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
|
122
|
+
say ""
|
123
|
+
say HighLine.color(req.to_pem, :bright_black)
|
124
124
|
end
|
125
125
|
end
|
126
126
|
end
|
@@ -139,17 +139,17 @@ command :sign do |c|
|
|
139
139
|
|
140
140
|
client = ChefSSL::Client.new
|
141
141
|
|
142
|
-
|
142
|
+
say "#{'Search name'.cyan}: #{options.name}"
|
143
143
|
|
144
144
|
client.common_name_search(options.name) do |req|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
145
|
+
say ""
|
146
|
+
say "#{' Node Hostname'.cyan}: #{req.host}"
|
147
|
+
say "#{' Certificate Type'.cyan}: #{req.type.bold}"
|
148
|
+
say "#{' Certificate DN'.cyan}: #{req.subject.bold}"
|
149
|
+
say "#{' Requested CA'.cyan}: #{req.ca.bold}"
|
150
|
+
say "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
|
151
|
+
say ""
|
152
|
+
say HighLine.color(req.to_pem, :bright_black)
|
153
153
|
|
154
154
|
cert = nil
|
155
155
|
|
@@ -169,13 +169,13 @@ command :sign do |c|
|
|
169
169
|
end
|
170
170
|
|
171
171
|
if cert
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
172
|
+
say "#{' Signed:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
|
173
|
+
say "#{'Subject:'.cyan} #{cert.subject}"
|
174
|
+
say "#{' Issuer:'.cyan} #{cert.issuer}"
|
175
|
+
say HighLine.color(cert.to_pem, :bright_black)
|
176
176
|
|
177
177
|
unless cert.subject == req.subject
|
178
|
-
|
178
|
+
say "#{'WARNING:'.red.bold} #{'Issued certificate DN does not match request DN!'.bold}"
|
179
179
|
end
|
180
180
|
|
181
181
|
HighLine.new.choose do |menu|
|
@@ -185,9 +185,9 @@ command :sign do |c|
|
|
185
185
|
menu.choice :yes do
|
186
186
|
begin
|
187
187
|
cert.save!
|
188
|
-
|
188
|
+
say "Saved OK"
|
189
189
|
rescue ChefSSL::Client::CertSaveFailed => e
|
190
|
-
|
190
|
+
say "Error saving: #{e.message}"
|
191
191
|
end
|
192
192
|
end
|
193
193
|
menu.choice :no do
|
@@ -220,17 +220,17 @@ command :autosign do |c|
|
|
220
220
|
|
221
221
|
client = ChefSSL::Client.new
|
222
222
|
|
223
|
-
|
223
|
+
say "#{'Search CA'.cyan}: #{options.ca_name}"
|
224
224
|
|
225
225
|
client.ca_search(options.ca_name) do |req|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
226
|
+
say ""
|
227
|
+
say "#{' Node Hostname'.cyan}: #{req.host}"
|
228
|
+
say "#{' Certificate Type'.cyan}: #{req.type.bold}"
|
229
|
+
say "#{' Certificate DN'.cyan}: #{req.subject.bold}"
|
230
|
+
say "#{' Requested CA'.cyan}: #{req.ca.bold}"
|
231
|
+
say "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
|
232
|
+
say ""
|
233
|
+
say HighLine.color(req.to_pem, :bright_black)
|
234
234
|
|
235
235
|
HighLine.new.choose do |menu|
|
236
236
|
menu.layout = :one_line
|
@@ -238,14 +238,14 @@ command :autosign do |c|
|
|
238
238
|
|
239
239
|
menu.choice :yes do
|
240
240
|
cert = authority.sign(req)
|
241
|
-
|
242
|
-
|
243
|
-
|
241
|
+
say ""
|
242
|
+
say "#{'Signed:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
|
243
|
+
say HighLine.color(cert.to_pem, :bright_black)
|
244
244
|
begin
|
245
245
|
cert.save!
|
246
|
-
|
246
|
+
say "Saved OK"
|
247
247
|
rescue ChefSSL::Client::CertSaveFailed => e
|
248
|
-
|
248
|
+
say "Error saving: #{e.message}"
|
249
249
|
end
|
250
250
|
end
|
251
251
|
|
@@ -255,6 +255,6 @@ command :autosign do |c|
|
|
255
255
|
end
|
256
256
|
end
|
257
257
|
|
258
|
-
|
258
|
+
say "All CSRs processed."
|
259
259
|
end
|
260
260
|
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
program :name, "chef-ssl"
|
2
|
+
program :version, ChefSSL::Client::VERSION
|
3
|
+
program :description, "Chef-automated SSL certificate signing tool"
|
4
|
+
program :help_formatter, :compact
|
5
|
+
|
6
|
+
default_command :help
|
7
|
+
|
8
|
+
command :issue do |c|
|
9
|
+
c.syntax = "chef-ssl issue [options]"
|
10
|
+
c.description = "Issue an ad hoc certificate"
|
11
|
+
c.example "Issue cert for www.venda.com",
|
12
|
+
"chef-ssl issue --ca-path ./myCA --dn /CN=foo --type server"
|
13
|
+
|
14
|
+
c.option "--ca-path=STRING", String, "the path to the new CA"
|
15
|
+
c.option "--dn=STRING", String, "the distinguished name for the new certificate"
|
16
|
+
c.option "--type=STRING", String, "the type of certificate, client or server"
|
17
|
+
|
18
|
+
c.action do |args, options|
|
19
|
+
raise "CA path is required" unless options.ca_path
|
20
|
+
raise "DN is required" unless options.dn
|
21
|
+
raise "type is required" unless options.type
|
22
|
+
|
23
|
+
begin
|
24
|
+
dn = OpenSSL::X509::Name.parse(options.dn)
|
25
|
+
rescue NoMethodError
|
26
|
+
raise "--dn is required and must be a distinguished name"
|
27
|
+
rescue TypeError
|
28
|
+
raise "--dn is required and must be a distinguished name"
|
29
|
+
rescue OpenSSL::X509::NameError => e
|
30
|
+
raise "--dn must specify a valid DN: #{e.message}"
|
31
|
+
end
|
32
|
+
|
33
|
+
unless options.type == 'server' || options.type == 'client'
|
34
|
+
raise "type must be server or client"
|
35
|
+
end
|
36
|
+
|
37
|
+
authority = ChefSSL::Client.load_authority(
|
38
|
+
:password => ask("Enter CA passphrase: ") { |q| q.echo = false },
|
39
|
+
:path => options.ca_path
|
40
|
+
)
|
41
|
+
|
42
|
+
key = EaSSL::Key.new
|
43
|
+
|
44
|
+
h = dn.to_a.reduce({}) { |h, elem| h[elem[0]] = elem[1]; h }
|
45
|
+
name = {
|
46
|
+
:city => h['L'],
|
47
|
+
:state => h['ST'],
|
48
|
+
:country => h['C'],
|
49
|
+
:department => h['OU'],
|
50
|
+
:common_name => h['CN'],
|
51
|
+
:organization => h['O'],
|
52
|
+
:email => h['emailAddress']
|
53
|
+
}
|
54
|
+
|
55
|
+
req = ChefSSL::Client::Request.create(key, options.type, name)
|
56
|
+
cert = authority.sign(req)
|
57
|
+
|
58
|
+
puts "#{'Key:'.cyan}"
|
59
|
+
puts HighLine.color(key.private_key.to_s, :bright_black)
|
60
|
+
puts
|
61
|
+
puts "#{'Certificate:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
|
62
|
+
puts HighLine.color(cert.to_pem, :bright_black)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
command :makeca do |c|
|
67
|
+
c.syntax = "chef-ssl makeca [options]"
|
68
|
+
c.description = "Creates a new CA"
|
69
|
+
c.example "Upload cert for CSR www.venda.com",
|
70
|
+
"chef-ssl makeca --dn '/CN=My New CA' --ca-path ./newCA"
|
71
|
+
|
72
|
+
c.option "--ca-path=STRING", String, "the path to the new CA"
|
73
|
+
c.option "--dn=STRING", String, "the distinguished name of the new CA"
|
74
|
+
|
75
|
+
c.action do |args, options|
|
76
|
+
begin
|
77
|
+
name = OpenSSL::X509::Name.parse(options.dn)
|
78
|
+
rescue NoMethodError
|
79
|
+
raise "--dn is required and must be a distinguished name"
|
80
|
+
rescue TypeError
|
81
|
+
raise "--dn is required and must be a distinguished name"
|
82
|
+
rescue OpenSSL::X509::NameError => e
|
83
|
+
raise "--dn must specify a valid DN: #{e.message}"
|
84
|
+
end
|
85
|
+
|
86
|
+
raise "CA path is required" unless options.ca_path
|
87
|
+
raise "CA path must not already exist" if Dir.glob(options.ca_path).length > 0
|
88
|
+
|
89
|
+
say "#{'New CA DN'.cyan}: #{name.to_s}"
|
90
|
+
say ""
|
91
|
+
|
92
|
+
passphrase = ask("Enter new CA passphrase: ") { |q| q.echo = false }
|
93
|
+
passphrase2 = ask("Re-enter new CA passphrase: ") { |q| q.echo = false }
|
94
|
+
raise "passphrases do not match" unless passphrase == passphrase2
|
95
|
+
|
96
|
+
say "\n#{'Creating new CA'.cyan}: "
|
97
|
+
ChefSSL::Client::SigningAuthority.create(name, options.ca_path, passphrase)
|
98
|
+
say "done"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
command :search do |c|
|
103
|
+
c.syntax = "chef-ssl search [options]"
|
104
|
+
c.description = "Searches for outstanding CSRs"
|
105
|
+
c.example "Search for all CSRs awaiting signing by CA bob",
|
106
|
+
"chef-ssl search --ca-name bob"
|
107
|
+
|
108
|
+
c.option "--ca-name=STRING", String, "a name of a CA to limit the search by"
|
109
|
+
|
110
|
+
c.action do |args, options|
|
111
|
+
client = ChefSSL::Client.new
|
112
|
+
|
113
|
+
say "#{'Search CA'.cyan}: #{options.ca_name}" if options.ca_name
|
114
|
+
|
115
|
+
client.ca_search(options.ca_name) do |req|
|
116
|
+
puts
|
117
|
+
say "#{' Node Hostname'.cyan}: #{req.host}"
|
118
|
+
say "#{' Certificate Type'.cyan}: #{req.type.bold}"
|
119
|
+
say "#{' Certificate DN'.cyan}: #{req.subject.bold}"
|
120
|
+
say "#{' Requested CA'.cyan}: #{req.ca.bold}"
|
121
|
+
say "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
|
122
|
+
puts
|
123
|
+
say HighLine.color(req.to_pem, :bright_black)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
command :sign do |c|
|
129
|
+
c.syntax = "chef-ssl sign [options]"
|
130
|
+
c.description = "Search for the given CSR by name and provide a signed certificate"
|
131
|
+
c.example "Provide signed cert for CSR www.venda.com",
|
132
|
+
"chef-ssl --name www.venda.com"
|
133
|
+
|
134
|
+
c.option "--name=STRING", String, "common name of the CSR to search for"
|
135
|
+
|
136
|
+
c.action do |args, options|
|
137
|
+
|
138
|
+
raise "--name is required" unless options.name
|
139
|
+
|
140
|
+
client = ChefSSL::Client.new
|
141
|
+
|
142
|
+
say "#{'Search name'.cyan}: #{options.name}"
|
143
|
+
|
144
|
+
client.common_name_search(options.name) do |req|
|
145
|
+
puts
|
146
|
+
say "#{' Node Hostname'.cyan}: #{req.host}"
|
147
|
+
say "#{' Certificate Type'.cyan}: #{req.type.bold}"
|
148
|
+
say "#{' Certificate DN'.cyan}: #{req.subject.bold}"
|
149
|
+
say "#{' Requested CA'.cyan}: #{req.ca.bold}"
|
150
|
+
say "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
|
151
|
+
puts
|
152
|
+
say HighLine.color(req.to_pem, :bright_black)
|
153
|
+
|
154
|
+
cert = nil
|
155
|
+
|
156
|
+
HighLine.new.choose do |menu|
|
157
|
+
menu.layout = :one_line
|
158
|
+
menu.prompt = "Sign this? "
|
159
|
+
|
160
|
+
menu.choice :yes do
|
161
|
+
cert_text = ask("Paste cert text") do |q|
|
162
|
+
q.gather = ""
|
163
|
+
end
|
164
|
+
cert = req.issue_certificate(cert_text.join("\n"))
|
165
|
+
end
|
166
|
+
menu.choice :no do
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
if cert
|
172
|
+
say "#{' Signed:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
|
173
|
+
say "#{'Subject:'.cyan} #{cert.subject}"
|
174
|
+
say "#{' Issuer:'.cyan} #{cert.issuer}"
|
175
|
+
say HighLine.color(cert.to_pem, :bright_black)
|
176
|
+
|
177
|
+
unless cert.subject == req.subject
|
178
|
+
say "#{'WARNING:'.red.bold} #{'Issued certificate DN does not match request DN!'.bold}"
|
179
|
+
end
|
180
|
+
|
181
|
+
HighLine.new.choose do |menu|
|
182
|
+
menu.layout = :one_line
|
183
|
+
menu.prompt = "Save certificate? "
|
184
|
+
|
185
|
+
menu.choice :yes do
|
186
|
+
begin
|
187
|
+
cert.save!
|
188
|
+
say "Saved OK"
|
189
|
+
rescue ChefSSL::Client::CertSaveFailed => e
|
190
|
+
say "Error saving: #{e.message}"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
menu.choice :no do
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
command :autosign do |c|
|
203
|
+
c.syntax = "chef-ssl autosign [options]"
|
204
|
+
c.description = "Search for CSRs and sign them with the given CA"
|
205
|
+
c.example "Sign with 'CA'",
|
206
|
+
"chef-ssl --ca-path CA --ca-name autoCA"
|
207
|
+
|
208
|
+
c.option "--ca-path=STRING", String, "the path to the signing CA"
|
209
|
+
c.option "--ca-name=STRING", String, "the name of the signing CA"
|
210
|
+
|
211
|
+
c.action do |args, options|
|
212
|
+
|
213
|
+
raise "--ca-path is required" unless options.ca_path
|
214
|
+
raise "--ca-name is required" unless options.ca_name
|
215
|
+
|
216
|
+
authority = ChefSSL::Client.load_authority(
|
217
|
+
:password => ask("Enter CA passphrase: ") { |q| q.echo = false },
|
218
|
+
:path => options.ca_path
|
219
|
+
)
|
220
|
+
|
221
|
+
client = ChefSSL::Client.new
|
222
|
+
|
223
|
+
say "#{'Search CA'.cyan}: #{options.ca_name}"
|
224
|
+
|
225
|
+
client.ca_search(options.ca_name) do |req|
|
226
|
+
puts
|
227
|
+
say "#{' Node Hostname'.cyan}: #{req.host}"
|
228
|
+
say "#{' Certificate Type'.cyan}: #{req.type.bold}"
|
229
|
+
say "#{' Certificate DN'.cyan}: #{req.subject.bold}"
|
230
|
+
say "#{' Requested CA'.cyan}: #{req.ca.bold}"
|
231
|
+
say "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
|
232
|
+
puts
|
233
|
+
say HighLine.color(req.to_pem, :bright_black)
|
234
|
+
|
235
|
+
HighLine.new.choose do |menu|
|
236
|
+
menu.layout = :one_line
|
237
|
+
menu.prompt = "#{'Sign with'.cyan}: #{HighLine.color(authority.dn, :bold)}\nSign this? "
|
238
|
+
|
239
|
+
menu.choice :yes do
|
240
|
+
cert = authority.sign(req)
|
241
|
+
puts
|
242
|
+
say "#{'Signed:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
|
243
|
+
say HighLine.color(cert.to_pem, :bright_black)
|
244
|
+
begin
|
245
|
+
cert.save!
|
246
|
+
say "Saved OK"
|
247
|
+
rescue ChefSSL::Client::CertSaveFailed => e
|
248
|
+
say "Error saving: #{e.message}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
menu.choice :no do
|
253
|
+
nil
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
say "All CSRs processed."
|
259
|
+
end
|
260
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chef-ssl-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: chef
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,43 +21,63 @@ dependencies:
|
|
21
21
|
version: 0.10.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.10.0
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: spice
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
|
-
- -
|
35
|
+
- - '='
|
31
36
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.0.
|
37
|
+
version: 1.0.4
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.4
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: eassl2
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ~>
|
42
52
|
- !ruby/object:Gem::Version
|
43
|
-
version: 2.0.
|
53
|
+
version: 2.0.1
|
44
54
|
type: :runtime
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.1
|
47
62
|
- !ruby/object:Gem::Dependency
|
48
63
|
name: highline
|
49
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
|
-
- -
|
67
|
+
- - ! '>='
|
53
68
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.6.
|
69
|
+
version: 1.6.15
|
55
70
|
type: :runtime
|
56
71
|
prerelease: false
|
57
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.6.15
|
58
78
|
- !ruby/object:Gem::Dependency
|
59
79
|
name: commander
|
60
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
61
81
|
none: false
|
62
82
|
requirements:
|
63
83
|
- - ~>
|
@@ -65,10 +85,15 @@ dependencies:
|
|
65
85
|
version: 4.1.0
|
66
86
|
type: :runtime
|
67
87
|
prerelease: false
|
68
|
-
version_requirements:
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 4.1.0
|
69
94
|
- !ruby/object:Gem::Dependency
|
70
95
|
name: multi_json
|
71
|
-
requirement:
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
72
97
|
none: false
|
73
98
|
requirements:
|
74
99
|
- - ! '>='
|
@@ -76,10 +101,15 @@ dependencies:
|
|
76
101
|
version: 1.0.0
|
77
102
|
type: :runtime
|
78
103
|
prerelease: false
|
79
|
-
version_requirements:
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.0.0
|
80
110
|
- !ruby/object:Gem::Dependency
|
81
111
|
name: activesupport
|
82
|
-
requirement:
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
83
113
|
none: false
|
84
114
|
requirements:
|
85
115
|
- - ! '>='
|
@@ -87,10 +117,15 @@ dependencies:
|
|
87
117
|
version: 3.1.0
|
88
118
|
type: :runtime
|
89
119
|
prerelease: false
|
90
|
-
version_requirements:
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 3.1.0
|
91
126
|
- !ruby/object:Gem::Dependency
|
92
127
|
name: rspec
|
93
|
-
requirement:
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
94
129
|
none: false
|
95
130
|
requirements:
|
96
131
|
- - ~>
|
@@ -98,10 +133,15 @@ dependencies:
|
|
98
133
|
version: 2.10.0
|
99
134
|
type: :development
|
100
135
|
prerelease: false
|
101
|
-
version_requirements:
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 2.10.0
|
102
142
|
- !ruby/object:Gem::Dependency
|
103
143
|
name: flexmock
|
104
|
-
requirement:
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
105
145
|
none: false
|
106
146
|
requirements:
|
107
147
|
- - ~>
|
@@ -109,10 +149,15 @@ dependencies:
|
|
109
149
|
version: 0.9.0
|
110
150
|
type: :development
|
111
151
|
prerelease: false
|
112
|
-
version_requirements:
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.9.0
|
113
158
|
- !ruby/object:Gem::Dependency
|
114
159
|
name: simplecov
|
115
|
-
requirement:
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
116
161
|
none: false
|
117
162
|
requirements:
|
118
163
|
- - ! '>='
|
@@ -120,10 +165,15 @@ dependencies:
|
|
120
165
|
version: '0'
|
121
166
|
type: :development
|
122
167
|
prerelease: false
|
123
|
-
version_requirements:
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
124
174
|
- !ruby/object:Gem::Dependency
|
125
175
|
name: rake
|
126
|
-
requirement:
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
127
177
|
none: false
|
128
178
|
requirements:
|
129
179
|
- - ! '>='
|
@@ -131,7 +181,12 @@ dependencies:
|
|
131
181
|
version: '0'
|
132
182
|
type: :development
|
133
183
|
prerelease: false
|
134
|
-
version_requirements:
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
135
190
|
description: A command-line client the ssl cookbook's signing requirements
|
136
191
|
email: auto-ssl@venda.com
|
137
192
|
executables:
|
@@ -145,10 +200,15 @@ files:
|
|
145
200
|
- bin/chef-ssl
|
146
201
|
- lib/chef-ssl/client/issued_certificate.rb
|
147
202
|
- lib/chef-ssl/client/request.rb
|
203
|
+
- lib/chef-ssl/client/request.rb~
|
148
204
|
- lib/chef-ssl/client/signing_authority.rb
|
205
|
+
- lib/chef-ssl/client/signing_authority.rb~
|
149
206
|
- lib/chef-ssl/client/version.rb
|
207
|
+
- lib/chef-ssl/client/version.rb~
|
150
208
|
- lib/chef-ssl/client.rb
|
209
|
+
- lib/chef-ssl/client.rb~
|
151
210
|
- lib/chef-ssl/command.rb
|
211
|
+
- lib/chef-ssl/command.rb~
|
152
212
|
homepage: https://github.com/VendaTech/chef-cookbook-ssl
|
153
213
|
licenses: []
|
154
214
|
post_install_message:
|
@@ -161,17 +221,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
161
221
|
- - ! '>='
|
162
222
|
- !ruby/object:Gem::Version
|
163
223
|
version: '0'
|
224
|
+
segments:
|
225
|
+
- 0
|
226
|
+
hash: -1808698484037623876
|
164
227
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
228
|
none: false
|
166
229
|
requirements:
|
167
230
|
- - ! '>='
|
168
231
|
- !ruby/object:Gem::Version
|
169
232
|
version: '0'
|
233
|
+
segments:
|
234
|
+
- 0
|
235
|
+
hash: -1808698484037623876
|
170
236
|
requirements: []
|
171
237
|
rubyforge_project:
|
172
|
-
rubygems_version: 1.8.
|
238
|
+
rubygems_version: 1.8.24
|
173
239
|
signing_key:
|
174
240
|
specification_version: 3
|
175
241
|
summary: A command-line client the ssl cookbook's signing requirements
|
176
242
|
test_files: []
|
177
|
-
has_rdoc: true
|