Skip to content

Commit ebdbe48

Browse files
committed
Added an Interface class to the CA to model puppetca's usage.
This class provides all of the semantics from puppetca, and appears to entirely duplicate the behaviour of the existing executable, with basically all of the code in a library file, instead of the executable. As such, I've deleted the test for the executable. We should have one, but it's not nearly as important.
1 parent 934fbba commit ebdbe48

6 files changed

Lines changed: 426 additions & 286 deletions

File tree

bin/puppetca

Lines changed: 19 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
# Licensed under the GNU Public License
9696

9797
require 'puppet'
98-
require 'puppet/sslcertificates'
98+
require 'puppet/ssl/certificate_authority'
9999
require 'getoptlong'
100100

101101
options = [
@@ -118,35 +118,27 @@ Puppet.settings.addargs(options)
118118

119119
result = GetoptLong.new(*options)
120120

121-
mode = nil
122-
all = false
123-
generate = nil
121+
modes = Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS
124122

125-
modes = [:clean, :list, :revoke, :generate, :sign, :print, :verify]
123+
all = false
124+
mode = nil
126125

127126
begin
128127
result.each { |opt,arg|
129128
case opt
129+
when "--clean"
130+
mode = :destroy
130131
when "--all"
131132
all = true
132133
when "--debug"
133134
Puppet::Util::Log.level = :debug
134-
when "--generate"
135-
generate = arg
136-
mode = :generate
137135
when "--help"
138136
if Puppet.features.usage?
139137
RDoc::usage && exit
140138
else
141139
puts "No help available unless you have RDoc::usage installed"
142140
exit
143141
end
144-
when "--list"
145-
mode = :list
146-
when "--revoke"
147-
mode = :revoke
148-
when "--sign"
149-
mode = :sign
150142
when "--version"
151143
puts "%s" % Puppet.version
152144
exit
@@ -172,12 +164,12 @@ Puppet.parse_config
172164
Puppet.genconfig
173165
Puppet.genmanifest
174166

167+
Puppet::Util::Log.newdestination :console
168+
175169
begin
176-
ca = Puppet::SSLCertificates::CA.new()
170+
ca = Puppet::SSL::CertificateAuthority.new
177171
rescue => detail
178-
if Puppet[:debug]
179-
puts detail.backtrace
180-
end
172+
puts detail.backtrace if Puppet[:trace]
181173
puts detail.to_s
182174
exit(23)
183175
end
@@ -187,157 +179,16 @@ unless mode
187179
exit(12)
188180
end
189181

190-
if [:verify, :print, :generate, :clean, :revoke, :list].include?(mode)
182+
if all
183+
hosts = :all
184+
else
191185
hosts = ARGV.collect { |h| h.downcase }
192186
end
193187

194-
if [:sign, :list].include?(mode)
195-
waiting = ca.list
196-
unless waiting.length > 0 or (mode == :list and all)
197-
puts "No certificates to sign"
198-
if ARGV.length > 0
199-
exit(17)
200-
else
201-
exit(0)
202-
end
203-
end
204-
end
205-
206-
case mode
207-
when :list
208-
waiting = ca.list
209-
if waiting.length > 0
210-
puts waiting.join("\n")
211-
end
212-
if all
213-
puts ca.list_signed.collect { |cert | cert.sub(/^/,"+ ") }.join("\n")
214-
end
215-
when :clean
216-
if hosts.empty?
217-
$stderr.puts "You must specify one or more hosts to clean"
218-
exit(24)
219-
end
220-
cleaned = false
221-
hosts.each do |host|
222-
cert = ca.getclientcert(host)[0]
223-
if cert.nil?
224-
$stderr.puts "Could not find client certificate for %s" % host
225-
next
226-
end
227-
ca.clean(host)
228-
cleaned = true
229-
end
230-
unless cleaned
231-
exit(27)
232-
end
233-
when :sign
234-
to_sign = ARGV.collect { |h| h.downcase }
235-
unless to_sign.length > 0 or all
236-
$stderr.puts(
237-
"You must specify to sign all certificates or you must specify hostnames"
238-
)
239-
exit(24)
240-
end
241-
242-
unless all
243-
to_sign.each { |host|
244-
unless waiting.include?(host)
245-
$stderr.puts "No waiting request for %s" % host
246-
end
247-
}
248-
waiting = waiting.find_all { |host|
249-
to_sign.include?(host)
250-
}
251-
end
252-
253-
waiting.each { |host|
254-
begin
255-
csr = ca.getclientcsr(host)
256-
rescue => detail
257-
$stderr.puts "Could not retrieve request for %s: %s" % [host, detail]
258-
end
259-
260-
begin
261-
ca.sign(csr)
262-
$stderr.puts "Signed %s" % host
263-
rescue => detail
264-
$stderr.puts "Could not sign request for %s: %s" % [host, detail]
265-
end
266-
267-
begin
268-
ca.removeclientcsr(host)
269-
rescue => detail
270-
$stderr.puts "Could not remove request for %s: %s" % [host, detail]
271-
end
272-
}
273-
when :generate
274-
# we need to generate a certificate for a host
275-
hosts.each { |host|
276-
puts "Generating certificate for %s" % host
277-
cert = Puppet::SSLCertificates::Certificate.new(
278-
:name => host
279-
)
280-
cert.mkcsr
281-
signedcert, cacert = ca.sign(cert.csr)
282-
283-
cert.cert = signedcert
284-
cert.cacert = cacert
285-
cert.write
286-
}
287-
when :print
288-
hosts.each { |h|
289-
cert = ca.getclientcert(h)[0]
290-
puts cert.to_text
291-
}
292-
when :revoke
293-
hosts.each { |h|
294-
serial = nil
295-
if h =~ /^0x[0-9a-f]+$/
296-
serial = h.to_i(16)
297-
elsif h =~ /^[0-9]+$/
298-
serial = h.to_i
299-
else
300-
cert = ca.getclientcert(h)[0]
301-
if cert.nil?
302-
$stderr.puts "Could not find client certificate for %s" % h
303-
else
304-
serial = cert.serial
305-
end
306-
end
307-
unless serial.nil?
308-
ca.revoke(serial)
309-
puts "Revoked certificate with serial #{serial}"
310-
end
311-
}
312-
when :verify
313-
unless ssl = %x{which openssl}.chomp
314-
raise "Can't verify certificates without the openssl binary and could not find one"
315-
end
316-
success = true
317-
318-
cacert = Puppet[:localcacert]
319-
320-
hosts.each do |host|
321-
print "%s: " % host
322-
file = ca.host2certfile(host)
323-
unless FileTest.exist?(file)
324-
puts "no certificate found"
325-
success = false
326-
next
327-
end
328-
329-
330-
command = %{#{ssl} verify -CAfile #{cacert} #{file}}
331-
output = %x{#{command}}
332-
if $? == 0
333-
puts "valid"
334-
else
335-
puts output
336-
success = false
337-
end
338-
end
339-
else
340-
$stderr.puts "Invalid mode %s" % mode
341-
exit(42)
188+
begin
189+
ca.apply(mode, :to => hosts)
190+
rescue => detail
191+
puts detail.backtrace if Puppet[:trace]
192+
puts detail.to_s
193+
exit(24)
342194
end
343-

lib/puppet/ssl/certificate_authority.rb

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,129 @@ class Puppet::SSL::CertificateAuthority
1414
require 'puppet/ssl/inventory'
1515
require 'puppet/ssl/certificate_revocation_list'
1616

17+
# This class is basically a hidden class that knows how to act
18+
# on the CA. It's only used by the 'puppetca' executable, and its
19+
# job is to provide a CLI-like interface to the CA class.
20+
class Interface
21+
INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify]
22+
23+
class InterfaceError < ArgumentError; end
24+
25+
attr_reader :method, :subjects
26+
27+
# Actually perform the work.
28+
def apply(ca)
29+
unless subjects or method == :list
30+
raise ArgumentError, "You must provide hosts or :all when using %s" % method
31+
end
32+
33+
begin
34+
if respond_to?(method)
35+
return send(method, ca)
36+
end
37+
38+
(subjects == :all ? ca.list : subjects).each do |host|
39+
ca.send(method, host)
40+
end
41+
rescue InterfaceError
42+
raise
43+
rescue => detail
44+
puts detail.backtrace if Puppet[:trace]
45+
Puppet.err "Could not call %s: %s" % [method, detail]
46+
end
47+
end
48+
49+
def generate(ca)
50+
raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all
51+
52+
subjects.each do |host|
53+
ca.generate(host)
54+
end
55+
end
56+
57+
def initialize(method, subjects)
58+
self.method = method
59+
self.subjects = subjects
60+
end
61+
62+
# List the hosts.
63+
def list(ca)
64+
unless subjects
65+
puts ca.waiting?.join("\n")
66+
return nil
67+
end
68+
69+
signed = ca.list
70+
requests = ca.waiting?
71+
72+
if subjects == :all
73+
hosts = [signed, requests].flatten
74+
else
75+
hosts = subjects
76+
end
77+
78+
hosts.uniq.sort.each do |host|
79+
if signed.include?(host)
80+
puts "+ " + host
81+
else
82+
puts host
83+
end
84+
end
85+
end
86+
87+
# Set the method to apply.
88+
def method=(method)
89+
raise ArgumentError, "Invalid method %s to apply" % method unless INTERFACE_METHODS.include?(method)
90+
@method = method
91+
end
92+
93+
# Print certificate information.
94+
def print(ca)
95+
(subjects == :all ? ca.list : subjects).each do |host|
96+
if value = ca.print(host)
97+
puts value
98+
else
99+
Puppet.err "Could not find certificate for %s" % host
100+
end
101+
end
102+
end
103+
104+
# Sign a given certificate.
105+
def sign(ca)
106+
list = subjects == :all ? ca.waiting? : subjects
107+
raise InterfaceError, "No waiting certificate requests to sign" if list.empty?
108+
list.each do |host|
109+
ca.sign(host)
110+
end
111+
end
112+
113+
# Set the list of hosts we're operating on. Also supports keywords.
114+
def subjects=(value)
115+
unless value == :all or value.is_a?(Array)
116+
raise ArgumentError, "Subjects must be an array or :all; not %s" % value
117+
end
118+
119+
if value.is_a?(Array) and value.empty?
120+
value = nil
121+
end
122+
123+
@subjects = value
124+
end
125+
end
126+
17127
attr_reader :name, :host
18128

129+
# Create and run an applicator. I wanted to build an interface where you could do
130+
# something like 'ca.apply(:generate).to(:all) but I don't think it's really possible.
131+
def apply(method, options)
132+
unless options[:to]
133+
raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all"
134+
end
135+
applier = Interface.new(method, options[:to])
136+
137+
applier.apply(self)
138+
end
139+
19140
# Retrieve (or create, if necessary) the certificate revocation list.
20141
def crl
21142
unless defined?(@crl)

lib/puppet/ssl/certificate_revocation_list.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def initialize(fakename)
3030
# CA, then write the CRL back to disk. The REASON must be one of the
3131
# OpenSSL::OCSP::REVOKED_* reasons
3232
def revoke(serial, cakey, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
33+
Puppet.notice "Revoked certificate with serial %s" % serial
3334
time = Time.now
3435

3536
# Add our revocation to the CRL.

0 commit comments

Comments
 (0)