puppetserver-ca 2.6.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
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