summaryrefslogtreecommitdiffstats
path: root/lib/puppet/face/ca.rb
blob: 00591d6374ae4b105d7ed3cae6f51974f04ba2c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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
160
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
203
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
231
232
233
require 'puppet/face'

Puppet::Face.define(:ca, '0.1.0') do
  copyright "Puppet Labs", 2011
  license   "Apache 2 license; see COPYING"

  summary "Local Puppet Certificate Authority management."

  description <<-TEXT
    This provides local management of the Puppet Certificate Authority.

    You can use this subcommand to sign outstanding certificate requests, list
    and manage local certificates, and inspect the state of the CA.
  TEXT

  action :list do
    summary "List certificates and/or certificate requests."

    description <<-TEXT
      This will list the current certificates and certificate signing requests
      in the Puppet CA.  You will also get the fingerprint, and any certificate
      verification failure reported.
    TEXT

    option "--[no-]all" do
      summary "Include all certificates and requests."
    end

    option "--[no-]pending" do
      summary "Include pending certificate signing requests."
    end

    option "--[no-]signed" do
      summary "Include signed certificates."
    end

    option "--subject PATTERN" do
      summary "Only list if the subject matches PATTERN."

      description <<-TEXT
        Only include certificates or requests where subject matches PATTERN.

        PATTERN is interpreted as a regular expression, allowing complex
        filtering of the content.
      TEXT
    end

    when_invoked do |options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      pattern = options[:subject].nil? ? nil :
        Regexp.new(options[:subject], Regexp::IGNORECASE)

      pending = options[:pending].nil? ? options[:all] : options[:pending]
      signed  = options[:signed].nil?  ? options[:all] : options[:signed]

      # By default we list pending, so if nothing at all was requested...
      unless pending or signed then pending = true end

      hosts = []

      pending and hosts += ca.waiting?
      signed  and hosts += ca.list

      pattern and hosts = hosts.select {|hostname| pattern.match hostname }

      hosts.sort.map {|host| Puppet::SSL::Host.new(host) }
    end

    when_rendering :console do |hosts|
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      length = hosts.map{|x| x.name.length }.max + 1

      hosts.map do |host|
        name = host.name.ljust(length)
        if host.certificate_request then
          "  #{name} (#{host.certificate_request.fingerprint})"
        else
          begin
            ca.verify(host.certificate)
            "+ #{name} (#{host.certificate.fingerprint})"
          rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e
            "- #{name} (#{host.certificate.fingerprint}) (#{e.to_s})"
          end
        end
      end.join("\n")
    end
  end

  action :destroy do
    when_invoked do |host, options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      ca.destroy host
    end
  end

  action :revoke do
    when_invoked do |host, options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      begin
        ca.revoke host
      rescue ArgumentError => e
        # This is a bit naff, but it makes the behaviour consistent with the
        # destroy action.  The underlying tools could be nicer for that sort
        # of thing; they have fairly inconsistent reporting of failures.
        raise unless e.to_s =~ /Could not find a serial number for /
        "Nothing was revoked"
      end
    end
  end

  action :generate do
    when_invoked do |host, options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      begin
        ca.generate host
      rescue RuntimeError => e
        if e.to_s =~ /already has a requested certificate/
          "#{host} already has a certificate request; use sign instead"
        else
          raise
        end
      rescue ArgumentError => e
        if e.to_s =~ /A Certificate already exists for /
          "#{host} already has a certificate"
        else
          raise
        end
      end
    end
  end

  action :sign do
    when_invoked do |host, options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      begin
        ca.sign host
      rescue ArgumentError => e
        if e.to_s =~ /Could not find certificate request/
          e.to_s
        else
          raise
        end
      end
    end
  end

  action :print do
    when_invoked do |host, options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      ca.print host
    end
  end

  action :fingerprint do
    option "--digest ALGORITHM" do
      summary "The hash algorithm to use when displaying the fingerprint"
    end

    when_invoked do |host, options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      begin
        # I want the default from the CA, not to duplicate it, but passing
        # 'nil' explicitly means that we don't get that.  This works...
        if options.has_key? :digest
          ca.fingerprint host, options[:digest]
        else
          ca.fingerprint host
        end
      rescue ArgumentError => e
        raise unless e.to_s =~ /Could not find a certificate or csr for/
        nil
      end
    end
  end

  action :verify do
    when_invoked do |host, options|
      raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
      unless ca = Puppet::SSL::CertificateAuthority.instance
        raise "Unable to fetch the CA"
      end

      begin
        ca.verify host
        { :host => host, :valid => true }
      rescue ArgumentError => e
        raise unless e.to_s =~ /Could not find a certificate for/
        { :host => host, :valid => false, :error => e.to_s }
      rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e
        { :host => host, :valid => false, :error => e.to_s }
      end
    end

    when_rendering :console do |value|
      if value[:valid]
        nil
      else
        "Could not verify #{value[:host]}: #{value[:error]}"
      end
    end
  end
end