require "optparse" require "shellwords" require_relative "utils.rb" ROOT_PATH = Pathname.new(File.expand_path("../../.." ,__FILE__)) EZJAIL_CONFIG_PATH = Pathname.new("/usr/local/etc/ezjail/") class Jail def initialize(name, properties={}) @name = name @properties = properties end attr_accessor :name, :properties def ip4 extract_ip("ip4") end def ip6 extract_ip("ip6") end private def extract_ip(type) ips = @properties[type] || [] ips.map do |addr| # example: em0|192.168.67.0 -> 192.168.67.0 addr =~ /\|?([^|]+)$/ $1 end end def pointers(&block) subnet_arpa = subnet.arpa version = subnet.version data["network"].each do |name, data| next unless data["ipv#{version}"] arpa = NetAddr::CIDR.create(data["ipv#{version}"]).arpa addr = arpa[0, arpa.size - subnet_arpa.size - 1] yield addr, name end end end class JailRegistry < Registry def add(name) data["jails"] ||= {} data["settings"] ||= {} ip4 = next_address("ip4") ip6 = next_address("ip6") data["jails"][name] = { "ip4" => [ip4], "ip6" => [ip6] } ipconfig = "#{ip4},#{ip6}" flavour = [] if settings["flavour"] flavour = ["-f", settings["flavour"]] end sh("ezjail-admin", "create", *flavour, name, ipconfig) end def update_pf_vars templ = Template.new(ROOT_PATH.join("templates/pf.erb")) path = ROOT_PATH.join("pf_vars.conf") atomic_write(path, templ.render(jails: jails)) end def update_jail_config jails.each do |jail| jail_data = default_jail_conf.merge(jail.properties) templ = Template.new(ROOT_PATH.join("templates/jail.erb")) properties = jail_properties(jail.name, jail_data) conf_path = EZJAIL_CONFIG_PATH.join(jail.name) atomic_write(conf_path, templ.render(name: jail.name, properties: properties, provide: jail_data["provide"], require_: jail_data["require"], before: jail_data["before"])) end end def update_fstabs templ = Template.new(ROOT_PATH.join("templates/fstab.erb")) jails.each do |jail| fstab = settings["fstab"].dup fstab.concat(jail.properties["fstab"] || []) fstab.map! do |entry| entry % { name: jail.name } end path = "/etc/fstab.#{jail.name}" atomic_write(path, templ.render(fstab: fstab)) end end def update_zone end def update_rzone end private def settings { "ip4_subnet" => "192.168.10.0/24", "ip6_subnet" => "fd7d:aed0:18aa::/48", "fstab" => [ "/usr/jails/basejail /usr/jails/%{name}/basejail nullfs ro 0 0", ], }.merge(data["settings"]) end def default_jail_conf { "provide" => "standard_ezjail", "require" => [], "before" => [], "exec_start" => "/bin/sh /etc/rc", "exec_stop" => nil, "hostname" => "%{name}", "rootdir" => "/usr/jails/%{name}", "mount_enable" => true, "devfs_ruleset" => "devfsrules_jails", "procfs_enable" => true, "fdescfs_enable" => true, "image" => nil, "imagetype" => nil, "attachparams" => nil, "attachblocking" => nil, "forceblocking" => nil, "zfs_datasets" => nil, "cpuset" => nil, "fib" => nil, "parentzfs" => nil, "parameters" => nil, "post_start_script" => nil, "retention_policy" => nil }.merge(data["default_jail_conf"]) end def jail_properties(name, properties) props = properties.dup ips = [] ips.concat(props.delete("ip4") || []) ips.concat(props.delete("ip6") || []) unless ips.empty? props["ip"] = ips.join(",") end props.each do |prop, value| props[prop] = serialize_property(name, value) end props end def serialize_property(name, value) str = case value when TrueClass return value ? "YES" : "NO" when String value % { name: name } else value end Shellwords.escape(str) end def jails data["jails"].map do |name, properties| Jail.new(name, properties) end end def next_address(type) subnets = [] data["jails"].each do |k,v| if v[type].is_a? Array v[type].each do |subnet| subnets << NetAddr::CIDR.create(subnet) end end end subnet = settings["#{type}_subnet"] next_free_subnet(NetAddr::CIDR.create(subnet), subnets) end end