From 8c207687e8dfa72f42f25cac7f46b99f895e4f57 Mon Sep 17 00:00:00 2001 From: elijah Date: Sat, 9 Jul 2016 02:47:55 -0700 Subject: refactor the command for ca and node --- lib/leap_cli/config/node.rb | 176 +++++++++++++++++++++++++++++++++++++-- lib/leap_cli/config/node_cert.rb | 124 +++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 lib/leap_cli/config/node_cert.rb (limited to 'lib/leap_cli/config') diff --git a/lib/leap_cli/config/node.rb b/lib/leap_cli/config/node.rb index f8ec0527..2d76b814 100644 --- a/lib/leap_cli/config/node.rb +++ b/lib/leap_cli/config/node.rb @@ -9,8 +9,11 @@ module LeapCli; module Config class Node < Object attr_accessor :file_paths - def initialize(environment=nil) + def initialize(environment=nil) #, name=nil) super(environment) + #if name + # self['name'] = name + #end @node = self @file_paths = [] end @@ -19,18 +22,20 @@ module LeapCli; module Config # returns true if this node has an ip address in the range of the vagrant network # def vagrant? + ip = self['ip_address'] + return false unless ip begin vagrant_range = IPAddr.new LeapCli.leapfile.vagrant_network - rescue ArgumentError => exc - Util::bail! { Util::log :invalid, "ip address '#{@node.ip_address}' vagrant.network" } + rescue ArgumentError + Util::bail! { Util::log :invalid, "vagrant_network in Leapfile or .leaprc" } end begin - ip_address = IPAddr.new @node.get('ip_address') - rescue ArgumentError => exc - Util::log :warning, "invalid ip address '#{@node.get('ip_address')}' for node '#{@node.name}'" + ip_addr = IPAddr.new(ip) + rescue ArgumentError + Util::log :warning, "invalid ip address '#{ip}' for node '#{@node.name}'" end - return vagrant_range.include?(ip_address) + return vagrant_range.include?(ip_addr) end # @@ -73,6 +78,163 @@ module LeapCli; module Config ) end + # + # Takes strings such as "openvpn.gateway_address:1.1.1.1" + # and converts this to data stored in this node. + # + def seed_from_args(args) + args.each do |seed| + key, value = seed.split(':', 2) + value = format_seed_value(value) + Util.assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'" + if key =~ /\./ + key_parts = key.split('.') + final_key = key_parts.pop + current_object = self + key_parts.each do |key_part| + current_object[key_part] ||= Config::Object.new + current_object = current_object[key_part] + end + current_object[final_key] = value + else + self[key] = value + end + end + end + + # + # Seeds values for this node from a template, based on the services. + # Values in the template will not override existing node values. + # + def seed_from_template + inherit_from!(manager.template('common')) + [self['services']].flatten.each do |service| + if service + template = manager.template(service) + if template + inherit_from!(template) + end + end + end + end + + # + # bails if the node is not valid. + # + def validate! + # + # validate ip_address + # + if self['ip_address'] == "REQUIRED" + Util.bail! do + Util.log :error, "ip_address is not set. " + + "Specify with `leap node add NAME ip_address:ADDRESS`." + end + elsif self['ip_address'] + begin + IPAddr.new(self['ip_address']) + rescue ArgumentError + Util.bail! do + Util.log :invalid, "ip_address #{self['ip_address'].inspect}" + end + end + end + + # + # validate name + # + self.class.validate_name!(self.name, self.vagrant?) + end + + # + # create or update all the configs needed for this node, + # including x.509 certs as needed. + # + # note: this method will write to disk EVERYTHING + # in the node, which is not what you want + # if the node has inheritance applied. + # + def write_configs + json = self.dump_json(:exclude => ['name']) + Util.write_file!([:node_config, name], json + "\n") + rescue LeapCli::ConfigError + Config::Node.remove_node_files(self.name) + end + + # + # modifies the config file nodes/NAME.json for this node. + # + def update_json(new_values) + self.env.update_node_json(node, new_values) + end + + # + # returns an array of all possible dns names for this node + # + def all_dns_names + names = [@node.domain.internal, @node.domain.full] + if @node['dns'] && @node.dns['aliases'] && @node.dns.aliases.any? + names += @node.dns.aliases + end + names.compact! + names.sort! + names.uniq! + return names + end + + def remove_files + self.class.remove_node_files(self.name) + end + + ## + ## Class Methods + ## + + def self.remove_node_files(node_name) + (Leap::Platform.node_files + [:node_files_dir]).each do |path| + Util.remove_file! [path, node_name] + end + end + + def self.validate_name!(name, local=false) + Util.assert! name, 'Node is missing a name.' + if local + Util.assert! name =~ /^[0-9a-z]+$/, + "illegal characters used in node name '#{name}' " + + "(note: Vagrant does not allow hyphens or underscores)" + else + Util.assert! name =~ /^[0-9a-z-]+$/, + "illegal characters used in node name '#{name}' " + + "(note: Linux does not allow underscores)" + end + end + + private + + # + # conversions: + # + # "x,y,z" => ["x","y","z"] + # + # "22" => 22 + # + # "5.1" => 5.1 + # + def format_seed_value(v) + if v =~ /,/ + v = v.split(',') + v.map! do |i| + i = i.to_i if i.to_i.to_s == i + i = i.to_f if i.to_f.to_s == i + i + end + else + v = v.to_i if v.to_i.to_s == v + v = v.to_f if v.to_f.to_s == v + end + return v + end + end end; end diff --git a/lib/leap_cli/config/node_cert.rb b/lib/leap_cli/config/node_cert.rb new file mode 100644 index 00000000..64842ffa --- /dev/null +++ b/lib/leap_cli/config/node_cert.rb @@ -0,0 +1,124 @@ +# +# x509 related methods for Config::Node +# +module LeapCli; module Config + + class Node < Object + + # + # creates a new server certificate file for this node + # + def generate_cert + require 'leap_cli/x509' + + if self['x509.use'] == false || + !Util.file_exists?(:ca_cert, :ca_key) || + !self.cert_needs_updating? + return false + end + + cert = CertificateAuthority::Certificate.new + provider = env.provider + + # set subject + cert.subject.common_name = self.domain.full + cert.serial_number.number = X509.cert_serial_number(self.domain.full) + + # set expiration + cert.not_before = X509.yesterday + cert.not_after = X509.yesterday_advance(provider.ca.server_certificates.life_span) + + # generate key + cert.key_material.generate_key(provider.ca.server_certificates.bit_size) + + # sign + cert.parent = X509.ca_root + cert.sign!(X509.server_signing_profile(self)) + + # save + Util.write_file!([:node_x509_key, self.name], cert.key_material.private_key.to_pem) + Util.write_file!([:node_x509_cert, self.name], cert.to_pem) + end + + # + # returns true if the certs associated with +node+ need to be regenerated. + # + def cert_needs_updating?(log_comments=true) + require 'leap_cli/x509' + + if log_comments + def log(*args, &block) + Util.log(*args, &block) + end + else + def log(*args); end + end + + node = self + if !Util.file_exists?([:node_x509_cert, node.name], [:node_x509_key, node.name]) + return true + else + cert = X509.load_certificate_file([:node_x509_cert, node.name]) + if !X509.created_by_authority?(cert) + log :updating, "cert for node '#{node.name}' because it was signed by an old CA root cert." + return true + end + if cert.not_after < Time.now.advance(:months => 2) + log :updating, "cert for node '#{node.name}' because it will expire soon" + return true + end + if cert.subject.common_name != node.domain.full + log :updating, "cert for node '#{node.name}' because domain.full has changed (was #{cert.subject.common_name}, now #{node.domain.full})" + return true + end + cert.openssl_body.extensions.each do |ext| + if ext.oid == "subjectAltName" + ips = [] + dns_names = [] + ext.value.split(",").each do |value| + value.strip! + ips << $1 if value =~ /^IP Address:(.*)$/ + dns_names << $1 if value =~ /^DNS:(.*)$/ + end + dns_names.sort! + if ips.first != node.ip_address + log :updating, "cert for node '#{node.name}' because ip_address has changed (from #{ips.first} to #{node.ip_address})" + return true + elsif dns_names != node.all_dns_names + log :updating, "cert for node '#{node.name}' because domain name aliases have changed" do + log "from: #{dns_names.inspect}" + log "to: #{node.all_dns_names.inspect})" + end + return true + end + end + end + end + return false + end + + # + # check the expiration of commercial certs, if any. + # + def warn_if_commercial_cert_will_soon_expire + require 'leap_cli/x509' + + self.all_dns_names.each do |domain| + if Util.file_exists?([:commercial_cert, domain]) + cert = X509.load_certificate_file([:commercial_cert, domain]) + path = Path.relative_path([:commercial_cert, domain]) + if cert.not_after < Time.now.utc + Util.log :error, "the commercial certificate '#{path}' has EXPIRED! " + + "You should renew it with `leap cert csr --domain #{domain}`." + elsif cert.not_after < Time.now.advance(:months => 2) + Util.log :warning, "the commercial certificate '#{path}' will expire soon (#{cert.not_after}). "+ + "You should renew it with `leap cert csr --domain #{domain}`." + end + end + end + end + + end + +end; end + -- cgit v1.2.3