diff --git a/jails.json b/jails.json index e023acf..d714308 100644 --- a/jails.json +++ b/jails.json @@ -1,5 +1,13 @@ { "settings": { + "soa": "ns1.higgsboson.tk.", + "serial": 114, + "refresh": "1H", + "hostmaster": "hostmaster.higgsboson.tk", + "domain": "eva.higgsboson.tk", + "retry": "4H", + "expire": "3W", + "minimum": "1D", "ip4_subnet": "192.168.67.0/24", "ip6_subnet": "2a03:b0c0:2:d0:1::/80", "flavor": "default", diff --git a/scripts/jail b/scripts/jail index 08843b5..e7ef6b2 100755 --- a/scripts/jail +++ b/scripts/jail @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require "optparse" -require "shellwords" -require_relative "utils" +require_relative "lib/utils.rb" +require_relative "lib/jail.rb" OPTION_PARSER = OptionParser.new do |opts| opts.banner = "Usage: jails [options] [subcommand [options]]" @@ -20,173 +19,6 @@ def usage exit(0) end -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 -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 env(name) - jail_data = data["jails"][name] or die("no jail with name #{name} found") - jail_data = default_jail_conf.merge(jail_data) - templ = Template.new(ROOT_PATH.join("templates/jail_env.erb")) - properties = jail_properties(name, jail_data) - puts(templ.render(name: name, properties: properties)) - 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_config_symlinks - conf_path = ROOT_PATH.join("scripts/jail_conf") - FileUtils.mkdir_p(EZJAIL_CONFIG_PATH) - jails.each do |jail| - FileUtils.ln_sf(conf_path, EZJAIL_CONFIG_PATH.join(jail.name)) - 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 - - 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 - { - "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 = 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 - def main(args) OPTION_PARSER.order!(args) @@ -198,19 +30,18 @@ def main(args) when "add" name = args.first or die "name required" registry.add(name) - registry.save when nil usage when "regenerate" - when "env" - name = args.first or die "name required" - registry.env(name) else die "unknown subcommand #{command}" end registry.update_pf_vars registry.update_fstabs - registry.update_config_symlinks + registry.update_jail_config + registry.update_zone + registry.update_rzone + registry.save end main(ARGV) diff --git a/scripts/jail_conf b/scripts/jail_conf deleted file mode 100644 index 3fb40f9..0000000 --- a/scripts/jail_conf +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -echo "TODO" -exit 1 diff --git a/scripts/lib/jail.rb b/scripts/lib/jail.rb new file mode 100644 index 0000000..5489db3 --- /dev/null +++ b/scripts/lib/jail.rb @@ -0,0 +1,183 @@ +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)) + 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 + { + "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 diff --git a/scripts/utils.rb b/scripts/lib/utils.rb similarity index 94% rename from scripts/utils.rb rename to scripts/lib/utils.rb index e9be254..8aef92a 100644 --- a/scripts/utils.rb +++ b/scripts/lib/utils.rb @@ -7,7 +7,7 @@ require "pry" require "netaddr" class Registry - PATH = Pathname.new(File.expand_path("../../jails.json", __FILE__)) + PATH = Pathname.new(File.expand_path("../../../jails.json", __FILE__)) def initialize @data = JSON.load(File.open(Registry::PATH)) end diff --git a/templates/jail_env.erb b/templates/jail.erb similarity index 66% rename from templates/jail_env.erb rename to templates/jail.erb index 1dcbed3..a6caf23 100644 --- a/templates/jail_env.erb +++ b/templates/jail.erb @@ -1,3 +1,4 @@ +# THIS FILE WAS GENERATED, CHANGES WILL BE OVERWRITTEN <% properties.each do |property,value| -%> export jail_<%= name %>_<%= property %>=<%= value %> <% end -%> diff --git a/templates/rzone.erb b/templates/rzone.erb new file mode 100644 index 0000000..47e0122 --- /dev/null +++ b/templates/rzone.erb @@ -0,0 +1,26 @@ +@ IN SOA <%= data["zone"]["soa"] %> <%= data["zone"]["hostmaster"] %> ( + <%= data["zone"]["serial"] %> ; serial + <%= data["zone"]["refresh"] %> ; refresh + <%= data["zone"]["retry"] %> ; retry + <%= data["zone"]["expire"] %> ; expire + <%= data["zone"]["minimum"] %>) ; minimum +<% data["jails"].each do |name, value| -%> +<% if value["ns"] -%> + IN NS <%= name %> +<% end -%> +<% end -%> + +<% data["jails"].each do |name, value| -%> +<% if value["ns"] -%> +<% if value["ipv4"] -%> +<%= name %> A <%= value["ipv4"] %> +<% end -%> +<% if value["ipv6"] -%> +<%= name %> AAAA <%= value["ipv6"] %> +<% end -%> +<% end -%> +<% end -%> + +<% pointers do |addr, name| -%> +<%= addr %> PTR <%= name %>.<%= data["zone"]["domain"] %>. +<% end -%> diff --git a/templates/zone.erb b/templates/zone.erb new file mode 100644 index 0000000..d9a5dbb --- /dev/null +++ b/templates/zone.erb @@ -0,0 +1,28 @@ +@ IN SOA <%= data["zone"]["soa"] %> <%= data["zone"]["hostmaster"] %> ( + <%= data["zone"]["serial"] %> ; serial + <%= data["zone"]["refresh"] %> ; refresh + <%= data["zone"]["retry"] %> ; retry + <%= data["zone"]["expire"] %> ; expire + <%= data["zone"]["minimum"] %>) ; minimum +<% data["jails"].each do |name, value| -%> +<% if value["ns"] -%> + IN NS <%= name %> +<% end -%> +<% end -%> + +<% jails.each do |jail| %> +<% jail.cname -%> +<%= name %> CNAME <%= jail.cname %> +<% end -%> +<% jail.srv.each do |srv| -%> +<%= name %> SRV <%= srv %> +<% end -%> +<% jail.ip4.each do |ip| -%> +<%= name %> A <%= ip %> +ipv4.<%= name %> A <%= ip %> +<% end -%> +<% if value["ipv6"] -%> +<%= name %> AAAA <%= ip(value["ipv6"]) %> +ipv6.<%= name %> AAAA <%= ip(value["ipv6"]) %> +<% end -%> +<% end -%>