puppetserver-ca 1.4.0 → 1.9.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
- SHA1:
3
- metadata.gz: e7759fd051a92708f8ac8aad4b8e89a636472b05
4
- data.tar.gz: f3cfd8de0bbdd17fd5c0f13656486dbbdbc243f1
2
+ SHA256:
3
+ metadata.gz: 03925b461bffbaec27b5c3d76c62393e68451a2e69ef9d82f34f51044ecd3fbd
4
+ data.tar.gz: e86c0137e287be5f8bf09dad3610e412aa49bb06b4b35c0c367a9ec6b5a76153
5
5
  SHA512:
6
- metadata.gz: bbaac0d6e00a9ed1d30dfb5f0ccc1d92e459fc07aaecb0fa2e8cdf61a320d3baf63d3752bf3adf1f4dacf9f25d051f4f239139bf45c4842975448e154e9353d0
7
- data.tar.gz: 1de3171122351341e0eb50ac675c6f168a8b6df37209f6e50723528485a120cf3fbdef023d28c066a80fd64b1a8f125930d81a620e4966e22bdb7468000fd0a1
6
+ metadata.gz: dcd960ea4199af2446c45cbc3ac692d79c5e1e6e1abefbb1d07c53b6605dd6fd5cfb86c7e808bfe6657919df7a9503c494653c3b111a2461d972bb3deee25719
7
+ data.tar.gz: 90d4c6a649898aa536392b653d6ba8d70c1c2b48e50bd0ecfeac57a6468f48d0ad040b9b3ff4c83de87150c678a659e34985b4ca90341c7885224862cd53a82b
@@ -6,6 +6,8 @@ rvm:
6
6
  - 2.3
7
7
  - 2.4
8
8
  - 2.5
9
+ - 2.6
10
+ - 2.7
9
11
  before_install:
10
12
  gem install bundler -v 1.16.1 && (gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true)
11
13
  script:
@@ -0,0 +1,4 @@
1
+ # This will cause the puppetserver-maintainers group to be assigned
2
+ # review of any opened PRs against the branches containing this file.
3
+
4
+ * @puppetlabs/puppetserver-maintainers
data/Gemfile CHANGED
@@ -5,6 +5,11 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
5
5
  # Specify your gem's dependencies in puppetserver-ca.gemspec
6
6
  gemspec
7
7
 
8
- gem 'pry'
9
- gem 'pry-byebug'
10
8
  gem 'hocon', '~> 1.2', require: false
9
+ gem 'rake', '~> 13.0', require: false
10
+ gem 'rspec', '~> 3.4', require: false
11
+
12
+ group(:development, optional: true) do
13
+ gem 'pry'
14
+ gem 'pry-byebug'
15
+ end
@@ -75,6 +75,9 @@ BANNER
75
75
  'Causes the cert to be generated offline.') do |ca_client|
76
76
  parsed['ca-client'] = true
77
77
  end
78
+ opts.on('--ttl TTL', 'The time-to-live for each cert generated and signed') do |ttl|
79
+ parsed['ttl'] = ttl
80
+ end
78
81
  end
79
82
  end
80
83
 
@@ -137,42 +140,14 @@ BANNER
137
140
  # Generate and save certs and associated keys
138
141
  if input['ca-client']
139
142
  # Refused to generate certs offfline if the CA service is running
140
- return 1 if check_server_online(puppet.settings)
143
+ return 1 if HttpClient.check_server_online(puppet.settings, @logger)
141
144
  all_passed = generate_authorized_certs(certnames, alt_names, puppet.settings, signer.digest)
142
145
  else
143
- all_passed = generate_certs(certnames, alt_names, puppet.settings, signer.digest)
146
+ all_passed = generate_certs(certnames, alt_names, puppet.settings, signer.digest, input['ttl'])
144
147
  end
145
148
  return all_passed ? 0 : 1
146
149
  end
147
150
 
148
- # Queries the simple status endpoint for the status of the CA service.
149
- # Returns true if it receives back a response of "running", and false if
150
- # no connection can be made, or a different response is received.
151
- def check_server_online(settings)
152
- status_url = HttpClient::URL.new('https', settings[:ca_server], settings[:ca_port], 'status', 'v1', 'simple', 'ca')
153
- begin
154
- # Generating certs offline is necessary if the master cert has been destroyed
155
- # or compromised. Since querying the status endpoint does not require a client cert, and
156
- # we commonly won't have one, don't require one for creating the connection.
157
- HttpClient.new(settings, with_client_cert: false).with_connection(status_url) do |conn|
158
- result = conn.get
159
- if result.body == "running"
160
- @logger.err "CA service is running. Please stop it before attempting to generate certs offline."
161
- true
162
- else
163
- false
164
- end
165
- end
166
- true
167
- rescue Puppetserver::Ca::ConnectionFailed => e
168
- if e.wrapped.is_a? Errno::ECONNREFUSED
169
- return false
170
- else
171
- raise e
172
- end
173
- end
174
- end
175
-
176
151
  # Certs authorized to talk to the CA API need to be signed offline,
177
152
  # in order to securely add the special auth extension.
178
153
  def generate_authorized_certs(certnames, alt_names, settings, digest)
@@ -209,8 +184,10 @@ BANNER
209
184
  # Generate csrs and keys, then submit them to CA, request for the CA to sign
210
185
  # them, download the signed certificates from the CA, and finally save
211
186
  # the signed certs and associated keys. Returns true if all certs were
212
- # successfully created and saved.
213
- def generate_certs(certnames, alt_names, settings, digest)
187
+ # successfully created and saved. Takes a ttl to use if certificates
188
+ # are signed by this CLI, not autosigned by the CA. if ttl is nil, uses
189
+ # the CA's settings.
190
+ def generate_certs(certnames, alt_names, settings, digest, ttl)
214
191
  # Make sure we have all the directories where we will be writing files
215
192
  FileSystem.ensure_dirs([settings[:ssldir],
216
193
  settings[:certdir],
@@ -228,17 +205,26 @@ BANNER
228
205
  next false unless submit_csr(certname, ca, settings, digest, current_alt_names)
229
206
 
230
207
  # Check if the CA autosigned the cert
231
- if download_cert(ca, certname, settings)
232
- @logger.inform "Certificate for #{certname} was autosigned."
233
- true
234
- else
235
- next false unless ca.sign_certs([certname])
236
- download_cert(ca, certname, settings)
237
- end
208
+ next acquire_signed_cert(ca, certname, settings, ttl)
238
209
  end
239
210
  passed.all?
240
211
  end
241
212
 
213
+ # Try to download a signed certificate; sign the cert with the given ttl if it needs
214
+ # signing before download.
215
+ def acquire_signed_cert(ca, certname, settings, ttl)
216
+ if download_cert(ca, certname, settings)
217
+ @logger.inform "Certificate for #{certname} was autosigned."
218
+ if ttl
219
+ @logger.warn "ttl was specified, but the CA autosigned the CSR. Unable to specify #{ttl} for #{certname}"
220
+ end
221
+ true
222
+ else
223
+ false unless ca.sign_certs([certname], ttl)
224
+ download_cert(ca, certname, settings)
225
+ end
226
+ end
227
+
242
228
  def submit_csr(certname, ca, settings, digest, alt_names)
243
229
  key, csr = generate_key_csr(certname, settings, digest, alt_names)
244
230
  return false unless csr
@@ -118,20 +118,41 @@ Options:
118
118
  end
119
119
 
120
120
  def output_certs(certs)
121
- padded = 0
121
+ cert_column_width = certs.map { |c| c['name'].size }.max
122
+
122
123
  certs.each do |cert|
123
- cert_size = cert["name"].size
124
- padded = cert_size if cert_size > padded
124
+ @logger.inform(format_cert(cert, cert_column_width))
125
125
  end
126
+ end
126
127
 
127
- certs.each do |cert|
128
- # In newer versions of the CA api we return subjcet_alt_names
129
- # in addition to dns_alt_names, this field includes DNS alt
130
- # names but also IP alt names.
131
- alt_names = cert["subject_alt_names"] || cert["dns_alt_names"]
132
- @logger.inform " #{cert["name"]}".ljust(padded + 6) + " (SHA256) " + " #{cert["fingerprints"]["SHA256"]}" +
133
- (alt_names.empty? ? "" : "\talt names: #{alt_names}")
134
- end
128
+ def format_cert(cert, cert_column_width)
129
+ [
130
+ format_cert_and_sha(cert, cert_column_width),
131
+ format_alt_names(cert),
132
+ format_authorization_extensions(cert)
133
+ ].compact.join("\t")
134
+ end
135
+
136
+ def format_cert_and_sha(cert, cert_column_width)
137
+ justified_certname = cert['name'].ljust(cert_column_width + 6)
138
+ sha = cert['fingerprints']['SHA256']
139
+ " #{justified_certname} (SHA256) #{sha}"
140
+ end
141
+
142
+ def format_alt_names(cert)
143
+ # In newer versions of the CA api we return subject_alt_names
144
+ # in addition to dns_alt_names, this field includes DNS alt
145
+ # names but also IP alt names.
146
+ alt_names = cert['subject_alt_names'] || cert['dns_alt_names']
147
+ "alt names: #{alt_names}" unless alt_names.empty?
148
+ end
149
+
150
+ def format_authorization_extensions(cert)
151
+ auth_exts = cert['authorization_extensions']
152
+ return nil if auth_exts.nil? || auth_exts.empty?
153
+
154
+ values = auth_exts.map { |ext, value| "#{ext}: #{value}" }.join(', ')
155
+ "authorization extensions: [#{values}]"
135
156
  end
136
157
 
137
158
  def separate_certs(all_certs)
@@ -0,0 +1,95 @@
1
+ require 'puppetserver/ca/utils/cli_parsing'
2
+ require 'puppetserver/ca/utils/file_system'
3
+ require 'puppetserver/ca/utils/http_client'
4
+
5
+ module Puppetserver
6
+ module Ca
7
+ module Action
8
+ class Migrate
9
+ include Puppetserver::Ca::Utils
10
+ PUPPETSERVER_CA_DIR = '/etc/puppetlabs/puppetserver/ca'
11
+
12
+ SUMMARY = "Migrate the existing CA directory to /etc/puppetlabs/puppetserver/ca"
13
+ BANNER = <<-BANNER
14
+ Usage:
15
+ puppetserver ca migrate [--help]
16
+ puppetserver ca migrate [--config PATH]
17
+
18
+ Description:
19
+ Migrate an existing CA directory to /etc/puppetlabs/puppetserver/ca. This is for
20
+ upgrading from Puppet Platform 6.x to Puppet 7. Use the currently configured
21
+ puppet.conf file in your installation, or supply one using the `--config` flag.
22
+ Options:
23
+ BANNER
24
+
25
+ def initialize(logger)
26
+ @logger = logger
27
+ end
28
+
29
+ def run(input)
30
+ config_path = input['config']
31
+ puppet = Config::Puppet.new(config_path)
32
+ puppet.load
33
+ return 1 if HttpClient.check_server_online(puppet.settings, @logger)
34
+
35
+ errors = FileSystem.check_for_existing_files(PUPPETSERVER_CA_DIR)
36
+ if !errors.empty?
37
+ instructions = <<-ERR
38
+ Migration will not overwrite the directory at #{PUPPETSERVER_CA_DIR}. Have you already
39
+ run this migration tool? Is this a puppet 7 installation? It is likely that you have
40
+ already successfully run the migration or do not need to run it.
41
+ ERR
42
+ errors << instructions
43
+ Errors.handle_with_usage(@logger, errors)
44
+ return 1
45
+ end
46
+
47
+ current_cadir = puppet.settings[:cadir]
48
+ if FileSystem.check_for_existing_files(current_cadir).empty?
49
+ error_message = <<-ERR
50
+ No CA dir found at #{current_cadir}. Please check the configured cadir setting in your
51
+ puppet.conf file and verify its contents.
52
+ ERR
53
+ Errors.handle_with_usage(@logger, [error_message])
54
+ return 1
55
+ end
56
+
57
+ migrate(current_cadir)
58
+
59
+ @logger.inform <<-SUCCESS_MESSAGE
60
+ CA dir successfully migrated to #{PUPPETSERVER_CA_DIR}. Symlink placed at #{current_cadir}
61
+ for backwards compatibility. The puppetserver can be safely restarted now.
62
+ SUCCESS_MESSAGE
63
+ return 0
64
+ end
65
+
66
+ def migrate(old_cadir, new_cadir=PUPPETSERVER_CA_DIR)
67
+ FileUtils.mv(old_cadir, new_cadir)
68
+ FileUtils.symlink(new_cadir, old_cadir)
69
+ end
70
+
71
+ def parse(args)
72
+ results = {}
73
+ parser = self.class.parser(results)
74
+ errors = CliParsing.parse_with_errors(parser, args)
75
+ errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
76
+ exit_code = errors_were_handled ? 1 : nil
77
+ return results, exit_code
78
+ end
79
+
80
+ def self.parser(parsed = {})
81
+ OptionParser.new do |opts|
82
+ opts.banner = BANNER
83
+ opts.on('--help', 'Display this command-specific help output') do |help|
84
+ parsed['help'] = true
85
+ end
86
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
87
+ parsed['config'] = conf
88
+ end
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+ end
@@ -32,6 +32,9 @@ Options:
32
32
  def self.parser(parsed = {})
33
33
  OptionParser.new do |opts|
34
34
  opts.banner = BANNER
35
+ opts.on('--ttl TTL', 'The time-to-live for each cert signed') do |ttl|
36
+ parsed['ttl'] = ttl
37
+ end
35
38
  opts.on('--certname NAME[,NAME]', Array, 'the name(s) of the cert(s) to be signed') do |cert|
36
39
  parsed['certname'] = cert
37
40
  end
@@ -72,7 +75,7 @@ Options:
72
75
  requested_certnames = input['certname']
73
76
  end
74
77
 
75
- success = ca.sign_certs(requested_certnames)
78
+ success = ca.sign_certs(requested_certnames, input['ttl'])
76
79
  return success ? 0 : 1
77
80
  end
78
81
 
@@ -8,6 +8,16 @@ module Puppetserver
8
8
 
9
9
  include Puppetserver::Ca::Utils
10
10
 
11
+ # Taken from puppet/lib/settings/duration_settings.rb
12
+ UNITMAP = {
13
+ # 365 days isn't technically a year, but is sufficient for most purposes
14
+ "y" => 365 * 24 * 60 * 60,
15
+ "d" => 24 * 60 * 60,
16
+ "h" => 60 * 60,
17
+ "m" => 60,
18
+ "s" => 1
19
+ }
20
+
11
21
  REVOKE_BODY = JSON.dump({ desired_state: 'revoked' })
12
22
  SIGN_BODY = JSON.dump({ desired_state: 'signed' })
13
23
 
@@ -35,11 +45,40 @@ module Puppetserver
35
45
  HttpClient::URL.new('https', @ca_server, @ca_port, 'puppet-ca', 'v1', resource_type, certname)
36
46
  end
37
47
 
38
- def sign_certs(certnames)
39
- results = put(certnames,
40
- resource_type: 'certificate_status',
41
- body: SIGN_BODY,
42
- type: :sign)
48
+ def process_ttl_input(ttl)
49
+ match = /^(\d+)(s|m|h|d|y)?$/.match(ttl)
50
+ if match
51
+ if match[2]
52
+ match[1].to_i * UNITMAP[match[2]].to_i
53
+ else
54
+ ttl
55
+ end
56
+ else
57
+ @logger.err "Error:"
58
+ @logger.err " '#{ttl}' is an invalid ttl value"
59
+ @logger.err "Value should match regex \"^(\d+)(s|m|h|d|y)?$\""
60
+ nil
61
+ end
62
+ end
63
+
64
+ def sign_certs(certnames, ttl=nil)
65
+ results = []
66
+ if ttl
67
+ lifetime = process_ttl_input(ttl)
68
+ return false if lifetime.nil?
69
+ body = JSON.dump({ desired_state: 'signed',
70
+ cert_ttl: lifetime})
71
+ results = put(certnames,
72
+ resource_type: 'certificate_status',
73
+ body: body,
74
+ type: :sign)
75
+ else
76
+ results = put(certnames,
77
+ resource_type: 'certificate_status',
78
+ body: SIGN_BODY,
79
+ type: :sign)
80
+ end
81
+
43
82
 
44
83
  results.all? { |result| result == :success }
45
84
  end
@@ -8,6 +8,7 @@ require 'puppetserver/ca/action/list'
8
8
  require 'puppetserver/ca/action/revoke'
9
9
  require 'puppetserver/ca/action/setup'
10
10
  require 'puppetserver/ca/action/sign'
11
+ require 'puppetserver/ca/action/migrate'
11
12
  require 'puppetserver/ca/errors'
12
13
  require 'puppetserver/ca/logger'
13
14
  require 'puppetserver/ca/utils/cli_parsing'
@@ -28,6 +29,7 @@ BANNER
28
29
  'import' => Action::Import,
29
30
  'setup' => Action::Setup,
30
31
  'enable' => Action::Enable,
32
+ 'migrate' => Action::Migrate,
31
33
  }
32
34
 
33
35
  MAINT_ACTIONS = {
@@ -47,6 +47,7 @@ module Puppetserver
47
47
  'pp_cloudplatform' => "1.3.6.1.4.1.34380.1.1.23",
48
48
  'pp_apptier' => "1.3.6.1.4.1.34380.1.1.24",
49
49
  'pp_hostname' => "1.3.6.1.4.1.34380.1.1.25",
50
+ 'pp_owner' => "1.3.6.1.4.1.34380.1.1.26",
50
51
  'pp_authorization' => "1.3.6.1.4.1.34380.1.3.1",
51
52
  'pp_auth_role' => "1.3.6.1.4.1.34380.1.3.13"}
52
53
 
@@ -159,6 +159,36 @@ module Puppetserver
159
159
 
160
160
  store
161
161
  end
162
+
163
+ # Queries the simple status endpoint for the status of the CA service.
164
+ # Returns true if it receives back a response of "running", and false if
165
+ # no connection can be made, or a different response is received.
166
+ def self.check_server_online(settings, logger)
167
+ status_url = URL.new('https', settings[:ca_server], settings[:ca_port], 'status', 'v1', 'simple', 'ca')
168
+ begin
169
+ # Generating certs offline is necessary if the master cert has been destroyed
170
+ # or compromised. Since querying the status endpoint does not require a client cert, and
171
+ # we commonly won't have one, don't require one for creating the connection.
172
+ # Additionally, we want to ensure the server is stopped before migrating the CA dir to
173
+ # avoid issues with writing to the CA dir and moving it.
174
+ self.new(settings, with_client_cert: false).with_connection(status_url) do |conn|
175
+ result = conn.get
176
+ if result.body == "running"
177
+ logger.err "CA service is running. Please stop it before attempting to run this command."
178
+ true
179
+ else
180
+ false
181
+ end
182
+ end
183
+ rescue Puppetserver::Ca::ConnectionFailed => e
184
+ if e.wrapped.is_a? Errno::ECONNREFUSED
185
+ return false
186
+ else
187
+ raise e
188
+ end
189
+ end
190
+ end
191
+
162
192
  end
163
193
  end
164
194
  end
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "1.4.0"
3
+ VERSION = "1.9.0"
4
4
  end
5
5
  end
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.add_runtime_dependency "facter", [">= 2.0.1", "< 4"]
23
+ spec.add_runtime_dependency "facter", [">= 2.0.1", "< 5"]
24
24
 
25
25
  spec.add_development_dependency "bundler", ">= 1.16"
26
26
  spec.add_development_dependency "rake", "~> 10.0"
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: 1.4.0
4
+ version: 1.9.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: 2019-08-15 00:00:00.000000000 Z
11
+ date: 2020-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: facter
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 2.0.1
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '4'
22
+ version: '5'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 2.0.1
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '4'
32
+ version: '5'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: bundler
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -83,6 +83,7 @@ files:
83
83
  - ".gitignore"
84
84
  - ".rspec"
85
85
  - ".travis.yml"
86
+ - CODEOWNERS
86
87
  - CODE_OF_CONDUCT.md
87
88
  - CONTRIBUTING.md
88
89
  - Gemfile
@@ -98,6 +99,7 @@ files:
98
99
  - lib/puppetserver/ca/action/generate.rb
99
100
  - lib/puppetserver/ca/action/import.rb
100
101
  - lib/puppetserver/ca/action/list.rb
102
+ - lib/puppetserver/ca/action/migrate.rb
101
103
  - lib/puppetserver/ca/action/revoke.rb
102
104
  - lib/puppetserver/ca/action/setup.rb
103
105
  - lib/puppetserver/ca/action/sign.rb
@@ -137,8 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
139
  - !ruby/object:Gem::Version
138
140
  version: '0'
139
141
  requirements: []
140
- rubyforge_project:
141
- rubygems_version: 2.5.1
142
+ rubygems_version: 3.0.8
142
143
  signing_key:
143
144
  specification_version: 4
144
145
  summary: A simple CLI tool for interacting with Puppet Server's Certificate Authority