puppetserver-ca 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78b8703a01de06ec5c4a6202f8341662452f4d33d37c1390b558556ce817253d
4
- data.tar.gz: eea0c603eee53c14a82de6b80f263014ca76261eb39fa2b679031ad6ecc69b1f
3
+ metadata.gz: f3405b0e0be4f947fcec85824880f6b62fb2630d1993e130e1782960e58b5a80
4
+ data.tar.gz: 92ae8e4b074c7d12609a79beb4c886a95f3299e44b2b4ff602f8b6acf7b002c2
5
5
  SHA512:
6
- metadata.gz: 64ff93865e33b2dccd3f4308580278315d007b450611acdbb36631aabc9794d82b5f8973aa5872384f05146abe62dfa3290b5e3774f8816bafa4b70db7bf3fbc
7
- data.tar.gz: 734dc57fa4e9f8ce390a966502ea8104cb7640aec4564759ca39c377fdaeb06ed8992fbab88dfa3ef5a2c24921e6400223e4bee2c902d0f4f92f676537f779c5
6
+ metadata.gz: 82fce6d5d561700df09dc132723a600f2943ca86d19b7a2cbe56b1c25c4ebce170cb4a973ef6a07d0271057587cccbd2185c1a392dba9217b6dd6a8c617ce3df
7
+ data.tar.gz: 130b8dc609b09c2ab7722459f62febcce52452ea419fa69f4ecb70d059b28428962574206a6bc5600f620f541c4205f86fe9e2eb7381a5bee6bb2e9fa24941b4
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
data/CODEOWNERS CHANGED
@@ -1,4 +1,4 @@
1
1
  # This will cause the puppetserver-maintainers group to be assigned
2
2
  # review of any opened PRs against the branches containing this file.
3
3
 
4
- * @puppetlabs/puppetserver-maintainers
4
+ * @puppetlabs/dumpling @puppetlabs/skeletor
data/README.md CHANGED
@@ -101,16 +101,11 @@ To test your changes on a VM:
101
101
  ```
102
102
  1. To confirm that installation was successful, run `puppetserver ca --help`
103
103
 
104
- ### Releasing
105
- To release a new version, run the [release pipeline](https://jenkins-platform.delivery.puppetlabs.net/job/platform_puppetserver-ca_init-multijob_main/), which will bump the version, tag, build, and release the gem.
106
-
107
104
  ## Contributing & Support
108
105
 
109
- Bug reports and feature requests are welcome in JIRA at
110
- https://tickets.puppetlabs.com/projects/SERVER/issues.
106
+ Bug reports and feature requests are welcome via GitHub issues.
111
107
 
112
- For interactive questions feel free to post to #puppet or #puppet-dev on
113
- Freenode, or the Puppet Community Slack channel.
108
+ For interactive questions feel free to post to #puppet or #puppet-dev on the Puppet Community Slack channel.
114
109
 
115
110
  Contributions are welcome at https://github.com/puppetlabs/puppetserver-ca-cli/pulls.
116
111
  Contributors should both be sure to read the
@@ -19,7 +19,7 @@ module Puppetserver
19
19
  Usage:
20
20
  puppetserver ca setup [--help]
21
21
  puppetserver ca setup [--config PATH] [--subject-alt-names NAME[,NAME]]
22
- [--certname NAME] [--ca-name NAME]
22
+ [--certname NAME] [--ca-name NAME] [--root-ca-name NAME]
23
23
 
24
24
  Description:
25
25
  Setup a root and intermediate signing CA for Puppet Server
@@ -51,6 +51,7 @@ BANNER
51
51
  settings_overrides = {}
52
52
  settings_overrides[:certname] = input['certname'] unless input['certname'].empty?
53
53
  settings_overrides[:ca_name] = input['ca-name'] unless input['ca-name'].empty?
54
+ settings_overrides[:root_ca_name] = input['root-ca-name'] unless input['root-ca-name'].empty?
54
55
  # Since puppet expects the key to be called 'dns_alt_names', we need to use that here
55
56
  # to ensure that the overriding works correctly.
56
57
  settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
@@ -153,6 +154,7 @@ ERR
153
154
  def self.parser(parsed = {})
154
155
  parsed['subject-alt-names'] = ''
155
156
  parsed['ca-name'] = ''
157
+ parsed['root-ca-name'] = ''
156
158
  parsed['certname'] = ''
157
159
  OptionParser.new do |opts|
158
160
  opts.banner = BANNER
@@ -170,6 +172,10 @@ ERR
170
172
  'Common name to use for the CA signing cert') do |name|
171
173
  parsed['ca-name'] = name
172
174
  end
175
+ opts.on('--root-ca-name NAME',
176
+ 'Common name to use for the self-signed Root CA cert') do |name|
177
+ parsed['root-ca-name'] = name
178
+ end
173
179
  opts.on('--certname NAME',
174
180
  'Common name to use for the server cert') do |name|
175
181
  parsed['certname'] = name
@@ -66,17 +66,33 @@ Options:
66
66
  return 1 if Errors.handle_with_usage(@logger, puppet.errors)
67
67
 
68
68
  ca = Puppetserver::Ca::CertificateAuthority.new(@logger, puppet.settings)
69
+ bulk_sign = ca.server_has_bulk_signing_endpoints
70
+
71
+ # Bulk sign endpoints don't allow setting TTL, so
72
+ # use single signing endpoint if TTL is specified.
73
+ success = false
74
+ if input['ttl'] || !bulk_sign
75
+ if input['all']
76
+ requested_certnames = get_all_pending_certs(ca)
77
+ return 1 if requested_certnames.nil?
78
+ return 24 if requested_certnames.empty?
79
+ else
80
+ requested_certnames = input['certname']
81
+ end
69
82
 
70
- if input['all']
71
- requested_certnames = get_all_pending_certs(ca)
72
- return 1 if requested_certnames.nil?
73
- return 24 if requested_certnames.empty?
83
+ success = ca.sign_certs(requested_certnames, input['ttl'])
84
+ return success ? 0 : 1
74
85
  else
75
- requested_certnames = input['certname']
86
+ result = input['all'] ? ca.sign_all : ca.sign_bulk(input['certname'])
87
+ case result
88
+ when :success
89
+ return 0
90
+ when :no_requests
91
+ return 24
92
+ else
93
+ return 1
94
+ end
76
95
  end
77
-
78
- success = ca.sign_certs(requested_certnames, input['ttl'])
79
- return success ? 0 : 1
80
96
  end
81
97
 
82
98
  def get_all_pending_certs(ca)
@@ -19,7 +19,6 @@ module Puppetserver
19
19
  }
20
20
 
21
21
  REVOKE_BODY = JSON.dump({ desired_state: 'revoked' })
22
- SIGN_BODY = JSON.dump({ desired_state: 'signed' })
23
22
 
24
23
  def initialize(logger, settings)
25
24
  @logger = logger
@@ -28,6 +27,15 @@ module Puppetserver
28
27
  @ca_port = settings[:ca_port]
29
28
  end
30
29
 
30
+ def server_has_bulk_signing_endpoints
31
+ url = HttpClient::URL.new('https', @ca_server, @ca_port, 'status', 'v1', 'services')
32
+ result = @client.with_connection(url) do |connection|
33
+ connection.get(url)
34
+ end
35
+ version = process_results(:server_version, nil, result)
36
+ return version >= Gem::Version.new('8.4.0')
37
+ end
38
+
31
39
  def worst_result(previous_result, current_result)
32
40
  %i{success invalid not_found error}.each do |state|
33
41
  if previous_result == state
@@ -61,13 +69,26 @@ module Puppetserver
61
69
  end
62
70
  end
63
71
 
72
+ def sign_all
73
+ return post(resource_type: 'sign',
74
+ resource_name: 'all',
75
+ body: '{}',
76
+ type: :sign_all)
77
+ end
78
+
79
+ def sign_bulk(certnames)
80
+ return post(resource_type: 'sign',
81
+ body: "{\"certnames\":#{certnames}}",
82
+ type: :sign_bulk
83
+ )
84
+ end
85
+
64
86
  def sign_certs(certnames, ttl=nil)
65
87
  results = []
66
88
  if ttl
67
89
  lifetime = process_ttl_input(ttl)
68
90
  return false if lifetime.nil?
69
- body = JSON.dump({ desired_state: 'signed',
70
- cert_ttl: lifetime})
91
+ body = JSON.dump({ desired_state: 'signed', cert_ttl: lifetime})
71
92
  results = put(certnames,
72
93
  resource_type: 'certificate_status',
73
94
  body: body,
@@ -75,7 +96,7 @@ module Puppetserver
75
96
  else
76
97
  results = put(certnames,
77
98
  resource_type: 'certificate_status',
78
- body: SIGN_BODY,
99
+ body: JSON.dump({ desired_state: 'signed' }),
79
100
  type: :sign)
80
101
  end
81
102
 
@@ -119,6 +140,48 @@ module Puppetserver
119
140
  end
120
141
  end
121
142
 
143
+ # Make an HTTP POST request to CA
144
+ # @param endpoint [String] the endpoint to post to for the url
145
+ # @param body [JSON/String] body of the post request
146
+ # @param type [Symbol] type of error processing to perform on result
147
+ # @return [Boolean] whether all requests were successful
148
+ def post(resource_type:, resource_name: nil, body:, type:, headers: {})
149
+ url = make_ca_url(resource_type, resource_name)
150
+ results = @client.with_connection(url) do |connection|
151
+ result = connection.post(body, url, headers)
152
+ process_results(type, nil, result)
153
+ end
154
+ end
155
+
156
+ # Handle the result data from the /sign and /sign/all endpoints
157
+ def process_bulk_sign_result_data(result)
158
+ data = JSON.parse(result.body)
159
+ signed = data.dig('signed') || []
160
+ no_csr = data.dig('no-csr') || []
161
+ signing_errors = data.dig('signing-errors') || []
162
+
163
+ if !signed.empty?
164
+ @logger.inform "Successfully signed the following certificate requests:"
165
+ signed.each { |s| @logger.inform " #{s}" }
166
+ end
167
+
168
+ @logger.err 'Error:' if !no_csr.empty? || !signing_errors.empty?
169
+ if !no_csr.empty?
170
+ @logger.err ' No certificate request found for the following nodes when attempting to sign:'
171
+ no_csr.each { |s| @logger.err " #{s}" }
172
+ end
173
+ if !signing_errors.empty?
174
+ @logger.err ' Error encountered when attempting to sign the certificate request for the following nodes:'
175
+ signing_errors.each { |s| @logger.err " #{s}" }
176
+ end
177
+ if no_csr.empty? && signing_errors.empty?
178
+ @logger.err 'No waiting certificate requests to sign.' if signed.empty?
179
+ return signed.empty? ? :no_requests : :success
180
+ else
181
+ return :error
182
+ end
183
+ end
184
+
122
185
  # logs the action and returns true/false for success
123
186
  def process_results(type, certname, result)
124
187
  case type
@@ -138,6 +201,50 @@ module Puppetserver
138
201
  @logger.err " body: #{result.body.to_s}" if result.body
139
202
  return :error
140
203
  end
204
+ when :sign_all
205
+ if result.code == '200'
206
+ if !result.body
207
+ @logger.err 'Error:'
208
+ @logger.err ' Response from /sign/all endpoint did not include a body. Unable to verify certificate requests were signed.'
209
+ return :error
210
+ end
211
+ begin
212
+ return process_bulk_sign_result_data(result)
213
+ rescue JSON::ParserError
214
+ @logger.err 'Error:'
215
+ @logger.err ' Unable to parse the response from the /sign/all endpoint.'
216
+ @logger.err " body #{result.body.to_s}"
217
+ return :error
218
+ end
219
+ else
220
+ @logger.err 'Error:'
221
+ @logger.err ' When attempting to sign all certificate requests, received:'
222
+ @logger.err " code: #{result.code}"
223
+ @logger.err " body: #{result.body.to_s}" if result.body
224
+ return :error
225
+ end
226
+ when :sign_bulk
227
+ if result.code == '200'
228
+ if !result.body
229
+ @logger.err 'Error:'
230
+ @logger.err ' Response from /sign endpoint did not include a body. Unable to verify certificate requests were signed.'
231
+ return :error
232
+ end
233
+ begin
234
+ return process_bulk_sign_result_data(result)
235
+ rescue JSON::ParserError
236
+ @logger.err 'Error:'
237
+ @logger.err ' Unable to parse the response from the /sign endpoint.'
238
+ @logger.err " body #{result.body.to_s}"
239
+ return :error
240
+ end
241
+ else
242
+ @logger.err 'Error:'
243
+ @logger.err ' When attempting to sign certificate requests, received:'
244
+ @logger.err " code: #{result.code}"
245
+ @logger.err " body: #{result.body.to_s}" if result.body
246
+ return :error
247
+ end
141
248
  when :revoke
142
249
  case result.code
143
250
  when '200', '204'
@@ -170,6 +277,19 @@ module Puppetserver
170
277
  @logger.err " body: #{result.body.to_s}" if result.body
171
278
  return :error
172
279
  end
280
+ when :server_version
281
+ if result.code == '200' && result.body
282
+ begin
283
+ data = JSON.parse(result.body)
284
+ version_str = data.dig('ca','service_version')
285
+ return Gem::Version.new(version_str.match('^\d+\.\d+\.\d+')[0])
286
+ rescue JSON::ParserError, NoMethodError
287
+ # If we get bad JSON, version_str is nil, or the matcher doesn't match,
288
+ # fall through to returning a version of 0.
289
+ end
290
+ end
291
+ @logger.debug 'Could not detect server version. Defaulting to legacy signing endpoints.'
292
+ return Gem::Version.new(0)
173
293
  end
174
294
  end
175
295
 
@@ -194,6 +194,9 @@ module Puppetserver
194
194
  # class keys/section names but nearly any character values (excluding
195
195
  # leading whitespace) up to one of whitespace, opening curly brace, or
196
196
  # hash sign (Our concern being to capture filesystem path values).
197
+ #
198
+ # ca_root and root_ca_name values may include whitespace
199
+ #
197
200
  # Put values without a section into :main.
198
201
  #
199
202
  # Return Hash of Symbol section names with Symbol setting keys and
@@ -205,10 +208,15 @@ module Puppetserver
205
208
  case line
206
209
  when /^\s*\[(\w+)\].*/
207
210
  current_section = $1.to_sym
208
- when /^\s*(\w+)\s*=\s*([^\s{#]+).*$/
211
+ when /^\s*(\w+)\s*=\s*(.+?)\s*(?=[{#]|$)/
209
212
  # Using a Hash with a default key breaks RSpec expectations.
210
213
  res[current_section] ||= {}
211
- res[current_section][$1.to_sym] = $2
214
+ res[current_section][$1.to_sym] =
215
+ if [:ca_name, :root_ca_name].include?($1.to_sym)
216
+ $2
217
+ else
218
+ $2.split(' ')[0]
219
+ end
212
220
  end
213
221
  end
214
222
 
@@ -130,6 +130,19 @@ module Puppetserver
130
130
  Result.new(result.code, result.body)
131
131
  end
132
132
 
133
+ def post(body, url_override = nil, header_overrides = {})
134
+ url = url_override || @url
135
+ headers = @default_headers.merge(header_overrides)
136
+
137
+ @logger.debug("Making a POST request at #{url.full_url}")
138
+
139
+ request = Net::HTTP::Post.new(url.to_uri, headers)
140
+ request.body = body
141
+ result = @conn.request(request)
142
+
143
+ Result.new(result.code, result.body)
144
+ end
145
+
133
146
  def delete(url_override = nil, header_overrides = {})
134
147
  url = url_override || @url
135
148
  headers = @default_headers.merge(header_overrides)
@@ -151,7 +164,7 @@ module Puppetserver
151
164
  :resource_type, :resource_name, :query) do
152
165
  def full_url
153
166
  url = protocol + '://' + host + ':' + port + '/' +
154
- [endpoint, version, resource_type, resource_name].join('/')
167
+ [endpoint, version, resource_type, resource_name].compact.join('/')
155
168
 
156
169
  url = url + "?" + URI.encode_www_form(query) unless query.nil? || query.empty?
157
170
  return url
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "2.6.0"
3
+ VERSION = "2.7.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppetserver-ca
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-10 00:00:00.000000000 Z
11
+ date: 2024-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: facter
@@ -80,6 +80,7 @@ executables:
80
80
  extensions: []
81
81
  extra_rdoc_files: []
82
82
  files:
83
+ - ".github/dependabot.yml"
83
84
  - ".github/workflows/mend.yaml"
84
85
  - ".gitignore"
85
86
  - ".rspec"
@@ -143,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
144
  - !ruby/object:Gem::Version
144
145
  version: '0'
145
146
  requirements: []
146
- rubygems_version: 3.4.12
147
+ rubygems_version: 3.4.20
147
148
  signing_key:
148
149
  specification_version: 4
149
150
  summary: A simple CLI tool for interacting with Puppet Server's Certificate Authority