puppetserver-ca 1.4.0 → 1.9.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
- 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