From aba9bb87d156d29f65e5d6dae9635011e12c11ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 22 Jan 2015 16:02:51 +0000 Subject: [PATCH] wip --- jails.json | 47 +++++++++++++ pf_vars.conf | 6 ++ scripts/jail | 156 +++++++++++++++++++++++++++++++++++++++++ scripts/utils.rb | 65 +++++++++++++++++ templates/fstab.erb | 4 ++ templates/jail_env.erb | 3 + templates/pf.erb | 14 ++++ 7 files changed, 295 insertions(+) create mode 100644 jails.json create mode 100644 pf_vars.conf create mode 100755 scripts/jail create mode 100644 scripts/utils.rb create mode 100644 templates/fstab.erb create mode 100644 templates/jail_env.erb create mode 100644 templates/pf.erb diff --git a/jails.json b/jails.json new file mode 100644 index 0000000..bcb42c3 --- /dev/null +++ b/jails.json @@ -0,0 +1,47 @@ +{ + "settings": { + "ip4_subnet": "192.168.67.0/24", + "ip6_subnet": "2a03:b0c0:2:d0:1::/80", + "flavor": "default" + }, + "default_jail_conf": { + "fstab": [ + "/usr/jails/basejail /usr/jails/%{name}/basejail nullfs ro 0 0", + "/data/pkg /usr/jails/%{name} nullfs ro 0 0" + ] + "exec_start": "/bin/sh /etc/rc", + "exec_stop": null, + "hostname": "%{name}", + "rootdir": "/usr/jails/%{name}", + "mount_enable": true, + "devfs_ruleset": "devfsrules_jails", + "procfs_enable": true, + "fdescfs_enable": true, + "image": null, + "imagetype": null, + "attachparams": null, + "attachblocking": null, + "forceblocking": null, + "zfs_datasets": null, + "cpuset": null, + "fib": null, + "parentzfs": null, + "parameters": null, + "post_start_script": null, + "retention_policy": null + }, + "jails": { + "dns": { + "ip4": ["192.168.67.2"], + "ip6": ["2a03:b0c0:2:d0::2a5:f002/128"] + }, + "dn42": { + "ip4": ["192.168.67.4"], + "ip6": ["2a03:b0c0:2:d0:1::3"] + }, + "mail": { + "ip4": ["192.168.67.1"], + "ip6": ["2a03:b0c0:2:d0:1::1"] + } + } +} diff --git a/pf_vars.conf b/pf_vars.conf new file mode 100644 index 0000000..0c04421 --- /dev/null +++ b/pf_vars.conf @@ -0,0 +1,6 @@ +ipv4_dns="192.168.67.2" +ipv6_dns="2a03:b0c0:2:d0::2a5:f002/128" +ipv4_dn42="192.168.67.4" +ipv6_dn42="2a03:b0c0:2:d0:1::3" +ipv4_mail="192.168.67.1" +ipv6_mail="2a03:b0c0:2:d0:1::1" diff --git a/scripts/jail b/scripts/jail new file mode 100755 index 0000000..ce67511 --- /dev/null +++ b/scripts/jail @@ -0,0 +1,156 @@ +#!/usr/bin/env ruby +require "optparse" +require "shellwords" +require_relative "utils" + +OPTION_PARSER = OptionParser.new do |opts| + opts.banner = "Usage: jails [options] [subcommand [options]]" + opts.separator "" + opts.separator < 192.168.67.0 + spec =~ /\|?([^|]+)$/ + $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 data["settings"]["flavour"] + flavour = ["-f", data["settings"]["flavour"]] + end + sh("ezjail-admin", "create", *flavour, name, ipconfig) + end + + def env(name) + jail_data = data[name] or die("no jail with name #{name} found") + templ = Template.new(ROOT_PATH.join("templates/jail.erb")) + puts(templ.render(name: name, properties: jail_properties(jail_data))) + 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") + jails.each do |name| + path = EZJAIL_CONFIG_PATH.join(name) + FileUtils.ln_sf(conf_path, path) + end + end + + def update_fstabs + templ = Template.new(ROOT_PATH.join("templates/fstab.erb")) + atomic_write(path, templ.render(jails: jails)) + end + + private + def jail_properties(name) + props = @properties.dup + + ips = props.delete("ip4") || [] + ips.concat(props.delete("ip6") || []) + if props["ip4"] || props["ip6"] + props["ip"] = ips.join(",") + end + props.each do |prop, value| + props[prop] = Shellwords.escape(value) + end + + props + end + + def jails + jails = {} + data["jails"].each do |name, properties| + jails[name] = Jail.new(name, properties) + end + jails + end + + def next_address(type) + assigned_subnets = data["jails"].map do |k,v| + NetAddr::CIDR.create(v[type]) if v[type] + end.compact + subnet = data["settings"]["#{type}_subnet"] + default = { "ip4" => DEFAULT_IP4_SUBNET, "ip6" => DEFAULT_IP6_SUBNET } + subnet ||= default[type] + next_free_subnet(NetAddr::CIDR.create(subnet), assigned_subnets) + end +end + +def main(args) + OPTION_PARSER.order!(args) + + usage if args.size < 1 + + registry = JailRegistry.new + + case command = args.shift + 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 +end + +main(ARGV) diff --git a/scripts/utils.rb b/scripts/utils.rb new file mode 100644 index 0000000..e9be254 --- /dev/null +++ b/scripts/utils.rb @@ -0,0 +1,65 @@ +require "ostruct" +require "fileutils" +require "erb" +require "json" +require "pathname" +require "pry" +require "netaddr" + +class Registry + PATH = Pathname.new(File.expand_path("../../jails.json", __FILE__)) + def initialize + @data = JSON.load(File.open(Registry::PATH)) + end + attr_accessor :data + def save + f = File.open(Registry::PATH, "w+") + f.puts JSON.pretty_generate(@data) + f.close + end +end + +def atomic_write(path, content) + dir = File.dirname(path) + unless Dir.exist?(dir) + FileUtils.mkdir_p(dir) + end + temp_path = path.to_s + ".tmp" + File.open(temp_path, 'w+') do |f| + f.write(content) + end + + FileUtils.mv(temp_path, path) +end + +def sh(cmd, *args) + puts "$ #{cmd} "+ args.map {|a| "'#{a}'" }.join(" ") + system(cmd, *args) +end + +def die(msg) + $stderr.puts(msg) + exit(1) +end + +def next_free_subnet(subnet, assigned_subnets) + subnet.enumerate(Limit: 1E4, Short: true)[1..1E4].each do |cidr| + assigned = assigned_subnets.find { |s| s.contains?(cidr) || s == cidr } + return cidr unless assigned + end +end + +class TemplateContext < OpenStruct + def get_binding + binding + end +end + +class Template + def initialize(path) + @erb = ERB.new(File.read(path), nil, '-') + end + def render(params={}) + @erb.result(TemplateContext.new(params).get_binding) + end +end diff --git a/templates/fstab.erb b/templates/fstab.erb new file mode 100644 index 0000000..003b62c --- /dev/null +++ b/templates/fstab.erb @@ -0,0 +1,4 @@ +# THIS FILE WAS GENERATED, CHANGES WILL BE OVERWRITTEN +<% fstab.each do |entry| -%> +<%= entry %> +<% end -%> diff --git a/templates/jail_env.erb b/templates/jail_env.erb new file mode 100644 index 0000000..4f251ac --- /dev/null +++ b/templates/jail_env.erb @@ -0,0 +1,3 @@ +<% jail_properties.each do |property,value| -%> +export jail_<%= name %>_<%= property %>=<%= value %> +<% end -%> diff --git a/templates/pf.erb b/templates/pf.erb new file mode 100644 index 0000000..ffc5ade --- /dev/null +++ b/templates/pf.erb @@ -0,0 +1,14 @@ +# THIS FILE WAS GENERATED, CHANGES WILL BE OVERWRITTEN +<% jails.each do |jail| -%> + +<%= name %>_ip4="{<%= jail.ip4.join(", ") %>}" +<% jail.ip4.each_with_index |ip, idx| -%> +<%= name %>_ip4_<%= idx %>="<%= ip %>" +<% end -%> + +<%= name %>_ip6="{<%= jail.ip6.join(", ") %>}" +<% jail.ip6.each_with_index |ip, idx| -%> +<%= name %>_ip6_<%= idx %>="<%= ip %>" +<% end -%> + +<% end -%>