File: trusted_information_spec.rb

package info (click to toggle)
puppet-agent 8.10.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 27,392 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (213 lines) | stat: -rw-r--r-- 6,946 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
require 'spec_helper'
require 'puppet/certificate_factory'

require 'puppet/context/trusted_information'

describe Puppet::Context::TrustedInformation, :unless => RUBY_PLATFORM == 'java' do
  let(:key) { OpenSSL::PKey::RSA.new(Puppet[:keylength]) }

  let(:csr) do
    csr = Puppet::SSL::CertificateRequest.new("csr")
    csr.generate(key, :extension_requests => {
      '1.3.6.1.4.1.15.1.2.1' => 'Ignored CSR extension',

      '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info',
      '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info',
    })
    csr
  end

  let(:cert) do
    cert = Puppet::SSL::Certificate.from_instance(Puppet::CertificateFactory.build('ca', csr, csr.content, 1))

    # The cert must be signed so that it can be successfully be DER-decoded later
    signer = Puppet::SSL::CertificateSigner.new
    signer.sign(cert.content, key)
    cert
  end

  let(:external_data) {
    {
      'string' => 'a',
      'integer' => 1,
      'boolean' => true,
      'hash' => { 'one' => 'two' },
      'array' => ['b', 2, {}]
    }
  }

  def allow_external_trusted_data(certname, data)
    command = 'generate_data.sh'
    Puppet[:trusted_external_command] = command
    # The expand_path bit is necessary b/c :trusted_external_command is a
    # file_or_directory setting, and file_or_directory settings automatically
    # expand the given path.
    allow(Puppet::Util::Execution).to receive(:execute).with([File.expand_path(command), certname], anything).and_return(JSON.dump(data))
  end

  it "defaults external to an empty hash" do
    trusted = Puppet::Context::TrustedInformation.new(false, 'ignored', nil)

    expect(trusted.external).to eq({})
  end

  context "when remote" do
    it "has no cert information when it isn't authenticated" do
      trusted = Puppet::Context::TrustedInformation.remote(false, 'ignored', nil)

      expect(trusted.authenticated).to eq(false)
      expect(trusted.certname).to be_nil
      expect(trusted.extensions).to eq({})
    end

    it "is remote and has certificate information when it is authenticated" do
      trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', cert)

      expect(trusted.authenticated).to eq('remote')
      expect(trusted.certname).to eq('cert name')
      expect(trusted.extensions).to eq({
        '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info',
        '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info',
      })
      expect(trusted.hostname).to eq('cert name')
      expect(trusted.domain).to be_nil
    end

    it "is remote but lacks certificate information when it is authenticated" do
      expect(Puppet).to receive(:info).once.with("TrustedInformation expected a certificate, but none was given.")

      trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', nil)

      expect(trusted.authenticated).to eq('remote')
      expect(trusted.certname).to eq('cert name')
      expect(trusted.extensions).to eq({})
    end

    it 'contains external trusted data' do
      allow_external_trusted_data('cert name', external_data)

      trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', nil)

      expect(trusted.external).to eq(external_data)
    end

    it 'does not run the trusted external command when creating a trusted context' do
      Puppet[:trusted_external_command] = '/usr/bin/generate_data.sh'

      expect(Puppet::Util::Execution).to receive(:execute).never
      Puppet::Context::TrustedInformation.remote(true, 'cert name', cert)
    end

    it 'only runs the trusted external command the first time it is invoked' do
      command = 'generate_data.sh'
      Puppet[:trusted_external_command] = command

      # See allow_external_trusted_data to understand why expand_path is necessary
      expect(Puppet::Util::Execution).to receive(:execute).with([File.expand_path(command), 'cert name'], anything).and_return(JSON.dump(external_data)).once

      trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', cert)
      trusted.external
      trusted.external
    end
  end

  context "when local" do
    it "is authenticated local with the nodes clientcert" do
      node = Puppet::Node.new('testing', :parameters => { 'clientcert' => 'cert name' })

      trusted = Puppet::Context::TrustedInformation.local(node)

      expect(trusted.authenticated).to eq('local')
      expect(trusted.certname).to eq('cert name')
      expect(trusted.extensions).to eq({})
      expect(trusted.hostname).to eq('cert name')
      expect(trusted.domain).to be_nil
    end

    it "is authenticated local with no clientcert when there is no node" do
      trusted = Puppet::Context::TrustedInformation.local(nil)

      expect(trusted.authenticated).to eq('local')
      expect(trusted.certname).to be_nil
      expect(trusted.extensions).to eq({})
      expect(trusted.hostname).to be_nil
      expect(trusted.domain).to be_nil
    end

    it 'contains external trusted data' do
      allow_external_trusted_data('cert name', external_data)

      trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', nil)

      expect(trusted.external).to eq(external_data)
    end
  end

  it "converts itself to a hash" do
    trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', cert)

    expect(trusted.to_h).to eq({
      'authenticated' => 'remote',
      'certname' => 'cert name',
      'extensions' => {
        '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info',
        '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info',
      },
      'hostname' => 'cert name',
      'domain' => nil,
      'external' => {},
    })
  end

  it "extracts domain and hostname from certname" do
    trusted = Puppet::Context::TrustedInformation.remote(true, 'hostname.domain.long', cert)

    expect(trusted.to_h).to eq({
      'authenticated' => 'remote',
      'certname' => 'hostname.domain.long',
      'extensions' => {
        '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info',
        '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info',
      },
      'hostname' => 'hostname',
      'domain' => 'domain.long',
      'external' => {},
    })
  end

  it "freezes the hash" do
    trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', cert)

    expect(trusted.to_h).to be_deeply_frozen
  end

  matcher :be_deeply_frozen do
    match do |actual|
      unfrozen_items(actual).empty?
    end

    failure_message do |actual|
      "expected all items to be frozen but <#{unfrozen_items(actual).join(', ')}> was not"
    end

    define_method :unfrozen_items do |actual|
      unfrozen = []
      stack = [actual]
      while item = stack.pop
        if !item.frozen?
          unfrozen.push(item)
        end

        case item
        when Hash
          stack.concat(item.keys)
          stack.concat(item.values)
        when Array
          stack.concat(item)
        end
      end

      unfrozen
    end
  end
end