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 CHANGED
@@ -1,6 +1,8 @@
1
1
  require "rubygems"
2
- require "commander/import"
2
+ # Load Chef before Commander, to avoid calls to Mixlib::Config going
3
+ # to Commmander's imports.
3
4
  require "chef-ssl/client"
5
+ require "commander/import"
4
6
 
5
7
  HighLine.colorize_strings
6
8
 
@@ -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
- path = File.expand_path('knife.rb', '~/.chef')
20
- Chef::Config.from_file(path)
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 = Chef::Config.chef_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
+
@@ -1,5 +1,5 @@
1
1
  module ChefSSL
2
2
  class Client
3
- VERSION = '1.0.0'
3
+ VERSION = '1.0.4'
4
4
  end
5
5
  end
@@ -0,0 +1,5 @@
1
+ module ChefSSL
2
+ class Client
3
+ VERSION = '1.0.3'
4
+ end
5
+ end
@@ -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
- 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)
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 --ca-name '/CN=My New CA' --ca-path ./newCA"
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 "--ca-name=STRING", String, "the distinguished name of the new CA"
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.ca_name)
77
+ name = OpenSSL::X509::Name.parse(options.dn)
78
78
  rescue NoMethodError
79
- raise "--ca-name is required and must be a distinguished name"
79
+ raise "--dn is required and must be a distinguished name"
80
80
  rescue TypeError
81
- raise "--ca-name is required and must be a distinguished name"
81
+ raise "--dn is required and must be a distinguished name"
82
82
  rescue OpenSSL::X509::NameError => e
83
- raise "--ca-name must specify a valid DN: #{e.message}"
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
- puts "#{'New CA DN'.cyan}: #{name.to_s}"
90
- puts
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
- print "\n#{'Creating new CA'.cyan}: "
96
+ say "\n#{'Creating new CA'.cyan}: "
97
97
  ChefSSL::Client::SigningAuthority.create(name, options.ca_path, passphrase)
98
- puts "done"
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
- puts "#{'Search CA'.cyan}: #{options.ca_name}" if options.ca_name
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
- puts
117
- puts "#{' Node Hostname'.cyan}: #{req.host}"
118
- puts "#{' Certificate Type'.cyan}: #{req.type.bold}"
119
- puts "#{' Certificate DN'.cyan}: #{req.subject.bold}"
120
- puts "#{' Requested CA'.cyan}: #{req.ca.bold}"
121
- puts "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
122
- puts
123
- puts HighLine.color(req.to_pem, :bright_black)
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
- puts "#{'Search name'.cyan}: #{options.name}"
142
+ say "#{'Search name'.cyan}: #{options.name}"
143
143
 
144
144
  client.common_name_search(options.name) do |req|
145
- puts
146
- puts "#{' Node Hostname'.cyan}: #{req.host}"
147
- puts "#{' Certificate Type'.cyan}: #{req.type.bold}"
148
- puts "#{' Certificate DN'.cyan}: #{req.subject.bold}"
149
- puts "#{' Requested CA'.cyan}: #{req.ca.bold}"
150
- puts "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
151
- puts
152
- puts HighLine.color(req.to_pem, :bright_black)
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
- puts "#{' Signed:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
173
- puts "#{'Subject:'.cyan} #{cert.subject}"
174
- puts "#{' Issuer:'.cyan} #{cert.issuer}"
175
- puts HighLine.color(cert.to_pem, :bright_black)
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
- puts "#{'WARNING:'.red.bold} #{'Issued certificate DN does not match request DN!'.bold}"
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
- puts "Saved OK"
188
+ say "Saved OK"
189
189
  rescue ChefSSL::Client::CertSaveFailed => e
190
- puts "Error saving: #{e.message}"
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
- puts "#{'Search CA'.cyan}: #{options.ca_name}"
223
+ say "#{'Search CA'.cyan}: #{options.ca_name}"
224
224
 
225
225
  client.ca_search(options.ca_name) do |req|
226
- puts
227
- puts "#{' Node Hostname'.cyan}: #{req.host}"
228
- puts "#{' Certificate Type'.cyan}: #{req.type.bold}"
229
- puts "#{' Certificate DN'.cyan}: #{req.subject.bold}"
230
- puts "#{' Requested CA'.cyan}: #{req.ca.bold}"
231
- puts "#{'Requested Validity'.cyan}: #{req.days.to_s.bold} days"
232
- puts
233
- puts HighLine.color(req.to_pem, :bright_black)
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
- puts
242
- puts "#{'Signed:'.cyan} SHA1 Fingerprint=#{cert.sha1_fingerprint}"
243
- puts HighLine.color(cert.to_pem, :bright_black)
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
- puts "Saved OK"
246
+ say "Saved OK"
247
247
  rescue ChefSSL::Client::CertSaveFailed => e
248
- puts "Error saving: #{e.message}"
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
- puts "All CSRs processed."
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.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: 2012-07-23 00:00:00.000000000Z
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: &2161212560 !ruby/object:Gem::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: *2161212560
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: &2161211920 !ruby/object:Gem::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.3
37
+ version: 1.0.4
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *2161211920
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: &2161211200 !ruby/object:Gem::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.0
53
+ version: 2.0.1
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *2161211200
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: &2161210300 !ruby/object:Gem::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.0
69
+ version: 1.6.15
55
70
  type: :runtime
56
71
  prerelease: false
57
- version_requirements: *2161210300
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: &2161209440 !ruby/object:Gem::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: *2161209440
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: &2161208680 !ruby/object:Gem::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: *2161208680
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: &2161207640 !ruby/object:Gem::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: *2161207640
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: &2161206800 !ruby/object:Gem::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: *2161206800
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: &2161205980 !ruby/object:Gem::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: *2161205980
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: &2161205520 !ruby/object:Gem::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: *2161205520
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: &2161204960 !ruby/object:Gem::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: *2161204960
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.6
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