Compare commits

...
2 Commits
Author SHA1 Message Date
Tobias Waldekranz 257f1df1d5 test: case: user_scripts: Add test
Verify that:
- Scripts are executed when the service is enabled
- Scripts are not enabled when the service is disabled
2024-05-21 22:02:28 +02:00
Tobias Waldekranz 8664c27096 confd: Add support for running user scripts
Add a new service that, when enabled, will execute run-parts on
`/cfg/user-scripts.d`.

This strikes a balance between two conflicting objectives:

1. There should be no implicit way to schedule arbitrary code
   execution on the device, i.e. no default run-parts directory.

2. It is very useful to have a way of scheduling arbitrary code
   execution on the device, e.g. being able to install a debug script
   on a production image.

With this feature, we still meet (1), since the feature has to be
explicitly enabled in the startup-config; but we also fulfill (2),
since we can easily enable it when needed.
2024-05-21 22:02:28 +02:00
6 changed files with 103 additions and 2 deletions
+2
View File
@@ -22,6 +22,8 @@ All notable changes to the project are documented in this file.
- Update documentation for use of VETH pairs in containers
- Issue #454: create bridges in `factory-config` with IGMP/MLD snooping
enabled by default
- Add support for optionally running user scripts from
`/cfg/user-scripts.d`
### Fixes
- Add missing LICENSE hash for factory reset tool
@@ -0,0 +1 @@
task [2] run-parts /cfg/user-scripts.d -- Running user startup scripts
+1 -1
View File
@@ -226,7 +226,7 @@ sysrepoctl -s $SEARCH \
-i infix-dhcp-client@2024-04-12.yang -g wheel -p 0660 \
-i infix-shell-type@2023-08-21.yang -g wheel -p 0660 \
-i infix-system@2024-04-12.yang -g wheel -p 0660 \
-i infix-services@2024-04-08.yang -g wheel -p 0660 \
-i infix-services@2024-05-21.yang -g wheel -p 0660 \
-i ieee802-ethernet-interface@2019-06-21.yang -g wheel -p 0660 \
-i infix-ethernet-interface@2024-02-27.yang -g wheel -p 0660 \
-I "${INIT_DATA}"
+19 -1
View File
@@ -25,6 +25,7 @@
SVC(web) \
SVC(ttyd) \
SVC(netbrowse) \
SVC(userscripts) \
SVC(all) /* must be last entry */
typedef enum {
@@ -52,7 +53,7 @@ struct mdns_svc {
};
static const struct srx_module_requirement reqs[] = {
{ .dir = YANG_PATH_, .name = "infix-services", .rev = "2024-04-08" },
{ .dir = YANG_PATH_, .name = "infix-services", .rev = "2024-05-21" },
{ .dir = YANG_PATH_, .name = "ieee802-dot1ab-lldp", .rev = "2022-03-15" },
{ .dir = YANG_PATH_, .name = "infix-lldp", .rev = "2023-08-23" },
{ NULL }
@@ -291,6 +292,21 @@ static int web_change(sr_session_ctx_t *session, uint32_t sub_id, const char *mo
return put(cfg, srv);
}
static int userscripts_change(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *_confd)
{
struct lyd_node *srv = NULL;
sr_data_t *cfg;
cfg = get(session, event, xpath, &srv, "user-scripts", NULL);
if (!cfg)
return SR_ERR_OK;
svc_enadis(lydx_is_enabled(srv, "enabled"), none, "user-scripts");
return put(cfg, srv);
}
int infix_services_init(struct confd *confd)
{
int rc;
@@ -312,6 +328,8 @@ int infix_services_init(struct confd *confd)
0, netbrowse_change, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ieee802-dot1ab-lldp", "/ieee802-dot1ab-lldp:lldp",
0, lldp_change, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "infix-services", "/infix-services:user-scripts",
0, userscripts_change, confd, &confd->sub);
return SR_ERR_OK;
fail:
@@ -7,6 +7,10 @@ module infix-services {
contact "kernelkit@googlegroups.com";
description "Infix services, generic.";
revision 2024-05-21 {
description "Add support for user-scripts.";
reference "internal";
}
revision 2024-04-08 {
description "Initial support for web services.";
reference "internal";
@@ -33,6 +37,19 @@ module infix-services {
}
}
container user-scripts {
description "Run scripts in /cfg/user-scripts.d on system startup
CAUTION! Anyone who can write to this directory will be able to
install a permanent backdoor on the system. This could include
a remote attacker with brief window of access.";
leaf enabled {
description "Enable or disable user script execution";
type boolean;
}
}
container web {
description "Web services";
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
#
import random
import string
import infamy
def attach(env):
return (env.attach("target", "mgmt"), env.attach("target", "mgmt", "ssh"))
with infamy.Test() as test:
with test.step("Initialize"):
env = infamy.Env(infamy.std_topology("1x1"))
target, tgtssh = attach(env)
cookie = "".join((random.choices(string.ascii_lowercase, k=16)))
script = f"/cfg/user-scripts.d/infamy-user-script-test.sh"
output = f"/tmp/{cookie}.cookie"
with test.step(f"Install {script}"):
tgtssh.runsh(f"""
set -e
mkdir -p $(dirname {script})
printf '#!/bin/sh\necho -n {cookie} >{output}\n' >{script}
chmod +x {script}
""")
with test.step("Enable user scripts and reboot"):
target.put_config_dict("infix-services", {
"user-scripts": {
"enabled": True
}
})
target.copy("running", "startup")
target.reboot()
infamy.util.wait_boot(target) or test.fail()
target, tgtssh = attach(env)
with test.step(f"Verify that {script} has run"):
cat = tgtssh.runsh(f"cat {output}").stdout
assert cat == cookie, f"Read back {repr(cat)}, expected {repr(cookie)}"
with test.step("Disable user scripts and reboot"):
target.put_config_dict("infix-services", {
"user-scripts": {
"enabled": False
}
})
target.copy("running", "startup")
target.reboot()
infamy.util.wait_boot(target) or test.fail()
target, tgtssh = attach(env)
with test.step(f"Verify that {script} has not run"):
exists = tgtssh.run(["test", "-f", f"{output}"]).returncode == 0
assert not exists, f"Unexpectedly found {output}"
with test.step(f"Remove {script}"):
tgtssh.runsh(f"rm {script}")
test.succeed()