157 lines
3.6 KiB
Ruby
Executable File
157 lines
3.6 KiB
Ruby
Executable File
#!/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 <<HELP
|
|
Available subcommands:
|
|
add [options] NAME [ipaddress[,ipaddress,...]]: add jail
|
|
regenerate: regenerate configuration
|
|
|
|
See 'jail COMMAND --help' for more information on a specific command.
|
|
HELP
|
|
end
|
|
|
|
def usage
|
|
puts(OPTION_PARSER.help)
|
|
exit(0)
|
|
end
|
|
|
|
ROOT_PATH = Pathname.new(File.expand_path("../.." ,__FILE__))
|
|
EZJAIL_CONFIG_PATH = Pathname.new("/usr/local/etc/ezjail/")
|
|
|
|
DEFAULT_IP4_SUBNET = "192.168.10.0/24"
|
|
DEFAULT_IP6_SUBNET = "fd7d:aed0:18aa::/48"
|
|
|
|
class Jail
|
|
def initialize(name, properties={})
|
|
@name = name
|
|
@properties = properties
|
|
end
|
|
attr_accessor :name
|
|
|
|
def ip4; extract_ip["ip4"]; end
|
|
def ip6; extract_ip["ip6"]; end
|
|
|
|
private
|
|
def extract_ip(type)
|
|
ips = @properties[ip] || []
|
|
ips.map do |addr| extract_ip(addr)
|
|
# example: em0|192.168.67.0 -> 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)
|