dn42-scripts/scripts/openvpn

192 lines
5.4 KiB
Ruby
Executable File

#!/usr/bin/env ruby
require_relative "utils"
require "netaddr"
require "optparse"
require "set"
class OpenvpnRegistry < Registry
def initialize
super
@host = data["host"]
@v4_tunnel_ip = @host["v4_tunnel"]
@start_port = @host["start_port"].to_i
@end_port = @host["end_port"].to_i
@openvpn_path = Pathname.new(File.expand_path("../../openvpn", __FILE__))
die("v4_tunnel not set for host") unless @v4_tunnel_ip
end
def add_peer(name, peer)
save_config(name, generate_config(name, peer))
peer_template = generate_config(name, peer_params(peer))
puts "==== openvpn.conf for #{name} ====="
puts peer_template
puts "===================================\n"
end
def remove_peer(peer)
if data["network"].delete(peer).nil?
die("failed to remove '#{peer}': no such peer found in registry.json")
end
key = @openvpn_path.join("#{name}.key")
unless File.exists?(key)
FileUtils.rm_f(key)
end
end
def update_configurations
data["network"].each do |name, peer|
next unless peer["type"] == "openvpn"
save_config(name, generate_config(name, peer))
end
end
def generate_config(name, peer)
key = @openvpn_path.join("#{name}.key")
unless File.exists?(key)
FileUtils.mkdir_p(File.dirname(key))
sh("openvpn", "--genkey", "--secret", key.to_s)
puts "===== shared key for #{name} ======"
puts File.read(key)
puts "===================================\n"
end
context = template_params(name, peer)
template_path = Pathname.new(File.expand_path("../../templates", __FILE__))
openvpn_template = Template.new(template_path.join("openvpn.conf.erb"))
openvpn_template.render(context)
end
private
def save_config(peer, template)
atomic_write(@openvpn_path.join("#{peer}.conf"), template)
end
def peer_params(peer)
params = peer.dup
params["rport"] = peer["lport"]
if peer["rport"]
params["lport"] = peer["rport"]
end
params["remote"] = if peer["proto"] == "udp6"
data["host"]["v6_public"]
else
data["host"]["v4_public"]
end
params
end
def template_params(name, params)
unless params["proto"]
die "proto not set for peer #{name}"
end
unless params["v4_tunnel"] # TODO
die "v4_tunnel not set for peer #{name}"
end
params["lport"] ||= next_free_port
if params["remote"] == "float"
params["rport"] ||= params["lport"]
end
params.merge(own_v4_tunnel: @v4_tunnel_ip)
end
def next_free_port
ports = Set.new(data["network"].map { |name, peer| peer["lport"] })
(@start_port..@end_port).each do |port|
unless ports.include?(port)
return port
end
end
die "no free local ports in range #{@start_port}:#{@end_port}"
end
end
GLOBAL_OPTIONS = OptionParser.new do |opts|
opts.banner = "Usage: openvpn [options] [subcommand [options]]"
opts.separator ""
opts.separator <<HELP
Available subcommands:
add [options] NAME PROTO REMOTE_ADDRESS|float: add openvpn peer
remove [options] NAME: remove openvpn peer
regenerate: regenerate all openvpn configurations
See 'openvpn COMMAND --help' for more information on a specific command.
HELP
end
class Application
def run(args)
GLOBAL_OPTIONS.order!(args)
@registry = OpenvpnRegistry.new
case command = args.shift
when "add"
add_command(args)
when "remove"
remove_command(args)
when nil
puts(GLOBAL_OPTIONS.help())
exit(0)
when "regenerate" # fall through
else
die "unknown subcommand #{command}"
end
@registry.save
@registry.update_configurations
end
def add_command(args)
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: openvpn add [options] NAME PROTO REMOTE_ADDRESS|float"
opts.on("--local-port PORT", Integer, "local openvpn port") do |port|
options["lport"] = port
end
opts.on("--remote-port PORT", Integer, "remote openvpn port") do |port|
options["rport"] = port
end
opts.on("-4", "--v4-tunnel IP", String, "remote v4 tunnel ip") do |address|
options["v4_tunnel"] = address
end
opts.on("-6", "--v6-tunnel IP", String, "remote v6 tunnel ip") do |address|
die("remote ipv6 tunnel ips are currently not implemented")
options["v6_tunnel"] = address
end
opts.on("--as AS", String, "remote autonomous system number") do |as|
options["as"] = as
end
end.order!(args)
if ARGV.size < 3
$stderr.puts "no enough arguments"
die(parser.help)
end
unless options["v4_tunnel"] || options["v6_tunnel"]
die("at least of one of these options have to specified: "+
"v4-tunnel-ip or v6-tunnel-ip")
end
name, proto, remote_address = args
options["remote"] = remote_address
unless ["udp", "udp6"].include?(proto)
die("proto must be either udp or udp6")
end
options["proto"] = proto
@registry.add_peer(name, options)
end
def remove_command(args)
parser = OptionParser.new do |opts|
opts.banner = "Usage: openvpn remove NAME"
end.order!(args)
if args.empty?
$stderr.puts "no enough arguments"
die(parser.help)
end
@registry.remove_peer(args.first)
end
end
Application.new.run(ARGV)