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.

API:

  • public

Defined Under Namespace

Classes: InterfaceError

Constant Summary collapse

INTERFACE_METHODS =

API:

  • public

[:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint, :reinventory]
DESTRUCTIVE_METHODS =

API:

  • public

[:destroy, :revoke]
SUBJECTLESS_METHODS =

API:

  • public

[:list, :reinventory]
CERT_STATUS_GLYPHS =

API:

  • public

{:signed => '+', :request => ' ', :invalid => '-'}
VALID_CONFIRMATION_VALUES =

API:

  • public

%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.

API:

  • public



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)

API:

  • public



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

def digest
  @digest
end

#methodObject

API:

  • public



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

def method
  @method
end

#optionsObject (readonly)

API:

  • public



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

def options
  @options
end

#subjectsObject

API:

  • public



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.

API:

  • public



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}"
  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}"
  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.

API:

  • public



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}"
    end
  end
end

#format_attrs_and_exts(cert) ⇒ Object

API:

  • public



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

API:

  • public



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:

API:

  • public



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

API:

  • public



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

API:

  • public



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.

API:

  • public



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

API:

  • public



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.

API:

  • public



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}"
    end
  end
end

#reinventory(ca) ⇒ Object

API:

  • public



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:

API:

  • public



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.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}"

    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