Class: MCollective::Util::Choria
- Inherits:
-
Object
- Object
- MCollective::Util::Choria
- Defined in:
- lib/mcollective/util/choria.rb
Defined Under Namespace
Constant Summary collapse
- VERSION =
"0.19.0".freeze
Instance Attribute Summary collapse
-
#ca ⇒ Object
writeonly
Sets the attribute ca.
Instance Method Summary collapse
-
#anon_tls? ⇒ Boolean
Determines if Choria is configured for anonymous TLS mode.
-
#ca_path ⇒ String
The path to the CA.
-
#callerid ⇒ String
The callerid for the current client.
-
#certname ⇒ String
The certname of the current context.
-
#check_ssl_setup(log = true) ⇒ Boolean
Checks all the required SSL files exist.
-
#client_private_key ⇒ String
The path to a client private key.
-
#client_public_cert ⇒ String
The path to a client public certificate.
-
#credential_file ⇒ String
Determines the configured path to the NATS credentials, empty when not set.
-
#credential_file? ⇒ Boolean
Determines if a credential file is configured.
-
#csr_path ⇒ String
The path to a CSR for this user.
-
#discovery_server ⇒ Hash
Looks for discovery proxy servers.
- #env_fetch(key, default = nil) ⇒ Object
-
#expand_path(path) ⇒ String
Expands full paths with special handling for empty string.
-
#facter_cmd ⇒ String?
Searches the machine for a working facter.
-
#facter_domain ⇒ String?
Retrieves the domain from facter networking.domain if facter is found.
-
#federated? ⇒ Boolean
Determines if there are any federations configured.
-
#federation_collectives ⇒ Array<String>
List of active collectives that form the federation.
-
#federation_middleware_servers ⇒ Array?
Looks for federation middleware servers when federated.
-
#file_security? ⇒ Boolean
Determines if the file security provider is enabled.
-
#get_option(opt, default = :_unset) ⇒ Object, Proc
Gets a config option.
-
#has_ca? ⇒ Boolean
Determines if the CA exist.
-
#has_client_private_key? ⇒ Boolean
Determines if the client_private_key exist.
-
#has_client_public_cert? ⇒ Boolean
Determines if teh client_public_cert exist.
-
#has_csr? ⇒ Boolean
Determines if the CSR exist.
-
#has_option?(opt) ⇒ Boolean
Determines if a config option is set.
-
#have_ssl_files?(log = true) ⇒ Boolean
Checks if all the required SSL files exist.
-
#http_get(path, headers = nil) ⇒ Net::HTTP::Get
Creates a Net::HTTP::Get instance for a path that defaults to accepting JSON.
-
#http_post(path, headers = nil) ⇒ Net::HTTP::Post
Creates a Net::HTTP::Post instance for a path that defaults to accepting JSON.
-
#https(server, force_puppet_ssl = false) ⇒ Net::HTTP
Create a Net::HTTP instance optionally set up with the Puppet certs.
-
#initialize(check_ssl = true) ⇒ Choria
constructor
A new instance of Choria.
-
#middleware_servers(default_host = "puppet", default_port = "4222") ⇒ Array<Array<String, String>>
Finds the middleware hosts in config or DNS.
-
#ngs? ⇒ Boolean
Determines if we are connecting to NGS based on credentials and the nats.ngs setting.
-
#nkeys? ⇒ Boolean
Attempts to load the optional nkeys library.
-
#parse_pubcert(pubcert, log = true) ⇒ Array<OpenSSL::X509::Certificate,nil>
Parses a public cert.
-
#pql_extract_certnames(results) ⇒ Array<String>
Extract certnames from PQL results, deactivated nodes are ignored.
-
#pql_query(query, only_certnames = false) ⇒ Array
Performs a PQL query against the configured PuppetDB.
-
#proxied_discovery? ⇒ Boolean
Determines if this is using a discovery proxy.
-
#proxy_discovery_query(query) ⇒ Array
Does a proxied discovery request.
-
#puppet_security? ⇒ Boolean
Determines if the puppet security provider is enabled.
-
#puppet_server ⇒ Hash
The Puppet server to connect to.
-
#puppet_setting(setting) ⇒ String
Initialises Puppet if needed and retrieve a config setting.
-
#puppetca_server ⇒ Hash
The Puppet server to connect to.
-
#puppetdb_server ⇒ Hash
The PuppetDB server to connect to.
-
#query_srv_records(records) {|Hash| ... } ⇒ Array<Hash>
Query DNS for a series of records.
-
#randomize_middleware_servers? ⇒ Boolean
Determines if servers should be randomized.
-
#remote_signer_configured? ⇒ Boolean
Determines if a remote signer is configured.
-
#resolver ⇒ Resolv::DNS
Retrieves a DNS resolver.
-
#security_provider ⇒ Object
Determines the security provider.
-
#server_resolver(config_option, srv_records, default_host = nil, default_port = nil) ⇒ Array?
Resolves server lists based on config and SRV records.
-
#should_use_srv? ⇒ Boolean
Determines if SRV records should be used.
-
#sort_srv_answers(answers) ⇒ Array<Resolv::DNS::Resource::IN::SRV>
Sorts SRV records according to rfc2782.
-
#srv_domain ⇒ String
Determines the domain to do SRV lookups in.
-
#srv_records(keys) ⇒ Array<String>
Determines the SRV records to look up.
-
#ssl_context ⇒ OpenSSL::SSL::SSLContext
Creates a SSL Context which includes the AIO SSL files.
-
#ssl_dir ⇒ String
The directory where SSL related files live.
-
#ssl_parse_chain(pemdata) ⇒ Array<OpenSSL::X509::Certificate,nil>
Split a string containing chained certificates into an Array of OpenSSL::X509::Certificate.
-
#ssl_split_pem(pemdata) ⇒ Array<String,nil>
Utility function to split a chained certificate String into an Array.
-
#tasks_cache_dir ⇒ String
Determines the Tasks Cache dir.
-
#tasks_spool_dir ⇒ String
Determines the Tasks Spool directory.
-
#tasks_support ⇒ TasksSupport
Creates a new TasksSupport instance with the configured cache dir.
-
#try_srv(names, default_target, default_port) ⇒ Hash
Attempts to look up some SRV records falling back to defaults.
-
#valid_certificate?(pubcert, name, log = true) ⇒ String, false
Validates a certificate against the CA.
-
#which(command) ⇒ String?
Searches the PATH for an executable command.
Constructor Details
Instance Attribute Details
#ca=(value) ⇒ Object (writeonly)
Sets the attribute ca
15 16 17 |
# File 'lib/mcollective/util/choria.rb', line 15 def ca=(value) @ca = value end |
Instance Method Details
#anon_tls? ⇒ Boolean
Determines if Choria is configured for anonymous TLS mode
865 866 867 |
# File 'lib/mcollective/util/choria.rb', line 865 def anon_tls? remote_signer_configured? && Util.str_to_bool(get_option("security.client_anon_tls", "false")) end |
#ca_path ⇒ String
The path to the CA
856 857 858 859 860 |
# File 'lib/mcollective/util/choria.rb', line 856 def ca_path return (get_option("security.file.ca", "")) if file_security? File.join(ssl_dir, "certs", "ca.pem") end |
#callerid ⇒ String
The callerid for the current client
460 461 462 |
# File 'lib/mcollective/util/choria.rb', line 460 def callerid PluginManager["security_plugin"].callerid end |
#certname ⇒ String
The certname of the current context
In the case of root that would be the configured ‘identity` for non root it would a string made up of the current username as determined by the USER environment variable or the configured `identity`
At present windows clients are probably not supported automatically as they will default to the certificate based on identity. Same as root. Windows will have to rely on the environment override until we can figure out what the best behaviour is
In all cases the certname can be overridden using the ‘MCOLLECTIVE_CERTNAME` environment variable
707 708 709 710 711 712 713 714 715 |
# File 'lib/mcollective/util/choria.rb', line 707 def certname if Process.uid == 0 || Util.windows? certname = @config.identity else certname = "%s.mcollective" % [env_fetch("USER", @config.identity)] end env_fetch("MCOLLECTIVE_CERTNAME", certname) end |
#check_ssl_setup(log = true) ⇒ Boolean
Checks all the required SSL files exist
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
# File 'lib/mcollective/util/choria.rb', line 469 def check_ssl_setup(log=true) return true if $choria_unsafe_disable_protocol_security # rubocop:disable Style/GlobalVars return true if anon_tls? raise(UserError, "The Choria client cannot be run as root") if Process.uid == 0 && PluginManager["security_plugin"].initiated_by == :client raise(UserError, "Not all required SSL files exist") unless have_ssl_files?(log) = nil begin = valid_certificate?(File.read(client_public_cert), certname) rescue raise(UserError, "The public certificate was not signed by the configured CA") end unless == certname raise(UserError, "The certname %s found in %s does not match the configured certname of %s" % [, client_public_cert, certname]) end true end |
#client_private_key ⇒ String
paths determined by Puppet AIO packages
The path to a client private key
840 841 842 843 844 |
# File 'lib/mcollective/util/choria.rb', line 840 def client_private_key return (get_option("security.file.key", "")) if file_security? File.join(ssl_dir, "private_keys", "%s.pem" % certname) end |
#client_public_cert ⇒ String
paths determined by Puppet AIO packages
The path to a client public certificate
823 824 825 826 827 |
# File 'lib/mcollective/util/choria.rb', line 823 def client_public_cert return (get_option("security.file.certificate", "")) if file_security? File.join(ssl_dir, "certs", "%s.pem" % certname) end |
#credential_file ⇒ String
Determines the configured path to the NATS credentials, empty when not set
26 27 28 |
# File 'lib/mcollective/util/choria.rb', line 26 def credential_file get_option("nats.credentials", "") end |
#credential_file? ⇒ Boolean
Determines if a credential file is configured
33 34 35 |
# File 'lib/mcollective/util/choria.rb', line 33 def credential_file? credential_file != "" end |
#csr_path ⇒ String
The path to a CSR for this user
879 880 881 882 883 |
# File 'lib/mcollective/util/choria.rb', line 879 def csr_path return "" if file_security? File.join(ssl_dir, "certificate_requests", "%s.pem" % certname) end |
#discovery_server ⇒ Hash
Looks for discovery proxy servers
Attempts to find servers in the following order:
* If choria.discovery_proxy is set to false, returns nil
* Configured hosts in choria.discovery_proxies
* SRV lookups in _mcollective-discovery._tcp
675 676 677 678 679 680 681 682 |
# File 'lib/mcollective/util/choria.rb', line 675 def discovery_server return unless proxied_discovery? d_host = get_option("choria.discovery_host", "puppet") d_port = get_option("choria.discovery_port", "8085") try_srv(["_mcollective-discovery._tcp"], d_host, d_port) end |
#env_fetch(key, default = nil) ⇒ Object
950 951 952 |
# File 'lib/mcollective/util/choria.rb', line 950 def env_fetch(key, default=nil) ENV.fetch(key, default) end |
#expand_path(path) ⇒ String
Expands full paths with special handling for empty string
File.expand_path will expand ‘“”` to cwd, this is not good for what we need in many cases so this returns `“”` in that case
813 814 815 816 817 |
# File 'lib/mcollective/util/choria.rb', line 813 def (path) return "" if path == "" File.(path) end |
#facter_cmd ⇒ String?
Searches the machine for a working facter
It checks AIO path first and then attempts to find it in PATH and supports both unix and windows
916 917 918 919 920 |
# File 'lib/mcollective/util/choria.rb', line 916 def facter_cmd return "/opt/puppetlabs/bin/facter" if File.executable?("/opt/puppetlabs/bin/facter") which("facter") end |
#facter_domain ⇒ String?
Retrieves the domain from facter networking.domain if facter is found
Potentially we could use the local facts in mcollective but that’s a chicken and egg and sometimes its only set after initial connection if something like a cron job generates the yaml cache file
124 125 126 127 128 |
# File 'lib/mcollective/util/choria.rb', line 124 def facter_domain if path = facter_cmd `"#{path}" networking.domain`.chomp end end |
#federated? ⇒ Boolean
Determines if there are any federations configured
92 93 94 |
# File 'lib/mcollective/util/choria.rb', line 92 def federated? !federation_collectives.empty? end |
#federation_collectives ⇒ Array<String>
List of active collectives that form the federation
99 100 101 102 103 104 105 106 107 |
# File 'lib/mcollective/util/choria.rb', line 99 def federation_collectives if !@config.federations.empty? @config.federations elsif (override_networks = env_fetch("CHORIA_FED_COLLECTIVE", nil)) override_networks.split(",").map(&:strip).reject(&:empty?) else get_option("choria.federation.collectives", "").split(",").map(&:strip).reject(&:empty?) end end |
#federation_middleware_servers ⇒ Array?
you’d still want to only get your middleware servers from #middleware_servers
Looks for federation middleware servers when federated
Attempts to find servers in the following order:
* Configured hosts in choria.federation_middleware_hosts
* SRV lookups in _mcollective-federation_server._tcp and _x-puppet-mcollective_federation._tcp
563 564 565 |
# File 'lib/mcollective/util/choria.rb', line 563 def federation_middleware_servers server_resolver("choria.federation_middleware_hosts", ["_mcollective-federation_server._tcp", "_x-puppet-mcollective_federation._tcp"]) end |
#file_security? ⇒ Boolean
Determines if the file security provider is enabled
797 798 799 |
# File 'lib/mcollective/util/choria.rb', line 797 def file_security? security_provider == "file" end |
#get_option(opt, default = :_unset) ⇒ Object, Proc
Gets a config option
928 929 930 931 932 933 934 935 936 937 938 939 940 |
# File 'lib/mcollective/util/choria.rb', line 928 def get_option(opt, default=:_unset) return @config.pluginconf[opt] if has_option?(opt) unless default == :_unset if default.is_a?(Proc) return default.call else return default end end raise(UserError, "No plugin.%s configuration option given" % opt) end |
#has_ca? ⇒ Boolean
Determines if the CA exist
872 873 874 |
# File 'lib/mcollective/util/choria.rb', line 872 def has_ca? File.exist?(ca_path) end |
#has_client_private_key? ⇒ Boolean
Determines if the client_private_key exist
849 850 851 |
# File 'lib/mcollective/util/choria.rb', line 849 def has_client_private_key? File.exist?(client_private_key) end |
#has_client_public_cert? ⇒ Boolean
Determines if teh client_public_cert exist
832 833 834 |
# File 'lib/mcollective/util/choria.rb', line 832 def has_client_public_cert? File.exist?(client_public_cert) end |
#has_csr? ⇒ Boolean
Determines if the CSR exist
888 889 890 |
# File 'lib/mcollective/util/choria.rb', line 888 def has_csr? File.exist?(csr_path) end |
#has_option?(opt) ⇒ Boolean
Determines if a config option is set
946 947 948 |
# File 'lib/mcollective/util/choria.rb', line 946 def has_option?(opt) @config.pluginconf.include?(opt) end |
#have_ssl_files?(log = true) ⇒ Boolean
Checks if all the required SSL files exist
357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/mcollective/util/choria.rb', line 357 def have_ssl_files?(log=true) [client_public_cert, client_private_key, ca_path].map do |path| Log.debug("Checking for SSL file %s" % path) if File.exist?(path) true else Log.warn("Cannot find SSL file %s" % path) if log false end end.all? end |
#http_get(path, headers = nil) ⇒ Net::HTTP::Get
Creates a Net::HTTP::Get instance for a path that defaults to accepting JSON
279 280 281 282 283 284 285 286 287 |
# File 'lib/mcollective/util/choria.rb', line 279 def http_get(path, headers=nil) headers ||= {} headers = { "Accept" => "application/json", "User-Agent" => "Choria version %s http://choria.io" % VERSION }.merge(headers) Net::HTTP::Get.new(path, headers) end |
#http_post(path, headers = nil) ⇒ Net::HTTP::Post
Creates a Net::HTTP::Post instance for a path that defaults to accepting JSON
293 294 295 296 297 298 299 300 301 |
# File 'lib/mcollective/util/choria.rb', line 293 def http_post(path, headers=nil) headers ||= {} headers = { "Accept" => "application/json", "User-Agent" => "Choria version %s http://choria.io" % VERSION }.merge(headers) Net::HTTP::Post.new(path, headers) end |
#https(server, force_puppet_ssl = false) ⇒ Net::HTTP
Create a Net::HTTP instance optionally set up with the Puppet certs
If the client_private_key and client_public_cert both exist they will be used to validate the connection
If the ca_path exist it will be used and full verification will be enabled
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/mcollective/util/choria.rb', line 251 def https(server, force_puppet_ssl=false) Log.debug("Creating new HTTPS connection to %s:%s" % [server[:target], server[:port]]) check_ssl_setup if force_puppet_ssl http = Net::HTTP.new(server[:target], server[:port]) http.use_ssl = true if has_client_private_key? && has_client_public_cert? http.cert = OpenSSL::X509::Certificate.new(File.read(client_public_cert)) http.key = OpenSSL::PKey::RSA.new(File.read(client_private_key)) end if has_ca? http.ca_file = ca_path http.verify_mode = OpenSSL::SSL::VERIFY_PEER else http.verify_mode = OpenSSL::SSL::VERIFY_NONE end http end |
#middleware_servers(default_host = "puppet", default_port = "4222") ⇒ Array<Array<String, String>>
Finds the middleware hosts in config or DNS
Attempts to find servers in the following order:
* connects.ngs.global if configured to be ngs and empty choria.middleware_hosts
* Any federation servers if in a federation
* Configured hosts in choria.middleware_hosts
* SRV lookups in _mcollective-server._tcp and _x-puppet-mcollective._tcp
* Supplied defaults
Eventually it’s intended that other middleware might be supported this would provide a single way to configure them all
544 545 546 547 548 549 550 551 552 |
# File 'lib/mcollective/util/choria.rb', line 544 def middleware_servers(default_host="puppet", default_port="4222") return [["connect.ngs.global", "4222"]] if ngs? && !has_option?("choria.middleware_hosts") if federated? && federation = federation_middleware_servers return federation end server_resolver("choria.middleware_hosts", ["_mcollective-server._tcp", "_x-puppet-mcollective._tcp"], default_host, default_port) end |
#ngs? ⇒ Boolean
Determines if we are connecting to NGS based on credentials and the nats.ngs setting
40 41 42 |
# File 'lib/mcollective/util/choria.rb', line 40 def ngs? credential_file != "" && Util.str_to_bool(get_option("nats.ngs", "false")) end |
#nkeys? ⇒ Boolean
Attempts to load the optional nkeys library
47 48 49 50 51 52 |
# File 'lib/mcollective/util/choria.rb', line 47 def nkeys? require "nkeys" true rescue LoadError false end |
#parse_pubcert(pubcert, log = true) ⇒ Array<OpenSSL::X509::Certificate,nil>
Parses a public cert
449 450 451 452 453 454 |
# File 'lib/mcollective/util/choria.rb', line 449 def parse_pubcert(pubcert, log=true) ssl_parse_chain(pubcert) rescue OpenSSL::X509::CertificateError Log.warn("Received certificate is not a valid x509 certificate: %s: %s" % [$!.class, $!.to_s]) if log nil end |
#pql_extract_certnames(results) ⇒ Array<String>
Extract certnames from PQL results, deactivated nodes are ignored
327 328 329 |
# File 'lib/mcollective/util/choria.rb', line 327 def pql_extract_certnames(results) results.reject {|n| n["deactivated"]}.map {|n| n["certname"]}.compact end |
#pql_query(query, only_certnames = false) ⇒ Array
Performs a PQL query against the configured PuppetDB
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/mcollective/util/choria.rb', line 337 def pql_query(query, only_certnames=false) Log.debug("Performing PQL query: %s" % query) path = "/pdb/query/v4?%s" % URI.encode_www_form("query" => query) resp, data = https(puppetdb_server, true).request(http_get(path)) raise("Failed to make request to PuppetDB: %s: %s: %s" % [resp.code, resp., resp.body]) unless resp.code == "200" result = JSON.parse(data || resp.body) Log.debug("Found %d records for query %s" % [result.size, query]) only_certnames ? pql_extract_certnames(result) : result end |
#proxied_discovery? ⇒ Boolean
Determines if this is using a discovery proxy
687 688 689 |
# File 'lib/mcollective/util/choria.rb', line 687 def proxied_discovery? has_option?("choria.discovery_host") || has_option?("choria.discovery_port") || Util.str_to_bool(get_option("choria.discovery_proxy", "false")) end |
#proxy_discovery_query(query) ⇒ Array
Does a proxied discovery request
308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/mcollective/util/choria.rb', line 308 def proxy_discovery_query(query) transport = https(discovery_server, true) request = http_get("/v1/discover") request.body = query.to_json request["Content-Type"] = "application/json" resp, data = transport.request(request) raise("Failed to make request to Discovery Proxy: %s: %s" % [resp.code, resp.body]) unless resp.code == "200" result = JSON.parse(data || resp.body) result["nodes"] end |
#puppet_security? ⇒ Boolean
Determines if the puppet security provider is enabled
802 803 804 |
# File 'lib/mcollective/util/choria.rb', line 802 def puppet_security? security_provider == "puppet" end |
#puppet_server ⇒ Hash
The Puppet server to connect to
Will consult SRV records for _x-puppet._tcp.example.net first then configurable using choria.puppetserver_host and choria.puppetserver_port defaults to puppet:8140.
614 615 616 617 618 619 |
# File 'lib/mcollective/util/choria.rb', line 614 def puppet_server d_host = get_option("choria.puppetserver_host", "puppet") d_port = get_option("choria.puppetserver_port", "8140") try_srv(["_x-puppet._tcp"], d_host, d_port) end |
#puppet_setting(setting) ⇒ String
Initialises Puppet if needed and retrieve a config setting
721 722 723 724 725 726 727 728 729 730 731 732 733 |
# File 'lib/mcollective/util/choria.rb', line 721 def puppet_setting(setting) require "puppet" unless Puppet.settings.app_defaults_initialized? Puppet.settings.preferred_run_mode = :agent Puppet.settings.initialize_global_settings([]) Puppet.settings.initialize_app_defaults(Puppet::Settings.app_defaults_for_run_mode(Puppet.run_mode)) Puppet.push_context(Puppet.base_context(Puppet.settings)) end Puppet.settings[setting] end |
#puppetca_server ⇒ Hash
The Puppet server to connect to
Will consult _x-puppet-ca._tcp.example.net then _x-puppet._tcp.example.net then configurable using choria.puppetca_host, defaults to puppet:8140
627 628 629 630 631 632 633 634 635 636 |
# File 'lib/mcollective/util/choria.rb', line 627 def puppetca_server d_port = get_option("choria.puppetca_port", "8140") if @ca {:target => @ca, :port => d_port} else d_host = get_option("choria.puppetca_host", "puppet") try_srv(["_x-puppet-ca._tcp", "_x-puppet._tcp"], d_host, d_port) end end |
#puppetdb_server ⇒ Hash
The PuppetDB server to connect to
Use choria.puppetdb_host if set, otherwise query _x-puppet-db._tcp.example.net then _x-puppet._tcp.example.net if SRV lookup is enabled, and fallback to puppet:8081 if nothing else worked.
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 |
# File 'lib/mcollective/util/choria.rb', line 645 def puppetdb_server d_port = get_option("choria.puppetdb_port", "8081") answer = { :target => get_option("choria.puppetdb_host", nil), :port => d_port } return answer if answer[:target] answer = try_srv(["_x-puppet-db._tcp"], nil, nil) return answer if answer[:target] # In the case where we take _x-puppet._tcp SRV records we unfortunately have # to force the port else it uses the one from Puppet which will 404 answer = try_srv(["_x-puppet._tcp"], "puppet", d_port) answer[:port] = d_port answer end |
#query_srv_records(records) {|Hash| ... } ⇒ Array<Hash>
Query DNS for a series of records
The given records will be passed through #srv_records to figure out the domain to query in.
Querying of records can be bypassed by setting choria.use_srv to false
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/mcollective/util/choria.rb', line 180 def query_srv_records(records) unless should_use_srv? Log.info("Skipping SRV record queries due to choria.query_srv_records setting") return [] end answers = Array(srv_records(records)).map do |record| Log.debug("Attempting to resolve SRV record %s" % record) answers = resolver.getresources(record, Resolv::DNS::Resource::IN::SRV) Log.debug("Found %d SRV records for %s" % [answers.size, record]) answers end.flatten answers = answers.sort_by(&:priority).chunk(&:priority).sort answers = sort_srv_answers(answers) answers.map do |result| Log.debug("Found %s:%s with priority %s and weight %s" % [result.target, result.port, result.priority, result.weight]) ans = { :port => result.port, :priority => result.priority, :weight => result.weight, :target => result.target } yield(ans) if block_given? ans end end |
#randomize_middleware_servers? ⇒ Boolean
Determines if servers should be randomized
570 571 572 |
# File 'lib/mcollective/util/choria.rb', line 570 def randomize_middleware_servers? Util.str_to_bool(get_option("choria.randomize_middleware_hosts", "true")) end |
#remote_signer_configured? ⇒ Boolean
Determines if a remote signer is configured
416 417 418 419 420 |
# File 'lib/mcollective/util/choria.rb', line 416 def remote_signer_configured? url = get_option("choria.security.request_signer.url", nil) ![nil, ""].include?(url) end |
#resolver ⇒ Resolv::DNS
mainly used for testing
Retrieves a DNS resolver
113 114 115 |
# File 'lib/mcollective/util/choria.rb', line 113 def resolver Resolv::DNS.new end |
#security_provider ⇒ Object
Determines the security provider
792 793 794 |
# File 'lib/mcollective/util/choria.rb', line 792 def security_provider get_option("security.provider", "puppet") end |
#server_resolver(config_option, srv_records, default_host = nil, default_port = nil) ⇒ Array?
Resolves server lists based on config and SRV records
Attempts to find server in the following order:
* Configured hosts in `config_option`
* SRV lookups of `srv_records`
* Defaults
* nil otherwise
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'lib/mcollective/util/choria.rb', line 506 def server_resolver(config_option, srv_records, default_host=nil, default_port=nil) if servers = get_option(config_option, nil) hosts = servers.split(",").map do |server| server.split(":").map(&:strip) end return hosts end srv_answers = query_srv_records(srv_records) unless srv_answers.empty? hosts = srv_answers.map do |answer| [answer[:target], answer[:port]] end return hosts end [[default_host, default_port]] if default_host && default_port end |
#should_use_srv? ⇒ Boolean
Determines if SRV records should be used
Setting choria.use_srv to anything other than t, true, yes or 1 will disable SRV records
167 168 169 |
# File 'lib/mcollective/util/choria.rb', line 167 def should_use_srv? ["t", "true", "yes", "1"].include?(get_option("choria.use_srv", "1").downcase) end |
#sort_srv_answers(answers) ⇒ Array<Resolv::DNS::Resource::IN::SRV>
this is probably still not correct :( so horrible
Sorts SRV records according to rfc2782
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/mcollective/util/choria.rb', line 217 def sort_srv_answers(answers) sorted_answers = [] # this is roughly based on the resolv-srv and supposedly mostly rfc2782 compliant answers.each do |_, available| total_weight = available.inject(0) {|a, e| a + e.weight + 1 } until available.empty? selector = Integer(rand * total_weight) + 1 selected_idx = available.find_index do |e| selector -= e.weight + 1 selector <= 0 end selected = available.delete_at(selected_idx) total_weight -= selected.weight + 1 sorted_answers << selected end end sorted_answers end |
#srv_domain ⇒ String
Determines the domain to do SRV lookups in
This is settable using the environment variable CHORIA_SRV_DOMAIN or choria.srv_domain and defaults to the domain as reported by facter
137 138 139 |
# File 'lib/mcollective/util/choria.rb', line 137 def srv_domain env_fetch("CHORIA_SRV_DOMAIN", nil) || get_option("choria.srv_domain", nil) || facter_domain end |
#srv_records(keys) ⇒ Array<String>
Determines the SRV records to look up
If an option choria.srv_domain is set that will be used else facter will be consulted, if neither of those provide a domain name a empty list is returned
148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/mcollective/util/choria.rb', line 148 def srv_records(keys) domain = srv_domain if domain.nil? || domain.empty? Log.warn("Cannot look up SRV records, facter is not functional and choria.srv_domain was not supplied") return [] end keys.map do |key| "%s.%s" % [key, domain] end end |
#ssl_context ⇒ OpenSSL::SSL::SSLContext
Creates a SSL Context which includes the AIO SSL files
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 |
# File 'lib/mcollective/util/choria.rb', line 738 def ssl_context context = OpenSSL::SSL::SSLContext.new context.ca_file = ca_path context.ssl_version = :TLSv1_2 # rubocop:disable Naming/VariableNumber if anon_tls? context.verify_mode = OpenSSL::SSL::VERIFY_NONE return context end public_cert = File.read(client_public_cert) private_key = File.read(client_private_key) cert_chain = ssl_parse_chain(public_cert) cert = cert_chain.first key = OpenSSL::PKey::RSA.new(private_key) extra_chain_cert = cert_chain[1..-1] if OpenSSL::SSL::SSLContext.method_defined?(:add_certificate) context.add_certificate(cert, key, extra_chain_cert) else context.cert = OpenSSL::X509::Certificate.new(File.read(client_public_cert)) context.key = OpenSSL::PKey::RSA.new(File.read(client_private_key)) context.extra_chain_cert = extra_chain_cert end context.verify_mode = OpenSSL::SSL::VERIFY_PEER context end |
#ssl_dir ⇒ String
The directory where SSL related files live
This is configurable using choria.ssldir which should be a path expandable using File.expand_path
On Windows or when running as root Puppet settings will be consulted but when running as a normal user it will default to the AIO path when not configured
781 782 783 784 785 786 787 788 789 |
# File 'lib/mcollective/util/choria.rb', line 781 def ssl_dir @_ssl_dir ||= if has_option?("choria.ssldir") File.(get_option("choria.ssldir")) elsif Util.windows? || Process.uid == 0 puppet_setting(:ssldir) else File.("~/.puppetlabs/etc/puppet/ssl") end end |
#ssl_parse_chain(pemdata) ⇒ Array<OpenSSL::X509::Certificate,nil>
Split a string containing chained certificates into an Array of OpenSSL::X509::Certificate.
438 439 440 441 442 |
# File 'lib/mcollective/util/choria.rb', line 438 def ssl_parse_chain(pemdata) ssl_split_pem(pemdata).map do |cpem| OpenSSL::X509::Certificate.new(cpem) end end |
#ssl_split_pem(pemdata) ⇒ Array<String,nil>
Utility function to split a chained certificate String into an Array
426 427 428 429 430 431 432 |
# File 'lib/mcollective/util/choria.rb', line 426 def ssl_split_pem(pemdata) # Chained certificates typically have the public certificate, along # with every intermediate certificiate. # OpenSSL will stop at the first certificate when using OpenSSL::X509::Certificate.new, # so we need to separate them into a list pemdata.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m) end |
#tasks_cache_dir ⇒ String
Determines the Tasks Cache dir
66 67 68 69 70 71 72 73 74 |
# File 'lib/mcollective/util/choria.rb', line 66 def tasks_cache_dir if Util.windows? File.join(Util.windows_prefix, "tasks-cache") elsif Process.uid == 0 "/opt/puppetlabs/mcollective/tasks-cache" else File.("~/.puppetlabs/mcollective/tasks-cache") end end |
#tasks_spool_dir ⇒ String
Determines the Tasks Spool directory
79 80 81 82 83 84 85 86 87 |
# File 'lib/mcollective/util/choria.rb', line 79 def tasks_spool_dir if Util.windows? File.join(Util.windows_prefix, "tasks-spool") elsif Process.uid == 0 "/opt/puppetlabs/mcollective/tasks-spool" else File.("~/.puppetlabs/mcollective/tasks-spool") end end |
#tasks_support ⇒ TasksSupport
Creates a new TasksSupport instance with the configured cache dir
57 58 59 60 61 |
# File 'lib/mcollective/util/choria.rb', line 57 def tasks_support require_relative "tasks_support" Util::TasksSupport.new(self, tasks_cache_dir) end |
#try_srv(names, default_target, default_port) ⇒ Hash
Attempts to look up some SRV records falling back to defaults
When given a array of multiple names it will try each name individually and check if it resolved to a answer, if it did it will use that one. Else it will move to the next. In this way you can prioritise one record over another like puppetdb over puppet and faill back to defaults.
This is a pretty naive implementation that right now just returns the first result, the correct behaviour needs to be determined but for now this gets us going with easily iterable code.
These names are mainly being used by #https so in theory it would be quite easy to support multiple results with fall back etc, but I am not really sure what would be the best behaviour here
593 594 595 596 597 598 599 600 601 602 603 604 605 |
# File 'lib/mcollective/util/choria.rb', line 593 def try_srv(names, default_target, default_port) srv_answers = Array(names).map do |name| answer = query_srv_records([name]) answer.empty? ? nil : answer end.compact.flatten if srv_answers.empty? {:target => default_target, :port => default_port} else {:target => srv_answers[0][:target].to_s, :port => srv_answers[0][:port]} end end |
#valid_certificate?(pubcert, name, log = true) ⇒ String, false
Validates a certificate against the CA
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/mcollective/util/choria.rb', line 378 def valid_certificate?(pubcert, name, log=true) return false unless name raise("Cannot find or read the CA in %s, cannot verify public certificate" % ca_path) unless File.readable?(ca_path) certs = parse_pubcert(pubcert, log) return false if certs.empty? incoming = certs.first chain = certs[1..-1] begin ca = OpenSSL::X509::Store.new.add_file(ca_path) rescue OpenSSL::X509::StoreError Log.warn("Failed to load CA from %s: %s: %s" % [ca_path, $!.class, $!.to_s]) if log raise end unless ca.verify(incoming, chain) Log.warn("Failed to verify certificate %s against CA %s in %s" % [incoming.subject.to_s, incoming.issuer.to_s, ca_path]) if log return false end Log.debug("Verified certificate %s against CA %s" % [incoming.subject.to_s, incoming.issuer.to_s]) if log if !remote_signer_configured? && !OpenSSL::SSL.verify_certificate_identity(incoming, name) raise("Could not parse certificate with subject %s as it has no CN part, or name %s invalid" % [incoming.subject.to_s, name]) end name end |
#which(command) ⇒ String?
Searches the PATH for an executable command
896 897 898 899 900 901 902 903 904 905 906 907 908 |
# File 'lib/mcollective/util/choria.rb', line 896 def which(command) exts = Array(env_fetch("PATHEXT", "").split(";")) exts << "" if exts.empty? env_fetch("PATH", "").split(File::PATH_SEPARATOR).each do |path| exts.each do |ext| exe = File.join(path, "%s%s" % [command, ext]) return exe if File.executable?(exe) && !File.directory?(exe) end end nil end |