#!/usr/bin/env ruby require_relative "utils" require_relative "bird" require "netaddr" require "optparse" require "set" class OpenvpnRegistry < Registry def initialize super @host = data["host"] @own_ipv4 = @host["ipv4"] or die("v4_subnet not set for host") @start_port = @host["start_port"].to_i @end_port = @host["end_port"].to_i @openvpn_path = Pathname.new(File.expand_path("../../openvpn", __FILE__)) 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" service_command("start", name) service_command("enable", name) 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 service_command("stop", name) service_command("disable", name) 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" @host["v6_public"] else @host["v4_public"] end params end def template_params(name, params) unless params["proto"] die "proto not set for peer #{name}" end unless params["ipv4"] # TODO die "internal ipv4 not set for peer #{name}" end params["lport"] ||= next_free_port if params["remote"] == "float" params["rport"] ||= params["lport"] end params.merge(own_ipv4: @own_ipv4) 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 def service_command(command_type, peer_name) cmd_name = "#{command_type}_command" command = try(@host, "openvpn", cmd_name) if command.nil? puts "skip to #{command_type} openvpn because #{cmd_name} is not defined" else CommandTemplate.new(command).execute(peer_name: peer_name) end end end GLOBAL_OPTIONS = OptionParser.new do |opts| opts.banner = "Usage: openvpn [options] [subcommand [options]]" opts.separator "" opts.separator <