From ed4fe58fdaac079a9638f4a4eb8fef40669f2a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 23 Feb 2026 19:52:16 +0100 Subject: [PATCH] confd: Add YANG pattern constraints for string leaves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add input validation patterns to string-type leaves in container, firewall, DHCP, and interface YANG models. Also use mkdtemp() for temporary key files in keystore. Signed-off-by: Mattias Walström --- .gitignore | 2 +- src/confd/src/keystore.c | 25 +++++++----- src/confd/yang/confd/infix-containers.yang | 40 ++++++++++++++----- ....yang => infix-containers@2026-02-23.yang} | 0 src/confd/yang/confd/infix-dhcp-server.yang | 12 ++++-- src/confd/yang/confd/infix-firewall.yang | 12 ++++-- src/confd/yang/confd/infix-interfaces.yang | 1 + src/confd/yang/containers.inc | 2 +- 8 files changed, 68 insertions(+), 26 deletions(-) rename src/confd/yang/confd/{infix-containers@2025-12-09.yang => infix-containers@2026-02-23.yang} (100%) diff --git a/.gitignore b/.gitignore index 354f63f0..40975fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .claude .gdb_history .claude +AGENTS.md /.backup /.ccache /dl @@ -11,4 +12,3 @@ /test/.log /local.mk /test/spec/Readme.adoc - diff --git a/src/confd/src/keystore.c b/src/confd/src/keystore.c index 06946748..6f493e5c 100644 --- a/src/confd/src/keystore.c +++ b/src/confd/src/keystore.c @@ -12,8 +12,6 @@ #define XPATH_KEYSTORE_ASYM "/ietf-keystore:keystore/asymmetric-keys" #define XPATH_KEYSTORE_SYM "/ietf-keystore:keystore/symmetric-keys" -#define SSH_PRIVATE_KEY "/tmp/ssh.key" -#define SSH_PUBLIC_KEY "/tmp/ssh.pub" /* return file size */ static size_t filesz(const char *fn) @@ -91,6 +89,9 @@ static int keystore_update(sr_session_ctx_t *session, struct lyd_node *config, s } for (size_t i = 0; i < count; i++) { + char tmpdir[] = "/tmp/keystore.XXXXXX"; + char priv_path[sizeof(tmpdir) + 16]; + char pub_path[sizeof(tmpdir) + 16]; char *name = srx_get_str(session, "%s/name", list[i].xpath); char *public_key_format, *private_key_format; char *pub_key = NULL, *priv_key = NULL; @@ -115,16 +116,24 @@ static int keystore_update(sr_session_ctx_t *session, struct lyd_node *config, s continue; NOTE("SSH key (%s) does not exist, generating...", name); - if (systemf("/usr/libexec/infix/mkkeys %s %s", SSH_PRIVATE_KEY, SSH_PUBLIC_KEY)) { + + if (!mkdtemp(tmpdir)) { + ERRNO("Failed creating temp dir for SSH key generation"); + goto next; + } + snprintf(priv_path, sizeof(priv_path), "%s/ssh.key", tmpdir); + snprintf(pub_path, sizeof(pub_path), "%s/ssh.pub", tmpdir); + + if (systemf("/usr/libexec/infix/mkkeys %s %s", priv_path, pub_path)) { ERROR("Failed generating SSH keys for %s", name); goto next; } - priv_key = filerd(SSH_PRIVATE_KEY, filesz(SSH_PRIVATE_KEY)); + priv_key = filerd(priv_path, filesz(priv_path)); if (!priv_key) goto next; - pub_key = filerd(SSH_PUBLIC_KEY, filesz(SSH_PUBLIC_KEY)); + pub_key = filerd(pub_path, filesz(pub_path)); if (!pub_key) goto next; @@ -140,10 +149,8 @@ static int keystore_update(sr_session_ctx_t *session, struct lyd_node *config, s goto next; } next: - if (erase(SSH_PRIVATE_KEY)) - ERRNO("Failed removing SSH server private key"); - if (erase(SSH_PUBLIC_KEY)) - ERRNO("Failed removing SSH server public key"); + if (rmrf(tmpdir)) + ERRNO("Failed removing temp dir %s", tmpdir); if (priv_key) free(priv_key); diff --git a/src/confd/yang/confd/infix-containers.yang b/src/confd/yang/confd/infix-containers.yang index a1ddd09e..f0950002 100644 --- a/src/confd/yang/confd/infix-containers.yang +++ b/src/confd/yang/confd/infix-containers.yang @@ -22,6 +22,11 @@ module infix-containers { prefix infix-sys; } + revision 2026-02-23 { + description "Input validation improvements"; + reference "internal"; + } + revision 2025-12-09 { description "Add resource management: - Add resource-limit container with memory and cpu configuration. @@ -84,6 +89,17 @@ module infix-containers { } } + + typedef image-reference { + description "OCI image reference or transport URI. + Allows registry references (e.g., quay.io/user/image:tag), + transport prefixes (docker://, oci-archive:, dir:), and + download URLs (ftp://, http://, https://)."; + type string { + pattern '[a-zA-Z0-9][a-zA-Z0-9_./:@=+%~-]*'; + } + } + typedef mount-type { type enumeration { enum bind { @@ -181,7 +197,7 @@ module infix-containers { container will be put on a queue that retries pull every time there is a route change in the host's system."; mandatory true; - type string; + type image-reference; } container checksum { @@ -240,7 +256,9 @@ module infix-containers { leaf command { description "Override ENTRYPOINT from image and run command + args."; - type string; + type string { + pattern '[a-zA-Z0-9_./ :=@%^,+-]+'; + } } leaf hostname { @@ -298,7 +316,9 @@ module infix-containers { NOTE: Some (*) options only work with masquerading container bridges, which automatically create VETH pairs with one end in the host bridge and the other in the container."; - type string; + type string { + pattern '[a-zA-Z_][a-zA-Z0-9_]*=[a-zA-Z0-9.:_/-]+'; + } } } @@ -310,7 +330,9 @@ module infix-containers { Sample: 8080:80 -- forward tcp port 8080 to container port 80 69:69/udp -- forward udp port 69 to container port 69 127.0.0.1:8080:80 -- forward only from loopback interface"; - type string; + type string { + pattern '[0-9.:\[\]]+(/[a-zA-Z]+)?'; + } } leaf-list dns { @@ -464,7 +486,7 @@ module infix-containers { When mounting files, directories (and globs) from the host, the source must be an absolute path."; type string { - pattern '/.*'; + pattern '/[a-zA-Z0-9_./*?-]+([ ]?[a-zA-Z0-9_./*?-]+)*'; } } } @@ -493,7 +515,7 @@ module infix-containers { or import the text file using the 'content' node."; mandatory true; type string { - pattern '/.*'; + pattern '/[a-zA-Z0-9_./-]+(/[a-zA-Z0-9_./-]+)*'; } } @@ -545,7 +567,7 @@ module infix-containers { description "Absolute path to target destination directory inside the container."; mandatory true; type string { - pattern '/.*'; + pattern '/[a-zA-Z0-9_./-]+(/[a-zA-Z0-9_./-]+)*'; } } } @@ -593,12 +615,12 @@ module infix-containers { input { leaf uri { description "The URL or local file path, e.g., /lib/oci/archive.tar.gz"; - type string; + type image-reference; mandatory true; } leaf name { description "Image name[:tag], default: basename of archive dir + :latest"; - type string; + type image-reference; } } } diff --git a/src/confd/yang/confd/infix-containers@2025-12-09.yang b/src/confd/yang/confd/infix-containers@2026-02-23.yang similarity index 100% rename from src/confd/yang/confd/infix-containers@2025-12-09.yang rename to src/confd/yang/confd/infix-containers@2026-02-23.yang diff --git a/src/confd/yang/confd/infix-dhcp-server.yang b/src/confd/yang/confd/infix-dhcp-server.yang index 84d6242d..10acf7fb 100644 --- a/src/confd/yang/confd/infix-dhcp-server.yang +++ b/src/confd/yang/confd/infix-dhcp-server.yang @@ -113,7 +113,9 @@ module infix-dhcp-server { case default-opt { leaf string { description "Generic string value."; - type string; + type string { + pattern '[^\p{Cc}]*'; + } } } } @@ -150,7 +152,9 @@ module infix-dhcp-server { leaf description { description "Additional information about this subnet (e.g., purpose, location, or notes)."; - type string; + type string { + pattern '[^\p{Cc}]*'; + } } leaf enabled { @@ -206,7 +210,9 @@ module infix-dhcp-server { leaf description { description "Additional information about this entry, e.g., owner's name, equipment details, or location."; - type string; + type string { + pattern '[^\p{Cc}]*'; + } } container match { diff --git a/src/confd/yang/confd/infix-firewall.yang b/src/confd/yang/confd/infix-firewall.yang index 92a9c7f5..ca2119db 100644 --- a/src/confd/yang/confd/infix-firewall.yang +++ b/src/confd/yang/confd/infix-firewall.yang @@ -201,7 +201,9 @@ module infix-firewall { leaf description { description "Free-form description of the zone."; - type string; + type string { + pattern '[^\p{Cc}<>&]*'; + } } leaf-list interface { @@ -304,7 +306,9 @@ module infix-firewall { leaf description { description "Free-form description of this policy's purpose and scope."; - type string; + type string { + pattern '[^\p{Cc}<>&]*'; + } } leaf-list ingress { @@ -421,7 +425,9 @@ module infix-firewall { leaf description { description "Free-form description of the service."; - type string; + type string { + pattern '[^\p{Cc}<>&]*'; + } } list port { diff --git a/src/confd/yang/confd/infix-interfaces.yang b/src/confd/yang/confd/infix-interfaces.yang index 097b581d..3e6d88ba 100644 --- a/src/confd/yang/confd/infix-interfaces.yang +++ b/src/confd/yang/confd/infix-interfaces.yang @@ -204,6 +204,7 @@ module infix-interfaces { deviate replace { type string { length "0..64"; + pattern '[^\p{Cc}]*'; } } } diff --git a/src/confd/yang/containers.inc b/src/confd/yang/containers.inc index 6f13f971..dc629ad9 100644 --- a/src/confd/yang/containers.inc +++ b/src/confd/yang/containers.inc @@ -1,5 +1,5 @@ # -*- sh -*- MODULES=( "infix-interfaces -e containers" - "infix-containers@2025-12-09.yang" + "infix-containers@2026-02-23.yang" )