puppetserver-ca 0.4.3 → 0.5.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
2
  SHA1:
3
- metadata.gz: 9c19462a3167093517421b973f330b005a6776b4
4
- data.tar.gz: 5c4dc2716c5063512594005c625ad391ca6ec3f0
3
+ metadata.gz: 2f958c8e39c9f5a84c417138710b43c09d088189
4
+ data.tar.gz: 33f332146f6f3c3318dbfe7cc786205d74a3a021
5
5
  SHA512:
6
- metadata.gz: cac3a5c1d00ddb4e4fc9948fc09c91bc6436e8c245cd17610b971e7dbc74c033ba41c7602d12fdbe11808eb51a0612c5fee71353af4b9956b4ac449fb889ec84
7
- data.tar.gz: 8e8ac33850a91d128282572c0ef2f4dd421dd447911e09cc6aa561eae1f6b381c3b06f0e9661581616c2c516da92490ad59d180744737bf6873c049a9d9d673f
6
+ metadata.gz: 77847b302859de89a4566dff4d4230e049c488f09baeca6b1b9ec86b68c68803942dfb6c187b37b5f9eb80a2ee531f62fe5fd0f4877ffd820cba10ecd672c0a5
7
+ data.tar.gz: 3234826e83a9a397216cf1133c80572e5ad3f4d8f06f4c0fc5189f1eac048d08e82814be8e54e4bc8008962fbd28c5780406952a64d03ba349aeb7fd90141c63
@@ -134,6 +134,7 @@ BANNER
134
134
 
135
135
  passed = certnames.map do |certname|
136
136
  key, csr = generate_key_csr(certname, settings, digest)
137
+ return false unless csr
137
138
  return false unless ca.submit_certificate_request(certname, csr)
138
139
  return false unless ca.sign_certs([certname])
139
140
  if result = ca.get_certificate(certname)
@@ -152,9 +153,13 @@ BANNER
152
153
  private_key = host.create_private_key(settings[:keylength])
153
154
  extensions = []
154
155
  if !settings[:subject_alt_names].empty?
155
- extensions << host.create_extension("subjectAltName", settings[:subject_alt_names])
156
+ extensions << OpenSSL::X509::Extension.new("subjectAltName", settings[:subject_alt_names], false)
156
157
  end
157
- csr = host.create_csr(certname, private_key, extensions)
158
+ csr = host.create_csr(name: certname,
159
+ key: private_key,
160
+ cli_extensions: extensions,
161
+ csr_attributes_path: settings[:csr_attributes])
162
+ return if CliParsing.handle_errors(@logger, host.errors)
158
163
 
159
164
  return private_key, csr
160
165
  end
@@ -78,6 +78,7 @@ BANNER
78
78
  root_key, root_cert, root_crl = ca.create_root_cert
79
79
  int_key, int_cert, int_crl = ca.create_intermediate_cert(root_key, root_cert)
80
80
  master_key, master_cert = ca.create_master_cert(int_key, int_cert)
81
+ return ca.errors if ca.errors.any?
81
82
 
82
83
  FileSystem.ensure_dirs([settings[:ssldir],
83
84
  settings[:cadir],
@@ -74,6 +74,7 @@ BANNER
74
74
  def import(loader, settings, signing_digest)
75
75
  ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
76
76
  master_key, master_cert = ca.create_master_cert(loader.key, loader.certs.first)
77
+ return ca.errors if ca.errors.any?
77
78
 
78
79
  FileSystem.ensure_dirs([settings[:ssldir],
79
80
  settings[:cadir],
@@ -111,7 +111,7 @@ Options:
111
111
 
112
112
  def get_all_certs(settings)
113
113
  result = Puppetserver::Ca::CertificateAuthority.new(@logger, settings).get_certificate_statuses
114
- JSON.parse(result.body)
114
+ JSON.parse(result.body) if result
115
115
  end
116
116
 
117
117
  def parse(args)
@@ -119,6 +119,7 @@ module Puppetserver
119
119
  :cacert => '$cadir/ca_crt.pem',
120
120
  :cakey => '$cadir/ca_key.pem',
121
121
  :capub => '$cadir/ca_pub.pem',
122
+ :csr_attributes => '$confdir/csr_attributes.yaml',
122
123
  :rootkey => '$cadir/root_key.pem',
123
124
  :cacrl => '$cadir/ca_crl.pem',
124
125
  :serial => '$cadir/serial',
@@ -130,7 +131,6 @@ module Puppetserver
130
131
  :hostcert => '$certdir/$certname.pem',
131
132
  :hostprivkey => '$privatekeydir/$certname.pem',
132
133
  :hostpubkey => '$publickeydir/$certname.pem',
133
- :publickeydir => '$ssldir/public_keys',
134
134
  :ca_ttl => '15y',
135
135
  :certificate_revocation => 'true',
136
136
  :signeddir => '$cadir/signed',
@@ -1,36 +1,161 @@
1
1
  require 'openssl'
2
+ require 'fileutils'
3
+ require 'yaml'
2
4
 
3
5
  module Puppetserver
4
6
  module Ca
5
7
  class Host
8
+ # Exclude OIDs that may conflict with how Puppet creates CSRs.
9
+ #
10
+ # We only have nominal support for Microsoft extension requests, but since we
11
+ # ultimately respect that field when looking for extension requests in a CSR
12
+ # we need to prevent that field from being written to directly.
13
+ PRIVATE_CSR_ATTRIBUTES = [
14
+ 'extReq', '1.2.840.113549.1.9.14',
15
+ 'msExtReq', '1.3.6.1.4.1.311.2.1.14',
16
+ ]
17
+
18
+ PRIVATE_EXTENSIONS = [
19
+ 'subjectAltName', '2.5.29.17',
20
+ ]
21
+
22
+ # A mapping of Puppet extension short names to their OIDs. These appear in
23
+ # csr_attributes.yaml.
24
+ PUPPET_SHORT_NAMES =
25
+ {'pp_uuid' => "1.3.6.1.4.1.34380.1.1.1",
26
+ 'pp_instance_id' => "1.3.6.1.4.1.34380.1.1.2",
27
+ 'pp_image_name' => "1.3.6.1.4.1.34380.1.1.3",
28
+ 'pp_preshared_key'=> "1.3.6.1.4.1.34380.1.1.4",
29
+ 'pp_cost_center' => "1.3.6.1.4.1.34380.1.1.5",
30
+ 'pp_product' => "1.3.6.1.4.1.34380.1.1.6",
31
+ 'pp_project' => "1.3.6.1.4.1.34380.1.1.7",
32
+ 'pp_application' => "1.3.6.1.4.1.34380.1.1.8",
33
+ 'pp_service'=> "1.3.6.1.4.1.34380.1.1.9",
34
+ 'pp_employee' => "1.3.6.1.4.1.34380.1.1.10",
35
+ 'pp_created_by' => "1.3.6.1.4.1.34380.1.1.11",
36
+ 'pp_environment' => "1.3.6.1.4.1.34380.1.1.12",
37
+ 'pp_role' => "1.3.6.1.4.1.34380.1.1.13",
38
+ 'pp_software_version' => "1.3.6.1.4.1.34380.1.1.14",
39
+ 'pp_department' => "1.3.6.1.4.1.34380.1.1.15",
40
+ 'pp_cluster' => "1.3.6.1.4.1.34380.1.1.16",
41
+ 'pp_provisioner' => "1.3.6.1.4.1.34380.1.1.17",
42
+ 'pp_region' => "1.3.6.1.4.1.34380.1.1.18",
43
+ 'pp_datacenter' => "1.3.6.1.4.1.34380.1.1.19",
44
+ 'pp_zone' => "1.3.6.1.4.1.34380.1.1.20",
45
+ 'pp_network' => "1.3.6.1.4.1.34380.1.1.21",
46
+ 'pp_securitypolicy' => "1.3.6.1.4.1.34380.1.1.22",
47
+ 'pp_cloudplatform' => "1.3.6.1.4.1.34380.1.1.23",
48
+ 'pp_apptier' => "1.3.6.1.4.1.34380.1.1.24",
49
+ 'pp_hostname' => "1.3.6.1.4.1.34380.1.1.25",
50
+ 'pp_authorization' => "1.3.6.1.4.1.34380.1.3.1",
51
+ 'pp_auth_role' => "1.3.6.1.4.1.34380.1.3.13"}
52
+
53
+ attr_reader :errors
6
54
 
7
55
  def initialize(digest)
8
56
  @digest = digest
57
+ @errors = []
9
58
  end
10
59
 
11
60
  def create_private_key(keylength)
12
61
  OpenSSL::PKey::RSA.new(keylength)
13
62
  end
14
63
 
15
- def create_csr(name, key, extensions = [])
64
+ def create_csr(name:, key:, cli_extensions: [], csr_attributes_path: '')
16
65
  csr = OpenSSL::X509::Request.new
17
66
  csr.public_key = key.public_key
18
67
  csr.subject = OpenSSL::X509::Name.new([["CN", name]])
19
68
  csr.version = 2
20
- add_csr_extension(csr, extensions) unless extensions.empty?
21
- csr.sign(key, @digest)
69
+
70
+ custom_attributes = get_custom_attributes(csr_attributes_path)
71
+ extension_requests = get_extension_requests(csr_attributes_path)
72
+
73
+ add_csr_attributes(csr, custom_attributes)
74
+ add_csr_extensions(csr, extension_requests, cli_extensions)
75
+
76
+ csr.sign(key, @digest) if @errors.empty?
22
77
 
23
78
  csr
24
79
  end
25
80
 
26
- def create_extension(extension_name, extension_value, critical = false)
27
- OpenSSL::X509::ExtensionFactory.new.create_extension(extension_name, extension_value, critical)
81
+ def extension_attribute(extensions)
82
+ seq = OpenSSL::ASN1::Sequence(extensions)
83
+ ext_req = OpenSSL::ASN1::Set([seq])
84
+ OpenSSL::X509::Attribute.new("extReq", ext_req)
85
+ end
86
+
87
+ def get_custom_attributes(attributes_path)
88
+ if csr_attributes = load_csr_attributes(attributes_path)
89
+ csr_attributes['custom_attributes']
90
+ end
91
+ end
92
+
93
+ def get_extension_requests(attributes_path)
94
+ if csr_attributes = load_csr_attributes(attributes_path)
95
+ csr_attributes['extension_requests']
96
+ end
97
+ end
98
+
99
+ # This loads all the custom_attributes and extension requests
100
+ # from the csr_attributes.yaml
101
+ def load_csr_attributes(attributes_path)
102
+ @custom_csr_attributes ||=
103
+ if File.exist?(attributes_path)
104
+ yaml = YAML.load_file(attributes_path)
105
+ if !yaml.is_a?(Hash)
106
+ @errors << "Invalid CSR attributes, expected instance of Hash, received instance of #{yaml.class}"
107
+ return
108
+ end
109
+ yaml
110
+ end
111
+ end
112
+
113
+ def add_csr_attributes(csr, csr_attributes)
114
+ if csr_attributes
115
+ csr_attributes.each do |oid, value|
116
+ begin
117
+ if PRIVATE_CSR_ATTRIBUTES.include? oid
118
+ @errors << "Cannot specify CSR attribute #{oid}: conflicts with internally used CSR attribute"
119
+ end
120
+ oid = PUPPET_SHORT_NAMES[oid] || oid
121
+ encoded = OpenSSL::ASN1::PrintableString.new(value.to_s)
122
+ attr_set = OpenSSL::ASN1::Set.new([encoded])
123
+
124
+ csr.add_attribute(OpenSSL::X509::Attribute.new(oid, attr_set))
125
+ rescue OpenSSL::X509::AttributeError => e
126
+ @errors << "Cannot create CSR with attribute #{oid}: #{e.message}"
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def add_csr_extensions(csr, extension_requests, cli_extensions)
133
+ if extension_requests || cli_extensions.any?
134
+ extensions =
135
+ if extension_requests
136
+ validated_extensions(extension_requests) + cli_extensions
137
+ else
138
+ cli_extensions
139
+ end
140
+ csr.add_attribute(extension_attribute(extensions))
141
+ end
28
142
  end
29
143
 
30
- def add_csr_extension(csr, extensions)
31
- attribute_values = OpenSSL::ASN1::Set [OpenSSL::ASN1::Sequence(extensions)]
32
- att = OpenSSL::X509::Attribute.new('extReq', attribute_values)
33
- csr.add_attribute(att)
144
+ def validated_extensions(extension_requests)
145
+ extensions = []
146
+ extension_requests.each do |oid, value|
147
+ begin
148
+ if PRIVATE_EXTENSIONS.include? oid
149
+ @errors << "Cannot specify CSR extension request #{oid}: conflicts with internally used extension request"
150
+ end
151
+ oid = PUPPET_SHORT_NAMES[oid] || oid
152
+ ext = OpenSSL::X509::Extension.new(oid, OpenSSL::ASN1::UTF8String.new(value.to_s).to_der, false)
153
+ extensions << ext
154
+ rescue OpenSSL::X509::ExtensionError => e
155
+ @errors << "Cannot create CSR with extension request #{oid}: #{e.message}"
156
+ end
157
+ end
158
+ extensions
34
159
  end
35
160
  end
36
161
  end
@@ -16,6 +16,8 @@ module Puppetserver
16
16
  SSL_SERVER_CERT = "1.3.6.1.5.5.7.3.1"
17
17
  SSL_CLIENT_CERT = "1.3.6.1.5.5.7.3.2"
18
18
 
19
+ CLI_AUTH_EXT_OID = "1.3.6.1.4.1.34380.1.3.39"
20
+
19
21
  MASTER_EXTENSIONS = [
20
22
  ["basicConstraints", "CA:FALSE", true],
21
23
  ["nsComment", "Puppet Server Internal Certificate", false],
@@ -39,6 +41,10 @@ module Puppetserver
39
41
  @settings = settings
40
42
  end
41
43
 
44
+ def errors
45
+ @host.errors
46
+ end
47
+
42
48
  def valid_until
43
49
  Time.now + @settings[:ca_ttl]
44
50
  end
@@ -62,7 +68,7 @@ module Puppetserver
62
68
 
63
69
  def create_master_cert(ca_key, ca_cert)
64
70
  master_key = @host.create_private_key(@settings[:keylength])
65
- master_csr = @host.create_csr(@settings[:certname], master_key)
71
+ master_csr = @host.create_csr(name: @settings[:certname], key: master_key)
66
72
  master_cert = sign_master_cert(ca_key, ca_cert, master_csr)
67
73
  return master_key, master_cert
68
74
  end
@@ -77,11 +83,28 @@ module Puppetserver
77
83
  cert.not_before = CERT_VALID_FROM
78
84
  cert.not_after = valid_until
79
85
 
86
+ return unless add_custom_extensions(cert)
87
+
80
88
  ef = extension_factory_for(int_cert, cert)
89
+ add_master_extensions(cert, ef)
90
+ add_subject_alt_names_extension(cert, ef)
91
+ cert.sign(int_key, @digest)
92
+
93
+ cert
94
+ end
95
+
96
+ def add_master_extensions(cert, ef)
81
97
  MASTER_EXTENSIONS.each do |ext|
82
98
  extension = ef.create_extension(*ext)
83
99
  cert.add_extension(extension)
84
100
  end
101
+
102
+ # Status API access for the CA CLI
103
+ cli_auth_ext = OpenSSL::X509::Extension.new(CLI_AUTH_EXT_OID, OpenSSL::ASN1::UTF8String.new("true").to_der, false)
104
+ cert.add_extension(cli_auth_ext)
105
+ end
106
+
107
+ def add_subject_alt_names_extension(cert, ef)
85
108
  sans =
86
109
  if @settings[:subject_alt_names].empty?
87
110
  "DNS:puppet, DNS:#{@settings[:certname]}"
@@ -90,9 +113,21 @@ module Puppetserver
90
113
  end
91
114
  alt_names_ext = ef.create_extension("subjectAltName", sans, false)
92
115
  cert.add_extension(alt_names_ext)
116
+ end
93
117
 
94
- cert.sign(int_key, @digest)
95
- cert
118
+ # This takes all the extension requests from csr_attributes.yaml and
119
+ # adds those to the cert
120
+ def add_custom_extensions(cert)
121
+ extension_requests = @host.get_extension_requests(@settings[:csr_attributes])
122
+
123
+ if extension_requests
124
+ extensions = @host.validated_extensions(extension_requests)
125
+ extensions.each do |ext|
126
+ cert.add_extension(ext)
127
+ end
128
+ end
129
+
130
+ @host.errors.empty?
96
131
  end
97
132
 
98
133
  def create_root_cert
@@ -146,7 +181,7 @@ module Puppetserver
146
181
 
147
182
  def create_intermediate_cert(root_key, root_cert)
148
183
  int_key = @host.create_private_key(@settings[:keylength])
149
- int_csr = @host.create_csr(@settings[:ca_name], int_key)
184
+ int_csr = @host.create_csr(name: @settings[:ca_name], key: int_key)
150
185
  int_cert = sign_intermediate(root_key, root_cert, int_csr)
151
186
  int_crl = create_crl_for(int_cert, int_key)
152
187
 
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "0.4.3"
3
+ VERSION = "0.5.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: 0.4.3
4
+ version: 0.5.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: 2018-08-29 00:00:00.000000000 Z
11
+ date: 2018-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: facter