From: Huifeng Le Date: Thu, 27 Feb 2020 13:44:44 +0000 (+0800) Subject: SDEWAN CNF Rest API support X-Git-Tag: v1.0~38^2 X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F62%2F2262%2F3;p=icn%2Fsdwan.git SDEWAN CNF Rest API support Add SDEWAN Rest API implementation for Service, Mwan3, Firewall and IPSec, API design can be found at: IPSec: https://wiki.akraino.org/display/AK/IPSec+Design#IPSecDesign Service/Mwan3/Firewall: https://wiki.akraino.org/display/AK/SDEWAN+CNF Signed-off-by: Huifeng Le Change-Id: I649305d27ab6f0de9f57ff4411a9f4b1267cf504 --- diff --git a/cnf/build/Dockerfile_1806_mwan3.tpl b/cnf/build/Dockerfile_1806_mwan3.tpl new file mode 100644 index 0000000..52bf6ac --- /dev/null +++ b/cnf/build/Dockerfile_1806_mwan3.tpl @@ -0,0 +1,26 @@ +FROM openwrt-1806-4-base + +#EXPOSE 80 +ENV http_proxy={docker_proxy} +ENV https_proxy={docker_proxy} +ENV no_proxy=localhost,120.0.0.1,192.168.* + +RUN mkdir /var/lock && \ + opkg update && \ + opkg install uhttpd-mod-lua && \ + uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \ + uci commit uhttpd && \ + opkg install mwan3 && \ + opkg install luci-app-mwan3; exit 0 + +COPY system /etc/config/system +COPY rest_v1 /usr/lib/lua/luci/controller/rest_v1 + +ENV http_proxy= +ENV https_proxy= +ENV no_proxy= + +USER root + +# using exec format so that /sbin/init is proc 1 (see procd docs) +CMD ["/sbin/init"] diff --git a/cnf/build/Dockerfile_1806_mwan3_noproxy.tpl b/cnf/build/Dockerfile_1806_mwan3_noproxy.tpl new file mode 100644 index 0000000..0b0590e --- /dev/null +++ b/cnf/build/Dockerfile_1806_mwan3_noproxy.tpl @@ -0,0 +1,19 @@ +FROM openwrt-1806-4-base + +#EXPOSE 80 + +RUN mkdir /var/lock && \ + opkg update && \ + opkg install uhttpd-mod-lua && \ + uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \ + uci commit uhttpd && \ + opkg install mwan3 && \ + opkg install luci-app-mwan3; exit 0 + +COPY system /etc/config/system +COPY rest_v1 /usr/lib/lua/luci/controller/rest_v1 + +USER root + +# using exec format so that /sbin/init is proc 1 (see procd docs) +CMD ["/sbin/init"] diff --git a/cnf/build/build_image.sh b/cnf/build/build_image.sh new file mode 100644 index 0000000..7ff6e20 --- /dev/null +++ b/cnf/build/build_image.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# usage: build_images.sh + +set -ex +base_image_tag=openwrt-1806-4-base +docker_file=Dockerfile_1806_mwan3 +image_tag=openwrt-1806-mwan3 +package=openwrt-18.06.4-x86-64-generic-rootfs + +# build openwrt base docker images +base_image=`docker images | grep $base_image_tag | awk '{print $1}'` +if [ -z "$base_image" ]; then + # download driver source package + if [ ! -e /tmp/$package.tar.gz ]; then + wget -P /tmp https://downloads.openwrt.org/releases/18.06.4/targets/x86/64/$package.tar.gz + fi + cp /tmp/$package.tar.gz . + + docker import $package.tar.gz $base_image_tag +fi + +# generate Dockerfile +test -f ./set_proxy && . set_proxy +docker_proxy=${docker_proxy-""} +if [ -z "$docker_proxy" ]; then + cp ${docker_file}_noproxy.tpl $docker_file +else + cp $docker_file.tpl $docker_file + sed -i "s,{docker_proxy},$docker_proxy,g" $docker_file +fi + +# build docker images for openwrt with wman3 +docker build --network=host -f $docker_file -t $image_tag . + +# clear +docker image rm $base_image_tag +rm -rf $docker_file +rm -rf $package.tar.gz diff --git a/cnf/build/rest_v1/firewall_rest.lua b/cnf/build/rest_v1/firewall_rest.lua new file mode 100644 index 0000000..d1531c8 --- /dev/null +++ b/cnf/build/rest_v1/firewall_rest.lua @@ -0,0 +1,285 @@ +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.rest_v1.firewall_rest", package.seeall) + +local uci = require "luci.model.uci" + +json = require "luci.jsonc" +io = require "io" +sys = require "luci.sys" +utils = require "luci.controller.rest_v1.utils" + +uci_conf = "firewall" + +zone_validator = { + create_section_name=false, + {name="name"}, + {name="network", item_validator=function(value) return is_network_interface_available(value) end, message="invalid network"}, + {name="masq", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="invalid masq"}, + {name="masq_src", item_validator=function(value) return is_valid_masq_subset(value) end, message="invalid masq_src"}, + {name="masq_dest", item_validator=function(value) return is_valid_masq_subset(value) end, message="invalid masq_dest"}, + {name="masq_allow_invalid", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="invalid masq_allow_invalid"}, + {name="mtu_fix", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="invalid mtu_fix"}, + {name="input", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP"}) end, message="invalid input"}, + {name="forward", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP"}) end, message="invalid forward"}, + {name="output", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP"}) end, message="invalid output"}, + {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"}, + {name="subnet", item_validator=function(value) return utils.is_valid_ip(value) end, message="invalid subnet"}, + {name="extra_src"}, + {name="etra_dest"}, +} + +redirect_validator = { + create_section_name=false, + object_validator=function(value) return check_redirect(value) end, + {name="name"}, + {name="src", validator=function(value) return is_zone_available(value) end, message="invalid src"}, + {name="src_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid src_ip"}, + {name="src_dip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid src_dip"}, + {name="src_mac", validator=function(value) return utils.is_valid_mac(value) end, message="invalid src_mac"}, + {name="src_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid src_port"}, + {name="src_dport", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid src_port"}, + {name="proto", validator=function(value) return utils.in_array(value, {"tcp", "udp", "tcpudp", "udplite", "icmp", "esp", "ah", "sctp", "all"}) end, message="invalid proto"}, + {name="dest", validator=function(value) return is_zone_available(value) end, message="invalid dest"}, + {name="dest_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid dest_ip"}, + {name="dest_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid dest_port"}, + {name="mark"}, + {name="target", validator=function(value) return utils.in_array(value, {"DNAT", "SNAT"}) end, message="invalid target"}, + {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"}, +} + +rule_validator = { + create_section_name=false, + object_validator=function(value) return check_rule(value) end, + {name="name"}, + {name="src", validator=function(value) return is_zone_available(value) end, message="invalid src"}, + {name="src_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid src_ip"}, + {name="src_mac", validator=function(value) return utils.is_valid_mac(value) end, message="invalid src_mac"}, + {name="src_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid src_port"}, + {name="proto", validator=function(value) return utils.in_array(value, {"tcp", "udp", "tcpudp", "udplite", "icmp", "esp", "ah", "sctp", "all"}) end, message="invalid proto"}, + {name="icmp_type", is_list=true, item_validator=function(value) return check_icmp_type(value) end, message="invalid icmp_type"}, + {name="dest", validator=function(value) return is_zone_available(value) end, message="invalid dest"}, + {name="dest_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid dest_ip"}, + {name="dest_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid dest_port"}, + {name="mark"}, + {name="target", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP", "MARK", "NOTRACK"}) end, message="invalid target"}, + {name="set_mark"}, + {name="set_xmark"}, + {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"}, + {name="extra"}, +} + +forwarding_validator = { + create_section_name=false, + {name="name"}, + {name="src", required=true, validator=function(value) return is_zone_available(value) end, message="invalid src"}, + {name="dest", required=true, validator=function(value) return is_zone_available(value) end, message="invalid dest"}, + {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"}, +} + +firewall_processor = { + zone={update="update_zone", delete="delete_zone", validator=zone_validator}, + redirect={validator=redirect_validator}, + rule={validator=rule_validator}, + forwarding={validator=forwarding_validator}, + configuration=uci_conf +} + +zone_checker = { + {name="rule", checker={"src", "dest"}}, + {name="forwarding", checker={"src", "dest"}}, + {name="redirect", checker={"src", "dest"}}, +} + +function index() + ver = "v1" + configuration = "firewall" + entry({"sdewan", configuration, ver, "zones"}, call("get_zones")) + entry({"sdewan", configuration, ver, "redirects"}, call("get_redirects")) + entry({"sdewan", configuration, ver, "rules"}, call("get_rules")) + entry({"sdewan", configuration, ver, "forwardings"}, call("get_forwardings")) + entry({"sdewan", configuration, ver, "zone"}, call("handle_request")).leaf = true + entry({"sdewan", configuration, ver, "redirect"}, call("handle_request")).leaf = true + entry({"sdewan", configuration, ver, "rule"}, call("handle_request")).leaf = true + entry({"sdewan", configuration, ver, "forwarding"}, call("handle_request")).leaf = true +end + +function is_network_interface_available(interface) + local interfaces = uci:get_all("network", interface) + if interfaces == nil then + return false, "Interface[" .. interface .. "] is not definied" + end + + return true, interface +end + +function is_valid_masq_subset(s) + local ip = s + if utils.start_with(ip, "!") then + ip = string.sub(ip, 2, string.len(ip)) + end + + local res, _ = utils.is_valid_ip(ip) + if res then + return true, s + else + return false + end +end + +function check_redirect(value) + local target = value["target"] + if target == "SNAT" then + if value["src_dip"] == nil then + return false, "src_dip is required for SNAT" + end + if value["dest"] == nil then + return false, "dest is required for SNAT" + end + end + + if target == "DNAT" then + if value["src"] == nil then + return false, "src is required for DNAT" + end + end + + return true, value +end + +function check_rule(value) + local target = value["target"] + if target == "MARK" then + if value["set_mark"] == nil and value["set_xmark"] == nil then + return false, "set_mark or set_xmark is required for MARK" + end + end + + return true, value +end + +function check_icmp_type(value) + return utils.in_array(value, {"address-mask-reply", "address-mask-request ", "any", "communication-prohibited", + "destination-unreachable", "echo-reply", "echo-request", "fragmentation-needed", "host-precedence-violation", + "host-prohibited", "host-redirect", "host-unknown", "host-unreachable", "ip-header-bad", "network-prohibited", + "network-redirect", "network-unknown", "network-unreachable", "parameter-problem", "ping", "pong", + "port-unreachable", "precedence-cutoff", "protocol-unreachable", "redirect", "required-option-missing", + "router-advertisement", "router-solicitation", "source-quench", "source-route-failed", "time-exceeded", + "timestamp-reply", "timestamp-request", "TOS-host-redirect", "TOS-host-unreachable", "TOS-network-redirect", + "TOS-network-unreachable", "ttl-exceeded", "ttl-zero-during-reassembly", "ttl-zero-during-transit"}) +end + +-- Request Handler +function handle_request() + local handler = utils.handles_table[utils.get_req_method()] + if handler == nil then + utils.response_error(405, "Method Not Allowed") + else + return utils[handler](_M, firewall_processor) + end +end + +-- Zone APIs +-- check if zone is used by rule, forwarding or redirect +function is_zone_used(name) + local is_used = false + + for i,v in pairs(zone_checker) do + local section_name = v["name"] + local checker = v["checker"] + uci:foreach(uci_conf, section_name, + function(section) + for j=1, #checker do + if name == section[checker[j]] then + is_used = true + return false + end + end + end + ) + + if is_used then + break + end + end + + return is_used +end + +function is_zone_available(name) + local zone = utils.get_object(_M, firewall_processor, "zone", name) + if zone == nil then + return false, "Zone[" .. name .. "] is not definied" + end + + return true, name +end + +-- get /zones +function get_zones() + utils.handle_get_objects("zones", uci_conf, "zone", zone_validator) +end + +-- delete a zone +function delete_zone(name, check_used) + -- check whether zone is defined + local zone = utils.get_object(_M, firewall_processor, "zone", name) + if zone == nil then + return false, 404, "zone " .. name .. " is not defined" + end + + if check_used == nil then + check_used = true + else + check_used = false + end + + -- Todo: check whether the zone is used by a rule + if check_used == true and is_zone_used(name) then + return false, 400, "zone " .. name .. " is used" + end + + -- delete zone + uci:foreach(uci_conf, "zone", + function (section) + if name == section[".name"] or name == section["name"] then + uci:delete(uci_conf, section[".name"]) + end + end + ) + + -- commit change + uci:save(uci_conf) + uci:commit(uci_conf) + + return true +end + +-- update a zone +function update_zone(zone) + local name = zone.name + res, code, msg = delete_zone(name, false) + if res == true then + return utils.create_object(_M, firewall_processor, "zone", zone) + end + + return false, code, msg +end + +-- Redirect APIs +-- get /redirects +function get_redirects() + utils.handle_get_objects("redirects", uci_conf, "redirect", redirect_validator) +end + +-- Rule APIs +-- get /rules +function get_rules() + utils.handle_get_objects("rules", uci_conf, "rule", rule_validator) +end + +-- Forwarding APIs +-- get /forwardings +function get_forwardings() + utils.handle_get_objects("forwardings", uci_conf, "forwarding", forwarding_validator) +end diff --git a/cnf/build/rest_v1/index.lua b/cnf/build/rest_v1/index.lua new file mode 100644 index 0000000..3a11e92 --- /dev/null +++ b/cnf/build/rest_v1/index.lua @@ -0,0 +1,16 @@ +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.rest_v1.index", package.seeall) + +function index() + ver = "v1" + entry({"sdewan", ver}, call("help")).dependent = false + entry({"sdewan", "mwan3", ver}, call("help")).dependent = false + entry({"sdewan", "firewall", ver}, call("help")).dependent = false + entry({"sdewan", "ipsec", ver}, call("help")).dependent = false +end + +function help() + luci.http.prepare_content("application/json") + luci.http.write('{"message":"sdewan restful API service v1"}') +end diff --git a/cnf/build/rest_v1/ipsec_rest.lua b/cnf/build/rest_v1/ipsec_rest.lua new file mode 100644 index 0000000..a158941 --- /dev/null +++ b/cnf/build/rest_v1/ipsec_rest.lua @@ -0,0 +1,265 @@ +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.rest_v1.ipsec_rest", package.seeall) + +local uci = require "luci.model.uci" + +json = require "luci.jsonc" +io = require "io" +sys = require "luci.sys" +utils = require "luci.controller.rest_v1.utils" + +uci_conf = "ipsec" + +proposal_validator = { + {name="name"}, + {name="encryption_algorithm", validator=function(value) return true, value end, message="invalid encryption_algorithm"}, + {name="hash_algorithm", validator=function(value) return true, value end, message="invalid hash_algorithm"}, + {name="dh_group", validator=function(value) return true, value end, message="invalid dh_group"}, +} + +connection_validator = { + config_type=function(value) return value["type"] end, + {name="name"}, + {name="type", required=true, validator=function(value) return utils.in_array(value, {"tunnel", "transport"}) end, load_func=function(value) return value[".type"] end, save_func=function(value) return true, "" end, message="invalid type"}, + {name="mode"}, + {name="local_subnet"}, + {name="local_nat"}, + {name="local_sourceip"}, + {name="local_updown"}, + {name="local_firewall"}, + {name="remote_subnet"}, + {name="remote_sourceip"}, + {name="remote_updown"}, + {name="remote_firewall"}, + {name="crypto_proposal", is_list=true, item_validator=function(value) return is_proposal_available(value) end, message="invalid crypto_proposal"}, +} + +site_validator = { + config_type="remote", + {name="name"}, + {name="gateway"}, + {name="pre_shared_key"}, + {name="authentication_method"}, + {name="local_identifier"}, + {name="remote_identifier"}, + {name="crypto_proposal", is_list=true, item_validator=function(value) return is_proposal_available(value) end, message="invalid crypto_proposal"}, + {name="force_crypto_proposal"}, + {name="local_public_cert", + load_func=function(value) return load_cert(value["local_public_cert"]) end, + save_func=function(value) return save_cert(value["local_public_cert"], "/tmp/" .. value["name"] .. "_public.cert") end, + delete_func=function(value) return delete_cert(value["local_public_cert"]) end}, + {name="local_private_cert", + load_func=function(value) return load_cert(value["local_private_cert"]) end, + save_func=function(value) return save_cert(value["local_private_cert"], "/tmp/" .. value["name"] .. "_private.cert") end, + delete_func=function(value) return delete_cert(value["local_private_cert"]) end}, + {name="shared_ca"}, + {name="connections", item_validator=connection_validator, message="invalid connection", + load_func=function(value) return load_connection(value) end, + save_func=function(value) return save_connection(value) end,}, +} + +ipsec_processor = { + proposal={update="update_proposal", delete="delete_proposal", validator=proposal_validator}, + site={validator=site_validator}, + configuration=uci_conf +} + +proposal_checker = { + {name="remote", checker={"crypto_proposal"}}, + {name="tunnel", checker={"crypto_proposal"}}, + {name="transport", checker={"crypto_proposal"}}, +} + +function index() + ver = "v1" + configuration = "ipsec" + entry({"sdewan", configuration, ver, "proposals"}, call("get_proposals")) + entry({"sdewan", configuration, ver, "sites"}, call("get_sites")) + entry({"sdewan", configuration, ver, "proposal"}, call("handle_request")).leaf = true + entry({"sdewan", configuration, ver, "site"}, call("handle_request")).leaf = true +end + +-- Request Handler +function handle_request() + local handler = utils.handles_table[utils.get_req_method()] + if handler == nil then + utils.response_error(405, "Method Not Allowed") + else + return utils[handler](_M, ipsec_processor) + end +end + +function save_cert(content, path) + local file = io.open(path, "w") + if file == nil then + return false, "Can not generate cert at: " .. path + end + + file:write(content) + file:close() + + return true, path +end + +function load_cert(path) + if path == nil then + return nil + end + content = path + local file = io.open(path, "rb") + if file ~= nil then + content = file:read "*a" + file:close() + end + return content +end + +function delete_cert(path) + if path ~= nil then + os.remove(path) + end +end + +function add_to_key_list(arr, key, value) + local sub_arr = nil + for i=1, #arr do + if key == arr[i].option then + sub_arr = arr[i].values + end + end + + if sub_arr == nil then + sub_arr = {} + arr[#arr+1] = {option=key, values=sub_arr} + end + sub_arr[#sub_arr+1] = value +end + +-- load uci configuration as format: {{section="tunnel/transport", name="section_name"},} +function load_connection(value) + local ret_value={} + local tunnels = value["tunnel"] + if tunnels ~= nil and #tunnels > 0 then + for i=1, #tunnels do + ret_value[#ret_value+1]={section="tunnel", name=tunnels[i]} + end + end + local transports = value["transport"] + if transports ~= nil and #transports > 0 then + for i=1, #transports do + ret_value[#ret_value+1]={section="transport", name=transports[i]} + end + end + if #ret_value == 0 then + return nil + end + return ret_value +end + +-- save connections as standard format: +-- {_standard_format=true, {option="tunnel", values={...}}, {option="transport", values={...}}} +function save_connection(value) + local connections = value["connections"] + local ret_value = {_standard_format=true} + for i=1, #connections do + add_to_key_list(ret_value, connections[i]["type"], connections[i]) + end + + return true, ret_value +end + +-- Site APIs +-- get /sites +function get_sites() + utils.handle_get_objects("sites", uci_conf, "remote", site_validator) +end + +-- Proposal APIs +-- check if proposal is used by connection, site +function is_proposal_used(name) + local is_used = false + + for i,v in pairs(proposal_checker) do + local section_name = v["name"] + local checker = v["checker"] + uci:foreach(uci_conf, section_name, + function(section) + for j=1, #checker do + for k=1, #section[checker[j]] do + if name == section[checker[j]][k] then + is_used = true + return false + end + end + end + end + ) + + if is_used then + break + end + end + + return is_used +end + +function is_proposal_available(name) + local proposal = utils.get_object(_M, ipsec_processor, "proposal", name) + if proposal == nil then + return false, "Proposal[" .. name .. "] is not definied" + end + + return true, name +end + +-- get /proposals +function get_proposals() + utils.handle_get_objects("proposals", uci_conf, "proposal", proposal_validator) +end + +-- delete a proposal +function delete_proposal(name, check_used) + -- check whether proposal is defined + local proposal = utils.get_object(_M, ipsec_processor, "proposal", name) + if proposal == nil then + return false, 404, "proposal " .. name .. " is not defined" + end + + if check_used == nil then + check_used = true + else + check_used = false + end + + -- Todo: check whether the proposal is used + if check_used == true and is_proposal_used(name) then + return false, 400, "proposal " .. name .. " is used" + end + + -- delete proposal + uci:foreach(uci_conf, "proposal", + function (section) + if name == section[".name"] or name == section["name"] then + uci:delete(uci_conf, section[".name"]) + end + end + ) + + -- commit change + uci:save(uci_conf) + uci:commit(uci_conf) + + return true +end + +-- update a proposal +function update_proposal(proposal) + local name = proposal.name + res, code, msg = delete_proposal(name, false) + if res == true then + return utils.create_object(_M, ipsec_processor, "proposal", proposal) + end + + return false, code, msg +end diff --git a/cnf/build/rest_v1/mwan3_rest.lua b/cnf/build/rest_v1/mwan3_rest.lua new file mode 100644 index 0000000..dd801b0 --- /dev/null +++ b/cnf/build/rest_v1/mwan3_rest.lua @@ -0,0 +1,237 @@ +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.rest_v1.mwan3_rest", package.seeall) + +local uci = require "luci.model.uci" + +json = require "luci.jsonc" +io = require "io" +sys = require "luci.sys" +utils = require "luci.controller.rest_v1.utils" + +policy_member_validator = { + {name="interface", required=true, validator=function(value) return is_interface_available(value) end, message="invalid interface"}, + {name="metric", required=true, validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="metric should be greater than 0"}, + {name="weight", required=true, validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="weight should be greater than 0"} +} + +policy_validator = { + {name="name"}, + {name="members", required=true, item_validator=policy_member_validator, message="Incorrect policy member"} +} + +rule_validator = { + {name="name"}, + {name="policy", required=true, target="use_policy", validator=function(value) return is_policy_available(value) end, message="invalid policy"}, + {name="src_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid ip address"}, + {name="src_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid src_port"}, + {name="dest_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid ip address"}, + {name="dest_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid dest_port"}, + {name="proto", validator=function(value) return utils.in_array(value, {"tcp", "udp", "icmp", "all"}) end, message="wrong proto"}, + {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "all"}) end, message="wrong family"}, + {name="sticky", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="wrong sticky"}, + {name="timeout", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid timeout"} +} + +mwan3_processor = { + policy={get="get_policy", update="update_policy", create="create_policy", delete="delete_policy", validator=policy_validator}, + rule={validator=rule_validator}, + configuration="mwan3" +} + +function index() + ver = "v1" + entry({"sdewan", "mwan3", ver, "policies"}, call("get_policies")) + entry({"sdewan", "mwan3", ver, "rules"}, call("get_rules")) + entry({"sdewan", "mwan3", ver, "policy"}, call("handle_request")).leaf = true + entry({"sdewan", "mwan3", ver, "rule"}, call("handle_request")).leaf = true +end + +-- Request Handler +function handle_request() + local handler = utils.handles_table[utils.get_req_method()] + if handler == nil then + utils.response_error(405, "Method Not Allowed") + else + return utils[handler](_M, mwan3_processor) + end +end + +-- check interface +function is_interface_available(name) + local interfaces = uci:get_all("mwan3", name) + if interfaces == nil then + return false, "Interface[" .. name .. "] is not definied" + end + + return true +end + +-- check policy +function is_policy_available(name) + local policy = uci:get_all("mwan3", name) + if policy == nil then + return false, "Policy[" .. name .. "] is not definied" + end + + return true +end + +-- policy APIs +-- get a policy +function get_policy(name) + local members = uci:get_all("mwan3", name) + if members == nil then + return nil + end + members = members["use_member"] + + local policy = {} + policy["name"] = name + policy["members"] = {} + + for i=1, #members do + local member_name = members[i] + local member_data = uci:get_all("mwan3", member_name) + if not (member_data == nil) then + local member = {} + member["name"] = member_name + utils.set_data("mwan3", policy_member_validator, member_data, member) + policy["members"][i] = member + end + end + + return policy +end + +-- get /policies +function get_policies() + if not (utils.validate_req_method("GET")) then + return + end + + local res = {} + res["policies"] = {} + + local index = 1 + uci:foreach("mwan3", "policy", + function (section) + policy = get_policy(section[".name"]) + if not (policy == nil) then + res["policies"][index] = policy + index = index + 1 + end + end + ) + + utils.response_object(res) +end + +-- create a policy +function create_policy(policy) + -- check whether policy is exist + local policy_name = policy.name + local exist_policy_obj = uci:get_all("mwan3", policy_name) + if exist_policy_obj ~= nil then + return false, 409, "Conflict" + end + + -- check member interfaces + local member_interface_names = {} + local members = policy.members + for i=1, #members do + if utils.in_array(policy_name .. "_" .. members[i].interface, member_interface_names) then + return false, 400, "Same interface is used in different members" + end + member_interface_names[i] = policy_name .. "_" .. members[i].interface + end + + -- create member + for i=1, #members do + uci:section("mwan3", "member", member_interface_names[i], { + interface = members[i].interface, + metric = members[i].metric, + weight = members[i].weight}) + end + + -- create policy + uci:section("mwan3", "policy", policy_name) + uci:set_list("mwan3", policy_name, "use_member", member_interface_names) + + -- commit change + uci:save("mwan3") + uci:commit("mwan3") + + return true +end + +-- check if policy is used by rule +function is_policy_used(name) + local is_used = false + + uci:foreach("mwan3", "rule", + function (section) + rule = utils.get_object(_M, mwan3_processor, "rule", section[".name"]) + if not (rule == nil) then + if rule.policy == name then + is_used = true + return false + end + end + end + ) + + return is_used +end + +-- delete a policy +function delete_policy(name, check_used) + -- check whether policy is defined + local policy = get_policy(name) + if policy == nil then + return false, 404, "policy " .. name .. " is not defined" + end + + if check_used == nil then + check_used = true + else + check_used = false + end + + -- Todo: check whether the policy is used by a rule + if check_used == true and is_policy_used(name) then + return false, 400, "policy " .. name .. " is used" + end + + -- delete policy + uci:delete("mwan3", name) + + -- delete members + local members = policy.members + for i=1, #members do + uci:delete("mwan3", members[i].name) + end + + -- commit change + uci:save("mwan3") + uci:commit("mwan3") + + return true +end + +-- update a policy +function update_policy(policy) + local name = policy.name + res, code, msg = delete_policy(name, false) + if res == true then + return create_policy(policy) + end + + return false, code, msg +end + +-- Rule APIs +-- get /rules +function get_rules() + utils.handle_get_objects("rules", "mwan3", "rule", rule_validator) +end diff --git a/cnf/build/rest_v1/service.lua b/cnf/build/rest_v1/service.lua new file mode 100644 index 0000000..6a54deb --- /dev/null +++ b/cnf/build/rest_v1/service.lua @@ -0,0 +1,89 @@ +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.rest_v1.service", package.seeall) + +json = require "luci.jsonc" +io = require "io" +sys = require "luci.sys" +utils = require "luci.controller.rest_v1.utils" + +available_services = {"mwan3", "firewall", "ipsec"} +action_tables = { + mwan3 = {command="/etc/init.d/mwan3", reload="restart"}, + firewall = {command="/etc/init.d/firewall", reload_command="fw3"}, + ipsec = {command="/etc/init.d/ipsec", reload="restart"} +} + +executeservice_validator = { + {name="action", required=true, validator=function(value) return utils.in_array(value, {"start", "stop", "restart", "reload"}) end, message="wrong action"} +} + +function index() + ver = "v1" + entry({"sdewan", ver, "services"}, call("getServices")) + entry({"sdewan", ver, "service"}, call("executeService")).leaf = true +end + +function getServices() + if not (utils.validate_req_method("GET")) then + return + end + + local res = {} + res["services"] = available_services + + utils.response_object(res) +end + +function getservicename() + local uri_list = utils.get_URI_list(6) + if uri_list == nil then + return nil + end + + local service = uri_list[#uri_list] + if not utils.in_array(service, available_services) then + utils.response_error(400, "Bad request URI") + return nil + end + + return service +end + +function executeService() + -- check request method + if not (utils.validate_req_method("PUT")) then + return + end + + -- get service name + local service = getservicename() + if service == nil then + return + end + + -- check content + local body_obj = utils.get_and_validate_body_object(executeservice_validator) + if body_obj == nil then + return + end + + local action = body_obj["action"] + + local exec_command = action_tables[service][action .. "_command"] + if exec_command == nil then + exec_command = action_tables[service]["command"] + end + local exec_action = action_tables[service][action] + if exec_action == nil then + exec_action = action + end + + exec_command = exec_command .. " " .. exec_action + + -- execute command + utils.log("Execute Command: %s" % exec_command) + sys.exec(exec_command) + + utils.response_success() +end diff --git a/cnf/build/rest_v1/utils.lua b/cnf/build/rest_v1/utils.lua new file mode 100644 index 0000000..5e35951 --- /dev/null +++ b/cnf/build/rest_v1/utils.lua @@ -0,0 +1,777 @@ +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.rest_v1.utils", package.seeall) + +local json = require "luci.jsonc" +local uci = require "luci.model.uci" +REQUEST_METHOD = "REQUEST_METHOD" + +function index() +end + +function log(obj) + return io.stderr:write(tostring(obj) .. "\n") +end + +function printTableKeys(t, prefix) + for key, value in pairs(t) do + if string.sub(key,1,string.len(prefix)) == prefix then + log(key) + end + end +end + +-- check whether s starts with prefix +function start_with(s, prefix) + return (string.find(s, "^" .. prefix) ~= nil) +end + +-- check ip +function is_match(s, reg) + local ret = string.match(s, reg) + if s == ret then + return true + end + return false +end + +function is_valid_ip_address(s) + local array = {} + local reg = string.format("([^%s]+)", ".") + for item in string.gmatch(s, reg) do + if is_match(item, "(25[0-5])") or is_match(item, "(2[0-4]%d)") or is_match(item, "([01]?%d?%d)") then + table.insert(array, item) + else + return false + end + end + + if #array ~= 4 then + return false + end + + return true, s +end + +function is_valid_ip(s) + local array = {} + local reg = string.format("([^%s]+)", "/") + for item in string.gmatch(s, reg) do + table.insert(array, item) + end + + if #array == 1 then + -- check ip address + return is_valid_ip_address(array[1]) + else + if #array == 2 then + if is_valid_ip_address(array[1]) then + -- check mask + if is_integer_and_in_range(array[2], -1, 33) then + return true, s + end + end + end + end + + return false +end + +function is_valid_mac(s) + local array = {} + local reg = string.format("([^%s]+)", ":") + for item in string.gmatch(s, reg) do + if is_match(item, "([a-fA-F0-9][a-fA-F0-9])") then + table.insert(array, item) + else + return false + end + end + + if #array == 6 then + -- check mac + return true, s + end + + return false +end + +-- trim a string +function trim(s) + return s:match("^%s*(.-)%s*$") +end + +-- split a string based on sep +function split_and_trim(str, sep) + local array = {} + local reg = string.format("([^%s]+)", sep) + for item in string.gmatch(str, reg) do + item_trimed = trim(item) + if string.len(item_trimed) > 0 then + table.insert(array, item_trimed) + end + end + return array +end + +-- Check whether value1 is equal to value2 +function equal(value1, value2) + return value1 == value2 +end + +-- Check whether value is in values array +function in_array(value, values) + for i=1, #values do + if (value == values[i]) then + return true, value + end + end + return false +end + +-- Check whether value is an integer and in special range +function is_integer_and_in_range(value, min_value, max_value) + if(type(value) == "string") then + local num_value = tonumber(value) + if not (num_value == nil) then + local int_value = math.floor(num_value) + if int_value == num_value then + if (min_value ~= nil) then + if (int_value <= min_value) then + return false + end + end + if (max_value ~= nil) then + if (int_value >= max_value) then + return false + end + end + + return true + end + end + end + + return false +end + +-- Rest API handler function +handles_table = { + PUT = "handle_put", + GET = "handle_get", + POST = "handle_post", + DELETE = "handle_delete" +} + +function get_validator_type(validator) + config_type = validator["config_type"] + if config_type ~= nil and type(config_type) == "function" then + config_type = nil + end + + return config_type +end + +-- set target from src based on data validator definition +function get_uci_section(configuration, validator, object_type, name) + local object_data = uci:get_all(configuration, name) + if object_data == nil then + -- check if name defined as option + local obj = {} + local found = false + uci:foreach(configuration, object_type, + function (section) + if name == section["name"] then + set_data(configuration, validator, section, obj) + found = true + return false + end + end + ) + + if found == true then + return obj + else + return nil + end + else + -- name is defined as section name + local obj = {} + obj["name"] = name + set_data(configuration, validator, object_data, obj) + + return obj + end +end + +function set_data(configuration, data_validator, src, target) + for i,v in pairs(data_validator) do + if(type(v) == "table") then + local name = v["name"] + local src_name = v["target"] + local is_list = v["is_list"] + if is_list == nil then + is_list = false + end + if src_name == nil then + src_name = name + end + local value = src[src_name] + if v["load_func"] ~= nil and type(v["load_func"]) == "function" then + value = v["load_func"](src) + end + if value ~= nil then + if is_list and type(value) ~= "table" then + value = { value } + end + + if v["item_validator"] ~= nil and type(v["item_validator"]) == "table" then + array_value = {} + local index = 1 + for j=1, #value do + local item_obj = nil + if value[j].section ~= nil then + item_obj = get_uci_section(configuration, v["item_validator"], value[j].section, value[j].name) + else + item_obj = get_uci_section(configuration, v["item_validator"], get_validator_type(v["item_validator"]), value[j]) + end + if item_obj ~= nil then + array_value[index] = item_obj + index = index + 1 + end + end + + target[name] = array_value + else + if v["validator"] ~= nil and type(v["validator"]) == "table" then + if value.section ~= nil then + target[name] = get_uci_section(configuration, value.section, value.name) + else + target[name] = get_uci_section(configuration, v["validator"], get_validator_type(v["validator"]), value) + end + else + target[name] = value + end + end + end + end + end +end + +-- get +function get_objects(type_names, configuration, type_name, validator) + local res = {} + res[type_names] = {} + + local index = 1 + uci:foreach(configuration, type_name, + function (section) + local obj = {} + obj["name"] = section[".name"] + set_data(configuration, validator, section, obj) + res[type_names][index] = obj + index = index + 1 + end + ) + + return res +end + +function handle_get_objects(type_names, configuration, type_name, validator) + if not (validate_req_method("GET")) then + return + end + + response_object(get_objects(type_names, configuration, type_name, validator)) +end + +function get_object(module_table, processors, object_type, name) + if processors ~= nil and processors[object_type] ~= nil + and processors[object_type]["get"] ~= nil + and module_table[processors[object_type]["get"]] ~= nil then + return module_table[processors[object_type]["get"]](name) + else + return get_uci_section(processors["configuration"], processors[object_type].validator, object_type, name) + end +end + +function handle_get(module_table, processors) + local uri_list = get_URI_list(7) + if uri_list == nil then + return + end + + local object_type = uri_list[#uri_list-1] + local name = uri_list[#uri_list] + local obj = get_object(module_table, processors, object_type, name) + + if obj == nil then + response_error(404, "Cannot find " .. object_type .. "[" .. name .. "]" ) + return + end + + response_object(obj) +end + +-- put +function update_object(module_table, processors, object_type, obj) + if processors ~= nil and processors[object_type] ~= nil + and processors[object_type]["update"] ~= nil + and module_table[processors[object_type]["update"]] ~= nil then + return module_table[processors[object_type]["update"]](obj) + else + local name = obj.name + res, code, msg = delete_object(module_table, processors, object_type, name) + if res == true then + return create_object(module_table, processors, object_type, obj) + end + + return false, code, msg + end +end + +function handle_put(module_table, processors) + local uri_list = get_URI_list(7) + if uri_list == nil then + return + end + + local object_type = uri_list[#uri_list-1] + local name = uri_list[#uri_list] + -- check content and get object + local obj = get_and_validate_body_object(processors[object_type].validator) + if obj == nil then + return + end + obj.name = name + + res, code, msg = update_object(module_table, processors, object_type, obj) + if res == false then + response_error(code, msg) + else + response_object(get_object(module_table, processors, object_type, name)) + end +end + +-- post +function create_uci_section(configuration, validator, object_type, obj) + -- check whether object is exist + local obj_name = obj.name + local exist_obj = uci:get_all(configuration, obj_name) + if exist_obj ~= nil then + return false, 409, "Conflict" + end + + -- create object + local create_section_name = validator["create_section_name"] + if create_section_name == nil then + create_section_name = true + end + + local obj_section = nil + local obj_section_type = object_type + local obj_section_config_type = validator["config_type"] + if obj_section_config_type ~= nil then + if type(obj_section_config_type) == "function" then + obj_section_type = obj_section_config_type(obj) + else + obj_section_type = obj_section_config_type + end + end + if create_section_name == true then + obj_section = uci:section(configuration, obj_section_type, obj_name) + else + obj_section = uci:section(configuration, obj_section_type) + end + + for i,v in pairs(validator) do + if(type(v) == "table") then + local name = v["name"] + if create_section_name == false or name ~= "name" then + if(obj[name] ~= nil) then + local target_name = v["target"] + local use_target_name = false + if(target_name == nil) then + target_name = name + else + use_target_name = true + end + local obj_value = obj[name] + if v["save_func"] ~= nil and type(v["save_func"]) == "function" then + res, obj_value = v["save_func"](obj) + if res == false then + return res, obj_value + end + end + if type(obj_value) == "table" then + -- Support creating multiple options/sections: + -- change obj_value to standard format: {{option="option_name", values={...}},} + if obj_value._standard_format == nil then + obj_value = {{option=target_name, values=obj_value},} + end + for j=1, #obj_value do + local option_name = obj_value[j].option + local section_obj = obj_value[j].values + if v["item_validator"] ~= nil and type(v["item_validator"]) == "table" then + local sub_obj_names = {} + for k=1, #section_obj do + sub_obj_names[k] = section_obj[k].name + local res, msg = create_uci_section(configuration, v["item_validator"], option_name, section_obj[k]) + if res == false then + return res, msg + end + end + uci:set_list(configuration, obj_section, option_name, sub_obj_names) + else + uci:set_list(configuration, obj_section, option_name, section_obj) + end + end + else + if v["validator"] ~= nil and type(v["validator"]) == "table" then + local res, msg = create_uci_section(configuration, v["validator"], target_name, obj_value) + if res == false then + return res, msg + end + uci:set(configuration, obj_section, target_name, obj_value.name) + else + if obj_value ~= "" then + uci:set(configuration, obj_section, target_name, obj_value) + end + end + end + end + end + end + end + + return true +end + +function create_object(module_table, processors, object_type, obj) + if processors ~= nil and processors[object_type] ~= nil + and processors[object_type]["create"] ~= nil + and module_table[processors[object_type]["create"]] ~= nil then + return module_table[processors[object_type]["create"]](obj) + else + local res, msg = create_uci_section(processors["configuration"], processors[object_type].validator, object_type, obj) + + if res == false then + uci:revert(processors["configuration"]) + return res, msg + end + + -- commit change + uci:save(processors["configuration"]) + uci:commit(processors["configuration"]) + + return true + end +end + +function handle_post(module_table, processors) + local uri_list = get_URI_list(6) + if uri_list == nil then + return + end + + local object_type = uri_list[#uri_list] + -- check content and get policy object + local obj = get_and_validate_body_object(processors[object_type].validator) + if obj == nil then + return + end + + -- check name is not empty + local name = obj.name + if name ~= nil and type(name) == "string" then + name = trim(name) + else + name = nil + end + if name == nil or name == "" then + response_error(400, "Field[name] checked failed: required") + return + end + + res, code, msg = create_object(module_table, processors, object_type, obj) + if res == false then + response_error(code, msg) + else + response_object(get_object(module_table, processors, object_type, name), 201) + end +end + +-- delete +function delete_uci_section(configuration, validator, obj, object_type) + local name = obj["name"] + local obj_section_config_type = validator["config_type"] + if obj_section_config_type ~= nil then + if type(obj_section_config_type) == "function" then + object_type = obj_section_config_type(obj) + else + object_type = obj_section_config_type + end + end + + -- delete depedency uci section + for i,v in pairs(validator) do + if(type(v) == "table") then + if v["item_validator"] ~= nil and type(v["item_validator"]) == "table" then + for j=1, #obj[v["name"]] do + local sub_obj = obj[v["name"]][j] + delete_uci_section(configuration, v["item_validator"], obj[v["name"]][j], v["name"]) + end + else + if v["validator"] ~= nil and type(v["validator"]) == "table" then + delete_uci_section(configuration, v["validator"], obj[v["name"]], v["name"]) + end + end + end + end + + -- delete uci section + uci:foreach(configuration, object_type, + function (section) + if name == section[".name"] or name == section["name"] then + for i,v in pairs(validator) do + if(type(v) == "table") then + if v["delete_func"] ~= nil and type(v["delete_func"]) == "function" then + v["delete_func"](section) + end + end + end + uci:delete(configuration, section[".name"]) + end + end + ) +end + +function delete_object(module_table, processors, object_type, name) + if processors ~= nil and processors[object_type] ~= nil + and processors[object_type]["delete"] ~= nil + and module_table[processors[object_type]["delete"]] ~= nil then + return module_table[processors[object_type]["delete"]](name) + else + local obj = get_object(module_table, processors, object_type, name) + if obj == nil then + return false, 404, object_type .. " " .. name .. " is not defined" + end + + delete_uci_section(processors["configuration"], processors[object_type].validator, obj, object_type) + + -- commit change + uci:save(processors["configuration"]) + uci:commit(processors["configuration"]) + + return true + end +end + +function handle_delete(module_table, processors) + local uri_list = get_URI_list(7) + if uri_list == nil then + return + end + + local object_type = uri_list[#uri_list-1] + local name = uri_list[#uri_list] + + res, code, msg = delete_object(module_table, processors, object_type, name) + if res == false then + response_error(code, msg) + else + response_success() + end +end + +-- Validate src with validator then set target (array) +function validate_and_set_data_array(validator, src) + if #src == 0 then + return false, "Not an array" + end + + local ret_obj = {} + for i=1, #src do + local res = false + local ret_obj_item = nil + if type(validator) == "function" then + res, ret_obj_item = validator(src[i]) + else + res, ret_obj_item = validate_and_set_data(validator, src[i]) + end + + if not res then + return false, ret_obj_item + end + ret_obj[i] = ret_obj_item + end + + return true, ret_obj +end + +-- Validate src with validator then set target (set data field with default value if applied) +-- return: +-- res: validate result +-- ret_obj: copy of src based on validator definition(res=true) or error message(res=false) +function validate_and_set_data(validator, src) + local target = {} + for i,v in pairs(validator) do + if(type(v) == "table") then + local name = v["name"] + local val_func = v["validator"] + local item_val_func = v["item_validator"] + local error_message = v["message"] + local required = v["required"] + local default = v["default"] + local target_name = name + + if required == nil then + required = false + end + + if error_message == nil then + error_message = "" + end + + local value = src[name] + if value ~= nil and type(value) == "string" then + value = trim(value) + end + + if value ~= nil and value ~= "" then + local res = true + local ret_obj = nil + if val_func ~= nil then + if type(val_func) == "function" then + res, ret_obj = val_func(value) + else + res, ret_obj = validate_and_set_data(val_func, value) + end + else + if item_val_func ~= nil then + res, ret_obj = validate_and_set_data_array(item_val_func, value) + end + end + + if res == false then + if ret_obj ~= nil and ret_obj ~= "" then + return false, "Field[" .. name .. "] checked failed: " .. error_message .. " [" .. ret_obj .. "]" + else + return false, "Field[" .. name .. "] checked failed: " .. error_message + end + else + if ret_obj ~= nil then + value = ret_obj + end + end + + target[target_name] = value + else + if required then + return false, "Field[" .. name .. "] checked failed: required" + else + -- set default value + if default ~= nil then + target[target_name] = default + end + end + end + end + end + + -- check with object_validator + local object_validator = validator["object_validator"] + if object_validator ~= nil then + return object_validator(target) + end + + return true, target +end + +-- parse URI +function get_URI_list(required_lenth) + local uri = luci.http.getenv("REQUEST_URI") + local uri_list = split_and_trim(uri, "/") + if not (#uri_list == required_lenth) then + response_error(400, "Bad request URI") + return nil + end + + return uri_list +end + +-- get request body +function get_request_body_object() + local content, size = luci.http.content() + local content_obj, err = json.parse(content) + if not (err == nil) then + response_error(400, "Body parse error: " .. err) + end + + return content_obj +end + +-- get and validate body object +function get_and_validate_body_object(validator) + local body_obj = get_request_body_object() + if body_obj == nil then + return nil + end + + local res, res_obj = validate_and_set_data(validator, body_obj) + if not res then + response_error(400, res_obj) + return nil + end + + return res_obj +end + +-- get request method +function get_req_method() + return luci.http.getenv("REQUEST_METHOD") +end + +-- check request method +function validate_req_method(method) + local res = equal(get_req_method(), method) + if not res then + response_error(405, "Method Not Allowed") + end + + return res +end + +-- response with error +function response_error(code, message) + if message == nil then + message = "" + end + luci.http.status(code, message) + luci.http.prepare_content("text/plain") + luci.http.write('{"message":"' .. message .. '"}') +end + +-- response with json object +function response_object(obj, code) + if code ~= nil then + luci.http.status(code, "") + end + luci.http.prepare_content("application/json") + luci.http.write_json(obj) +end + +-- response success message +function response_success(code) + local message = '{"message":"success"}' + if code ~= nil then + luci.http.status(code, message) + end + response_message(message) +end + +-- response with message +function response_message(message) + luci.http.prepare_content("text/plain") + luci.http.write(message) +end \ No newline at end of file diff --git a/cnf/build/set_proxy b/cnf/build/set_proxy new file mode 100644 index 0000000..1ad2350 --- /dev/null +++ b/cnf/build/set_proxy @@ -0,0 +1,2 @@ +# set docker proxy with below line +#docker_proxy= diff --git a/cnf/build/system b/cnf/build/system new file mode 100644 index 0000000..5165430 --- /dev/null +++ b/cnf/build/system @@ -0,0 +1,7 @@ +config system + option log_file '/var/log/mylog' + option timezone 'UTC' + option ttylogin '0' + option log_size '64' + option urandom_seed '0' +EOF diff --git a/cnf/test/openwrtclient_fw_test.go b/cnf/test/openwrtclient_fw_test.go new file mode 100644 index 0000000..e692e61 --- /dev/null +++ b/cnf/test/openwrtclient_fw_test.go @@ -0,0 +1,1138 @@ +package openwrt + +import ( + "openwrt" + "reflect" + "testing" + "flag" + "encoding/json" + "fmt" + "os" +) + +var fw openwrt.FirewallClient +var available_zone_1 string +var available_zone_2 string + +func TestMain(m *testing.M) { + servIp := flag.String("ip", "10.244.0.18", "SDEWAN CNF Management IP Address") + flag.Parse() + + client := openwrt.NewOpenwrtClient(*servIp, "root", "") + fw = openwrt.FirewallClient{client} + available_zone_1 = "wan" + available_zone_2 = "lan" + + os.Exit(m.Run()) +} + +// Error handler +func handleError(t *testing.T, err error, name string, expectedErr bool, errorCode int) { + if (err != nil) { + if (expectedErr) { + switch err.(type) { + case *openwrt.OpenwrtError: + if(errorCode != err.(*openwrt.OpenwrtError).Code) { + t.Errorf("Test case '%s': expected '%d', but got '%d'", name, errorCode, err.(*openwrt.OpenwrtError).Code) + } else { + fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message) + } + default: + t.Errorf("Test case '%s': expected openwrt.OpenwrtError, but got '%s'", name, reflect.TypeOf(err).String()) + } + } else { + t.Errorf("Test case '%s': expected success, but got '%s'", name, reflect.TypeOf(err).String()) + } + } else { + if (expectedErr) { + t.Errorf("Test case '%s': expected error code '%d', but success", name, errorCode) + } + } +} + +func printError(err error) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message) + default: + fmt.Printf("%s\n", reflect.TypeOf(err).String()) + } +} + +// Firewall Zone API Test +func TestGetZones(t *testing.T) { + res, err := fw.GetZones() + if res == nil { + printError(err) + t.Errorf("Test case GetZones: can not get firewall zones") + return + } + + if len(res.Zones) == 0 { + fmt.Printf("Test case GetZones: no zone defined") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetZone(t *testing.T) { + tcases := []struct { + name string + zone string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetAvailableZone", + zone: available_zone_1, + }, + { + name: "GetFoolRule", + zone: "foo_zone", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.GetZone(tcase.zone) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolZone(t *testing.T) { + tcases := []struct { + name string + zone openwrt.SdewanFirewallZone + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + zone: openwrt.SdewanFirewallZone{Name:" "}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidNetwork", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", Network:[]string{"fool_name"}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidMasq", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", Masq:"2"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidMasqSrc", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", MasqSrc:[]string{"256.0.0.0/0"}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidMasqDest", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", MasqDest:[]string{"256.0.0.0/0"}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidMasqAllowInvalid", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", MasqAllowInvalid:"2"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidMtuFix", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", MtuFix:"2"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidInput", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", Input:"FOOL"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidForward", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", Forward:"FOOL"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidOutput", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", Output:"FOOL"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidFamily", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", Family:"FOOL"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidSubnet", + zone: openwrt.SdewanFirewallZone{Name:"test_zone", Subnet:[]string{"256.0.0.0/0"}}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := fw.CreateZone(tcase.zone) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolZone(t *testing.T) { + tcases := []struct { + name string + zone string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + zone: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + zone: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + err := fw.DeleteZone(tcase.zone) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteAvailableZone(t *testing.T) { + var err error + var zone_name = "test_zone_1" + var redirect_name = "test_redirect_1" + var zone = openwrt.SdewanFirewallZone{Name:zone_name, Network:[]string{"wan", "lan"}, Input:"REJECT", Masq:"1"} + var redirect = openwrt.SdewanFirewallRedirect{Name:redirect_name, Src:zone_name, SrcDPort:"22000", Dest:"lan", DestPort:"22", Proto:"tcp", Target:"DNAT"} + + // create zone + _, err = fw.GetZone(zone_name) + if (err == nil) { + err = fw.DeleteZone(zone_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestDeleteAvailableZone: failed to delete zone '%s'", zone_name) + return + } + } + + _, err = fw.CreateZone(zone) + if (err != nil) { + printError(err) + t.Errorf("Test case TestDeleteAvailableZone: failed to create zone '%s'", zone_name) + return + } + + // create redirect + _, err = fw.GetRedirect(redirect_name) + if (err == nil) { + err = fw.DeleteRedirect(redirect_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestDeleteAvailableZone: failed to delete redirect '%s'", redirect_name) + return + } + } + + _, err = fw.CreateRedirect(redirect) + if (err != nil) { + printError(err) + t.Errorf("Test case TestDeleteAvailableZone: failed to create redirect '%s'", redirect_name) + return + } + + // try to delete a used zone + err = fw.DeleteZone(zone_name) + if (err != nil) { + printError(err) + } else { + t.Errorf("Test case TestDeleteAvailableZone: error to delete zone '%s'", zone_name) + return + } + + // clean + err = fw.DeleteRedirect(redirect_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestDeleteAvailableZone: failed to delete redirect '%s'", redirect_name) + return + } + + err = fw.DeleteZone(zone_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestDeleteAvailableZone: failed to delete zone '%s'", zone_name) + return + } +} + +func TestUpdateFoolZone(t *testing.T) { + tcases := []struct { + name string + zone openwrt.SdewanFirewallZone + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + zone: openwrt.SdewanFirewallZone{Name:"fool_name"}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.UpdateZone(tcase.zone) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestZone(t *testing.T) { + var err error + var ret_zone *openwrt.SdewanFirewallZone + + var zone_name = "test_zone" + var zone = openwrt.SdewanFirewallZone{Name:zone_name, Network:[]string{"wan", "lan"}, Input:"REJECT", Masq:"1"} + var update_zone = openwrt.SdewanFirewallZone{Name:zone_name, Network:[]string{"lan"}, Input:"ACCEPT", MasqSrc:[]string{"!0.0.0.0/0", "172.16.1.1"}, Subnet:[]string{"172.16.0.1/24"}} + + _, err = fw.GetZone(zone_name) + if (err == nil) { + err = fw.DeleteZone(zone_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestZone: failed to delete zone '%s'", zone_name) + return + } + } + + // Create zone + ret_zone, err = fw.CreateZone(zone) + if (err != nil) { + printError(err) + t.Errorf("Test case TestZone: failed to create zone '%s'", zone_name) + return + } else { + p_data, _ := json.Marshal(ret_zone) + fmt.Printf("Created Zone: %s\n", string(p_data)) + } + + ret_zone, err = fw.GetZone(zone_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestZone: failed to get created zone") + return + } else { + if( ret_zone.Input != "REJECT" ) { + t.Errorf("Test case TestZone: failed to create zone") + return + } + } + + // Update zone + ret_zone, err = fw.UpdateZone(update_zone) + if (err != nil) { + printError(err) + t.Errorf("Test case TestZone: failed to update zone '%s'", zone_name) + return + } else { + p_data, _ := json.Marshal(ret_zone) + fmt.Printf("Updated Zone: %s\n", string(p_data)) + } + + ret_zone, err = fw.GetZone(zone_name) + if (err != nil) { + t.Errorf("Test case TestZone: failed to get updated zone") + return + } else { + if( ret_zone.Input != "ACCEPT" ) { + t.Errorf("Test case TestZone: failed to update zone") + return + } + } + + // Delete zone + err = fw.DeleteZone(zone_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestZone: failed to delete zone '%s'", zone_name) + return + } + + ret_zone, err = fw.GetZone(zone_name) + if (err == nil) { + t.Errorf("Test case TestZone: failed to delete zone") + return + } +} + +// Firewall Forwarding API Test +func TestGetForwardings(t *testing.T) { + res, err := fw.GetForwardings() + if res == nil { + printError(err) + t.Errorf("Test case GetForwardings: can not get firewall forwardings") + return + } + + if len(res.Forwardings) == 0 { + fmt.Printf("Test case GetForwardings: no forwarding defined") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetForwarding(t *testing.T) { + tcases := []struct { + name string + forwarding string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetFoolForwarding", + forwarding: "foo_forwarding", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.GetForwarding(tcase.forwarding) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolForwarding(t *testing.T) { + tcases := []struct { + name string + forwarding openwrt.SdewanFirewallForwarding + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + forwarding: openwrt.SdewanFirewallForwarding{Name:" "}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "NoSrc", + forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Dest: available_zone_1}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidSrc", + forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Src:"fool_zone", Dest: available_zone_1}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "NoDest", + forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Src: available_zone_1}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidDest", + forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Src:available_zone_1, Dest:"fool_zone"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidFamily", + forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Family:"fool_family", Src:available_zone_1, Dest:available_zone_2}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := fw.CreateForwarding(tcase.forwarding) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolForwarding(t *testing.T) { + tcases := []struct { + name string + forwarding string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + forwarding: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + forwarding: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + err := fw.DeleteForwarding(tcase.forwarding) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestUpdateFoolForwarding(t *testing.T) { + tcases := []struct { + name string + forwarding openwrt.SdewanFirewallForwarding + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + forwarding: openwrt.SdewanFirewallForwarding{Name:"fool_name", Src:available_zone_1, Dest:available_zone_2}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.UpdateForwarding(tcase.forwarding) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestForwarding(t *testing.T) { + var err error + var ret_forwarding *openwrt.SdewanFirewallForwarding + + var forwarding_name = "test_forwarding" + var forwarding = openwrt.SdewanFirewallForwarding{Name:forwarding_name, Src:available_zone_1, Dest:available_zone_2} + var update_forwarding = openwrt.SdewanFirewallForwarding{Name:forwarding_name, Src:available_zone_2, Dest:available_zone_1} + + _, err = fw.GetForwarding(forwarding_name) + if (err == nil) { + err = fw.DeleteForwarding(forwarding_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestForwarding: failed to delete forwarding '%s'", forwarding_name) + return + } + } + + // Create forwarding + ret_forwarding, err = fw.CreateForwarding(forwarding) + if (err != nil) { + printError(err) + t.Errorf("Test case TestForwarding: failed to create forwarding '%s'", forwarding_name) + return + } else { + p_data, _ := json.Marshal(ret_forwarding) + fmt.Printf("Created Forwarding: %s\n", string(p_data)) + } + + ret_forwarding, err = fw.GetForwarding(forwarding_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestForwarding: failed to get created forwarding") + return + } else { + if( ret_forwarding.Dest != available_zone_2 ) { + t.Errorf("Test case TestForwarding: failed to create forwarding") + return + } + } + + // Update forwarding + ret_forwarding, err = fw.UpdateForwarding(update_forwarding) + if (err != nil) { + printError(err) + t.Errorf("Test case TestForwarding: failed to update forwarding '%s'", forwarding_name) + return + } else { + p_data, _ := json.Marshal(ret_forwarding) + fmt.Printf("Updated Forwarding: %s\n", string(p_data)) + } + + ret_forwarding, err = fw.GetForwarding(forwarding_name) + if (err != nil) { + t.Errorf("Test case TestForwarding: failed to get updated forwarding") + return + } else { + if( ret_forwarding.Dest != available_zone_1 ) { + t.Errorf("Test case TestForwarding: failed to update forwarding") + return + } + } + + // Delete forwarding + err = fw.DeleteForwarding(forwarding_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestForwarding: failed to delete forwarding '%s'", forwarding_name) + return + } + + ret_forwarding, err = fw.GetForwarding(forwarding_name) + if (err == nil) { + t.Errorf("Test case TestForwarding: failed to delete forwarding") + return + } +} + +// Firewall Redirect API Test +func TestGetRedirects(t *testing.T) { + res, err := fw.GetRedirects() + if res == nil { + printError(err) + t.Errorf("Test case GetRedirects: can not get firewall redirects") + return + } + + if len(res.Redirects) == 0 { + fmt.Printf("Test case GetRedirects: no redirect defined") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetRedirect(t *testing.T) { + tcases := []struct { + name string + redirect string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetFoolRedirect", + redirect: "foo_redirect", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.GetRedirect(tcase.redirect) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolRedirect(t *testing.T) { + tcases := []struct { + name string + redirect openwrt.SdewanFirewallRedirect + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + redirect: openwrt.SdewanFirewallRedirect{Name:" "}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "No Src for DNAT", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "No SrcDIp for SNAT", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"SNAT"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "No Dest for SNAT", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcDIp:"192.168.1.1", DestPort:"22", Proto:"tcp", Target:"SNAT"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Src", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Src:"fool_zone"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SrcIp", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcIp:"192.168.1.300"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SrcDIp", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcDIp:"192.168.1.300"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SrcMac", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcMac:"00:00"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SrcPort", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcPort:"1a"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SrcDPort", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcDPort:"1a"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Proto", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Proto:"fool_protocol"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Dest", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Dest:"fool_zone"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid DestIp", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", DestIp:"192.168.1a.1"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid DestPort", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", DestPort:"0"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Target", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Target:"fool_NAT"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Family", + redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Family:"fool_family"}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := fw.CreateRedirect(tcase.redirect) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolRedirect(t *testing.T) { + tcases := []struct { + name string + redirect string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + redirect: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + redirect: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + err := fw.DeleteRedirect(tcase.redirect) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestUpdateFoolRedirect(t *testing.T) { + tcases := []struct { + name string + redirect openwrt.SdewanFirewallRedirect + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + redirect: openwrt.SdewanFirewallRedirect{Name:"fool_name", Src:available_zone_1, SrcDPort:"22000", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.UpdateRedirect(tcase.redirect) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestRedirect(t *testing.T) { + var err error + var ret_redirect *openwrt.SdewanFirewallRedirect + + var redirect_name = "test_redirect" + var redirect = openwrt.SdewanFirewallRedirect{Name:redirect_name, Src:available_zone_1, SrcDPort:"22000", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"} + var update_redirect = openwrt.SdewanFirewallRedirect{Name:redirect_name, Src:available_zone_1, SrcDPort:"22001", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"} + + _, err = fw.GetRedirect(redirect_name) + if (err == nil) { + err = fw.DeleteRedirect(redirect_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRedirect: failed to delete redirect '%s'", redirect_name) + return + } + } + + // Create redirect + ret_redirect, err = fw.CreateRedirect(redirect) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRedirect: failed to create redirect '%s'", redirect_name) + return + } else { + p_data, _ := json.Marshal(ret_redirect) + fmt.Printf("Created Redirect: %s\n", string(p_data)) + } + + ret_redirect, err = fw.GetRedirect(redirect_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRedirect: failed to get created redirect") + return + } else { + if( ret_redirect.SrcDPort != "22000" ) { + t.Errorf("Test case TestRedirect: failed to create redirect") + return + } + } + + // Update redirect + ret_redirect, err = fw.UpdateRedirect(update_redirect) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRedirect: failed to update redirect '%s'", redirect_name) + return + } else { + p_data, _ := json.Marshal(ret_redirect) + fmt.Printf("Updated Redirect: %s\n", string(p_data)) + } + + ret_redirect, err = fw.GetRedirect(redirect_name) + if (err != nil) { + t.Errorf("Test case TestRedirect: failed to get updated redirect") + return + } else { + if( ret_redirect.SrcDPort != "22001" ) { + t.Errorf("Test case TestRedirect: failed to update redirect") + return + } + } + + // Delete redirect + err = fw.DeleteRedirect(redirect_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRedirect: failed to delete redirect '%s'", redirect_name) + return + } + + ret_redirect, err = fw.GetRedirect(redirect_name) + if (err == nil) { + t.Errorf("Test case TestRedirect: failed to delete redirect") + return + } +} + +// Firewall Rule API Test +func TestGetRules(t *testing.T) { + res, err := fw.GetRules() + if res == nil { + printError(err) + t.Errorf("Test case GetRules: can not get firewall rules") + return + } + + if len(res.Rules) == 0 { + fmt.Printf("Test case GetRules: no rule defined") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetRule(t *testing.T) { + tcases := []struct { + name string + rule string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetFoolRule", + rule: "foo_rule", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.GetRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolRule(t *testing.T) { + tcases := []struct { + name string + rule openwrt.SdewanFirewallRule + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + rule: openwrt.SdewanFirewallRule{Name:" "}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "No SetMark and SetXmark for DNAT", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", Target:"MARK"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SRC", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", Src:"fool_zone"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SrcIp", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcIp:"191.11.aa.10"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Valid SrcMac", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcMac:"01:02:a0:0F:aC:EE"}, + }, + { + name: "Invalid SrcMac", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcMac:"11:rt:00:00:00:00"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid SrcPort", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcPort:"0"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Proto", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", Proto:"fool_proto"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid IcmpType", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", IcmpType:[]string{"network-unreachable", "address-mask-reply", "fool_icmp_type"}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Dest", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", Dest:"fool_zone"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid DestIp", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", DestIp:"192.168.1.2.1"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid DestPort", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", DestPort:"aa"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Target", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", Target:"fool_target"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Invalid Family", + rule: openwrt.SdewanFirewallRule{Name:"test_rule", Family:"fool_family"}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := fw.CreateRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolRule(t *testing.T) { + tcases := []struct { + name string + rule string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + rule: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + rule: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + err := fw.DeleteRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestUpdateFoolRule(t *testing.T) { + tcases := []struct { + name string + rule openwrt.SdewanFirewallRule + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + rule: openwrt.SdewanFirewallRule{Name:"fool_name", Src:available_zone_1, Proto:"udp", DestPort:"68", Target:"REJECT", Family:"ipv4"}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := fw.UpdateRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestRule(t *testing.T) { + var err error + var ret_rule *openwrt.SdewanFirewallRule + + var rule_name = "test_rule" + var rule = openwrt.SdewanFirewallRule{Name:rule_name, Src:available_zone_1, Proto:"udp", DestPort:"68", Target:"REJECT", Family:"ipv4"} + var update_rule = openwrt.SdewanFirewallRule{Name:rule_name, Src:available_zone_1, IcmpType:[]string{"host-redirect", "echo-request"}, Proto:"udp", DestPort:"68", Target:"ACCEPT", Family:"ipv4"} + + _, err = fw.GetRule(rule_name) + if (err == nil) { + err = fw.DeleteRule(rule_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name) + return + } + } + + // Create rule + ret_rule, err = fw.CreateRule(rule) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to create rule '%s'", rule_name) + return + } else { + p_data, _ := json.Marshal(ret_rule) + fmt.Printf("Created Rule: %s\n", string(p_data)) + } + + ret_rule, err = fw.GetRule(rule_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to get created rule") + return + } else { + if( ret_rule.Target != "REJECT" ) { + t.Errorf("Test case TestRule: failed to create rule") + return + } + } + + // Update rule + ret_rule, err = fw.UpdateRule(update_rule) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to update rule '%s'", rule_name) + return + } else { + p_data, _ := json.Marshal(ret_rule) + fmt.Printf("Updated Rule: %s\n", string(p_data)) + } + + ret_rule, err = fw.GetRule(rule_name) + if (err != nil) { + t.Errorf("Test case TestRule: failed to get updated rule") + return + } else { + if( ret_rule.Target != "ACCEPT" ) { + t.Errorf("Test case TestRule: failed to update rule") + return + } + } + + // Delete rule + err = fw.DeleteRule(rule_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name) + return + } + + ret_rule, err = fw.GetRule(rule_name) + if (err == nil) { + t.Errorf("Test case TestRule: failed to delete rule") + return + } +} diff --git a/cnf/test/openwrtclient_ipsec_test.go b/cnf/test/openwrtclient_ipsec_test.go new file mode 100644 index 0000000..5500e30 --- /dev/null +++ b/cnf/test/openwrtclient_ipsec_test.go @@ -0,0 +1,605 @@ +package openwrt + +import ( + "openwrt" + "reflect" + "testing" + "flag" + "encoding/json" + "fmt" + "os" +) + +var ic openwrt.IpsecClient +var available_proposal_1 string +var available_proposal_2 string +var available_site string + +func TestMain(m *testing.M) { + servIp := flag.String("ip", "10.244.0.18", "SDEWAN CNF Management IP Address") + flag.Parse() + + var err error + client := openwrt.NewOpenwrtClient(*servIp, "root", "") + ic = openwrt.IpsecClient{client} + available_proposal_1 = "test_proposal1" + available_proposal_2 = "test_proposal2" + available_site = "test_default_site" + + // create default proposals + var proposall = openwrt.SdewanIpsecProposal{Name:available_proposal_1, EncryptionAlgorithm:"aes128", HashAlgorithm:"sha256", DhGroup:"modp3072",} + var proposal2 = openwrt.SdewanIpsecProposal{Name:available_proposal_2, EncryptionAlgorithm:"aes256", HashAlgorithm:"sha128", DhGroup:"modp4096",} + + _, err = ic.GetProposal(available_proposal_1) + if (err != nil) { + // Create proposal + _, err = ic.CreateProposal(proposall) + if (err != nil) { + printError(err) + return + } + } + + _, err = ic.GetProposal(available_proposal_2) + if (err != nil) { + // Create proposal + _, err = ic.CreateProposal(proposal2) + if (err != nil) { + printError(err) + return + } + } + + // create default site + var site = openwrt.SdewanIpsecSite{ + Name:available_site, + Gateway:"10.0.1.2", + PreSharedKey:"test_key", + AuthenticationMethod:"psk", + LocalIdentifier:"C=CH, O=strongSwan, CN=peer", + RemoteIdentifier:"C=CH, O=strongSwan, CN=peerB", + ForceCryptoProposal:"true", + LocalPublicCert:"public cert\npublic cert value", + CryptoProposal:[]string{available_proposal_1}, + Connections:[]openwrt.SdewanIpsecConnection{{ + Name:available_site+"_conn", + Type:"tunnel", + Mode:"start", + LocalSubnet:"192.168.1.1/24", + LocalSourceip:"10.0.1.1", + RemoteSubnet:"192.168.0.1/24", + RemoteSourceip:"10.0.1.2", + CryptoProposal:[]string{available_proposal_1, available_proposal_2}, + }, + }, + } + + _, err = ic.GetSite(available_site) + if (err == nil) { + // Update site + _, err = ic.UpdateSite(site) + if (err != nil) { + printError(err) + return + } + } else { + // Create site + _, err = ic.CreateSite(site) + if (err != nil) { + printError(err) + return + } + } + + var ret = m.Run() + + // clean + ic.DeleteSite(available_site) + ic.DeleteProposal(available_proposal_1) + ic.DeleteProposal(available_proposal_2) + + os.Exit(ret) +} + +// Error handler +func handleError(t *testing.T, err error, name string, expectedErr bool, errorCode int) { + if (err != nil) { + if (expectedErr) { + switch err.(type) { + case *openwrt.OpenwrtError: + if(errorCode != err.(*openwrt.OpenwrtError).Code) { + t.Errorf("Test case '%s': expected '%d', but got '%d'", name, errorCode, err.(*openwrt.OpenwrtError).Code) + } else { + fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message) + } + default: + t.Errorf("Test case '%s': expected openwrt.OpenwrtError, but got '%s'", name, reflect.TypeOf(err).String()) + } + } else { + t.Errorf("Test case '%s': expected success, but got '%s'", name, reflect.TypeOf(err).String()) + } + } else { + if (expectedErr) { + t.Errorf("Test case '%s': expected error code '%d', but success", name, errorCode) + } + } +} + +func printError(err error) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message) + default: + fmt.Printf("%s\n", reflect.TypeOf(err).String()) + } +} + +// IpSec Site API Test +func TestGetSites(t *testing.T) { + res, err := ic.GetSites() + if res == nil { + printError(err) + t.Errorf("Test case GetSites: can not get IpSec sites") + return + } + + if len(res.Sites) == 0 { + fmt.Printf("Test case GetSites: no site found") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetSite(t *testing.T) { + tcases := []struct { + name string + site string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetAvailableSite", + site: available_site, + }, + { + name: "GetFoolSite", + site: "foo_site", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := ic.GetSite(tcase.site) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolSite(t *testing.T) { + tcases := []struct { + name string + site openwrt.SdewanIpsecSite + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + site: openwrt.SdewanIpsecSite{Name:" "}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidConnectionType", + site: openwrt.SdewanIpsecSite{Name:"test_site", + Connections:[]openwrt.SdewanIpsecConnection{{Name:"test_connection_name", Type:"fool_type",},},}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidCryptoProtocolInConnection", + site: openwrt.SdewanIpsecSite{Name:"test_site", + Connections:[]openwrt.SdewanIpsecConnection{{Name:"test_connection_name", Type:"tunnel", CryptoProposal:[]string{available_proposal_1, "fool_protocol"}},},}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "InvalidCryptoProtocolInSite", + site: openwrt.SdewanIpsecSite{Name:"test_site", CryptoProposal:[]string{available_proposal_1, "fool_protocol"},}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := ic.CreateSite(tcase.site) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolSite(t *testing.T) { + tcases := []struct { + name string + site string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + site: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + site: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + err := ic.DeleteSite(tcase.site) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestUpdateFoolSite(t *testing.T) { + tcases := []struct { + name string + site openwrt.SdewanIpsecSite + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + site: openwrt.SdewanIpsecSite{Name:"fool_name"}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := ic.UpdateSite(tcase.site) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestSite(t *testing.T) { + var err error + var ret_site *openwrt.SdewanIpsecSite + + var site_name = "test_site" + var conn_name1 = "test_name1" + var conn_name2 = "test_name2" + var conn_name3 = "test_name3" + var site = openwrt.SdewanIpsecSite{ + Name:site_name, + Gateway:"10.0.1.2", + PreSharedKey:"test_key", + AuthenticationMethod:"psk", + LocalIdentifier:"C=CH, O=strongSwan, CN=peer", + RemoteIdentifier:"C=CH, O=strongSwan, CN=peerB", + ForceCryptoProposal:"true", + CryptoProposal:[]string{available_proposal_1}, + Connections:[]openwrt.SdewanIpsecConnection{{ + Name:conn_name1, + Type:"tunnel", + Mode:"start", + LocalSubnet:"192.168.1.1/24", + LocalSourceip:"10.0.1.1", + RemoteSubnet:"192.168.0.1/24", + RemoteSourceip:"10.0.1.2", + CryptoProposal:[]string{available_proposal_1, available_proposal_2}, + }, + }, + } + + var update_site = openwrt.SdewanIpsecSite{ + Name:site_name, + Gateway:"10.0.21.2", + PreSharedKey:"test_key_2", + AuthenticationMethod:"psk", + LocalIdentifier:"C=CH, O=strongSwan, CN=peer", + RemoteIdentifier:"C=CH, O=strongSwan, CN=peerB", + ForceCryptoProposal:"true", + CryptoProposal:[]string{available_proposal_1, available_proposal_2}, + Connections:[]openwrt.SdewanIpsecConnection{{ + Name:conn_name1, + Type:"tunnel", + Mode:"start", + LocalSubnet:"192.168.21.1/24", + LocalSourceip:"10.0.1.1", + RemoteSubnet:"192.168.0.1/24", + RemoteSourceip:"10.0.21.2", + CryptoProposal:[]string{available_proposal_2}, + }, + { + Name:conn_name2, + Type:"transport", + Mode:"start", + LocalSubnet:"192.168.31.1/24", + LocalSourceip:"10.0.11.1", + RemoteSubnet:"192.168.10.1/24", + RemoteSourceip:"10.0.31.2", + CryptoProposal:[]string{available_proposal_1, available_proposal_2}, + }, + { + Name:conn_name3, + Type:"tunnel", + Mode:"start", + LocalSubnet:"192.168.41.1/24", + LocalSourceip:"10.0.11.1", + RemoteSubnet:"192.168.10.1/24", + RemoteSourceip:"10.0.41.2", + CryptoProposal:[]string{available_proposal_1}, + }, + }, + } + + _, err = ic.GetSite(site_name) + if (err == nil) { + err = ic.DeleteSite(site_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestSite: failed to delete site '%s'", site_name) + return + } + } + + // Create site + ret_site, err = ic.CreateSite(site) + if (err != nil) { + printError(err) + t.Errorf("Test case TestSite: failed to create site '%s'", site_name) + return + } else { + p_data, _ := json.Marshal(ret_site) + fmt.Printf("Created Site: %s\n", string(p_data)) + } + + ret_site, err = ic.GetSite(site_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestSite: failed to get created site") + return + } else { + if( len(ret_site.Connections) != 1 || ret_site.Connections[0].LocalSubnet != "192.168.1.1/24" ) { + t.Errorf("Test case TestSite: failed to create site") + return + } + } + + // Update site + ret_site, err = ic.UpdateSite(update_site) + if (err != nil) { + printError(err) + t.Errorf("Test case TestSite: failed to update site '%s'", site_name) + return + } else { + p_data, _ := json.Marshal(ret_site) + fmt.Printf("Updated Site: %s\n", string(p_data)) + } + + ret_site, err = ic.GetSite(site_name) + if (err != nil) { + t.Errorf("Test case TestSite: failed to get updated site") + return + } else { + if( len(ret_site.Connections) != 3 || ret_site.Connections[0].LocalSubnet != "192.168.21.1/24" ) { + t.Errorf("Test case TestSite: failed to update site") + return + } + } + + // Delete site + err = ic.DeleteSite(site_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestSite: failed to delete site '%s'", site_name) + return + } + + ret_site, err = ic.GetSite(site_name) + if (err == nil) { + t.Errorf("Test case TestSite: failed to delete site") + return + } +} + +// IpSec Proposal API Test +func TestGetProposals(t *testing.T) { + res, err := ic.GetProposals() + if res == nil { + printError(err) + t.Errorf("Test case TestGetProposals: can not get IpSec proposals") + return + } + + if len(res.Proposals) == 0 { + fmt.Printf("Test case TestGetProposals: no proposal found") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetProposal(t *testing.T) { + tcases := []struct { + name string + proposal string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetAvailableProposal", + proposal: available_proposal_1, + }, + { + name: "GetFoolProposal", + proposal: "foo_proposal", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := ic.GetProposal(tcase.proposal) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolProposal(t *testing.T) { + tcases := []struct { + name string + proposal openwrt.SdewanIpsecProposal + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + proposal: openwrt.SdewanIpsecProposal{Name:" "}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := ic.CreateProposal(tcase.proposal) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolProposal(t *testing.T) { + tcases := []struct { + name string + proposal string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + proposal: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "DeleteInUsedProposal", + proposal: available_proposal_1, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + proposal: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + err := ic.DeleteProposal(tcase.proposal) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestUpdateFoolProposal(t *testing.T) { + tcases := []struct { + name string + proposal openwrt.SdewanIpsecProposal + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + proposal: openwrt.SdewanIpsecProposal{Name:"fool_name"}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := ic.UpdateProposal(tcase.proposal) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestProposal(t *testing.T) { + var err error + var ret_proposal *openwrt.SdewanIpsecProposal + + var proposal_name = "test_proposal" + var proposal = openwrt.SdewanIpsecProposal{Name:proposal_name,EncryptionAlgorithm:"aes128", HashAlgorithm:"sha256", DhGroup:"modp3072",} + var update_proposal = openwrt.SdewanIpsecProposal{Name:proposal_name,EncryptionAlgorithm:"aes256", HashAlgorithm:"sha128", DhGroup:"modp3072",} + + _, err = ic.GetProposal(proposal_name) + if (err == nil) { + err = ic.DeleteProposal(proposal_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestProposal: failed to delete proposal '%s'", proposal_name) + return + } + } + + // Create proposal + ret_proposal, err = ic.CreateProposal(proposal) + if (err != nil) { + printError(err) + t.Errorf("Test case TestProposal: failed to create proposal '%s'", proposal_name) + return + } else { + p_data, _ := json.Marshal(ret_proposal) + fmt.Printf("Created Proposal: %s\n", string(p_data)) + } + + ret_proposal, err = ic.GetProposal(proposal_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestProposal: failed to get created proposal") + return + } else { + if( ret_proposal.EncryptionAlgorithm != "aes128" ) { + t.Errorf("Test case TestProposal: failed to create proposal") + return + } + } + + // Update proposal + ret_proposal, err = ic.UpdateProposal(update_proposal) + if (err != nil) { + printError(err) + t.Errorf("Test case TestProposal: failed to update proposal '%s'", proposal_name) + return + } else { + p_data, _ := json.Marshal(ret_proposal) + fmt.Printf("Updated Proposal: %s\n", string(p_data)) + } + + ret_proposal, err = ic.GetProposal(proposal_name) + if (err != nil) { + t.Errorf("Test case TestProposal: failed to get updated proposal") + return + } else { + if( ret_proposal.EncryptionAlgorithm != "aes256" ) { + t.Errorf("Test case TestProposal: failed to update proposal") + return + } + } + + // Delete proposal + err = ic.DeleteProposal(proposal_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestProposal: failed to delete proposal '%s'", proposal_name) + return + } + + ret_proposal, err = ic.GetProposal(proposal_name) + if (err == nil) { + t.Errorf("Test case TestProposal: failed to delete proposal") + return + } +} diff --git a/cnf/test/openwrtclient_test.go b/cnf/test/openwrtclient_test.go new file mode 100644 index 0000000..17299b4 --- /dev/null +++ b/cnf/test/openwrtclient_test.go @@ -0,0 +1,606 @@ +package openwrt + +import ( + "openwrt" + "reflect" + "testing" + "flag" + "encoding/json" + "fmt" + "os" +) + +var mwan3 openwrt.Mwan3Client +var service openwrt.ServiceClient +var available_policy string +var available_interface string +var available_interfaceb string + +func TestMain(m *testing.M) { + servIp := flag.String("ip", "10.244.0.18", "SDEWAN CNF Management IP Address") + flag.Parse() + + client := openwrt.NewOpenwrtClient(*servIp, "root", "") + mwan3 = openwrt.Mwan3Client{client} + service = openwrt.ServiceClient{client} + available_policy = "balanced" + available_interface = "wan" + available_interfaceb = "wanb" + + os.Exit(m.Run()) +} + +// Error handler +func handleError(t *testing.T, err error, name string, expectedErr bool, errorCode int) { + if (err != nil) { + if (expectedErr) { + switch err.(type) { + case *openwrt.OpenwrtError: + if(errorCode != err.(*openwrt.OpenwrtError).Code) { + t.Errorf("Test case '%s': expected '%d', but got '%d'", name, errorCode, err.(*openwrt.OpenwrtError).Code) + } else { + fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message) + } + default: + t.Errorf("Test case '%s': expected openwrt.OpenwrtError, but got '%s'", name, reflect.TypeOf(err).String()) + } + } else { + t.Errorf("Test case '%s': expected success, but got '%s'", name, reflect.TypeOf(err).String()) + } + } else { + if (expectedErr) { + t.Errorf("Test case '%s': expected error code '%d', but success", name, errorCode) + } + } +} + +func printError(err error) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message) + default: + fmt.Printf("%s\n", reflect.TypeOf(err).String()) + } +} + +// Service API Test +func TestGetServices(t *testing.T) { + res, _ := service.GetAvailableServices() + + if res == nil { + t.Errorf("Test case GetServices: no available services") + return + } + + if !reflect.DeepEqual(res.Services, []string{"mwan3", "firewall", "ipsec"}) { + t.Errorf("Test case GetServices: error available services returned") + } +} + +func TestExecuteService(t *testing.T) { + tcases := []struct { + name string + service string + action string + expectedErr bool + expectedErrCode int + }{ + { + name: "Foo_Service", + service: "foo_service", + action: "restart", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Foo_action", + service: "mwan3", + action: "foo_action", + expectedErr: true, + expectedErrCode: 400, + }, + } + for _, tcase := range tcases { + _, err := service.ExecuteService(tcase.service, tcase.action) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +// MWAN3 Rule API Test +func TestGetInterfaceStatus(t *testing.T) { + res, _ := mwan3.GetInterfaceStatus() + if res == nil { + t.Errorf("Test case GetInterfaceStatus: can not get interfaces status") + } +} + +func TestGetRules(t *testing.T) { + res, _ := mwan3.GetRules() + if res == nil { + t.Errorf("Test case GetRules: can not get mwan3 rules") + return + } + + if len(res.Rules) == 0 { + t.Errorf("Test case GetRules: no rule defined") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetRule(t *testing.T) { + tcases := []struct { + name string + rule string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetAvailableRule", + rule: "default_rule", + }, + { + name: "GetFoolRule", + rule: "foo_rule", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := mwan3.GetRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolRule(t *testing.T) { + tcases := []struct { + name string + rule openwrt.SdewanRule + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolPolicy", + rule: openwrt.SdewanRule{Name:" ", Policy: "fool_policy"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_src_ip", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, SrcIp: "10.200.200.500"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_src_port", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, SrcPort: "-1"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_dest_ip", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, DestIp: "10.200"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_dest_port", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, DestPort: "0"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_proto", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Proto: "fool_protocol"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_family", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Family: "fool_family"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_sticky", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Sticky: "2"}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Wrong_timeout", + rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Timeout: "-1"}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := mwan3.CreateRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolRule(t *testing.T) { + tcases := []struct { + name string + rule string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + rule: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + rule: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + err := mwan3.DeleteRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestUpdateFoolRule(t *testing.T) { + tcases := []struct { + name string + rule openwrt.SdewanRule + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + rule: openwrt.SdewanRule{Name:"fool_name", Policy: available_policy}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := mwan3.UpdateRule(tcase.rule) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestRule(t *testing.T) { + var err error + var ret_rule *openwrt.SdewanRule + + var rule_name = "test_rule" + var rule = openwrt.SdewanRule{Name:rule_name, Policy: available_policy, SrcIp: "10.10.10.10/24", SrcPort: "80"} + var update_rule = openwrt.SdewanRule{Name:rule_name, Policy: available_policy, SrcIp: "100.100.100.100/24", SrcPort: "8000", DestIp: "172.172.172.172/16", DestPort: "8080"} + + _, err = mwan3.GetRule(rule_name) + if (err == nil) { + err = mwan3.DeleteRule(rule_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name) + return + } + } + + // Create rule + ret_rule, err = mwan3.CreateRule(rule) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to create rule '%s'", rule_name) + return + } else { + p_data, _ := json.Marshal(ret_rule) + fmt.Printf("Created Rule: %s\n", string(p_data)) + } + + ret_rule, err = mwan3.GetRule(rule_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to get created rule") + return + } else { + if( ret_rule.SrcPort != "80" ) { + t.Errorf("Test case TestRule: failed to create rule") + return + } + } + + // Update rule + ret_rule, err = mwan3.UpdateRule(update_rule) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to update rule '%s'", rule_name) + return + } else { + p_data, _ := json.Marshal(ret_rule) + fmt.Printf("Updated Rule: %s\n", string(p_data)) + } + + ret_rule, err = mwan3.GetRule(rule_name) + if (err != nil) { + t.Errorf("Test case TestRule: failed to get updated rule") + return + } else { + if( ret_rule.SrcIp != "100.100.100.100/24" ) { + t.Errorf("Test case TestRule: failed to update rule") + return + } + } + + // Delete rule + err = mwan3.DeleteRule(rule_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name) + return + } + + ret_rule, err = mwan3.GetRule(rule_name) + if (err == nil) { + t.Errorf("Test case TestRule: failed to delete rule") + return + } +} + +// MWAN3 Policy API Tests +func TestGetPolicies(t *testing.T) { + res, _ := mwan3.GetPolicies() + if res == nil { + t.Errorf("Test case GetPolicies: can not get mwan3 policies") + return + } + + if len(res.Policies) == 0 { + t.Errorf("Test case GetPolicies: no policy defined") + return + } + + p_data, _ := json.Marshal(res) + fmt.Printf("%s\n", string(p_data)) +} + +func TestGetPolicy(t *testing.T) { + tcases := []struct { + name string + policy string + expectedErr bool + expectedErrCode int + }{ + { + name: "GetAvailablePolicy", + policy: available_policy, + }, + { + name: "GetFoolPolicy", + policy: "foo_policy", + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := mwan3.GetPolicy(tcase.policy) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestCreateFoolPolicy(t *testing.T) { + tcases := []struct { + name string + policy openwrt.SdewanPolicy + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + policy: openwrt.SdewanPolicy{Name:" ", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "EmptyMember", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "Policy-Conflict", + policy: openwrt.SdewanPolicy{Name:"wan_only", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}}, + expectedErr: true, + expectedErrCode: 409, + }, + { + name: "WrongMember-interface", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:""}}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "WrongMember-interface-name", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:"fool-name", Metric:"1", Weight:"2"}}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "WrongMember-duplicate-interface", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}, {Interface:available_interface, Metric:"2", Weight:"3"}}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "WrongMember-Metric", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:""}}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "WrongMember-Weight", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"2"}}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "WrongMember-Metric", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"-1", Weight:"2"}}}, + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "WrongMember-Weight", + policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"2", Weight:"0"}}}, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + _, err := mwan3.CreatePolicy(tcase.policy) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestDeleteFoolPolicy(t *testing.T) { + tcases := []struct { + name string + policy string + expectedErr bool + expectedErrCode int + }{ + { + name: "EmptyName", + policy: "", + expectedErr: true, + expectedErrCode: 400, + }, + { + name: "FoolName", + policy: "fool_name", + expectedErr: true, + expectedErrCode: 404, + }, + { + name: "Delete-InUsePolicy", + policy: available_policy, + expectedErr: true, + expectedErrCode: 400, + }, + } + + for _, tcase := range tcases { + err := mwan3.DeletePolicy(tcase.policy) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestUpdateFoolPolicy(t *testing.T) { + tcases := []struct { + name string + policy openwrt.SdewanPolicy + expectedErr bool + expectedErrCode int + }{ + { + name: "FoolName", + policy: openwrt.SdewanPolicy{Name:"fool_name", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}}, + expectedErr: true, + expectedErrCode: 404, + }, + } + + for _, tcase := range tcases { + _, err := mwan3.UpdatePolicy(tcase.policy) + handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode) + } +} + +func TestPolicy(t *testing.T) { + var err error + var ret_policy *openwrt.SdewanPolicy + + var policy_name = "test_policy" + var policy = openwrt.SdewanPolicy{Name:policy_name, Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}} + var update_policy = openwrt.SdewanPolicy{Name:policy_name, Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"2", Weight:"3"}, {Interface:available_interfaceb, Metric:"2", Weight:"3"}}} + + _, err = mwan3.GetPolicy(policy_name) + if (err == nil) { + err = mwan3.DeletePolicy(policy_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestPolicy: failed to delete policy '%s'", policy_name) + return + } + } + + // Create policy + ret_policy, err = mwan3.CreatePolicy(policy) + if (err != nil) { + printError(err) + t.Errorf("Test case TestPolicy: failed to create policy '%s'", policy_name) + return + } else { + p_data, _ := json.Marshal(ret_policy) + fmt.Printf("Created Policy: %s\n", string(p_data)) + } + + ret_policy, err = mwan3.GetPolicy(policy_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestPolicy: failed to get created policy") + return + } else { + if( len(ret_policy.Members) != 1 || ret_policy.Members[0].Metric != "1" ) { + t.Errorf("Test case TestPolicy: failed to create policy") + return + } + } + + // Update policy + ret_policy, err = mwan3.UpdatePolicy(update_policy) + if (err != nil) { + printError(err) + t.Errorf("Test case TestPolicy: failed to update policy '%s'", policy_name) + return + } else { + p_data, _ := json.Marshal(ret_policy) + fmt.Printf("Updated Policy: %s\n", string(p_data)) + } + + ret_policy, err = mwan3.GetPolicy(policy_name) + if (err != nil) { + t.Errorf("Test case TestPolicy: failed to get updated policy") + return + } else { + if( len(ret_policy.Members) != 2 || ret_policy.Members[1].Metric != "2" ) { + t.Errorf("Test case TestPolicy: failed to update policy") + return + } + } + + // Delete policy + err = mwan3.DeletePolicy(policy_name) + if (err != nil) { + printError(err) + t.Errorf("Test case TestPolicy: failed to delete policy '%s'", policy_name) + return + } + + ret_policy, err = mwan3.GetPolicy(policy_name) + if (err == nil) { + t.Errorf("Test case TestPolicy: failed to delete policy") + return + } +} diff --git a/cnf/test/test.go b/cnf/test/test.go new file mode 100644 index 0000000..c353c16 --- /dev/null +++ b/cnf/test/test.go @@ -0,0 +1,163 @@ +package main + +import ( + "openwrt" + "fmt" + "encoding/json" + "strconv" +) + +func testGetServices(serv *openwrt.ServiceClient) { + res, err := serv.GetAvailableServices() + if(err != nil) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("Openwrt Error: %s\n", err.Error()) + fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code) + default: + fmt.Printf("%s\n", err.Error()) + } + } else { + servs, err := json.Marshal(res) + if(err != nil) { + fmt.Printf("%s\n", err.Error()) + } else { + fmt.Printf("%s\n", string(servs)) + } + } +} + +func testPutService(serv *openwrt.ServiceClient) { + res, err := serv.ExecuteService("mwan3", "start") + if(err != nil) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("Openwrt Error: %s\n", err.Error()) + fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code) + default: + fmt.Printf("%s\n", err.Error()) + } + } else { + fmt.Printf("Result: %s\n", strconv.FormatBool(res)) + } +} + +func testGetInterfaceStatus(mwan3 *openwrt.Mwan3Client) { + is, err := mwan3.GetInterfaceStatus() + if(err != nil) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("Openwrt Error: %s\n", err.Error()) + fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code) + default: + fmt.Printf("%s\n", err.Error()) + } + } else { + fmt.Printf("Wan interface status: %s\n", is.Interfaces["wan"].Status) + is_data, err := json.Marshal(is) + if(err != nil) { + fmt.Printf("%s\n", err.Error()) + } else { + fmt.Printf("%s\n", string(is_data)) + } + } + + is = nil +} + +func testGetPolicies(mwan3 *openwrt.Mwan3Client) { + res, err := mwan3.GetPolicies() + if(err != nil) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("Openwrt Error: %s\n", err.Error()) + fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code) + default: + fmt.Printf("%s\n", err.Error()) + } + } else { + p_data, err := json.Marshal(res) + if(err != nil) { + fmt.Printf("%s\n", err.Error()) + } else { + fmt.Printf("%s\n", string(p_data)) + } + } +} + +func testGetPolicy(mwan3 *openwrt.Mwan3Client, policy string) { + res, err := mwan3.GetPolicy(policy) + if(err != nil) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("Openwrt Error: %s\n", err.Error()) + fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code) + default: + fmt.Printf("%s\n", err.Error()) + } + } else { + p_data, err := json.Marshal(res) + if(err != nil) { + fmt.Printf("%s\n", err.Error()) + } else { + fmt.Printf("%s\n", string(p_data)) + } + } +} + +func testGetRules(mwan3 *openwrt.Mwan3Client) { + res, err := mwan3.GetRules() + if(err != nil) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("Openwrt Error: %s\n", err.Error()) + fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code) + default: + fmt.Printf("%s\n", err.Error()) + } + } else { + p_data, err := json.Marshal(res) + if(err != nil) { + fmt.Printf("%s\n", err.Error()) + } else { + fmt.Printf("%s\n", string(p_data)) + } + } +} + +func testGetRule(mwan3 *openwrt.Mwan3Client, rule string) { + res, err := mwan3.GetRule(rule) + if(err != nil) { + switch err.(type) { + case *openwrt.OpenwrtError: + fmt.Printf("Openwrt Error: %s\n", err.Error()) + fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code) + default: + fmt.Printf("%s\n", err.Error()) + } + } else { + p_data, err := json.Marshal(res) + if(err != nil) { + fmt.Printf("%s\n", err.Error()) + } else { + fmt.Printf("%s\n", string(p_data)) + } + } +} + +func main() { + client := openwrt.NewOpenwrtClient("10.244.0.18", "root", "") + mwan3 := openwrt.Mwan3Client{OpenwrtClient: client} + service := openwrt.ServiceClient{client} + + testGetServices(&service) + + testGetInterfaceStatus(&mwan3) + testGetPolicies(&mwan3) + testGetPolicy(&mwan3, "balanced") + testGetPolicy(&mwan3, "foo_policy") + + testGetRules(&mwan3) + testGetRule(&mwan3, "https") + testGetRule(&mwan3, "foo_rule") +} diff --git a/openwrt/firewall.go b/openwrt/firewall.go new file mode 100644 index 0000000..58a36e1 --- /dev/null +++ b/openwrt/firewall.go @@ -0,0 +1,437 @@ +package openwrt + +import ( + "encoding/json" +) + +const ( + firewallBaseURL = "sdewan/firewall/v1/" +) + +type FirewallClient struct { + OpenwrtClient *openwrtClient +} + +// Firewall Zones +type SdewanFirewallZone struct { + Name string `json:"name"` + Network []string `json:"network"` + Masq string `json:"masq"` + MasqSrc []string `json:"masq_src"` + MasqDest []string `json:"masq_dest"` + MasqAllowInvalid string `json:"masq_allow_invalid"` + MtuFix string `json:"mtu_fix"` + Input string `json:"input"` + Forward string `json:"forward"` + Output string `json:"output"` + Family string `json:"family"` + Subnet []string `json:"subnet"` + ExtraSrc string `json:"extra_src"` + ExtraDest string `json:"etra_dest"` +} + +type SdewanFirewallZones struct { + Zones []SdewanFirewallZone `json:"zones"` +} + +// Firewall Forwarding +type SdewanFirewallForwarding struct { + Name string `json:"name"` + Src string `json:"src"` + Dest string `json:"dest"` + Family string `json:"family"` +} + +type SdewanFirewallForwardings struct { + Forwardings []SdewanFirewallForwarding `json:"forwardings"` +} + +// Firewall Rule +type SdewanFirewallRule struct { + Name string `json:"name"` + Src string `json:"src"` + SrcIp string `json:"src_ip"` + SrcMac string `json:"src_mac"` + SrcPort string `json:"src_port"` + Proto string `json:"proto"` + IcmpType []string `json:"icmp_type"` + Dest string `json:"dest"` + DestIp string `json:"dest_ip"` + DestPort string `json:"dest_port"` + Mark string `json:"mark"` + Target string `json:"target"` + SetMark string `json:"set_mark"` + SetXmark string `json:"set_xmark"` + Family string `json:"family"` + Extra string `json:"extra"` +} + +type SdewanFirewallRules struct { + Rules []SdewanFirewallRule `json:"rules"` +} + +// Firewall Redirect +type SdewanFirewallRedirect struct { + Name string `json:"name"` + Src string `json:"src"` + SrcIp string `json:"src_ip"` + SrcDIp string `json:"src_dip"` + SrcMac string `json:"src_mac"` + SrcPort string `json:"src_port"` + SrcDPort string `json:"src_dport"` + Proto string `json:"proto"` + Dest string `json:"dest"` + DestIp string `json:"dest_ip"` + DestPort string `json:"dest_port"` + Mark string `json:"mark"` + Target string `json:"target"` + Family string `json:"family"` +} + +type SdewanFirewallRedirects struct { + Redirects []SdewanFirewallRedirect `json:"redirects"` +} + +// Zone APIs +// get zones +func (f *FirewallClient) GetZones() (*SdewanFirewallZones, error) { + var response string + var err error + response, err = f.OpenwrtClient.Get(firewallBaseURL + "zones") + if (err != nil) { + return nil, err + } + + var sdewanFirewallZones SdewanFirewallZones + err = json.Unmarshal([]byte(response), &sdewanFirewallZones) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallZones, nil +} + +// get zone +func (m *FirewallClient) GetZone(zone string) (*SdewanFirewallZone, error) { + var response string + var err error + response, err = m.OpenwrtClient.Get(firewallBaseURL + "zone/" + zone) + if (err != nil) { + return nil, err + } + + var sdewanFirewallZone SdewanFirewallZone + err = json.Unmarshal([]byte(response), &sdewanFirewallZone) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallZone, nil +} + +// create zone +func (m *FirewallClient) CreateZone(zone SdewanFirewallZone) (*SdewanFirewallZone, error) { + var response string + var err error + zone_obj, _ := json.Marshal(zone) + response, err = m.OpenwrtClient.Post(firewallBaseURL + "zone", string(zone_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallZone SdewanFirewallZone + err = json.Unmarshal([]byte(response), &sdewanFirewallZone) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallZone, nil +} + +// delete zone +func (m *FirewallClient) DeleteZone(zone_name string) (error) { + _, err := m.OpenwrtClient.Delete(firewallBaseURL + "zone/" + zone_name) + if (err != nil) { + return err + } + + return nil +} + +// update zone +func (m *FirewallClient) UpdateZone(zone SdewanFirewallZone) (*SdewanFirewallZone, error) { + var response string + var err error + zone_obj, _ := json.Marshal(zone) + zone_name := zone.Name + response, err = m.OpenwrtClient.Put(firewallBaseURL + "zone/" + zone_name, string(zone_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallZone SdewanFirewallZone + err = json.Unmarshal([]byte(response), &sdewanFirewallZone) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallZone, nil +} + +// Rule APIs +// get rules +func (f *FirewallClient) GetRules() (*SdewanFirewallRules, error) { + var response string + var err error + response, err = f.OpenwrtClient.Get(firewallBaseURL + "rules") + if (err != nil) { + return nil, err + } + + var sdewanFirewallRules SdewanFirewallRules + err = json.Unmarshal([]byte(response), &sdewanFirewallRules) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRules, nil +} + +// get rule +func (m *FirewallClient) GetRule(rule string) (*SdewanFirewallRule, error) { + var response string + var err error + response, err = m.OpenwrtClient.Get(firewallBaseURL + "rule/" + rule) + if (err != nil) { + return nil, err + } + + var sdewanFirewallRule SdewanFirewallRule + err = json.Unmarshal([]byte(response), &sdewanFirewallRule) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRule, nil +} + +// create rule +func (m *FirewallClient) CreateRule(rule SdewanFirewallRule) (*SdewanFirewallRule, error) { + var response string + var err error + rule_obj, _ := json.Marshal(rule) + response, err = m.OpenwrtClient.Post(firewallBaseURL + "rule", string(rule_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallRule SdewanFirewallRule + err = json.Unmarshal([]byte(response), &sdewanFirewallRule) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRule, nil +} + +// delete rule +func (m *FirewallClient) DeleteRule(rule_name string) (error) { + _, err := m.OpenwrtClient.Delete(firewallBaseURL + "rule/" + rule_name) + if (err != nil) { + return err + } + + return nil +} + +// update rule +func (m *FirewallClient) UpdateRule(rule SdewanFirewallRule) (*SdewanFirewallRule, error) { + var response string + var err error + rule_obj, _ := json.Marshal(rule) + rule_name := rule.Name + response, err = m.OpenwrtClient.Put(firewallBaseURL + "rule/" + rule_name, string(rule_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallRule SdewanFirewallRule + err = json.Unmarshal([]byte(response), &sdewanFirewallRule) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRule, nil +} + +// Forwarding APIs +// get forwardings +func (f *FirewallClient) GetForwardings() (*SdewanFirewallForwardings, error) { + var response string + var err error + response, err = f.OpenwrtClient.Get(firewallBaseURL + "forwardings") + if (err != nil) { + return nil, err + } + + var sdewanFirewallForwardings SdewanFirewallForwardings + err = json.Unmarshal([]byte(response), &sdewanFirewallForwardings) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallForwardings, nil +} + +// get forwarding +func (m *FirewallClient) GetForwarding(forwarding string) (*SdewanFirewallForwarding, error) { + var response string + var err error + response, err = m.OpenwrtClient.Get(firewallBaseURL + "forwarding/" + forwarding) + if (err != nil) { + return nil, err + } + + var sdewanFirewallForwarding SdewanFirewallForwarding + err = json.Unmarshal([]byte(response), &sdewanFirewallForwarding) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallForwarding, nil +} + +// create forwarding +func (m *FirewallClient) CreateForwarding(forwarding SdewanFirewallForwarding) (*SdewanFirewallForwarding, error) { + var response string + var err error + forwarding_obj, _ := json.Marshal(forwarding) + response, err = m.OpenwrtClient.Post(firewallBaseURL + "forwarding", string(forwarding_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallForwarding SdewanFirewallForwarding + err = json.Unmarshal([]byte(response), &sdewanFirewallForwarding) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallForwarding, nil +} + +// delete forwarding +func (m *FirewallClient) DeleteForwarding(forwarding_name string) (error) { + _, err := m.OpenwrtClient.Delete(firewallBaseURL + "forwarding/" + forwarding_name) + if (err != nil) { + return err + } + + return nil +} + +// update forwarding +func (m *FirewallClient) UpdateForwarding(forwarding SdewanFirewallForwarding) (*SdewanFirewallForwarding, error) { + var response string + var err error + forwarding_obj, _ := json.Marshal(forwarding) + forwarding_name := forwarding.Name + response, err = m.OpenwrtClient.Put(firewallBaseURL + "forwarding/" + forwarding_name, string(forwarding_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallForwarding SdewanFirewallForwarding + err = json.Unmarshal([]byte(response), &sdewanFirewallForwarding) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallForwarding, nil +} + +// Redirect APIs +// get redirects +func (f *FirewallClient) GetRedirects() (*SdewanFirewallRedirects, error) { + var response string + var err error + response, err = f.OpenwrtClient.Get(firewallBaseURL + "redirects") + if (err != nil) { + return nil, err + } + + var sdewanFirewallRedirects SdewanFirewallRedirects + err = json.Unmarshal([]byte(response), &sdewanFirewallRedirects) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRedirects, nil +} + +// get redirect +func (m *FirewallClient) GetRedirect(redirect string) (*SdewanFirewallRedirect, error) { + var response string + var err error + response, err = m.OpenwrtClient.Get(firewallBaseURL + "redirect/" + redirect) + if (err != nil) { + return nil, err + } + + var sdewanFirewallRedirect SdewanFirewallRedirect + err = json.Unmarshal([]byte(response), &sdewanFirewallRedirect) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRedirect, nil +} + +// create redirect +func (m *FirewallClient) CreateRedirect(redirect SdewanFirewallRedirect) (*SdewanFirewallRedirect, error) { + var response string + var err error + redirect_obj, _ := json.Marshal(redirect) + response, err = m.OpenwrtClient.Post(firewallBaseURL + "redirect", string(redirect_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallRedirect SdewanFirewallRedirect + err = json.Unmarshal([]byte(response), &sdewanFirewallRedirect) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRedirect, nil +} + +// delete redirect +func (m *FirewallClient) DeleteRedirect(redirect_name string) (error) { + _, err := m.OpenwrtClient.Delete(firewallBaseURL + "redirect/" + redirect_name) + if (err != nil) { + return err + } + + return nil +} + +// update redirect +func (m *FirewallClient) UpdateRedirect(redirect SdewanFirewallRedirect) (*SdewanFirewallRedirect, error) { + var response string + var err error + redirect_obj, _ := json.Marshal(redirect) + redirect_name := redirect.Name + response, err = m.OpenwrtClient.Put(firewallBaseURL + "redirect/" + redirect_name, string(redirect_obj)) + if (err != nil) { + return nil, err + } + + var sdewanFirewallRedirect SdewanFirewallRedirect + err = json.Unmarshal([]byte(response), &sdewanFirewallRedirect) + if (err != nil) { + return nil, err + } + + return &sdewanFirewallRedirect, nil +} diff --git a/openwrt/ipsec.go b/openwrt/ipsec.go new file mode 100644 index 0000000..7a1eb34 --- /dev/null +++ b/openwrt/ipsec.go @@ -0,0 +1,233 @@ +package openwrt + +import ( + "encoding/json" +) + +const ( + ipsecBaseURL = "sdewan/ipsec/v1/" +) + +type IpsecClient struct { + OpenwrtClient *openwrtClient +} + +// Proposals +type SdewanIpsecProposal struct { + Name string `json:"name"` + EncryptionAlgorithm string `json:"encryption_algorithm"` + HashAlgorithm string `json:"hash_algorithm"` + DhGroup string `json:"dh_group"` +} + +type SdewanIpsecProposals struct { + Proposals []SdewanIpsecProposal `json:"proposals"` +} + +// Sites +type SdewanIpsecConnection struct { + Name string `json:"name"` + Type string `json:"type"` + Mode string `json:"mode"` + LocalSubnet string `json:"local_subnet"` + LocalNat string `json:"local_nat"` + LocalSourceip string `json:"local_sourceip"` + LocalUpdown string `json:"local_updown"` + LocalFirewall string `json:"local_firewall"` + RemoteSubnet string `json:"remote_subnet"` + RemoteSourceip string `json:"remote_sourceip"` + RemoteUpdown string `json:"remote_updown"` + RemoteFirewall string `json:"remote_firewall"` + CryptoProposal []string `json:"crypto_proposal"` +} + +type SdewanIpsecSite struct { + Name string `json:"name"` + Gateway string `json:"gateway"` + PreSharedKey string `json:"pre_shared_key"` + AuthenticationMethod string `json:"authentication_method"` + LocalIdentifier string `json:"local_identifier"` + RemoteIdentifier string `json:"remote_identifier"` + CryptoProposal []string `json:"crypto_proposal"` + ForceCryptoProposal string `json:"force_crypto_proposal"` + LocalPublicCert string `json:"local_public_cert"` + LocalPrivateCert string `json:"local_private_cert"` + SharedCa string `json:"shared_ca"` + Connections []SdewanIpsecConnection `json:"connections"` +} + +type SdewanIpsecSites struct { + Sites []SdewanIpsecSite `json:"sites"` +} + +// Proposal APIs +// get proposals +func (f *IpsecClient) GetProposals() (*SdewanIpsecProposals, error) { + var response string + var err error + response, err = f.OpenwrtClient.Get(ipsecBaseURL + "proposals") + if (err != nil) { + return nil, err + } + + var sdewanIpsecProposals SdewanIpsecProposals + err = json.Unmarshal([]byte(response), &sdewanIpsecProposals) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecProposals, nil +} + +// get proposal +func (m *IpsecClient) GetProposal(proposal string) (*SdewanIpsecProposal, error) { + var response string + var err error + response, err = m.OpenwrtClient.Get(ipsecBaseURL + "proposal/" + proposal) + if (err != nil) { + return nil, err + } + + var sdewanIpsecProposal SdewanIpsecProposal + err = json.Unmarshal([]byte(response), &sdewanIpsecProposal) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecProposal, nil +} + +// create proposal +func (m *IpsecClient) CreateProposal(proposal SdewanIpsecProposal) (*SdewanIpsecProposal, error) { + var response string + var err error + proposal_obj, _ := json.Marshal(proposal) + response, err = m.OpenwrtClient.Post(ipsecBaseURL + "proposal", string(proposal_obj)) + if (err != nil) { + return nil, err + } + + var sdewanIpsecProposal SdewanIpsecProposal + err = json.Unmarshal([]byte(response), &sdewanIpsecProposal) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecProposal, nil +} + +// delete proposal +func (m *IpsecClient) DeleteProposal(proposal_name string) (error) { + _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "proposal/" + proposal_name) + if (err != nil) { + return err + } + + return nil +} + +// update proposal +func (m *IpsecClient) UpdateProposal(proposal SdewanIpsecProposal) (*SdewanIpsecProposal, error) { + var response string + var err error + proposal_obj, _ := json.Marshal(proposal) + proposal_name := proposal.Name + response, err = m.OpenwrtClient.Put(ipsecBaseURL + "proposal/" + proposal_name, string(proposal_obj)) + if (err != nil) { + return nil, err + } + + var sdewanIpsecProposal SdewanIpsecProposal + err = json.Unmarshal([]byte(response), &sdewanIpsecProposal) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecProposal, nil +} + +// Site APIs +// get sites +func (f *IpsecClient) GetSites() (*SdewanIpsecSites, error) { + var response string + var err error + response, err = f.OpenwrtClient.Get(ipsecBaseURL + "sites") + if (err != nil) { + return nil, err + } + + var sdewanIpsecSites SdewanIpsecSites + err = json.Unmarshal([]byte(response), &sdewanIpsecSites) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecSites, nil +} + +// get site +func (m *IpsecClient) GetSite(site string) (*SdewanIpsecSite, error) { + var response string + var err error + response, err = m.OpenwrtClient.Get(ipsecBaseURL + "site/" + site) + if (err != nil) { + return nil, err + } + + var sdewanIpsecSite SdewanIpsecSite + err = json.Unmarshal([]byte(response), &sdewanIpsecSite) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecSite, nil +} + +// create site +func (m *IpsecClient) CreateSite(site SdewanIpsecSite) (*SdewanIpsecSite, error) { + var response string + var err error + site_obj, _ := json.Marshal(site) + response, err = m.OpenwrtClient.Post(ipsecBaseURL + "site", string(site_obj)) + if (err != nil) { + return nil, err + } + + var sdewanIpsecSite SdewanIpsecSite + err = json.Unmarshal([]byte(response), &sdewanIpsecSite) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecSite, nil +} + +// delete site +func (m *IpsecClient) DeleteSite(site_name string) (error) { + _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "site/" + site_name) + if (err != nil) { + return err + } + + return nil +} + +// update site +func (m *IpsecClient) UpdateSite(site SdewanIpsecSite) (*SdewanIpsecSite, error) { + var response string + var err error + site_obj, _ := json.Marshal(site) + site_name := site.Name + response, err = m.OpenwrtClient.Put(ipsecBaseURL + "site/" + site_name, string(site_obj)) + if (err != nil) { + return nil, err + } + + var sdewanIpsecSite SdewanIpsecSite + err = json.Unmarshal([]byte(response), &sdewanIpsecSite) + if (err != nil) { + return nil, err + } + + return &sdewanIpsecSite, nil +} diff --git a/readme.md b/readme.md index 04e35aa..d1f9295 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ develop the firewall and the ipsec functions. Mwan3Conf is validated by k8s api admission webhook. For each created Sdewan instance, the controller creates a pod, a configmap -and a service for the instance. The pod runs openswrt which provides network +and a service for the instance. The pod runs openwrt which provides network services, i.e. sdwan, firewall, ipsec etc. The configmap stores the network interface information and the entrypoint.sh. @@ -51,6 +51,14 @@ The simple installation steps: 1. kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.11.0/cert-manager.yaml 2. kubectl apply -f sdewan-deploy.yaml +## Create Sdewan CNF docker image +1. update build/set_proxy file with required proxy for docker build +2. execute below commands to generate Sdewan CNF docker image which tagged with 'openwrt-1806-mwan3' +``` +cd build +sudo bash build_image.sh +``` + ## References - https://book.kubebuilder.io/