puppet 6.3.0-universal-darwin → 6.4.0-universal-darwin

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/CODEOWNERS +30 -0
  3. data/Gemfile.lock +9 -9
  4. data/lib/puppet.rb +13 -0
  5. data/lib/puppet/application/agent.rb +8 -12
  6. data/lib/puppet/application/device.rb +2 -3
  7. data/lib/puppet/application/filebucket.rb +6 -1
  8. data/lib/puppet/application/ssl.rb +102 -55
  9. data/lib/puppet/configurer.rb +8 -7
  10. data/lib/puppet/defaults.rb +3 -1
  11. data/lib/puppet/file_system.rb +24 -4
  12. data/lib/puppet/file_system/file_impl.rb +25 -0
  13. data/lib/puppet/file_system/jruby.rb +23 -0
  14. data/lib/puppet/file_system/windows.rb +84 -0
  15. data/lib/puppet/indirector/rest.rb +4 -2
  16. data/lib/puppet/loaders.rb +1 -0
  17. data/lib/puppet/network/http.rb +1 -0
  18. data/lib/puppet/network/http/base_pool.rb +18 -0
  19. data/lib/puppet/network/http/connection.rb +49 -17
  20. data/lib/puppet/network/http/nocache_pool.rb +9 -4
  21. data/lib/puppet/network/http/pool.rb +10 -11
  22. data/lib/puppet/network/http/session.rb +3 -2
  23. data/lib/puppet/network/http_pool.rb +32 -0
  24. data/lib/puppet/pops/loader/generic_plan_instantiator.rb +28 -0
  25. data/lib/puppet/pops/loader/loader_paths.rb +46 -10
  26. data/lib/puppet/pops/loader/module_loaders.rb +10 -3
  27. data/lib/puppet/provider/file/windows.rb +49 -1
  28. data/lib/puppet/provider/package/windows.rb +5 -1
  29. data/lib/puppet/reports/http.rb +2 -1
  30. data/lib/puppet/rest/client.rb +7 -3
  31. data/lib/puppet/rest/routes.rb +9 -44
  32. data/lib/puppet/ssl.rb +6 -0
  33. data/lib/puppet/ssl/error.rb +26 -0
  34. data/lib/puppet/ssl/host.rb +9 -92
  35. data/lib/puppet/ssl/ssl_context.rb +30 -0
  36. data/lib/puppet/ssl/ssl_provider.rb +232 -0
  37. data/lib/puppet/ssl/state_machine.rb +261 -0
  38. data/lib/puppet/ssl/validator.rb +1 -0
  39. data/lib/puppet/ssl/validator/default_validator.rb +1 -0
  40. data/lib/puppet/ssl/validator/no_validator.rb +2 -0
  41. data/lib/puppet/ssl/verifier.rb +134 -0
  42. data/lib/puppet/ssl/verifier_adapter.rb +48 -0
  43. data/lib/puppet/test/test_helper.rb +2 -1
  44. data/lib/puppet/type/exec.rb +30 -6
  45. data/lib/puppet/type/file/mode.rb +6 -1
  46. data/lib/puppet/type/file/source.rb +2 -2
  47. data/lib/puppet/type/filebucket.rb +12 -8
  48. data/lib/puppet/type/user.rb +14 -1
  49. data/lib/puppet/util/connection.rb +10 -5
  50. data/lib/puppet/util/feature.rb +11 -2
  51. data/lib/puppet/util/http_proxy.rb +3 -2
  52. data/lib/puppet/util/pidlock.rb +1 -1
  53. data/lib/puppet/util/ssl.rb +1 -10
  54. data/lib/puppet/util/windows/security.rb +29 -8
  55. data/lib/puppet/version.rb +1 -1
  56. data/lib/puppet/x509.rb +7 -0
  57. data/lib/puppet/x509/cert_provider.rb +286 -0
  58. data/lib/puppet/x509/pem_store.rb +55 -0
  59. data/locales/ja/puppet.po +740 -590
  60. data/locales/puppet.pot +433 -208
  61. data/man/man5/puppet.conf.5 +6 -3
  62. data/man/man8/puppet-agent.8 +1 -1
  63. data/man/man8/puppet-apply.8 +1 -1
  64. data/man/man8/puppet-catalog.8 +1 -1
  65. data/man/man8/puppet-config.8 +1 -1
  66. data/man/man8/puppet-describe.8 +1 -1
  67. data/man/man8/puppet-device.8 +1 -1
  68. data/man/man8/puppet-doc.8 +1 -1
  69. data/man/man8/puppet-epp.8 +1 -1
  70. data/man/man8/puppet-facts.8 +1 -1
  71. data/man/man8/puppet-filebucket.8 +6 -2
  72. data/man/man8/puppet-generate.8 +1 -1
  73. data/man/man8/puppet-help.8 +1 -1
  74. data/man/man8/puppet-key.8 +1 -1
  75. data/man/man8/puppet-lookup.8 +1 -1
  76. data/man/man8/puppet-man.8 +1 -1
  77. data/man/man8/puppet-module.8 +1 -1
  78. data/man/man8/puppet-node.8 +1 -1
  79. data/man/man8/puppet-parser.8 +1 -1
  80. data/man/man8/puppet-plugin.8 +1 -1
  81. data/man/man8/puppet-report.8 +1 -1
  82. data/man/man8/puppet-resource.8 +1 -1
  83. data/man/man8/puppet-script.8 +1 -1
  84. data/man/man8/puppet-ssl.8 +5 -1
  85. data/man/man8/puppet-status.8 +1 -1
  86. data/man/man8/puppet.8 +2 -2
  87. data/spec/fixtures/ssl/127.0.0.1-key.pem +67 -0
  88. data/spec/fixtures/ssl/127.0.0.1.pem +48 -0
  89. data/spec/fixtures/ssl/bad-basic-constraints.pem +59 -0
  90. data/spec/fixtures/ssl/bad-int-basic-constraints.pem +59 -0
  91. data/spec/fixtures/ssl/ca.pem +59 -0
  92. data/spec/fixtures/ssl/crl.pem +30 -0
  93. data/spec/fixtures/ssl/encrypted-key.pem +70 -0
  94. data/spec/fixtures/ssl/intermediate-agent-crl.pem +31 -0
  95. data/spec/fixtures/ssl/intermediate-agent.pem +60 -0
  96. data/spec/fixtures/ssl/intermediate-crl.pem +36 -0
  97. data/spec/fixtures/ssl/intermediate.pem +60 -0
  98. data/spec/fixtures/ssl/netlock-arany-utf8.pem +23 -0
  99. data/spec/fixtures/ssl/pluto-key.pem +67 -0
  100. data/spec/fixtures/ssl/pluto.pem +44 -0
  101. data/spec/fixtures/ssl/request-key.pem +67 -0
  102. data/spec/fixtures/ssl/request.pem +39 -0
  103. data/spec/fixtures/ssl/revoked-key.pem +67 -0
  104. data/spec/fixtures/ssl/revoked.pem +44 -0
  105. data/spec/fixtures/ssl/signed-key.pem +67 -0
  106. data/spec/fixtures/ssl/signed.pem +44 -0
  107. data/spec/fixtures/ssl/tampered-cert.pem +44 -0
  108. data/spec/fixtures/ssl/tampered-csr.pem +39 -0
  109. data/spec/integration/network/http_pool_spec.rb +222 -0
  110. data/spec/integration/provider/file/windows_spec.rb +162 -0
  111. data/spec/integration/rest/client_spec.rb +73 -0
  112. data/spec/integration/type/file_spec.rb +0 -19
  113. data/spec/lib/puppet/test_ca.rb +87 -50
  114. data/spec/lib/puppet_spec/fixtures.rb +20 -0
  115. data/spec/lib/puppet_spec/https.rb +84 -0
  116. data/spec/unit/application/agent_spec.rb +29 -30
  117. data/spec/unit/application/device_spec.rb +12 -49
  118. data/spec/unit/application/ssl_spec.rb +24 -38
  119. data/spec/unit/configurer_spec.rb +11 -11
  120. data/spec/unit/file_system/uniquefile_spec.rb +6 -0
  121. data/spec/unit/file_system_spec.rb +214 -0
  122. data/spec/unit/indirector/rest_spec.rb +3 -3
  123. data/spec/unit/network/http/connection_spec.rb +30 -90
  124. data/spec/unit/network/http/factory_spec.rb +1 -0
  125. data/spec/unit/network/http/nocache_pool_spec.rb +8 -8
  126. data/spec/unit/network/http/pool_spec.rb +63 -33
  127. data/spec/unit/network/http/session_spec.rb +8 -1
  128. data/spec/unit/network/http_pool_spec.rb +36 -0
  129. data/spec/unit/pops/loaders/loader_spec.rb +26 -1
  130. data/spec/unit/provider/package/windows_spec.rb +12 -1
  131. data/spec/unit/reports/http_spec.rb +7 -7
  132. data/spec/unit/rest/client_spec.rb +4 -6
  133. data/spec/unit/ssl/host_spec.rb +39 -33
  134. data/spec/unit/ssl/ssl_provider_spec.rb +428 -0
  135. data/spec/unit/ssl/state_machine_spec.rb +502 -0
  136. data/spec/unit/ssl/verifier_spec.rb +123 -0
  137. data/spec/unit/type/exec_spec.rb +63 -0
  138. data/spec/unit/type/file/source_spec.rb +5 -5
  139. data/spec/unit/type/filebucket_spec.rb +8 -6
  140. data/spec/unit/util/feature_spec.rb +2 -2
  141. data/spec/unit/util/storage_spec.rb +19 -19
  142. data/spec/unit/x509/cert_provider_spec.rb +527 -0
  143. data/spec/unit/x509/pem_store_spec.rb +160 -0
  144. data/tasks/generate_cert_fixtures.rake +158 -0
  145. metadata +78 -4
  146. data/MAINTAINERS +0 -47
  147. data/lib/puppet/rest/ssl_context.rb +0 -13
@@ -182,6 +182,7 @@ module Puppet::Util::HttpProxy
182
182
  end
183
183
 
184
184
  response = proxy.send(:head, current_uri.path, headers)
185
+ Puppet.debug("HTTP HEAD request to #{current_uri} returned #{response.code} #{response.message}")
185
186
 
186
187
  if [301, 302, 307].include?(response.code.to_i)
187
188
  # handle the redirection
@@ -195,9 +196,9 @@ module Puppet::Util::HttpProxy
195
196
  else
196
197
  response = proxy.send(method, current_uri.path, headers)
197
198
  end
198
- end
199
199
 
200
- Puppet.debug("HTTP #{method.to_s.upcase} request to #{current_uri} returned #{response.code} #{response.message}")
200
+ Puppet.debug("HTTP #{method.to_s.upcase} request to #{current_uri} returned #{response.code} #{response.message}")
201
+ end
201
202
 
202
203
  return response
203
204
  end
@@ -62,7 +62,7 @@ class Puppet::Util::Pidlock
62
62
  # POSIX and Windows platforms (PUP-9247).
63
63
  if Puppet.features.posix?
64
64
  procname = Puppet::Util::Execution.execute(["ps", "-p", lock_pid, "-o", "comm="]).strip
65
- @lockfile.unlock unless procname =~ /puppet$/
65
+ @lockfile.unlock unless procname =~ /puppet(-.*)?$/
66
66
  elsif Puppet.features.microsoft_windows?
67
67
  # On Windows, we're checking if the filesystem path name of the running
68
68
  # process is our vendored ruby:
@@ -75,16 +75,7 @@ module Puppet::Util::SSL
75
75
  msg << ": [" + verifier.verify_errors.join('; ') + "]"
76
76
  raise Puppet::Error, msg, error.backtrace
77
77
  elsif peer_cert && !OpenSSL::SSL.verify_certificate_identity(peer_cert, host)
78
- valid_certnames = [peer_cert.subject.to_s.sub(/.*=/, ''),
79
- *Puppet::SSL::Certificate.subject_alt_names_for(peer_cert)].uniq
80
- if valid_certnames.size > 1
81
- expected_certnames = _("expected one of %{certnames}") % { certnames: valid_certnames.join(', ') }
82
- else
83
- expected_certnames = _("expected %{certname}") % { certname: valid_certnames.first }
84
- end
85
-
86
- msg = _("Server hostname '%{host}' did not match server certificate; %{expected_certnames}") % { host: host, expected_certnames: expected_certnames }
87
- raise Puppet::Error, msg, error.backtrace
78
+ raise Puppet::SSL::CertMismatchError.new(peer_cert, host)
88
79
  else
89
80
  raise error
90
81
  end
@@ -274,7 +274,7 @@ module Puppet::Util::Windows::Security
274
274
  # mode. Only a user with the SE_BACKUP_NAME and SE_RESTORE_NAME
275
275
  # privileges in their process token can change the mode for objects
276
276
  # that they do not have read and write access to.
277
- def set_mode(mode, path, protected = true)
277
+ def set_mode(mode, path, protected = true, managing_owner = false, managing_group = false)
278
278
  sd = get_security_descriptor(path)
279
279
  well_known_world_sid = Puppet::Util::Windows::SID::Everyone
280
280
  well_known_nobody_sid = Puppet::Util::Windows::SID::Nobody
@@ -319,6 +319,8 @@ module Puppet::Util::Windows::Security
319
319
  nobody_allow |= FILE::FILE_APPEND_DATA;
320
320
  end
321
321
 
322
+ isownergroup = sd.owner == sd.group
323
+
322
324
  # caller is NOT managing SYSTEM by using group or owner, so set to FULL
323
325
  if ! [sd.owner, sd.group].include? well_known_system_sid
324
326
  # we don't check S_ISYSTEM_MISSING bit, but automatically carry over existing SYSTEM perms
@@ -328,15 +330,35 @@ module Puppet::Util::Windows::Security
328
330
  # It is possible to set SYSTEM with a mode other than Full Control (7) however this makes no sense and in practical terms
329
331
  # should not be done. We can trap these instances and correct them before being applied.
330
332
  if (sd.owner == well_known_system_sid) && (owner_allow != FILE::FILE_ALL_ACCESS)
331
- #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
332
- Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the owner, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
333
- owner_allow = FILE::FILE_ALL_ACCESS
333
+ # If owner and group are both SYSTEM but group is unmanaged the control rights of system will be set to FullControl by
334
+ # the unmanaged group, so there is no need for the warning
335
+ if managing_owner && (!isownergroup || managing_group)
336
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
337
+ Puppet.warning _("Setting control rights for %{path} owner SYSTEM to less than Full Control rights. Setting SYSTEM rights to less than Full Control may have unintented consequences for operations on this file") % { path: path }
338
+ elsif managing_owner && isownergroup
339
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
340
+ Puppet.debug _("%{path} owner and group both set to user SYSTEM, but group is not managed directly: SYSTEM user rights will be set to FullControl by group") % { path: path }
341
+ else
342
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
343
+ Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the owner, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
344
+ owner_allow = FILE::FILE_ALL_ACCESS
345
+ end
334
346
  end
335
347
 
336
348
  if (sd.group == well_known_system_sid) && (group_allow != FILE::FILE_ALL_ACCESS)
337
- #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
338
- Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the group, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
339
- group_allow = FILE::FILE_ALL_ACCESS
349
+ # If owner and group are both SYSTEM but owner is unmanaged the control rights of system will be set to FullControl by
350
+ # the unmanaged owner, so there is no need for the warning.
351
+ if managing_group && (!isownergroup || managing_owner)
352
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
353
+ Puppet.warning _("Setting control rights for %{path} group SYSTEM to less than Full Control rights. Setting SYSTEM rights to less than Full Control may have unintented consequences for operations on this file") % { path: path }
354
+ elsif managing_group && isownergroup
355
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
356
+ Puppet.debug _("%{path} owner and group both set to user SYSTEM, but owner is not managed directly: SYSTEM user rights will be set to FullControl by owner") % { path: path }
357
+ else
358
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
359
+ Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the group, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
360
+ group_allow = FILE::FILE_ALL_ACCESS
361
+ end
340
362
  end
341
363
  end
342
364
 
@@ -353,7 +375,6 @@ module Puppet::Util::Windows::Security
353
375
  end
354
376
 
355
377
  # if owner and group the same, then map group permissions to the one owner ACE
356
- isownergroup = sd.owner == sd.group
357
378
  if isownergroup
358
379
  owner_allow |= group_allow
359
380
  end
@@ -6,7 +6,7 @@
6
6
  # Raketasks and such to set the version based on the output of `git describe`
7
7
 
8
8
  module Puppet
9
- PUPPETVERSION = '6.3.0'
9
+ PUPPETVERSION = '6.4.0'
10
10
 
11
11
  ##
12
12
  # version is a public API method intended to always provide a fast and
@@ -0,0 +1,7 @@
1
+ require 'puppet'
2
+ require 'openssl'
3
+
4
+ module Puppet::X509 # :nodoc:
5
+ require 'puppet/x509/pem_store'
6
+ require 'puppet/x509/cert_provider'
7
+ end
@@ -0,0 +1,286 @@
1
+ require 'puppet/x509'
2
+
3
+ # Class for loading and saving cert related objects.
4
+ #
5
+ # @api private
6
+ class Puppet::X509::CertProvider
7
+ include Puppet::X509::PemStore
8
+
9
+ # Only allow printing ascii characters, excluding /
10
+ VALID_CERTNAME = /\A[ -.0-~]+\Z/
11
+ CERT_DELIMITERS = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m
12
+ CRL_DELIMITERS = /-----BEGIN X509 CRL-----.*?-----END X509 CRL-----/m
13
+
14
+ def initialize(capath: Puppet[:localcacert],
15
+ crlpath: Puppet[:hostcrl],
16
+ privatekeydir: Puppet[:privatekeydir],
17
+ certdir: Puppet[:certdir],
18
+ requestdir: Puppet[:requestdir])
19
+ @capath = capath
20
+ @crlpath = crlpath
21
+ @privatekeydir = privatekeydir
22
+ @certdir = certdir
23
+ @requestdir = requestdir
24
+ end
25
+
26
+ # Save `certs` to the configured `capath`.
27
+ #
28
+ # @param certs [Array<OpenSSL::X509::Certificate>] Array of CA certs to save
29
+ # @raise [Puppet::Error] if the certs cannot be saved
30
+ # @api private
31
+ def save_cacerts(certs)
32
+ save_pem(certs.map(&:to_pem).join, @capath, **permissions_for_setting(:localcacert))
33
+ rescue SystemCallError => e
34
+ raise Puppet::Error.new(_("Failed to save CA certificates to '%{capath}'") % {capath: @capath}, e)
35
+ end
36
+
37
+ # Load CA certs from the configured `capath`.
38
+ #
39
+ # @param required [Boolean] If true, raise if they are missing
40
+ # @return (see #load_cacerts_from_pem)
41
+ # @raise (see #load_cacerts_from_pem)
42
+ # @raise [Puppet::Error] if the certs cannot be loaded
43
+ # @api private
44
+ def load_cacerts(required: false)
45
+ pem = load_pem(@capath)
46
+ if !pem && required
47
+ raise Puppet::Error, _("The CA certificates are missing from '%{path}'") % { path: @capath }
48
+ end
49
+ pem ? load_cacerts_from_pem(pem) : nil
50
+ rescue SystemCallError => e
51
+ raise Puppet::Error.new(_("Failed to load CA certificates from '%{capath}'") % {capath: @capath}, e)
52
+ end
53
+
54
+ # Load PEM encoded CA certificates.
55
+ #
56
+ # @param pem [String] PEM encoded certificate(s)
57
+ # @return [Array<OpenSSL::X509::Certificate>] Array of CA certs
58
+ # @raise [OpenSSL::X509::CertificateError] The `pem` text does not contain a valid cert
59
+ # @api private
60
+ def load_cacerts_from_pem(pem)
61
+ # TRANSLATORS 'PEM' is an acronym and shouldn't be translated
62
+ raise OpenSSL::X509::CertificateError, _("Failed to parse CA certificates as PEM") if pem !~ CERT_DELIMITERS
63
+
64
+ pem.scan(CERT_DELIMITERS).map do |text|
65
+ OpenSSL::X509::Certificate.new(text)
66
+ end
67
+ end
68
+
69
+ # Save `crls` to the configured `crlpath`.
70
+ #
71
+ # @param crls [Array<OpenSSL::X509::CRL>] Array of CRLs to save
72
+ # @raise [Puppet::Error] if the CRLs cannot be saved
73
+ # @api private
74
+ def save_crls(crls)
75
+ save_pem(crls.map(&:to_pem).join, @crlpath, **permissions_for_setting(:hostcrl))
76
+ rescue SystemCallError => e
77
+ raise Puppet::Error.new(_("Failed to save CRLs to '%{crlpath}'") % {crlpath: @crlpath}, e)
78
+ end
79
+
80
+ # Load CRLs from the configured `crlpath` path.
81
+ #
82
+ # @param required [Boolean] If true, raise if they are missing
83
+ # @return (see #load_crls_from_pem)
84
+ # @raise (see #load_crls_from_pem)
85
+ # @raise [Puppet::Error] if the CRLs cannot be loaded
86
+ # @api private
87
+ def load_crls(required: false)
88
+ pem = load_pem(@crlpath)
89
+ if !pem && required
90
+ raise Puppet::Error, _("The CRL is missing from '%{path}'") % { path: @crlpath }
91
+ end
92
+ pem ? load_crls_from_pem(pem) : nil
93
+ rescue SystemCallError => e
94
+ raise Puppet::Error.new(_("Failed to load CRLs from '%{crlpath}'") % {crlpath: @crlpath}, e)
95
+ end
96
+
97
+ # Load PEM encoded CRL(s).
98
+ #
99
+ # @param pem [String] PEM encoded CRL(s)
100
+ # @return [Array<OpenSSL::X509::CRL>] Array of CRLs
101
+ # @raise [OpenSSL::X509::CRLError] The `pem` text does not contain a valid CRL
102
+ # @api private
103
+ def load_crls_from_pem(pem)
104
+ # TRANSLATORS 'PEM' is an acronym and shouldn't be translated
105
+ raise OpenSSL::X509::CRLError, _("Failed to parse CRLs as PEM") if pem !~ CRL_DELIMITERS
106
+
107
+ pem.scan(CRL_DELIMITERS).map do |text|
108
+ OpenSSL::X509::CRL.new(text)
109
+ end
110
+ end
111
+
112
+ # Save named private key in the configured `privatekeydir`. For
113
+ # historical reasons, names are case insensitive.
114
+ #
115
+ # @param name [String] The private key identity
116
+ # @param key [OpenSSL::PKey::RSA] private key
117
+ # @raise [Puppet::Error] if the private key cannot be saved
118
+ # @api private
119
+ def save_private_key(name, key)
120
+ path = to_path(@privatekeydir, name)
121
+ save_pem(key.to_pem, path, **permissions_for_setting(:hostprivkey))
122
+ rescue SystemCallError => e
123
+ raise Puppet::Error.new(_("Failed to save private key for '%{name}'") % {name: name}, e)
124
+ end
125
+
126
+ # Load a private key from the configured `privatekeydir`. For
127
+ # historical reasons, names are case-insensitive.
128
+ #
129
+ # @param name [String] The private key identity
130
+ # @param required [Boolean] If true, raise if it is missing
131
+ # @return (see #load_private_key_from_pem)
132
+ # @raise (see #load_private_key_from_pem)
133
+ # @raise [Puppet::Error] if the private key cannot be loaded
134
+ # @api private
135
+ def load_private_key(name, required: false)
136
+ path = to_path(@privatekeydir, name)
137
+ pem = load_pem(path)
138
+ if !pem && required
139
+ raise Puppet::Error, _("The private key is missing from '%{path}'") % { path: path }
140
+ end
141
+ pem ? load_private_key_from_pem(pem) : nil
142
+ rescue SystemCallError => e
143
+ raise Puppet::Error.new(_("Failed to load private key for '%{name}'") % {name: name}, e)
144
+ end
145
+
146
+ # Load a PEM encoded private key.
147
+ #
148
+ # @param pem [String] PEM encoded private key
149
+ # @return [OpenSSL::PKey::RSA] The private key
150
+ # @raise [OpenSSL::PKey::RSAError] The `pem` text does not contain a valid key
151
+ # @api private
152
+ def load_private_key_from_pem(pem)
153
+ # set a non-nil passphrase to ensure openssl doesn't prompt
154
+ # but ruby 2.4.0 & 2.4.1 require at least 4 bytes, see
155
+ # https://github.com/ruby/ruby/commit/f012932218fd609f75f9268812df61fb26e2d0f1#diff-40e4270ec386990ac60d7ab5ff8045a4
156
+ OpenSSL::PKey::RSA.new(pem, ' ')
157
+ end
158
+
159
+ # Save a named client cert to the configured `certdir`.
160
+ #
161
+ # @param name [String] The client cert identity
162
+ # @param cert [OpenSSL::X509::Certificate] The cert to save
163
+ # @raise [Puppet::Error] if the client cert cannot be saved
164
+ # @api private
165
+ def save_client_cert(name, cert)
166
+ path = to_path(@certdir, name)
167
+ save_pem(cert.to_pem, path, **permissions_for_setting(:hostcert))
168
+ rescue SystemCallError => e
169
+ raise Puppet::Error.new(_("Failed to save client certificate for '%{name}'") % {name: name}, e)
170
+ end
171
+
172
+ # Load a named client cert from the configured `certdir`.
173
+ #
174
+ # @param name [String] The client cert identity
175
+ # @param required [Boolean] If true, raise it is missing
176
+ # @return (see #load_request_from_pem)
177
+ # @raise (see #load_client_cert_from_pem)
178
+ # @raise [Puppet::Error] if the client cert cannot be loaded
179
+ # @api private
180
+ def load_client_cert(name, required: false)
181
+ path = to_path(@certdir, name)
182
+ pem = load_pem(path)
183
+ if !pem && required
184
+ raise Puppet::Error, _("The client certificate is missing from '%{path}'") % { path: path }
185
+ end
186
+ pem ? load_client_cert_from_pem(pem) : nil
187
+ rescue SystemCallError => e
188
+ raise Puppet::Error.new(_("Failed to load client certificate for '%{name}'") % {name: name}, e)
189
+ end
190
+
191
+ # Load a PEM encoded certificate.
192
+ #
193
+ # @param pem [String] PEM encoded cert
194
+ # @return [OpenSSL::X509::Certificate] the certificate
195
+ # @raise [OpenSSL::X509::CertificateError] The `pem` text does not contain a valid cert
196
+ # @api private
197
+ def load_client_cert_from_pem(pem)
198
+ OpenSSL::X509::Certificate.new(pem)
199
+ end
200
+
201
+ # Create a certificate signing request (CSR).
202
+ #
203
+ # @param name [String] the request identity
204
+ # @param private_key [OpenSSL::PKey::RSA] private key
205
+ # @return [Puppet::X509::Request] The request
206
+ #
207
+ def create_request(name, private_key)
208
+ options = {}
209
+
210
+ if Puppet[:dns_alt_names] && Puppet[:dns_alt_names] != ''
211
+ options[:dns_alt_names] = Puppet[:dns_alt_names]
212
+ end
213
+
214
+ csr_attributes = Puppet::SSL::CertificateRequestAttributes.new(Puppet[:csr_attributes])
215
+ if csr_attributes.load
216
+ options[:csr_attributes] = csr_attributes.custom_attributes
217
+ options[:extension_requests] = csr_attributes.extension_requests
218
+ end
219
+
220
+ csr = Puppet::SSL::CertificateRequest.new(name)
221
+ csr.generate(private_key, options)
222
+ end
223
+
224
+ # Save a certificate signing request (CSR) to the configured `requestdir`.
225
+ #
226
+ # @param name [String] the request identity
227
+ # @param csr [OpenSSL::X509::Request] the request
228
+ # @raise [Puppet::Error] if the cert request cannot be saved
229
+ # @api private
230
+ def save_request(name, csr)
231
+ path = to_path(@requestdir, name)
232
+ save_pem(csr.to_pem, path, **permissions_for_setting(:hostcsr))
233
+ rescue SystemCallError => e
234
+ raise Puppet::Error.new(_("Failed to save certificate request for '%{name}'") % {name: name}, e)
235
+ end
236
+
237
+ # Load a named certificate signing request (CSR) from the configured `requestdir`.
238
+ #
239
+ # @param name [String] The request identity
240
+ # @return (see #load_request_from_pem)
241
+ # @raise (see #load_request_from_pem)
242
+ # @raise [Puppet::Error] if the cert request cannot be saved
243
+ # @api private
244
+ def load_request(name)
245
+ path = to_path(@requestdir, name)
246
+ pem = load_pem(path)
247
+ pem ? load_request_from_pem(pem) : nil
248
+ rescue SystemCallError => e
249
+ raise Puppet::Error.new(_("Failed to load certificate request for '%{name}'") % {name: name}, e)
250
+ end
251
+
252
+ # Delete a named certificate signing request (CSR) from the configured `requestdir`.
253
+ #
254
+ # @param name [String] The request identity
255
+ # @return [Boolean] true if the CSR was deleted
256
+ def delete_request(name)
257
+ path = to_path(@requestdir, name)
258
+ delete_pem(path)
259
+ rescue SystemCallError => e
260
+ raise Puppet::Error.new(_("Failed to delete certificate request for '%{name}'") % {name: name}, e)
261
+ end
262
+
263
+ # Load a PEM encoded certificate signing request (CSR).
264
+ #
265
+ # @param pem [String] PEM encoded request
266
+ # @return [OpenSSL::X509::Request] the request
267
+ # @raise [OpenSSL::X509::RequestError] The `pem` text does not contain a valid request
268
+ # @api private
269
+ def load_request_from_pem(pem)
270
+ OpenSSL::X509::Request.new(pem)
271
+ end
272
+
273
+ private
274
+
275
+ def to_path(base, name)
276
+ raise _("Certname %{name} must not contain unprintable or non-ASCII characters") % { name: name.inspect } unless name =~ VALID_CERTNAME
277
+ File.join(base, "#{name.downcase}.pem")
278
+ end
279
+
280
+ def permissions_for_setting(name)
281
+ setting = Puppet.settings.setting(name)
282
+ perm = { mode: setting.mode.to_i(8) }
283
+ perm.merge!(owner: setting.owner, group: setting.group) if Puppet.features.root? && !Puppet::Util::Platform.windows?
284
+ perm
285
+ end
286
+ end
@@ -0,0 +1,55 @@
1
+ require 'puppet/x509'
2
+
3
+ # Methods for managing PEM encoded files. While PEM encoded strings are
4
+ # always ASCII, the files may contain user specified comments, so they are
5
+ # UTF-8 encoded.
6
+ #
7
+ # @api private
8
+ module Puppet::X509::PemStore
9
+ # Load a pem encoded object.
10
+ #
11
+ # @param path [String] file path
12
+ # @return [String, nil] The PEM encoded object or nil if the
13
+ # path does not exist
14
+ # @raise [Errno::EACCES] if permission is denied
15
+ # @api private
16
+ def load_pem(path)
17
+ Puppet::FileSystem.read(path, encoding: 'UTF-8')
18
+ rescue Errno::ENOENT
19
+ nil
20
+ end
21
+
22
+ # Save pem encoded content to a file. If the file doesn't exist, it
23
+ # will be created. Otherwise, the file will be overwritten. In both
24
+ # cases the contents will be overwritten atomically so other
25
+ # processes don't see a partially written file.
26
+ #
27
+ # @param pem [String] The PEM encoded object to write
28
+ # @param path [String] The file path to write to
29
+ # @raise [Errno::EACCES] if permission is denied
30
+ # @raise [Errno::EPERM] if the operation cannot be completed
31
+ # @api private
32
+ def save_pem(pem, path, owner: nil, group: nil, mode: 0644)
33
+ Puppet::FileSystem.replace_file(path, mode) do |f|
34
+ f.set_encoding('UTF-8')
35
+ f.write(pem.encode('UTF-8'))
36
+ end
37
+
38
+ if !Puppet::Util::Platform.windows? && Puppet.features.root? && (owner || group)
39
+ FileUtils.chown(owner, group, path)
40
+ end
41
+ end
42
+
43
+ # Delete a pem encoded object, if it exists.
44
+ #
45
+ # @param path [String] The file path to delete
46
+ # @return [Boolean] Returns true if the file was deleted, false otherwise
47
+ # @raise [Errno::EACCES] if permission is denied
48
+ # @api private
49
+ def delete_pem(path)
50
+ Puppet::FileSystem.unlink(path)
51
+ true
52
+ rescue Errno::ENOENT
53
+ false
54
+ end
55
+ end