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