Class: Puppet::SSL::Host
Overview
The class that manages all aspects of our SSL certificates – private keys, public keys, requests, etc.
Constant Summary collapse
- Key =
Yay, ruby’s strange constant lookups.
Puppet::SSL::Key
- CA_NAME =
Puppet::SSL::CA_NAME
- Certificate =
Puppet::SSL::Certificate
- CertificateRequest =
Puppet::SSL::CertificateRequest
Instance Attribute Summary collapse
- #certificate ⇒ Object
-
#certificate_request ⇒ Puppet::SSL::CertificateRequest?
Search for an existing CSR for this host either cached on disk or stored by the CA.
-
#crl_path ⇒ Object
readonly
Returns the value of attribute crl_path.
-
#crl_usage ⇒ Object
writeonly
Sets the attribute crl_usage.
-
#device ⇒ Object
readonly
Returns the value of attribute device.
- #key ⇒ Object
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Class Method Summary collapse
-
.configure_indirection(terminus, cache = nil) ⇒ Object
Configure how our various classes interact with their various terminuses.
- .from_data_hash(data) ⇒ Object
- .localhost ⇒ Object
- .reset ⇒ Object
Instance Method Summary collapse
-
#check_for_certificate_on_disk(cert_name) ⇒ Puppet::SSL::Certificate?
Checks for the requested certificate on disc, at a location determined by this host’s configuration.
-
#clean_params ⇒ Object
The puppet parameters for commands output by the validate_ methods depend upon whether this is an agent or a device.
-
#download_certificate_from_ca(cert_name) ⇒ Puppet::SSL::Certificate?
Attempts to download this host’s certificate from the CA server.
- #download_host_certificate ⇒ Object
-
#generate ⇒ Object
Generate all necessary parts of our ssl host.
-
#generate_certificate_request(options = {}) ⇒ Object
Our certificate request requires the key but that’s all.
-
#generate_key ⇒ Object
This is the private key; we can create it from scratch with no inputs.
- #http_client(ssl_context) ⇒ Object
-
#initialize(name = nil, device = false) ⇒ Host
constructor
A new instance of Host.
-
#public_key ⇒ Object
Extract the public key from the private key.
- #puppet_params ⇒ Object
-
#save_host_certificate(cert) ⇒ Object
Saves the given certificate to disc, at a location determined by this host’s configuration.
-
#ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) ⇒ Object
Create/return a store that uses our SSL info to validate connections.
-
#submit_request ⇒ Puppet::SSL::CertificateRequest?
Generate a keypair, generate a CSR, and submit it.
- #use_crl? ⇒ Boolean
- #use_crl_chain? ⇒ Boolean
-
#validate_certificate_with_key(cert) ⇒ Object
Validate that our private key matches the specified certificate.
-
#wait_for_cert(time) ⇒ Object
Attempt to retrieve a cert, if we don’t already have one.
Constructor Details
#initialize(name = nil, device = false) ⇒ Host
Returns a new instance of Host.
281 282 283 284 285 286 287 288 |
# File 'lib/puppet/ssl/host.rb', line 281 def initialize(name = nil, device = false) @name = (name || Puppet[:certname]).downcase @device = device Puppet::SSL::Base.validate_certname(@name) @key = @certificate = @certificate_request = nil @crl_usage = Puppet.settings[:certificate_revocation] @crl_path = Puppet.settings[:hostcrl] end |
Instance Attribute Details
#certificate ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/puppet/ssl/host.rb', line 131 def certificate unless @certificate generate_key unless key # get CA and optional CRL sm = Puppet::SSL::StateMachine.new sm.ensure_ca_certificates cert = get_host_certificate return nil unless cert validate_certificate_with_key(cert) @certificate = cert end @certificate end |
#certificate_request ⇒ Puppet::SSL::CertificateRequest?
Search for an existing CSR for this host either cached on disk or stored by the CA. Returns nil if no request exists.
192 193 194 195 196 197 198 199 200 201 |
# File 'lib/puppet/ssl/host.rb', line 192 def certificate_request unless @certificate_request if csr = load_certificate_request_from_file @certificate_request = csr elsif csr = download_csr_from_ca @certificate_request = csr end end @certificate_request end |
#crl_path ⇒ Object (readonly)
Returns the value of attribute crl_path.
27 28 29 |
# File 'lib/puppet/ssl/host.rb', line 27 def crl_path @crl_path end |
#crl_usage=(value) ⇒ Object (writeonly)
Sets the attribute crl_usage
29 30 31 |
# File 'lib/puppet/ssl/host.rb', line 29 def crl_usage=(value) @crl_usage = value end |
#device ⇒ Object (readonly)
Returns the value of attribute device.
27 28 29 |
# File 'lib/puppet/ssl/host.rb', line 27 def device @device end |
#key ⇒ Object
78 79 80 |
# File 'lib/puppet/ssl/host.rb', line 78 def key @key ||= Key.indirection.find(name) end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
27 28 29 |
# File 'lib/puppet/ssl/host.rb', line 27 def name @name end |
Class Method Details
.configure_indirection(terminus, cache = nil) ⇒ Object
Configure how our various classes interact with their various terminuses.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/puppet/ssl/host.rb', line 44 def self.configure_indirection(terminus, cache = nil) Certificate.indirection.terminus_class = terminus CertificateRequest.indirection.terminus_class = terminus if cache # This is weird; we don't actually cache our keys, we # use what would otherwise be the cache as our normal # terminus. Key.indirection.terminus_class = cache else Key.indirection.terminus_class = terminus end if cache Certificate.indirection.cache_class = cache CertificateRequest.indirection.cache_class = cache else # Make sure we have no cache configured. puppet master # switches the configurations around a bit, so it's important # that we specify the configs for absolutely everything, every # time. Certificate.indirection.cache_class = nil CertificateRequest.indirection.cache_class = nil end end |
.from_data_hash(data) ⇒ Object
70 71 72 73 74 75 76 |
# File 'lib/puppet/ssl/host.rb', line 70 def self.from_data_hash(data) instance = new(data["name"]) if data["desired_state"] instance.desired_state = data["desired_state"] end instance end |
.localhost ⇒ Object
31 32 33 34 35 36 37 |
# File 'lib/puppet/ssl/host.rb', line 31 def self.localhost return @localhost if @localhost @localhost = new @localhost.generate unless @localhost.certificate @localhost.key @localhost end |
.reset ⇒ Object
39 40 41 |
# File 'lib/puppet/ssl/host.rb', line 39 def self.reset @localhost = nil end |
Instance Method Details
#check_for_certificate_on_disk(cert_name) ⇒ Puppet::SSL::Certificate?
Checks for the requested certificate on disc, at a location determined by this host’s configuration.
467 468 469 470 471 472 473 474 475 476 |
# File 'lib/puppet/ssl/host.rb', line 467 def check_for_certificate_on_disk(cert_name) file_path = certificate_location(cert_name) if Puppet::FileSystem.exist?(file_path) begin Puppet::SSL::Certificate.from_s(Puppet::FileSystem.read(file_path)) rescue OpenSSL::X509::CertificateError raise Puppet::Error, _("The certificate at %{file_path} is invalid. Could not load.") % { file_path: file_path } end end end |
#clean_params ⇒ Object
The puppet parameters for commands output by the validate_ methods depend upon whether this is an agent or a device.
151 152 153 |
# File 'lib/puppet/ssl/host.rb', line 151 def clean_params @device ? "--target #{Puppet[:certname]}" : '' end |
#download_certificate_from_ca(cert_name) ⇒ Puppet::SSL::Certificate?
Attempts to download this host’s certificate from the CA server. Returns nil if the CA does not yet have a signed cert for this host.
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/puppet/ssl/host.rb', line 485 def download_certificate_from_ca(cert_name) begin cert = Puppet::Rest::Routes.get_certificate( cert_name, Puppet::SSL::SSLContext.new(store: ssl_store) ) begin Puppet::SSL::Certificate.from_s(cert) rescue OpenSSL::X509::CertificateError raise Puppet::Error, _("Response from the CA did not contain a valid certificate for %{cert_name}.") % { cert_name: cert_name } end rescue Puppet::Rest::ResponseError => e if e.response.code.to_i == 404 Puppet.debug _("No certificate for %{cert_name} on CA") % { cert_name: cert_name } nil else raise Puppet::Rest::ResponseError, _("Could not download host certificate: %{message}") % { message: e. } end end end |
#download_host_certificate ⇒ Object
180 181 182 183 184 185 186 187 |
# File 'lib/puppet/ssl/host.rb', line 180 def download_host_certificate cert = download_certificate_from_ca(name) return nil unless cert validate_certificate_with_key(cert) save_host_certificate(cert) cert end |
#generate ⇒ Object
Generate all necessary parts of our ssl host.
204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/puppet/ssl/host.rb', line 204 def generate generate_key unless key existing_request = certificate_request # if CSR downloaded from master, but the local keypair was just generated and # does not match the public key in the CSR, fail hard validate_csr_with_key(existing_request, key) if existing_request generate_certificate_request unless existing_request end |
#generate_certificate_request(options = {}) ⇒ Object
Our certificate request requires the key but that’s all.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/puppet/ssl/host.rb', line 97 def generate_certificate_request( = {}) generate_key unless key # If this CSR is for the current machine... if name == Puppet[:certname].downcase # ...add our configured dns_alt_names if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' [:dns_alt_names] ||= Puppet[:dns_alt_names] end end csr_attributes = Puppet::SSL::CertificateRequestAttributes.new(Puppet[:csr_attributes]) if csr_attributes.load [:csr_attributes] = csr_attributes.custom_attributes [:extension_requests] = csr_attributes.extension_requests end @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key.content, ) begin submit_certificate_request(@certificate_request) save_certificate_request(@certificate_request) rescue @certificate_request = nil raise end true end |
#generate_key ⇒ Object
This is the private key; we can create it from scratch with no inputs.
84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/puppet/ssl/host.rb', line 84 def generate_key @key = Key.new(name) @key.generate begin Key.indirection.save(@key) rescue @key = nil raise end true end |
#http_client(ssl_context) ⇒ Object
127 128 129 |
# File 'lib/puppet/ssl/host.rb', line 127 def http_client(ssl_context) Puppet::Rest::Client.new(ssl_context: ssl_context) end |
#public_key ⇒ Object
Extract the public key from the private key.
291 292 293 |
# File 'lib/puppet/ssl/host.rb', line 291 def public_key key.content.public_key end |
#puppet_params ⇒ Object
155 156 157 |
# File 'lib/puppet/ssl/host.rb', line 155 def puppet_params @device ? "device -v --target #{Puppet[:certname]}" : 'agent -t' end |
#save_host_certificate(cert) ⇒ Object
Saves the given certificate to disc, at a location determined by this host’s configuration.
348 349 350 351 352 353 |
# File 'lib/puppet/ssl/host.rb', line 348 def save_host_certificate(cert) file_path = certificate_location(name) Puppet::Util.replace_file(file_path, 0644) do |f| f.write(cert.to_s) end end |
#ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) ⇒ Object
Create/return a store that uses our SSL info to validate connections.
305 306 307 308 309 310 |
# File 'lib/puppet/ssl/host.rb', line 305 def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) if @ssl_store.nil? @ssl_store = build_ssl_store(purpose) end @ssl_store end |
#submit_request ⇒ Puppet::SSL::CertificateRequest?
Generate a keypair, generate a CSR, and submit it. If a local key pair already exists it will be used to generate the CSR. If a local CSR already exists and matches the key then the existing CSR will be submitted. If the CSR and key do not match an exception will be raised.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/puppet/ssl/host.rb', line 222 def submit_request generate_key unless key csr = load_certificate_request_from_file if csr if key.content.public_key.to_s != csr.content.public_key.to_s Puppet.warning("The local CSR does not match the agent's public key. Generating a new CSR.") request_path = certificate_request_location(name) Puppet::FileSystem.unlink(request_path) csr = nil end end if csr validate_csr_with_key(csr, key) submit_certificate_request(csr) @certificate_request = csr else generate_certificate_request end @certificate_request end |
#use_crl? ⇒ Boolean
295 296 297 |
# File 'lib/puppet/ssl/host.rb', line 295 def use_crl? !!@crl_usage end |
#use_crl_chain? ⇒ Boolean
299 300 301 |
# File 'lib/puppet/ssl/host.rb', line 299 def use_crl_chain? @crl_usage == true || @crl_usage == :chain end |
#validate_certificate_with_key(cert) ⇒ Object
Validate that our private key matches the specified certificate.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/puppet/ssl/host.rb', line 163 def validate_certificate_with_key(cert) raise Puppet::Error, _("No certificate to validate.") unless cert raise Puppet::Error, _("No private key with which to validate certificate with fingerprint: %{fingerprint}") % { fingerprint: cert.fingerprint } unless key unless cert.content.check_private_key(key.content) raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: cert.fingerprint, cert_name: Puppet[:certname], clean_params: clean_params, puppet_params: puppet_params } The certificate retrieved from the master does not match the agent's private key. Did you forget to run as root? Certificate fingerprint: %{fingerprint} To fix this, remove the certificate from both the master and the agent and then start a puppet run, which will automatically regenerate a certificate. On the master: puppetserver ca clean --certname %{cert_name} On the agent: 1. puppet ssl clean %{clean_params} 2. puppet %{puppet_params} ERROR_STRING end end |
#wait_for_cert(time) ⇒ Object
Attempt to retrieve a cert, if we don’t already have one.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/puppet/ssl/host.rb', line 313 def wait_for_cert(time) begin return if certificate generate return if certificate rescue StandardError => detail Puppet.log_exception(detail, _("Could not request certificate: %{message}") % { message: detail. }) if time < 1 puts _("Exiting; failed to retrieve certificate and waitforcert is disabled") exit(1) else sleep(time) end retry end if time < 1 puts _("Exiting; no certificate found and waitforcert is disabled") exit(1) end while true sleep time begin break if certificate Puppet.notice _("Did not receive certificate") rescue StandardError => detail Puppet.log_exception(detail, _("Could not request certificate: %{message}") % { message: detail. }) end end end |