summaryrefslogtreecommitdiff
path: root/lib/leap_cli/config
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2016-07-09 02:47:55 -0700
committerelijah <elijah@riseup.net>2016-08-23 13:35:13 -0700
commit8c207687e8dfa72f42f25cac7f46b99f895e4f57 (patch)
treeabc0a703dd91b6af43c123fa7b4bd6b2d5f3ced4 /lib/leap_cli/config
parent05fc9658915bfbd1006d9ff8da83fd34df2c78d1 (diff)
refactor the command for ca and node
Diffstat (limited to 'lib/leap_cli/config')
-rw-r--r--lib/leap_cli/config/node.rb176
-rw-r--r--lib/leap_cli/config/node_cert.rb124
2 files changed, 293 insertions, 7 deletions
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
+