File: import_spec.rb

package info (click to toggle)
ruby-puppetserver-ca-cli 2.7.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 696 kB
  • sloc: ruby: 6,970; sh: 4; makefile: 3
file content (272 lines) | stat: -rw-r--r-- 10,319 bytes parent folder | download | duplicates (2)
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
require 'spec_helper'
require 'utils/ssl'
require 'shared_examples/setup'

require 'tmpdir'
require 'fileutils'

require 'puppetserver/ca/logger'
require 'puppetserver/ca/action/import'

RSpec.describe Puppetserver::Ca::Action::Import do
  include Utils::SSL

  let(:stdout) { StringIO.new }
  let(:stderr) { StringIO.new }
  let(:logger) { Puppetserver::Ca::Logger.new(:info, stdout, stderr) }
  let(:usage) do
    /.*Usage:.* puppetserver ca import.*Display this command-specific help output.*/m
  end

  subject { Puppetserver::Ca::Action::Import.new(logger) }

  it 'prints the help output & returns 1 if invalid flags are given' do
    _, exit_code = subject.parse(['--hello'])
    expect(stderr.string).to match(/Error.*--hello/m)
    expect(stderr.string).to match(usage)
    expect(exit_code).to be 1
  end


  it 'prints the help output & returns 1 if no input is given' do
    _, exit_code = subject.parse([])
    expect(stderr.string).to match(usage)
    expect(exit_code).to be 1
  end

  it 'does not print the help output if called correctly' do
    Dir.mktmpdir do |tmpdir|
      with_files_in tmpdir do |bundle, key, chain, conf|
        _, maybe_code = subject.parse(['--cert-bundle', bundle,
                                       '--private-key', key,
                                       '--crl-chain', chain,
                                       '--config', conf])
        expect(stderr.string).to be_empty
        expect(maybe_code).to be nil
      end
    end
  end

  describe 'validation' do
    it 'requires the --cert-bundle' do
      _, exit_code = subject.parse(['--private-key', 'foo', '--crl-chain', 'bar'])
      expect(stderr.string).to include('Missing required argument')
      expect(stderr.string).to match(usage)
      expect(exit_code).to be 1
    end

    it 'requires the --private-key' do
      _, exit_code = subject.parse(['--cert-bundle', 'foo', '--crl-chain', 'bar'])
      expect(stderr.string).to include('Missing required argument')
      expect(stderr.string).to match(usage)
      expect(exit_code).to be 1
    end

    it 'requires the --crl-chain' do
      _, exit_code = subject.parse(['--cert-bundle', 'foo', '--private-key', 'bar'])
      expect(stderr.string).to include('Missing required argument')
      expect(stderr.string).to match(usage)
      expect(exit_code).to be 1
    end

    it 'requires cert-bundle, private-key, and crl-chain to be readable' do
      # All errors are surfaced from validations
      Dir.mktmpdir do |tmpdir|
        exit_code = subject.run({ 'cert-bundle' => File.join(tmpdir, 'cert_bundle.pem'),
                                  'private-key' => File.join(tmpdir, 'private_key.pem'),
                                  'crl-chain' => File.join(tmpdir, 'crl_chain.pem'),
                                  'certname' => '' })
        expect(stderr.string).to match(/Could not read .*cert_bundle.pem/)
        expect(stderr.string).to match(/Could not read .*private_key.pem/)
        expect(stderr.string).to match(/Could not read .*crl_chain.pem/)
        expect(exit_code).to be 1
      end
    end

    it 'validates all certs in bundle are parseable' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          File.open(bundle, 'a') do |f|
            f.puts '-----BEGIN CERTIFICATE-----'
            f.puts 'garbage'
            f.puts '-----END CERTIFICATE-----'
          end
          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain,
                                    'certname' => '' })
          expect(stderr.string).to match(/Could not parse .*bundle.pem/)
          expect(stderr.string).to include('garbage')
        end
      end
    end

    it 'validates that there are certs in the bundle' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          File.open(bundle, 'w') {|f| f.puts '' }
          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain,
                                    'certname' => '' })
          expect(stderr.string).to match(/Could not detect .*bundle.pem/)
        end
      end
    end

    it 'validates the private key' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          File.open(key, 'w') {|f| f.puts '' }
          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain })
          expect(stderr.string).to match(/Could not parse .*key.pem/)
        end
      end
    end

    it 'validates the private key and leaf cert match' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          File.open(key, 'w') {|f| f.puts OpenSSL::PKey::RSA.new(512).to_pem }
          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain })
          expect(stderr.string).to include('Could not find certificate matching private key')
        end
      end
    end

    it 'validates all crls in chain are parseable' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          File.open(chain, 'a') do |f|
            f.puts '-----BEGIN X509 CRL-----'
            f.puts 'garbage'
            f.puts '-----END X509 CRL-----'
          end
          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain })
          expect(stderr.string).to match(/Could not parse .*chain.pem/)
          expect(stderr.string).to include('garbage')
        end
      end
    end

    it 'validates that there are crls in the chain, if given chain' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          File.open(chain, 'w') {|f| f.puts '' }
          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain })
          expect(stderr.string).to match(/Could not detect .*chain.pem/)
        end
      end
    end

    it 'generates a leaf crl if none is provided' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          loader = Puppetserver::Ca::X509Loader.new(bundle, key, chain)

          crls_without_leaf = loader.crls.reject {|c| c.issuer == loader.cert.subject }
          File.open(chain, 'w') do |f|
            crls_without_leaf.each {|c| f.puts(c.to_pem) }
          end

          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain,
                                    'config' => conf,
                                    'certname' => '',
                                    'subject-alt-names' => '' })

          expect(exit_code).to be 0

          new_crls = File.read(File.join(tmpdir, 'ca', 'ca_crl.pem')).scan(/-----BEGIN X509 CRL-----.*?-----END X509 CRL-----/m)
          new_crls.map! {|s| OpenSSL::X509::CRL.new(s) }

          expect(new_crls.any? {|c| c.issuer == loader.cert.subject }).to be true
        end
      end
    end

    it 'validates the root crl is present after generating a leaf crl' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          baz_key = OpenSSL::PKey::RSA.new(512)
          baz_cert = create_cert(baz_key, 'baz')
          baz_crl = create_crl(baz_cert, baz_key)

          File.open(chain, 'w') { |f| f.puts(baz_crl.to_pem) }

          exit_code = subject.run({ 'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain,
                                    'config' => conf,
                                    'certname' => '',
                                    'subject-alt-names' => '' })

          expect(exit_code).to be 1
          expect(stderr.string).to include('unable to get certificate CRL')
        end
      end
    end

    it 'validates that leaf cert is valid wrt the provided chain/bundle' do
      Dir.mktmpdir do |tmpdir|
        bundle_file = File.join(tmpdir, 'bundle.pem')
        key_file = File.join(tmpdir, 'key.pem')
        chain_file = File.join(tmpdir, 'chain.pem')

        root_key = OpenSSL::PKey::RSA.new(512)
        leaf_key = OpenSSL::PKey::RSA.new(512)

        File.open(key_file, 'w') do |f|
          f.puts leaf_key.to_pem
        end

        root_cert = create_cert(root_key, 'foo')
        leaf_cert = create_cert(leaf_key, 'bar', root_key, root_cert)

        File.open(bundle_file, 'w') do |f|
          f.puts leaf_cert.to_pem
          f.puts root_cert.to_pem
        end

        # This should ensure the leaf cert is revoked
        root_crl = create_crl(root_cert, root_key, [leaf_cert])
        leaf_crl = create_crl(leaf_cert, leaf_key)

        File.open(chain_file, 'w') do |f|
          f.puts leaf_crl.to_pem
          f.puts root_crl.to_pem
        end

        exit_code = subject.run({ 'cert-bundle' => bundle_file,
                                  'private-key'=> key_file,
                                  'crl-chain' => chain_file })
        expect(stderr.string).to include('Leaf certificate could not be validated')
      end
    end

    it 'validates config from cli is readable' do
      Dir.mktmpdir do |tmpdir|
        with_files_in tmpdir do |bundle, key, chain, conf|
          FileUtils.rm conf
          exit_code = subject.run({ 'config' => conf,
                                    'cert-bundle' => bundle,
                                    'private-key'=> key,
                                    'crl-chain' => chain })
          expect(stderr.string).to match(/Could not read file .*puppet.conf/)
        end
      end
    end
  end

  include_examples 'properly sets up ca and ssl dir', Puppetserver::Ca::Action::Import

end