puppetserver-ca 2.5.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: 020665126fef9a8fe7bc63652247bf13cf909ab37e2a3336d43724bc63b26d32
4
- data.tar.gz: c8d62416aaa1c4ded40daee19907798a13ed6fa3ea123436701adf88b9b497cd
3
+ metadata.gz: f3405b0e0be4f947fcec85824880f6b62fb2630d1993e130e1782960e58b5a80
4
+ data.tar.gz: 92ae8e4b074c7d12609a79beb4c886a95f3299e44b2b4ff602f8b6acf7b002c2
5
5
  SHA512:
6
- metadata.gz: 89f506f4124c95b8b7029043ce46f216b837d3863875077476bfb835cbaf83399734d02dc7bd64ec39234d4d68735e98997ea81a724146ccef7b540e02d59849
7
- data.tar.gz: 33b9fa7893650441e456e39edda8d5d4532bcd52fe42f0832769eafad982655d2eb26357b51080a90d1da313b4a69d02f01119ae9dc1e4af645641664e9f8699
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/.travis.yml CHANGED
@@ -8,6 +8,8 @@ rvm:
8
8
  - 2.5
9
9
  - 2.6
10
10
  - 2.7
11
+ - 3.1
12
+ - 3.2
11
13
  before_install:
12
14
  gem install bundler -v 1.16.1 && (gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true)
13
15
  script:
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
 
@@ -10,17 +10,12 @@ module Puppetserver
10
10
  # Utilities for doing HTTPS against the CA that wraps Net::HTTP constructs
11
11
  class HttpClient
12
12
 
13
- DEFAULT_HEADERS = {
14
- 'User-Agent' => 'PuppetserverCaCli',
15
- 'Content-Type' => 'application/json',
16
- 'Accept' => 'application/json'
17
- }
18
-
19
13
  attr_reader :store
20
14
 
21
15
  # Not all connections require a client cert to be present.
22
16
  # For example, when querying the status endpoint.
23
17
  def initialize(logger, settings, with_client_cert: true)
18
+ @default_headers = make_headers(ENV['HOME'])
24
19
  @logger = logger
25
20
  @store = make_store(settings[:localcacert],
26
21
  settings[:certificate_revocation],
@@ -52,7 +47,7 @@ module Puppetserver
52
47
  # The Connection object should have HTTP verbs defined on it that take
53
48
  # a body (and optional overrides). Returns whatever the block given returned.
54
49
  def with_connection(url, &block)
55
- request = ->(conn) { block.call(Connection.new(conn, url, @logger)) }
50
+ request = ->(conn) { block.call(Connection.new(conn, url, @logger, @default_headers)) }
56
51
 
57
52
  begin
58
53
  Net::HTTP.start(url.host, url.port,
@@ -68,7 +63,22 @@ module Puppetserver
68
63
 
69
64
  private
70
65
 
71
- def load_with_errors(path, setting, &block)
66
+ def make_headers(home)
67
+ headers = {
68
+ 'User-Agent' => 'PuppetserverCaCli',
69
+ 'Content-Type' => 'application/json',
70
+ 'Accept' => 'application/json'
71
+ }
72
+
73
+ token_path = "#{home}/.puppetlabs/token"
74
+ if File.exist?(token_path)
75
+ headers['X-Authentication'] = File.read(token_path)
76
+ end
77
+
78
+ headers
79
+ end
80
+
81
+ def load_with_errors(path, setting, &block)
72
82
  begin
73
83
  content = File.read(path)
74
84
  block.call(content)
@@ -81,21 +91,23 @@ module Puppetserver
81
91
  "Could not parse '#{setting}' at '#{path}'.\n" +
82
92
  " OpenSSL returned: #{e.message}")
83
93
  end
84
- end
94
+ end
85
95
 
86
96
  # Helper class that wraps a Net::HTTP connection, a HttpClient::URL
87
97
  # and defines methods named after HTTP verbs that are called on the
88
98
  # saved connection, returning a Result.
89
99
  class Connection
90
- def initialize(net_http_connection, url_struct, logger)
100
+
101
+ def initialize(net_http_connection, url_struct, logger, default_headers)
91
102
  @conn = net_http_connection
92
103
  @url = url_struct
93
104
  @logger = logger
105
+ @default_headers = default_headers
94
106
  end
95
107
 
96
- def get(url_overide = nil, headers = {})
108
+ def get(url_overide = nil, header_overrides = {})
97
109
  url = url_overide || @url
98
- headers = DEFAULT_HEADERS.merge(headers)
110
+ headers = @default_headers.merge(header_overrides)
99
111
 
100
112
  @logger.debug("Making a GET request at #{url.full_url}")
101
113
 
@@ -105,9 +117,9 @@ module Puppetserver
105
117
 
106
118
  end
107
119
 
108
- def put(body, url_override = nil, headers = {})
120
+ def put(body, url_override = nil, header_overrides = {})
109
121
  url = url_override || @url
110
- headers = DEFAULT_HEADERS.merge(headers)
122
+ headers = @default_headers.merge(header_overrides)
111
123
 
112
124
  @logger.debug("Making a PUT request at #{url.full_url}")
113
125
 
@@ -118,9 +130,22 @@ module Puppetserver
118
130
  Result.new(result.code, result.body)
119
131
  end
120
132
 
121
- def delete(url_override = nil, headers = {})
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
+
146
+ def delete(url_override = nil, header_overrides = {})
122
147
  url = url_override || @url
123
- headers = DEFAULT_HEADERS.merge(headers)
148
+ headers = @default_headers.merge(header_overrides)
124
149
 
125
150
  @logger.debug("Making a DELETE request at #{url.full_url}")
126
151
 
@@ -139,7 +164,7 @@ module Puppetserver
139
164
  :resource_type, :resource_name, :query) do
140
165
  def full_url
141
166
  url = protocol + '://' + host + ':' + port + '/' +
142
- [endpoint, version, resource_type, resource_name].join('/')
167
+ [endpoint, version, resource_type, resource_name].compact.join('/')
143
168
 
144
169
  url = url + "?" + URI.encode_www_form(query) unless query.nil? || query.empty?
145
170
  return url
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "2.5.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.5.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-03-14 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.0.9
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