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.
This commit is contained in:
NavneetBarwal-RA
2025-12-02 19:45:44 +05:30
parent 24600df4ad
commit 0282b2b6ca
9 changed files with 341 additions and 51 deletions

View File

@@ -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"
}

View File

@@ -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;
}
}

View File

@@ -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";
}
};

View File

@@ -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;
}
};

View File

@@ -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}}
}
{%
}
%}
}
{%
}
%}
}

View File

@@ -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}); %}
}

View File

@@ -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 %}

View File

@@ -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}}"
{%
}
%}
}
{%
}
%}
}
{%
}
%}
}
}

View File

@@ -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