Class: Puppet::SSL::CertificateAuthority::Interface

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/ssl/certificate_authority/interface.rb

Overview

This class is basically a hidden class that knows how to act on the CA. Its job is to provide a CLI-like interface to the CA class.

Defined Under Namespace

Classes: InterfaceError

Constant Summary collapse

INTERFACE_METHODS =
[:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint, :reinventory]
DESTRUCTIVE_METHODS =
[:destroy, :revoke]
SUBJECTLESS_METHODS =
[:list, :reinventory]
CERT_STATUS_GLYPHS =
{:signed => '+', :request => ' ', :invalid => '-'}
VALID_CONFIRMATION_VALUES =
%w{y Y yes Yes YES}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(method, options) ⇒ Interface

Returns a new instance of Interface.



48
49
50
51
52
53
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 48

def initialize(method, options)
  self.method = method
  self.subjects = options.delete(:to)
  @digest = options.delete(:digest)
  @options = options
end

Instance Attribute Details

#digestObject (readonly)

Returns the value of attribute digest.



16
17
18
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 16

def digest
  @digest
end

#methodObject

Returns the value of attribute method.



16
17
18
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 16

def method
  @method
end

#optionsObject (readonly)

Returns the value of attribute options.



16
17
18
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 16

def options
  @options
end

#subjectsObject

Returns the value of attribute subjects.



16
17
18
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 16

def subjects
  @subjects
end

Instance Method Details

#apply(ca) ⇒ Object

Actually perform the work.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 19

def apply(ca)
  unless subjects || SUBJECTLESS_METHODS.include?(method)
    raise ArgumentError, _("You must provide hosts or --all when using %{method}") % { method: method }
  end

  destructive_subjects = [:signed, :all].include?(subjects)
  if DESTRUCTIVE_METHODS.include?(method) && destructive_subjects
    subject_text = (subjects == :all ? subjects : _("all signed"))
    raise ArgumentError, _("Refusing to %{method} %{subject_text} certs, provide an explicit list of certs to %{method}") % { method: method, subject_text: subject_text }
  end

  # if the interface implements the method, use it instead of the ca's method
  if respond_to?(method)
    send(method, ca)
  else
    (subjects == :all ? ca.list : subjects).each do |host|
      ca.send(method, host)
    end
  end
end

#fingerprint(ca) ⇒ Object

Print certificate information.



259
260
261
262
263
264
265
266
267
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 259

def fingerprint(ca)
  (subjects == :all ? ca.list + ca.waiting?: subjects).each do |host|
    if cert = (Puppet::SSL::Certificate.indirection.find(host) || Puppet::SSL::CertificateRequest.indirection.find(host))
      puts "#{host} #{cert.digest(@digest)}"
    else
	      raise ArgumentError, _("Could not find certificate for %{host}") % { host: host }
    end
  end
end

#format_attrs_and_exts(cert) ⇒ Object



232
233
234
235
236
237
238
239
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 232

def format_attrs_and_exts(cert)
  exts = []
  exts += cert.custom_extensions if cert.respond_to?(:custom_extensions)
  exts += cert.custom_attributes if cert.respond_to?(:custom_attributes)
  exts += cert.request_extensions if cert.respond_to?(:request_extensions)

  exts.map {|e| "#{e['oid']}: #{e['value'].inspect}" }.sort
end

#format_host(host, info, width, format) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 120

def format_host(host, info, width, format)
  case format
  when :machine
    machine_host_formatting(host, info)
  when :human
    human_host_formatting(host, info)
  else
    if options[:verbose]
      machine_host_formatting(host, info)
    else
      legacy_host_formatting(host, info, width)
    end
  end
end

#generate(ca) ⇒ Object

Raises:



40
41
42
43
44
45
46
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 40

def generate(ca)
  raise InterfaceError, _("It makes no sense to generate all hosts; you must specify a list") if subjects == :all

  subjects.each do |host|
    ca.generate(host, options)
  end
end

#human_host_formatting(host, info) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 161

def human_host_formatting(host, info)
  type         = info[:type]
  verify_error = info[:verify_error]
  cert         = info[:cert]
  alt_names    = cert.subject_alt_names - [host]
  extensions   = format_attrs_and_exts(cert)

  glyph       = CERT_STATUS_GLYPHS[type]
  fingerprint = cert.digest(@digest).to_s

  if type == :invalid || (extensions.empty? && alt_names.empty?)
    extension_string = ''
  else
    if !alt_names.empty?
      extensions.unshift("alt names: #{alt_names.map(&:inspect).join(', ')}")
    end

    extension_string = "\n    Extensions:\n      "
    extension_string << extensions.join("\n      ")
  end

  if type == :signed
    expiration_string = "\n    Expiration: #{cert.expiration.iso8601}"
  else
    expiration_string = ''
  end

  status = case type
           when :invalid then "Invalid - #{verify_error}"
           when :request then "Request Pending"
           when :signed then "Signed"
           end

  output = "#{glyph} #{host.inspect}"
  output << "\n  #{fingerprint}"
  output << "\n    Status: #{status}"
  output << expiration_string
  output << extension_string
  output << "\n"

  output
end

#legacy_host_formatting(host, info, width) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 204

def legacy_host_formatting(host, info, width)
  type         = info[:type]
  verify_error = info[:verify_error]
  cert         = info[:cert]
  alt_names    = cert.subject_alt_names - [host]
  extensions   = format_attrs_and_exts(cert)

  glyph       = CERT_STATUS_GLYPHS[type]
  name        = host.inspect.ljust(width)
  fingerprint = cert.digest(@digest).to_s

  if type != :invalid
    if alt_names.empty?
      alt_name_string = nil
    else
      alt_name_string = "(alt names: #{alt_names.map(&:inspect).join(', ')})"
    end

    if extensions.empty?
      extension_string = nil
    else
      extension_string = "**"
    end
  end

  [glyph, name, fingerprint, alt_name_string, verify_error, extension_string].compact.join(' ')
end

#list(ca) ⇒ Object

List the hosts.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 56

def list(ca)
  signed = ca.list if [:signed, :all].include?(subjects)
  requests = ca.waiting?

  case subjects
  when :all
    hosts = [signed, requests].flatten
  when :signed
    hosts = signed.flatten
  when nil
    hosts = requests
  else
    hosts = subjects
    signed = ca.list(hosts)
  end

  certs = {:signed => {}, :invalid => {}, :request => {}}

  return if hosts.empty?

  hosts.uniq.sort.each do |host|
    verify_error = nil

    begin
      ca.verify(host) unless requests.include?(host)
    rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details
      verify_error = "(#{details.to_s})"
    end

    if verify_error
      type = :invalid
      cert = Puppet::SSL::Certificate.indirection.find(host)
    elsif (signed and signed.include?(host))
      type = :signed
      cert = Puppet::SSL::Certificate.indirection.find(host)
    else
      type = :request
      cert = Puppet::SSL::CertificateRequest.indirection.find(host)
    end

    certs[type][host] = {
      :cert         => cert,
      :type         => type,
      :verify_error => verify_error,
    }
  end

  names = certs.values.map(&:keys).flatten

  name_width = names.sort_by(&:length).last.length rescue 0
  # We quote these names, so account for those characters
  name_width += 2

  output = [:request, :signed, :invalid].map do |type|
    next if certs[type].empty?

    certs[type].map do |host, info|
      format_host(host, info, name_width, options[:format])
    end
  end.flatten.compact.sort.join("\n")

  puts output
end

#machine_host_formatting(host, info) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 135

def machine_host_formatting(host, info)
  type         = info[:type]
  verify_error = info[:verify_error]
  cert         = info[:cert]
  alt_names    = cert.subject_alt_names - [host]
  extensions   = format_attrs_and_exts(cert)

  glyph       = CERT_STATUS_GLYPHS[type]
  name        = host.inspect
  fingerprint = cert.digest(@digest).to_s

  expiration  = cert.expiration.iso8601 if type == :signed

  if type != :invalid
    if !alt_names.empty?
      extensions.unshift("alt names: #{alt_names.map(&:inspect).join(', ')}")
    end

    if !extensions.empty?
       = "(#{extensions.join(', ')})" unless extensions.empty?
    end
  end

  [glyph, name, fingerprint, expiration, , verify_error].compact.join(' ')
end

Print certificate information.



248
249
250
251
252
253
254
255
256
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 248

def print(ca)
  (subjects == :all ? ca.list  : subjects).each do |host|
    if value = ca.print(host)
      puts value
    else
      raise ArgumentError, _("Could not find certificate for %{host}") % { host: host }
    end
  end
end

#reinventory(ca) ⇒ Object



308
309
310
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 308

def reinventory(ca)
  ca.inventory.rebuild
end

#sign(ca) ⇒ Object

Signs given certificates or all waiting if subjects == :all

Raises:



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/puppet/ssl/certificate_authority/interface.rb', line 270

def sign(ca)
  list = subjects == :all ? ca.waiting? : subjects
  raise InterfaceError, _("No waiting certificate requests to sign") if list.empty?

  signing_options = options.select { |k,_|
    [:allow_authorization_extensions, :allow_dns_alt_names].include?(k)
  }

  list.each do |host|
    cert = Puppet::SSL::CertificateRequest.indirection.find(host)

    raise InterfaceError, _("Could not find CSR for: %{host}.") % { host: host.inspect } unless cert

    # ca.sign will also do this - and it should if it is called
    # elsewhere - but we want to reject an attempt to sign a
    # problematic csr as early as possible for usability concerns.
    ca.check_internal_signing_policies(host, cert, signing_options)

    name_width = host.inspect.length
    info = {:type => :request, :cert => cert}
    host_string = format_host(host, info, name_width, options[:format])
    puts _("Signing Certificate Request for:\n%{host_string}") % { host_string: host_string }

    if options[:interactive]
      STDOUT.print _("Sign Certificate Request? [y/N] ")

      if !options[:yes]
        input = STDIN.gets.chomp
        raise InterfaceError, _("NOT Signing Certificate Request") unless VALID_CONFIRMATION_VALUES.include?(input)
      else
        puts _("Assuming YES from `-y' or `--assume-yes' flag")
      end
    end

    ca.sign(host, signing_options)
  end
end