puppetserver-ca 0.3.1 → 0.4.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 +4 -4
- data/lib/puppetserver/ca/action/clean.rb +102 -0
- data/lib/puppetserver/ca/action/create.rb +161 -0
- data/lib/puppetserver/ca/action/generate.rb +313 -0
- data/lib/puppetserver/ca/action/import.rb +132 -0
- data/lib/puppetserver/ca/action/list.rb +132 -0
- data/lib/puppetserver/ca/action/revoke.rb +101 -0
- data/lib/puppetserver/ca/action/sign.rb +126 -0
- data/lib/puppetserver/ca/certificate_authority.rb +224 -0
- data/lib/puppetserver/ca/cli.rb +17 -16
- data/lib/puppetserver/ca/config/puppet.rb +242 -0
- data/lib/puppetserver/ca/config/puppetserver.rb +85 -0
- data/lib/puppetserver/ca/utils/cli_parsing.rb +82 -0
- data/lib/puppetserver/ca/utils/config.rb +13 -0
- data/lib/puppetserver/ca/utils/file_system.rb +90 -0
- data/lib/puppetserver/ca/utils/http_client.rb +129 -0
- data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
- data/lib/puppetserver/ca/version.rb +1 -1
- metadata +17 -17
- data/lib/puppetserver/ca/clean_action.rb +0 -157
- data/lib/puppetserver/ca/config_utils.rb +0 -11
- data/lib/puppetserver/ca/create_action.rb +0 -265
- data/lib/puppetserver/ca/generate_action.rb +0 -227
- data/lib/puppetserver/ca/import_action.rb +0 -153
- data/lib/puppetserver/ca/list_action.rb +0 -153
- data/lib/puppetserver/ca/puppet_config.rb +0 -197
- data/lib/puppetserver/ca/puppetserver_config.rb +0 -83
- data/lib/puppetserver/ca/revoke_action.rb +0 -136
- data/lib/puppetserver/ca/sign_action.rb +0 -190
- data/lib/puppetserver/ca/utils.rb +0 -80
- data/lib/puppetserver/settings/ttl_setting.rb +0 -48
- data/lib/puppetserver/utils/file_utilities.rb +0 -78
- data/lib/puppetserver/utils/http_client.rb +0 -129
- data/lib/puppetserver/utils/signing_digest.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98b359182e9c882ea8a66b4315b873f9d87185de
|
4
|
+
data.tar.gz: 3de8823031139ae413aa7a151dc6bbd0c10176b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 327b9b653764428a428f9531dcec525fde0dd67913be851fef86f25a9993d041624dd6f70cec1357687af50f22cd77f690ee1f7bc570bd79a255f53105e81b4a
|
7
|
+
data.tar.gz: baa44b1f602863f5323911446b1896ec9145eeb4360c1fcaa85fb0c63ad173b5acd59b81f5fd6d1fcd183161526154f4d9599ece1c3a0b7b5241d3b6fb372e82
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
2
|
+
require 'puppetserver/ca/utils/file_system'
|
3
|
+
require 'puppetserver/ca/config/puppet'
|
4
|
+
require 'puppetserver/ca/action/revoke'
|
5
|
+
require 'puppetserver/ca/certificate_authority'
|
6
|
+
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
module Puppetserver
|
10
|
+
module Ca
|
11
|
+
module Action
|
12
|
+
class Clean
|
13
|
+
|
14
|
+
include Puppetserver::Ca::Utils
|
15
|
+
|
16
|
+
CERTNAME_BLACKLIST = %w{--all --config}
|
17
|
+
|
18
|
+
SUMMARY = 'Clean files from the CA for certificate(s)'
|
19
|
+
BANNER = <<-BANNER
|
20
|
+
Usage:
|
21
|
+
puppetserver ca clean [--help]
|
22
|
+
puppetserver ca clean [--config] --certname CERTNAME[,ADDLCERTNAME]
|
23
|
+
|
24
|
+
Description:
|
25
|
+
Given one or more valid certnames, instructs the CA to revoke certificates
|
26
|
+
matching the given certnames if they exist, and then remove files pertaining
|
27
|
+
to them (keys, cert, and certificate request) over HTTPS using the local
|
28
|
+
agent's PKI
|
29
|
+
|
30
|
+
Options:
|
31
|
+
BANNER
|
32
|
+
|
33
|
+
def self.parser(parsed = {})
|
34
|
+
parsed['certnames'] = []
|
35
|
+
OptionParser.new do |o|
|
36
|
+
o.banner = BANNER
|
37
|
+
o.on('--certname foo,bar', Array,
|
38
|
+
'One or more comma separated certnames') do |certs|
|
39
|
+
parsed['certnames'] += certs
|
40
|
+
end
|
41
|
+
o.on('--config PUPPET.CONF', 'Custom path to puppet.conf') do |conf|
|
42
|
+
parsed['config'] = conf
|
43
|
+
end
|
44
|
+
o.on('--help', 'Display this clean specific help output') do |help|
|
45
|
+
parsed['help'] = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(logger)
|
51
|
+
@logger = logger
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse(args)
|
55
|
+
results = {}
|
56
|
+
parser = self.class.parser(results)
|
57
|
+
|
58
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
59
|
+
|
60
|
+
results['certnames'].each do |certname|
|
61
|
+
if CERTNAME_BLACKLIST.include?(certname)
|
62
|
+
errors << " Cannot manage cert named `#{certname}` from " +
|
63
|
+
"the CLI, if needed use the HTTP API directly"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if results['certnames'].empty?
|
68
|
+
errors << ' At least one certname is required to clean'
|
69
|
+
end
|
70
|
+
|
71
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
72
|
+
|
73
|
+
exit_code = errors_were_handled ? 1 : nil
|
74
|
+
|
75
|
+
return results, exit_code
|
76
|
+
end
|
77
|
+
|
78
|
+
def run(args)
|
79
|
+
certnames = args['certnames']
|
80
|
+
config = args['config']
|
81
|
+
|
82
|
+
if config
|
83
|
+
errors = FileSystem.validate_file_paths(config)
|
84
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
85
|
+
end
|
86
|
+
|
87
|
+
puppet = Config::Puppet.parse(config)
|
88
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
89
|
+
|
90
|
+
passed = clean_certs(certnames, puppet.settings)
|
91
|
+
|
92
|
+
return passed ? 0 : 1
|
93
|
+
end
|
94
|
+
|
95
|
+
def clean_certs(certnames, settings)
|
96
|
+
ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
|
97
|
+
ca.clean_certs(certnames)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
2
|
+
require 'puppetserver/ca/host'
|
3
|
+
require 'puppetserver/ca/certificate_authority'
|
4
|
+
require 'puppetserver/ca/config/puppet'
|
5
|
+
require 'puppetserver/ca/utils/file_system'
|
6
|
+
require 'puppetserver/ca/utils/signing_digest'
|
7
|
+
|
8
|
+
module Puppetserver
|
9
|
+
module Ca
|
10
|
+
module Action
|
11
|
+
class Create
|
12
|
+
|
13
|
+
include Puppetserver::Ca::Utils
|
14
|
+
|
15
|
+
# Only allow printing ascii characters, excluding /
|
16
|
+
VALID_CERTNAME = /\A[ -.0-~]+\Z/
|
17
|
+
CERTNAME_BLACKLIST = %w{--all --config}
|
18
|
+
|
19
|
+
SUMMARY = "Create a new certificate signed by the CA"
|
20
|
+
BANNER = <<-BANNER
|
21
|
+
Usage:
|
22
|
+
puppetserver ca create [--help]
|
23
|
+
puppetserver ca create [--config] --certname CERTNAME[,ADDLCERTNAME]
|
24
|
+
|
25
|
+
Description:
|
26
|
+
Creates a new certificate signed by the intermediate CA
|
27
|
+
and stores generated keys and certs on disk.
|
28
|
+
|
29
|
+
To determine the target location, the default puppet.conf
|
30
|
+
is consulted for custom values. If using a custom puppet.conf
|
31
|
+
provide it with the --config flag
|
32
|
+
|
33
|
+
Options:
|
34
|
+
BANNER
|
35
|
+
def initialize(logger)
|
36
|
+
@logger = logger
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.parser(parsed = {})
|
40
|
+
parsed['certnames'] = []
|
41
|
+
OptionParser.new do |opts|
|
42
|
+
opts.banner = BANNER
|
43
|
+
opts.on('--certname FOO,BAR', Array,
|
44
|
+
'One or more comma separated certnames') do |certs|
|
45
|
+
parsed['certnames'] += certs
|
46
|
+
end
|
47
|
+
opts.on('--help', 'Display this create specific help output') do |help|
|
48
|
+
parsed['help'] = true
|
49
|
+
end
|
50
|
+
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
51
|
+
parsed['config'] = conf
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse(args)
|
57
|
+
results = {}
|
58
|
+
parser = self.class.parser(results)
|
59
|
+
|
60
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
61
|
+
|
62
|
+
if results['certnames'].empty?
|
63
|
+
errors << ' At least one certname is required to create'
|
64
|
+
else
|
65
|
+
results['certnames'].each do |certname|
|
66
|
+
if CERTNAME_BLACKLIST.include?(certname)
|
67
|
+
errors << " Cannot manage cert named `#{certname}` from " +
|
68
|
+
"the CLI, if needed use the HTTP API directly"
|
69
|
+
end
|
70
|
+
|
71
|
+
if certname.match(/\p{Upper}/)
|
72
|
+
errors << " Certificate names must be lower case"
|
73
|
+
end
|
74
|
+
|
75
|
+
unless certname =~ VALID_CERTNAME
|
76
|
+
errors << " Certname #{certname} must not contain unprintable or non-ASCII characters"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
82
|
+
|
83
|
+
exit_code = errors_were_handled ? 1 : nil
|
84
|
+
|
85
|
+
return results, exit_code
|
86
|
+
end
|
87
|
+
|
88
|
+
def run(input)
|
89
|
+
certnames = input['certnames']
|
90
|
+
config_path = input['config']
|
91
|
+
|
92
|
+
# Validate config_path provided
|
93
|
+
if config_path
|
94
|
+
errors = FileSystem.validate_file_paths(config_path)
|
95
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Load, resolve, and validate puppet config settings
|
99
|
+
puppet = Config::Puppet.parse(config_path)
|
100
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
101
|
+
|
102
|
+
# Load most secure signing digest we can for csr signing.
|
103
|
+
signer = SigningDigest.new
|
104
|
+
return 1 if CliParsing.handle_errors(@logger, signer.errors)
|
105
|
+
|
106
|
+
# Make sure we have all the directories where we will be writing files
|
107
|
+
FileSystem.ensure_dir(puppet.settings[:certdir])
|
108
|
+
FileSystem.ensure_dir(puppet.settings[:privatekeydir])
|
109
|
+
FileSystem.ensure_dir(puppet.settings[:publickeydir])
|
110
|
+
|
111
|
+
# Generate and save certs and associated keys
|
112
|
+
all_passed = generate_certs(certnames, puppet.settings, signer.digest)
|
113
|
+
return all_passed ? 0 : 1
|
114
|
+
end
|
115
|
+
|
116
|
+
# Create csrs and keys, then submit them to CA, request for the CA to sign
|
117
|
+
# them, download the signed certificates from the CA, and finally save
|
118
|
+
# the signed certs and associated keys. Returns true if all certs were
|
119
|
+
# successfully created and saved.
|
120
|
+
def generate_certs(certnames, settings, digest)
|
121
|
+
ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
|
122
|
+
|
123
|
+
passed = certnames.map do |certname|
|
124
|
+
key, csr = generate_key_csr(certname, settings, digest)
|
125
|
+
return false unless ca.submit_certificate_request(certname, csr)
|
126
|
+
return false unless ca.sign_certs([certname])
|
127
|
+
if result = ca.get_certificate(certname)
|
128
|
+
save_file(result.body, certname, settings[:certdir], "Certificate")
|
129
|
+
save_keys(certname, settings, key)
|
130
|
+
true
|
131
|
+
else
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
passed.all?
|
136
|
+
end
|
137
|
+
|
138
|
+
def generate_key_csr(certname, settings, digest)
|
139
|
+
host = Puppetserver::Ca::Host.new(digest)
|
140
|
+
private_key = host.create_private_key(settings[:keylength])
|
141
|
+
csr = host.create_csr(certname, private_key)
|
142
|
+
|
143
|
+
return private_key, csr
|
144
|
+
end
|
145
|
+
|
146
|
+
def save_keys(certname, settings, key)
|
147
|
+
public_key = key.public_key
|
148
|
+
save_file(key, certname, settings[:privatekeydir], "Private key")
|
149
|
+
save_file(public_key, certname, settings[:publickeydir], "Public key")
|
150
|
+
end
|
151
|
+
|
152
|
+
def save_file(content, certname, dir, type)
|
153
|
+
location = File.join(dir, "#{certname}.pem")
|
154
|
+
@logger.warn "#{type} #{certname}.pem already exists, overwriting" if File.exist?(location)
|
155
|
+
FileSystem.write_file(location, content, 0640)
|
156
|
+
@logger.inform "Successfully saved #{type.downcase} for #{certname} to #{location}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'openssl'
|
3
|
+
require 'puppetserver/ca/utils/file_system'
|
4
|
+
require 'puppetserver/ca/host'
|
5
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
6
|
+
require 'puppetserver/ca/utils/signing_digest'
|
7
|
+
require 'puppetserver/ca/config/puppet'
|
8
|
+
require 'facter'
|
9
|
+
|
10
|
+
module Puppetserver
|
11
|
+
module Ca
|
12
|
+
module Action
|
13
|
+
class Generate
|
14
|
+
include Puppetserver::Ca::Utils
|
15
|
+
|
16
|
+
CA_EXTENSIONS = [
|
17
|
+
["basicConstraints", "CA:TRUE", true],
|
18
|
+
["keyUsage", "keyCertSign, cRLSign", true],
|
19
|
+
["subjectKeyIdentifier", "hash", false],
|
20
|
+
["nsComment", "Puppet Server Internal Certificate", false],
|
21
|
+
["authorityKeyIdentifier", "keyid:always", false]
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
SSL_SERVER_CERT = "1.3.6.1.5.5.7.3.1"
|
25
|
+
SSL_CLIENT_CERT = "1.3.6.1.5.5.7.3.2"
|
26
|
+
|
27
|
+
MASTER_EXTENSIONS = [
|
28
|
+
["basicConstraints", "CA:FALSE", true],
|
29
|
+
["nsComment", "Puppet Server Internal Certificate", false],
|
30
|
+
["authorityKeyIdentifier", "keyid:always", false],
|
31
|
+
["extendedKeyUsage", "#{SSL_SERVER_CERT}, #{SSL_CLIENT_CERT}", true],
|
32
|
+
["keyUsage", "keyEncipherment, digitalSignature", true],
|
33
|
+
["subjectKeyIdentifier", "hash", false]
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
# Make the certificate valid as of yesterday, because so many people's
|
37
|
+
# clocks are out of sync. This gives one more day of validity than people
|
38
|
+
# might expect, but is better than making every person who has a messed up
|
39
|
+
# clock fail, and better than having every cert we generate expire a day
|
40
|
+
# before the user expected it to when they asked for "one year".
|
41
|
+
CERT_VALID_FROM = (Time.now - (60*60*24)).freeze
|
42
|
+
|
43
|
+
SUMMARY = "Generate a root and intermediate signing CA for Puppet Server"
|
44
|
+
BANNER = <<-BANNER
|
45
|
+
Usage:
|
46
|
+
puppetserver ca generate [--help]
|
47
|
+
puppetserver ca generate [--config PATH]
|
48
|
+
puppetserver ca generate [--subject-alt-names ALTNAME1[,ALTNAME2...]]
|
49
|
+
|
50
|
+
Description:
|
51
|
+
Generate a root and intermediate signing CA for Puppet Server
|
52
|
+
and store generated CA keys, certs, and crls on disk.
|
53
|
+
|
54
|
+
The `--subject-alt-names` flag can be used to add SANs to the
|
55
|
+
certificate generated for the Puppet master. Multiple names can be
|
56
|
+
listed as a comma separated string. These can be either DNS names or
|
57
|
+
IP addresses, differentiated by prefixes: `DNS:foo.bar.com,IP:123.456.789`.
|
58
|
+
Names with no prefix will be treated as DNS names.
|
59
|
+
|
60
|
+
To determine the target location, the default puppet.conf
|
61
|
+
is consulted for custom values. If using a custom puppet.conf
|
62
|
+
provide it with the --config flag
|
63
|
+
|
64
|
+
Options:
|
65
|
+
BANNER
|
66
|
+
|
67
|
+
def initialize(logger)
|
68
|
+
@logger = logger
|
69
|
+
end
|
70
|
+
|
71
|
+
def run(input)
|
72
|
+
# Validate config_path provided
|
73
|
+
config_path = input['config']
|
74
|
+
if config_path
|
75
|
+
errors = FileSystem.validate_file_paths(config_path)
|
76
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Load, resolve, and validate puppet config settings
|
80
|
+
puppet = Config::Puppet.parse(config_path)
|
81
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
82
|
+
|
83
|
+
# Load most secure signing digest we can for cers/crl/csr signing.
|
84
|
+
signer = SigningDigest.new
|
85
|
+
return 1 if CliParsing.handle_errors(@logger, signer.errors)
|
86
|
+
|
87
|
+
if input['subject_alt_names'].empty?
|
88
|
+
subject_alt_names = munge_alt_names(puppet.settings[:subject_alt_names])
|
89
|
+
else
|
90
|
+
subject_alt_names = munge_alt_names(input['subject_alt_names'])
|
91
|
+
end
|
92
|
+
|
93
|
+
# Generate root and intermediate ca and put all the certificates, crls,
|
94
|
+
# and keys where they should go.
|
95
|
+
errors = generate_pki(puppet.settings, signer.digest, subject_alt_names)
|
96
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
97
|
+
|
98
|
+
@logger.inform "Generation succeeded. Find your files in #{puppet.settings[:cadir]}"
|
99
|
+
return 0
|
100
|
+
end
|
101
|
+
|
102
|
+
def generate_pki(settings, signing_digest, subject_alt_names = '')
|
103
|
+
valid_until = Time.now + settings[:ca_ttl]
|
104
|
+
host = Puppetserver::Ca::Host.new(signing_digest)
|
105
|
+
|
106
|
+
root_key = host.create_private_key(settings[:keylength])
|
107
|
+
root_cert = self_signed_ca(root_key, settings[:root_ca_name], valid_until, signing_digest)
|
108
|
+
root_crl = create_crl_for(root_cert, root_key, valid_until, signing_digest)
|
109
|
+
|
110
|
+
int_key = host.create_private_key(settings[:keylength])
|
111
|
+
int_csr = host.create_csr(settings[:ca_name], int_key)
|
112
|
+
int_cert = sign_intermediate(root_key, root_cert, int_csr, valid_until, signing_digest)
|
113
|
+
int_crl = create_crl_for(int_cert, int_key, valid_until, signing_digest)
|
114
|
+
|
115
|
+
master_key = host.create_private_key(settings[:keylength])
|
116
|
+
master_csr = host.create_csr(settings[:certname], master_key)
|
117
|
+
master_cert = sign_master_cert(int_key, int_cert, master_csr,
|
118
|
+
valid_until, signing_digest, subject_alt_names)
|
119
|
+
|
120
|
+
FileSystem.ensure_dir(settings[:cadir])
|
121
|
+
FileSystem.ensure_dir(settings[:certdir])
|
122
|
+
FileSystem.ensure_dir(settings[:privatekeydir])
|
123
|
+
FileSystem.ensure_dir(settings[:publickeydir])
|
124
|
+
|
125
|
+
public_files = [
|
126
|
+
[settings[:cacert], [int_cert, root_cert]],
|
127
|
+
[settings[:cacrl], [int_crl, root_crl]],
|
128
|
+
[settings[:hostcert], master_cert],
|
129
|
+
[settings[:localcacert], [int_cert, root_cert]],
|
130
|
+
[settings[:localcacrl], [int_crl, root_crl]],
|
131
|
+
[settings[:hostpubkey], master_key.public_key],
|
132
|
+
[settings[:capub], int_key.public_key],
|
133
|
+
[settings[:cert_inventory], inventory_entry(master_cert)],
|
134
|
+
[settings[:serial], "0x0002"],
|
135
|
+
]
|
136
|
+
|
137
|
+
private_files = [
|
138
|
+
[settings[:hostprivkey], master_key],
|
139
|
+
[settings[:rootkey], root_key],
|
140
|
+
[settings[:cakey], int_key],
|
141
|
+
]
|
142
|
+
|
143
|
+
errors = FileSystem.check_for_existing_files(public_files.map(&:first))
|
144
|
+
errors += FileSystem.check_for_existing_files(private_files.map(&:first))
|
145
|
+
|
146
|
+
if !errors.empty?
|
147
|
+
instructions = <<-ERR
|
148
|
+
If you would really like to replace your CA, please delete the existing files first.
|
149
|
+
Note that any certificates that were issued by this CA will become invalid if you
|
150
|
+
replace it!
|
151
|
+
ERR
|
152
|
+
errors << instructions
|
153
|
+
return errors
|
154
|
+
end
|
155
|
+
|
156
|
+
public_files.each do |location, content|
|
157
|
+
FileSystem.write_file(location, content, 0644)
|
158
|
+
end
|
159
|
+
|
160
|
+
private_files.each do |location, content|
|
161
|
+
FileSystem.write_file(location, content, 0640)
|
162
|
+
end
|
163
|
+
|
164
|
+
return []
|
165
|
+
end
|
166
|
+
|
167
|
+
def self_signed_ca(key, name, valid_until, signing_digest)
|
168
|
+
cert = OpenSSL::X509::Certificate.new
|
169
|
+
|
170
|
+
cert.public_key = key.public_key
|
171
|
+
cert.subject = OpenSSL::X509::Name.new([["CN", name]])
|
172
|
+
cert.issuer = cert.subject
|
173
|
+
cert.version = 2
|
174
|
+
cert.serial = 1
|
175
|
+
|
176
|
+
cert.not_before = CERT_VALID_FROM
|
177
|
+
cert.not_after = valid_until
|
178
|
+
|
179
|
+
ef = extension_factory_for(cert, cert)
|
180
|
+
CA_EXTENSIONS.each do |ext|
|
181
|
+
extension = ef.create_extension(*ext)
|
182
|
+
cert.add_extension(extension)
|
183
|
+
end
|
184
|
+
|
185
|
+
cert.sign(key, signing_digest)
|
186
|
+
|
187
|
+
cert
|
188
|
+
end
|
189
|
+
|
190
|
+
def inventory_entry(cert)
|
191
|
+
"0x%04x %s %s %s" % [cert.serial, format_time(cert.not_before),
|
192
|
+
format_time(cert.not_after), cert.subject]
|
193
|
+
end
|
194
|
+
|
195
|
+
def format_time(time)
|
196
|
+
time.strftime('%Y-%m-%dT%H:%M:%S%Z')
|
197
|
+
end
|
198
|
+
|
199
|
+
def extension_factory_for(ca, cert = nil)
|
200
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
201
|
+
ef.issuer_certificate = ca
|
202
|
+
ef.subject_certificate = cert if cert
|
203
|
+
|
204
|
+
ef
|
205
|
+
end
|
206
|
+
|
207
|
+
def create_crl_for(ca_cert, ca_key, valid_until, signing_digest)
|
208
|
+
crl = OpenSSL::X509::CRL.new
|
209
|
+
crl.version = 1
|
210
|
+
crl.issuer = ca_cert.subject
|
211
|
+
|
212
|
+
ef = extension_factory_for(ca_cert)
|
213
|
+
crl.add_extension(
|
214
|
+
ef.create_extension(["authorityKeyIdentifier", "keyid:always", false]))
|
215
|
+
crl.add_extension(
|
216
|
+
OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))
|
217
|
+
|
218
|
+
crl.last_update = CERT_VALID_FROM
|
219
|
+
crl.next_update = valid_until
|
220
|
+
crl.sign(ca_key, signing_digest)
|
221
|
+
|
222
|
+
crl
|
223
|
+
end
|
224
|
+
|
225
|
+
def sign_intermediate(ca_key, ca_cert, csr, valid_until, signing_digest)
|
226
|
+
cert = OpenSSL::X509::Certificate.new
|
227
|
+
|
228
|
+
cert.public_key = csr.public_key
|
229
|
+
cert.subject = csr.subject
|
230
|
+
cert.issuer = ca_cert.subject
|
231
|
+
cert.version = 2
|
232
|
+
cert.serial = 2
|
233
|
+
|
234
|
+
cert.not_before = CERT_VALID_FROM
|
235
|
+
cert.not_after = valid_until
|
236
|
+
|
237
|
+
ef = extension_factory_for(ca_cert, cert)
|
238
|
+
CA_EXTENSIONS.each do |ext|
|
239
|
+
extension = ef.create_extension(*ext)
|
240
|
+
cert.add_extension(extension)
|
241
|
+
end
|
242
|
+
|
243
|
+
cert.sign(ca_key, signing_digest)
|
244
|
+
|
245
|
+
cert
|
246
|
+
end
|
247
|
+
|
248
|
+
def sign_master_cert(int_key, int_cert, csr, valid_until, signing_digest, subject_alt_names)
|
249
|
+
cert = OpenSSL::X509::Certificate.new
|
250
|
+
cert.public_key = csr.public_key
|
251
|
+
cert.subject = csr.subject
|
252
|
+
cert.issuer = int_cert.subject
|
253
|
+
cert.version = 2
|
254
|
+
cert.serial = 1
|
255
|
+
cert.not_before = CERT_VALID_FROM
|
256
|
+
cert.not_after = valid_until
|
257
|
+
|
258
|
+
ef = extension_factory_for(int_cert, cert)
|
259
|
+
MASTER_EXTENSIONS.each do |ext|
|
260
|
+
extension = ef.create_extension(*ext)
|
261
|
+
cert.add_extension(extension)
|
262
|
+
end
|
263
|
+
|
264
|
+
if !subject_alt_names.empty?
|
265
|
+
alt_names_ext = ef.create_extension("subjectAltName", subject_alt_names, false)
|
266
|
+
cert.add_extension(alt_names_ext)
|
267
|
+
end
|
268
|
+
|
269
|
+
cert.sign(int_key, signing_digest)
|
270
|
+
cert
|
271
|
+
end
|
272
|
+
|
273
|
+
def munge_alt_names(names)
|
274
|
+
raw_names = names.split(/\s*,\s*/).map(&:strip)
|
275
|
+
munged_names = raw_names.map do |name|
|
276
|
+
# Prepend the DNS tag if no tag was specified
|
277
|
+
if !name.start_with?("IP:") && !name.start_with?("DNS:")
|
278
|
+
"DNS:#{name}"
|
279
|
+
else
|
280
|
+
name
|
281
|
+
end
|
282
|
+
end.sort.uniq.join(", ")
|
283
|
+
end
|
284
|
+
|
285
|
+
def parse(cli_args)
|
286
|
+
results = {}
|
287
|
+
parser = self.class.parser(results)
|
288
|
+
errors = CliParsing.parse_with_errors(parser, cli_args)
|
289
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
290
|
+
exit_code = errors_were_handled ? 1 : nil
|
291
|
+
return results, exit_code
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.parser(parsed = {})
|
295
|
+
parsed['subject_alt_names'] = ''
|
296
|
+
OptionParser.new do |opts|
|
297
|
+
opts.banner = BANNER
|
298
|
+
opts.on('--help', 'Display this generate specific help output') do |help|
|
299
|
+
parsed['help'] = true
|
300
|
+
end
|
301
|
+
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
302
|
+
parsed['config'] = conf
|
303
|
+
end
|
304
|
+
opts.on('--subject-alt-names NAME1[,NAME2]',
|
305
|
+
'Subject alternative names for the CA signing cert') do |sans|
|
306
|
+
parsed['subject_alt_names'] = sans
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|