chef-ssl-client 1.0.0 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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