From 0282b2b6ca22efee2ba84287ae61880300ebb2c2 Mon Sep 17 00:00:00 2001 From: NavneetBarwal-RA Date: Tue, 2 Dec 2025 19:45:44 +0530 Subject: [PATCH] Modified the earlier logic of applying uCentral Config to VYOS ie.(load(config.boot file) + merge) To new logic ( retrieve(necessary configs) + load(necessary configs + Received Config from Ucentral) Due to this logic the data path will not get affected for configuration changes. Also Added the vyos_version.uc to prevent migration scripts from running. --- .../rootfs/etc/ucentral/vyos-info.json | 5 +- .../rootfs/usr/share/ucentral/ucentral.uc | 31 +++--- .../usr/share/ucentral/vyos/config_prepare.uc | 36 ++++++- .../share/ucentral/vyos/https_server_api.uc | 99 ++++++++++++++----- .../usr/share/ucentral/vyos/templates/pki.uc | 54 ++++++++++ .../share/ucentral/vyos/templates/service.uc | 58 ++++++++++- .../ucentral/vyos/templates/services/https.uc | 36 +++++++ .../share/ucentral/vyos/templates/system.uc | 70 +++++++++++++ .../share/ucentral/vyos/templates/version.uc | 3 + 9 files changed, 341 insertions(+), 51 deletions(-) create mode 100644 ucentral-client/rootfs/usr/share/ucentral/vyos/templates/pki.uc create mode 100644 ucentral-client/rootfs/usr/share/ucentral/vyos/templates/services/https.uc create mode 100644 ucentral-client/rootfs/usr/share/ucentral/vyos/templates/system.uc create mode 100644 ucentral-client/rootfs/usr/share/ucentral/vyos/templates/version.uc diff --git a/ucentral-client/rootfs/etc/ucentral/vyos-info.json b/ucentral-client/rootfs/etc/ucentral/vyos-info.json index 999ccd9..20f4593 100644 --- a/ucentral-client/rootfs/etc/ucentral/vyos-info.json +++ b/ucentral-client/rootfs/etc/ucentral/vyos-info.json @@ -1,6 +1,5 @@ { - "op": "merge", - "host": "https://192.168.76.195", - "key": "MY-HTTPS-API-PLAINTEXT-KEY" + "host": "https://192.168.76.195", + "key": "MY-HTTPS-API-PLAINTEXT-KEY" } diff --git a/ucentral-client/rootfs/usr/share/ucentral/ucentral.uc b/ucentral-client/rootfs/usr/share/ucentral/ucentral.uc index 8e1ec3d..1909122 100755 --- a/ucentral-client/rootfs/usr/share/ucentral/ucentral.uc +++ b/ucentral-client/rootfs/usr/share/ucentral/ucentral.uc @@ -6,9 +6,8 @@ push(REQUIRE_SEARCH_PATH, let schemareader = require("schemareader"); let fs = require("fs"); let ubus = require("ubus").connect(); - let vyos = require("vyos.config_prepare"); - +let vyos_api = require("vyos.https_server_api"); let inputfile = fs.open(ARGV[0], "r"); let inputjson = json(inputfile.read("all")); let custom_config = (split(ARGV[0], ".")[0] != "/etc/ucentral/ucentral"); @@ -27,32 +26,26 @@ if (fs.stat(args_path)) { f.close(); } -let op = (ARGV.length > 1 && ARGV[1] != "-") ? ARGV[1] : (args.op ?? null); let host = (ARGV.length > 2 && ARGV[2] != "-") ? ARGV[2] : (args.host ?? null); let key = (ARGV.length > 3 && ARGV[3] != "-") ? ARGV[3] : (args.key ?? null); -if (!op || !host || !key) { +if (!host || !key) { print("Missing op/host/key. Provide them in /etc/ucentral/vyos-info.json or pass '-' placeholders and ensure file exists.\n"); exit(1); } try { - for (let cmd in [ 'rm -rf /tmp/ucentral', - 'mkdir /tmp/ucentral', - 'rm /tmp/dnsmasq.conf', - '/etc/init.d/spotfilter stop', - 'touch /tmp/dnsmasq.conf' ]) - system(cmd); - let state = schemareader.validate(inputjson, logs); - let vyos_config_payload = vyos.vyos_render(state); - let scope = { - vyos_config_payload, op, host, key - }; - let rc = include('vyos/https_server_api.uc', scope); - /* TODO: Return Handling to be done yet */ - if(rc != 0){ - error = 0; + let op_arg = { }; + vyos_config_payload = vyos.vyos_render(state); + op_arg.string = vyos_config_payload; + let op = "load"; + let rc = vyos_api.vyos_api_call(op_arg, op, host, key); + if(rc != ''){ + rc = json(rc); + } + if(rc != '' && rc.success == false){ + error = 1; } } diff --git a/ucentral-client/rootfs/usr/share/ucentral/vyos/config_prepare.uc b/ucentral-client/rootfs/usr/share/ucentral/vyos/config_prepare.uc index d089fd4..cd0eef0 100755 --- a/ucentral-client/rootfs/usr/share/ucentral/vyos/config_prepare.uc +++ b/ucentral-client/rootfs/usr/share/ucentral/vyos/config_prepare.uc @@ -1,7 +1,7 @@ // This file is to generatre full VyOS style configuration from uCentral config let fs = require("fs"); - +let vyos_api = require("vyos.https_server_api"); function load_capabilities() { let capabfile = fs.open("/etc/ucentral/capabilities.json", "r"); if (!capabfile) @@ -49,6 +49,23 @@ function convert_lease_time_to_seconds(s, def){ if(u=="d") return n*86400; return def; } +function vyos_retrieve_info(op_arg, op) +{ + let args_path = "/etc/ucentral/vyos-info.json"; + let args = {}; + if (fs.stat(args_path)) { + let f = fs.open(args_path, "r"); + args = json(f.read("all")); + f.close(); + } + + let host = args.host; + let key = args.key; + let resp = vyos_api.vyos_api_call(op_arg, op, host, key); + //TODO:Check Return Value and handle response from here + let jsn = json(resp); + return jsn; +} return { vyos_render: function(config) { @@ -63,6 +80,12 @@ return { if (type(capab.network.lan) == "array" && length(capab.network.lan) > 0) lan_ifname = capab.network.lan[0]; } + let op_arg = { }; + let op = "showConfig"; + op_arg.path = ["pki"]; + + let rc = vyos_retrieve_info(op_arg, op); + let pki = render('templates/pki.uc', {rc}); let interfaces = render('templates/interface.uc', { config, @@ -70,14 +93,21 @@ return { lan_ifname }); + op_arg.path = ["system", "login"]; + let systeminfo = vyos_retrieve_info(op_arg, op); + let system = render('templates/system.uc',{systeminfo}); + let nat = render('templates/nat.uc', { config, wan_ifname, network_base }); + op_arg.path = ["service", "https"]; + let https = vyos_retrieve_info(op_arg, op); let services = render('templates/service.uc', { config, + https, split_ip_prefix, network_base, add_host, @@ -88,7 +118,9 @@ return { int_to_tuple, convert_lease_time_to_seconds }); + //TODO: Need to understand Firmware Upgrade and Migration Logic of VyOS then modify this + let vyos_version = render('templates/version.uc'); - return interfaces + "\n" + nat + "\n" + services + "\n"; + return interfaces + "\n" + nat + "\n" + services + "\n" + pki + "\n" + system + "\n" + vyos_version + "\n"; } }; diff --git a/ucentral-client/rootfs/usr/share/ucentral/vyos/https_server_api.uc b/ucentral-client/rootfs/usr/share/ucentral/vyos/https_server_api.uc index 2cfba87..049e21d 100755 --- a/ucentral-client/rootfs/usr/share/ucentral/vyos/https_server_api.uc +++ b/ucentral-client/rootfs/usr/share/ucentral/vyos/https_server_api.uc @@ -1,37 +1,84 @@ -#!/usr/bin/ucode -push(REQUIRE_SEARCH_PATH, - "/usr/lib/ucode/*.so", - "/usr/share/ucentral/*.uc"); +// This provides support to call VyOS Https Server API's as per operation mode let fs = require("fs"); - -if (!key) { fprintf(stderr, "Missing API key\n"); exit(2); } -/* TODO: pass as container environment variable for load */ -let loadapi_payload_obj = { op: "load", file: "/opt/vyatta/etc/config/config.boot" }; -let loadapi_payload_str = sprintf("%J", loadapi_payload_obj); -let api_payload_obj = { op: op, string: vyos_config_payload }; -let api_payload_str = sprintf("%J", api_payload_obj); function quoteForShell(s) { if (s == null) return "''"; + let parts = split(s, "'"); return "'" + join("'\"'\"'", parts) + "'"; } -let url = host + "/config-file"; -printf("url is %s\n",url); -let api_load_op_cmd = sprintf( - "curl -skL --connect-timeout 3 -m 5 -X POST %s --form-string data=%s --form key=%s", - quoteForShell(url), quoteForShell(loadapi_payload_str), quoteForShell(key) -); -printf("api_load_op_cmd is %s\n\n", api_load_op_cmd); -system(api_load_op_cmd); +return{ + vyos_api_call: function(op_arg, op, host, key) { + // Basic argument validation + if (!key) { + fprintf(stderr, "Missing API key\n"); + return null; + } + if (!host) { + fprintf(stderr, "Missing host\n"); + return null; + } + if (!op) { + fprintf(stderr, "Missing op\n"); + return null; + } -let api_op_cmd = sprintf( - "curl -skL --connect-timeout 3 -m 5 -X POST %s --form-string data=%s --form key=%s", - quoteForShell(url), quoteForShell(api_payload_str), quoteForShell(key) -); + // Determine endpoint and payload based on op + let endpoint; + let payloadObj = { op: op }; -printf("api_op_cmd is %s\n\n", api_op_cmd); -let rc = system(api_op_cmd); -return rc; + if (op == "load" || op == "merge") { + endpoint = "/config-file"; + + if (op_arg && op_arg.file) { + payloadObj.file = op_arg.file; + } else if (op_arg && op_arg.string) { + payloadObj.string = op_arg.string; + } else { + fprintf(stderr, "Unsupported op_arg for op %s\n", op); + return null; + } + + } else if (op == "showConfig") { + endpoint = "/retrieve"; + + if (op_arg && op_arg.path) { + payloadObj.path = op_arg.path; + } else { + // default: whole config + payloadObj.path = []; + } + + } else { + fprintf(stderr, "Unsupported op: %s\n", op); + return null; + } + + // Convert payload to JSON string + let payloadStr = sprintf("%J", payloadObj); + let url = host + endpoint; + + // Build the curl command + let cmd = sprintf( + "curl -skL --connect-timeout 3 -m 5 -X POST %s " + + "--form-string data=%s --form key=%s", + quoteForShell(url), + quoteForShell(payloadStr), + quoteForShell(key) + ); + + // Run curl and capture output + let proc = fs.popen(cmd, "r"); + if (!proc) { + fprintf(stderr, "Failed to start curl\n"); + return null; + } + + let out = proc.read("all"); + proc.close(); + + return out; + } +}; diff --git a/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/pki.uc b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/pki.uc new file mode 100644 index 0000000..273464e --- /dev/null +++ b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/pki.uc @@ -0,0 +1,54 @@ +{% + +let data = (type(rc) == "object" && type(rc.data) == "object") ? rc.data : {}; +let ca_map = (type(data.ca) == "object") ? data.ca : {}; +let cert_map = (type(data.certificate) == "object") ? data.certificate : {}; + +let ca_names = keys(ca_map); +let cert_names = keys(cert_map); +%} +pki { +{% +/* ---- CA blocks ---- */ +for (let i = 0; i < length(ca_names); i++) { + let name = ca_names[i]; + let ca = ca_map[name] || {}; +%} + ca {{name}} { + certificate {{ca.certificate}} +{% + if (type(ca.private) == "object" && ca.private.key) { +%} + private { + key {{ca.private.key}} + } +{% + } +%} + } +{% +} + +/* ---- certificate blocks ---- */ +for (let i = 0; i < length(cert_names); i++) { + let name = cert_names[i]; + let cert = cert_map[name] || {}; +%} + certificate {{name}} { + certificate {{cert.certificate}} +{% + if (type(cert.private) == "object" && cert.private.key) { +%} + private { + key {{cert.private.key}} + } +{% + } +%} + } +{% +} +%} +} + + diff --git a/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/service.uc b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/service.uc index fcfd831..0a2fbe6 100644 --- a/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/service.uc +++ b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/service.uc @@ -132,10 +132,66 @@ type(config.services.ssh) == "object" && config.services.ssh.port) ssh_port = int(config.services.ssh.port); -%} + let https_enabled = false; + let https_allow_from = []; + let https_keys = []; + let https_ca_cert = null; + let https_cert = null; + let https_port = 443; + + if (type(https) == "object" && + https.success === true && + type(https.data) == "object") { + + let h = https.data; + + // ----- allow-client ----- + if (type(h["allow-client"]) == "object") { + let ac = h["allow-client"]; + + if (type(ac.address) == "string") { + push(https_allow_from, ac.address); + } else if (type(ac.address) == "array") { + for (let addr in ac.address) { + if (type(addr) == "string") + push(https_allow_from, addr); + } + } + } + + // ----- api keys ----- + if (type(h.api) == "object" && + type(h.api.keys) == "object" && + type(h.api.keys.id) == "object") { + + for (let kid in keys(h.api.keys.id)) { + let kobj = h.api.keys.id[kid]; + if (type(kobj) == "object" && kobj.key) { + push(https_keys, { id: kid, key: kobj.key }); + } + } + } + + // ----- certificates ----- + if (type(h.certificates) == "object") { + if (type(h.certificates["ca-certificate"]) == "string") + https_ca_cert = h.certificates["ca-certificate"]; + + if (type(h.certificates.certificate) == "string") + https_cert = h.certificates.certificate; + } + + // ----- port ----- + if (h.port) + https_port = int(h.port); + + https_enabled = true; + } +%} service { {% include('services/dhcp-server.uc', {lans}); %} {% include('services/dns-forwarding.uc', {lans}); %} {% include('services/ssh.uc', {ssh_port}); %} + {% include('services/https.uc', {https_enabled, https_allow_from, https_keys, https_ca_cert, https_cert, https_port}); %} } diff --git a/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/services/https.uc b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/services/https.uc new file mode 100644 index 0000000..725d351 --- /dev/null +++ b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/services/https.uc @@ -0,0 +1,36 @@ +{% if (https_enabled): %} +https { + {% if (length(https_allow_from) > 0): %} + allow-client { + {% for (let a in https_allow_from): %} + address {{ a }} + {% endfor %} + } + {% endif %} + + {% if (length(https_keys) > 0): %} + api { + keys { + {% for (let k in https_keys): %} + id {{ k.id }} { + key {{ k.key }} + } + {% endfor %} + } + rest { + } + } + {% endif %} + + certificates { + {% if (https_ca_cert): %} + ca-certificate {{ https_ca_cert }} + {% endif %} + {% if (https_cert): %} + certificate {{ https_cert }} + {% endif %} + } + + port {{ https_port }} +} +{% endif %} diff --git a/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/system.uc b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/system.uc new file mode 100644 index 0000000..eb87b88 --- /dev/null +++ b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/system.uc @@ -0,0 +1,70 @@ +{% +let data = (type(systeminfo) == "object" && type(systeminfo.data) == "object") + ? systeminfo.data : {}; + +let og_map = (type(data["operator-group"]) == "object") ? data["operator-group"] : {}; +let user_map = (type(data.user) == "object") ? data.user : {}; + +let og_names = keys(og_map); +let user_names = keys(user_map); + +%} +system { + login { +{% +for (let i = 0; i < length(og_names); i++) { + let og_name = og_names[i]; + let og = og_map[og_name] || {}; + let cp = (type(og["command-policy"]) == "object") ? og["command-policy"] : {}; + let cp_allow = cp.allow || null; +%} + operator-group {{og_name}} { +{% + if (cp_allow) { +%} + command-policy { + allow "{{cp_allow}}" + } +{% + } +%} + } +{% +} + +for (let i = 0; i < length(user_names); i++) { + let user_name = user_names[i]; + let u = user_map[user_name] || {}; + let auth = (type(u.authentication) == "object") ? u.authentication : {}; + + let enc_pw = auth["encrypted-password"] || null; + let plain_pw = auth["plaintext-password"] ? auth["plaintext-password"] : ""; +%} + user {{user_name}} { +{% + if (type(auth) == "object") { +%} + authentication { +{% + if (enc_pw) { +%} + encrypted-password "{{enc_pw}}" +{% + } + if (plain_pw) { +%} + plaintext-password "{{plain_pw}}" +{% + } +%} + } +{% + } +%} + } +{% +} +%} + } +} + diff --git a/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/version.uc b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/version.uc new file mode 100644 index 0000000..e0cbdbb --- /dev/null +++ b/ucentral-client/rootfs/usr/share/ucentral/vyos/templates/version.uc @@ -0,0 +1,3 @@ +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@6:broadcast-relay@1:cluster@2:config-management@1:conntrack@6:conntrack-sync@2:container@3:dhcp-relay@2:dhcp-server@11:dhcpv6-server@6:dns-dynamic@4:dns-forwarding@4:firewall@20:flow-accounting@2:https@7:ids@2:interfaces@33:ipoe-server@4:ipsec@13:isis@3:l2tp@9:lldp@3:mdns@1:monitoring@2:nat@8:nat66@3:nhrp@1:ntp@3:openconnect@3:openvpn@4:ospf@2:pim@1:policy@9:pppoe-server@11:pptp@5:qos@3:quagga@12:reverse-proxy@3:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@29:vpp@1:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@4:webproxy@2" +// Release version: 2025.09.10-0018-rolling