From c800fe7fdd8284b3e41027b1c0cd77a462ea1460 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer <mschiffer@universe-factory.net> Date: Sat, 29 Mar 2025 13:51:06 +0100 Subject: [PATCH] gluon-autoupdater: add support for HTTPS and protocol-less URLs The autoupdater supports HTTPS when a ustream TLS backend is installed, but we did not allow this in site.conf. However, just allowing HTTPS URLs unconditionally is also a bad idea, as it might result in nodes being unable to reach the mirror, in particular if the `tls` feature is enabled only for some devices. Solve this by allowing https:// URLs only if the marker file installed by gluon-tls is found, failing the site check with an error message like the following otherwise: *** All of the following alternatives have failed: 1) site.conf error: expected autoupdater.branches.test.mirrors.1 to match pattern 'http://', but it is "https://..." (a string value) 2) site.conf error: expected autoupdater.branches.test.mirrors.1 to use HTTPS only if the 'tls' feature is enabled, but it is "https://..." (a string value) 3) site.conf error: expected autoupdater.branches.test.mirrors.1 to match pattern '^//', but it is "https://..." (a string value) In addition, introduce support for protocol-less //server/path URLs, which will use either HTTP or HTTPS depending on the availablility of the `tls` feature. No fallback happens when `tls` is available, but the HTTPS connection fails, preventing downgrade attack. Based-on-patch-by: Kevin Olbrich <ko@sv01.de> --- docs/multidomain-site-example/site.conf | 10 ++++++++- docs/site-example/site.conf | 10 ++++++++- docs/user/site.rst | 15 ++++++++++++- package/gluon-autoupdater/check_site.lua | 21 ++++++++++++++++++- .../luasrc/lib/gluon/upgrade/500-autoupdater | 18 +++++++++++++++- 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/docs/multidomain-site-example/site.conf b/docs/multidomain-site-example/site.conf index fc5298f7a..88f0f9822 100644 --- a/docs/multidomain-site-example/site.conf +++ b/docs/multidomain-site-example/site.conf @@ -39,7 +39,15 @@ branches = { stable = { name = 'stable', - mirrors = {'http://update.example.org/stable/sysupgrade'}, + mirrors = { + 'http://1.updates.example.org/stable/sysupgrade', + + -- Requires the tls feature in image-customization.lua + -- 'https://2.updates.example.org/stable/sysupgrade', + + -- Uses http or https depending on the tls feature in image-customization.lua + '//3.updates.example.org/stable/sysupgrade', + }, good_signatures = 2, pubkeys = { 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', -- Alice diff --git a/docs/site-example/site.conf b/docs/site-example/site.conf index 0c037e9d6..3e529c302 100644 --- a/docs/site-example/site.conf +++ b/docs/site-example/site.conf @@ -174,7 +174,15 @@ name = 'stable', -- List of mirrors to fetch images from. IPv6 required! - mirrors = {'http://1.updates.services.ffhl/stable/sysupgrade'}, + mirrors = { + 'http://1.updates.example.org/stable/sysupgrade', + + -- Requires the tls feature in image-customization.lua + -- 'https://2.updates.example.org/stable/sysupgrade', + + -- Uses http or https depending on the tls feature in image-customization.lua + '//3.updates.example.org/stable/sysupgrade', + }, -- Number of good signatures required. -- Have multiple maintainers sign your build and only diff --git a/docs/user/site.rst b/docs/user/site.rst index da432dde9..60fc14457 100644 --- a/docs/user/site.rst +++ b/docs/user/site.rst @@ -467,7 +467,10 @@ autoupdater \: package name = 'stable', mirrors = { 'http://[fdca:ffee:babe:1::fec1]/firmware/stable/sysupgrade/', - 'http://autoupdate.alpha-centauri.freifunk.net/firmware/stable/sysupgrade/', + -- Requires the tls feature in image-customization.lua + 'https://autoupdate.alpha-centauri.freifunk.net/firmware/stable/sysupgrade/', + -- Uses http or https depending on the tls feature in image-customization.lua + '//autoupdate2.alpha-centauri.freifunk.net/firmware/stable/sysupgrade/', }, -- Number of good signatures required good_signatures = 2, @@ -482,6 +485,16 @@ autoupdater \: package All configured mirrors must be reachable from the nodes via IPv6. If you don't want to set an IPv6 address explicitly, but use a hostname (which is recommended), see also the :ref:`FAQ <faq-dns>`. + HTTPS URLs can be used if the **tls** feature is enabled in **image-customization.lua**. + + Use protocol-less ``//server/path`` URLs to use HTTPS if the **tls** feature is available, + but fall back to HTTP otherwise. The server **must** allow HTTPS connections and provide + a valid certificate in this case; the autoupdater will not fall back to HTTP if the **tls** + feature is enabled, but the HTTPS connection fails. + + Note that the validity period of TLS certificates is checked as well, so care must be taken + to provide working NTP servers in addition to the update mirrors when using HTTPS. + .. _user-site-config_mode: config_mode \: optional diff --git a/package/gluon-autoupdater/check_site.lua b/package/gluon-autoupdater/check_site.lua index aaf1763c3..aa9ca6044 100644 --- a/package/gluon-autoupdater/check_site.lua +++ b/package/gluon-autoupdater/check_site.lua @@ -1,8 +1,27 @@ +local has_tls = (function() + local f = io.open((os.getenv('IPKG_INSTROOT') or '') .. '/lib/gluon/features/tls') + if f then + f:close() + return true + end + return false +end)() + local branches = table_keys(need_table({'autoupdater', 'branches'}, function(branch) need_alphanumeric_key(branch) need_string(in_site(extend(branch, {'name'}))) - need_string_array_match(extend(branch, {'mirrors'}), '^http://') + need_array(extend(branch, {'mirrors'}), function(mirror) + alternatives(function() + need_string_match(mirror, 'http://') + end, function() + need_string_match(mirror, 'https://') + need(mirror, function() return has_tls end, nil, + "use HTTPS only if the 'tls' feature is enabled") + end, function() + need_string_match(mirror, '^//') + end) + end) local pubkeys = need_string_array_match(in_site(extend(branch, {'pubkeys'})), '^%x+$') need_number(in_site(extend(branch, {'good_signatures'}))) diff --git a/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater b/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater index 351c8e04e..f5e6ce78d 100755 --- a/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater +++ b/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater @@ -4,14 +4,30 @@ local site = require 'gluon.site' local uci = require('simple-uci').cursor() local unistd = require 'posix.unistd' +local has_tls = unistd.access('/lib/gluon/features/tls') ~= nil +local default_scheme = has_tls and 'https:' or 'http:' local min_branch +local function mirror_urls(mirrors) + local ret = {} + + for _, mirror in ipairs(mirrors) do + if string.match(mirror, '^//') ~= nil then + table.insert(ret, default_scheme .. mirror) + else + table.insert(ret, mirror) + end + end + + return ret +end + for name, config in pairs(site.autoupdater.branches()) do uci:delete('autoupdater', name) uci:section('autoupdater', 'branch', name, { name = config.name, - mirror = config.mirrors, + mirror = mirror_urls(config.mirrors), good_signatures = config.good_signatures, pubkey = config.pubkeys, }) -- GitLab