File: http_client_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 (171 lines) | stat: -rw-r--r-- 6,779 bytes parent folder | download
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
require 'spec_helper'
require 'utils/ssl'

require 'fileutils'

require 'puppetserver/ca/utils/http_client'
require 'puppetserver/ca/utils/signing_digest'
require 'puppetserver/ca/logger'
require 'puppetserver/ca/action/setup'

RSpec.describe Puppetserver::Ca::Utils::HttpClient do
  include Utils::SSL

  Result = Struct.new(:code, :body)

  let(:log_level) { :info }

  before do
    @stdout = StringIO.new
    @stderr = StringIO.new
    @logger = Puppetserver::Ca::Logger.new(log_level, @stdout, @stderr)
  end

  it 'creates a store that can validate connections to CA' do
    Dir.mktmpdir do |tmpdir|
      with_ca_in(tmpdir) do |config, confdir|
        settings = Puppetserver::Ca::Config::Puppet.new(config).load(cli_overrides: {
          :hostcert    => "#{tmpdir}/hostcert.pem",
          :hostprivkey => "#{tmpdir}/hostkey.pem",
          :confdir     => confdir
        }, logger: @logger)

        setup_action = Puppetserver::Ca::Action::Setup.new(@logger)

        signer = Puppetserver::Ca::Utils::SigningDigest.new
        setup_action.generate_pki(settings, signer.digest)

        loader = Puppetserver::Ca::X509Loader.new(settings[:cacert], settings[:cakey], settings[:cacrl])
        cakey = loader.key
        cacert = loader.cert

        hostkey = OpenSSL::PKey::RSA.new(512)
        hostcert = create_cert(hostkey, 'foobar', cakey, cacert)
        File.write("#{tmpdir}/hostcert.pem", hostcert)
        File.write("#{tmpdir}/hostkey.pem", hostkey)

        FileUtils.cp(settings[:cacert], settings[:localcacert])
        FileUtils.cp(settings[:cacrl], settings[:hostcrl])

        client = Puppetserver::Ca::Utils::HttpClient.new(@logger, settings)
        store = client.store

        expect(store.verify(hostcert)).to be(true)
        expect(store.verify(cacert)).to be(true)
      end
    end
  end

  it 'create a URL with query params correctly' do
    query = { :state => "requested" }
    url = Puppetserver::Ca::Utils::HttpClient::URL.new('https', 'localhost', '8140',
                                                       'puppet-ca', 'v1', 'certificate_statuses', 'any_key', query)
    result = url.to_uri
    expect(result.to_s).to eq("https://localhost:8140/puppet-ca/v1/certificate_statuses/any_key?state=requested")
  end

  it 'creates valid default headers for HTTP requests when token file is not present' do
    Dir.mktmpdir do |tmpdir|
      with_ca_in(tmpdir) do |config, confdir|
        settings = Puppetserver::Ca::Config::Puppet.new(config).load(cli_overrides: {}, logger: @logger, ca_dir_warn: false)

        FileUtils.mkdir_p(settings[:certdir])
        FileUtils.cp(settings[:cacert], settings[:localcacert])
        FileUtils.cp(settings[:cacrl], settings[:hostcrl])

        url = Puppetserver::Ca::Utils::HttpClient::URL.new('https', 'localhost', '8140',
                                                           'puppet-ca', 'v1', 'certificate_status', 'any_key', {"desired_state" => "signed"})
        unauthed_client = Puppetserver::Ca::Utils::HttpClient.new(@logger, settings, with_client_cert: false)

        base_headers = { 'User-Agent'   => 'PuppetserverCaCli',
                         'Content-Type' => 'application/json',
                         'Accept'       => 'application/json' }

        auth_header = 'X-Authentication'
        token = 'foo'

        # Here we create a mock Net::HTTP::Connection object and validate the HTTP method the client created has the correct headers
        mock_unauthed_conn = double('connection')
        allow(mock_unauthed_conn).to receive(:request) do |http_method|
          headers = http_method.each_header.map {|h,v| [h.downcase, v]}.to_h

          base_headers.each_pair do |header, value|
            expect(headers[header.downcase]).to eq(value)
          end

          expect(headers[auth_header.downcase]).to eq(nil)

          Result.new(200, 'foo body')
        end

        # This bypasses the actual HTTP request and allows us to insert our mock Net::HTTP::Connection object.
        # The mock stdlib connection object will then be wrapped by our client & custom connection object.
        allow(Net::HTTP).to receive(:start) do |host, port, use_ssl:, cert_store:, cert:, key:, &request|
          request.call(mock_unauthed_conn)
        end

        # This receives our custom connection object and triggers the above expectations.
        unauthed_client.with_connection(url) do |connection|
          result = connection.put('input body', url, {})
          expect(result.body).to eq('foo body')
        end
      end
    end
  end

  it 'creates valid default headers for HTTP requests when token file is present' do
    Dir.mktmpdir do |tmpdir|
      with_ca_in(tmpdir) do |config, confdir|
        settings = Puppetserver::Ca::Config::Puppet.new(config).load(cli_overrides: {}, logger: @logger, ca_dir_warn: false)

        FileUtils.mkdir_p(settings[:certdir])
        FileUtils.cp(settings[:cacert], settings[:localcacert])
        FileUtils.cp(settings[:cacrl], settings[:hostcrl])

        url = Puppetserver::Ca::Utils::HttpClient::URL.new('https', 'localhost', '8140',
                                                           'puppet-ca', 'v1', 'certificate_status', 'any_key', {"desired_state" => "signed"})

        base_headers = { 'User-Agent'   => 'PuppetserverCaCli',
                         'Content-Type' => 'application/json',
                         'Accept'       => 'application/json' }

        auth_header = 'X-Authentication'
        token = 'foo'

        Dir.mkdir("#{tmpdir}/.puppetlabs")
        File.write("#{tmpdir}/.puppetlabs/token", token)

        env = ENV.to_h.dup
        env['HOME'] = tmpdir
        stub_const('ENV', env)

        authed_client = Puppetserver::Ca::Utils::HttpClient.new(@logger, settings, with_client_cert: false)

        # See the previous test for why this is necessary
        mock_authed_conn = double('connection')
        allow(mock_authed_conn).to receive(:request) do |http_method|
          headers = http_method.each_header.map {|h,v| [h.downcase, v]}.to_h

          base_headers.each_pair do |header, value|
            expect(headers[header.downcase]).to eq(value)
          end

          expect(headers[auth_header.downcase]).to eq(token)

          Result.new(200, 'bar body')
        end

        # See the previous test for why this is necessary
        allow(Net::HTTP).to receive(:start) do |host, port, use_ssl:, cert_store:, cert:, key:, &request|
          request.call(mock_authed_conn)
        end

        # See the previous test for why this is necessary
        authed_client.with_connection(url) do |connection|
          result = connection.put('input body', url, {})
          expect(result.body).to eq('bar body')
        end
      end
    end
  end
end