git: e2adf7a4e801 - main - net/zerotier: fix local auth, add internal webserver

From: Dave Cottlehuber <dch_at_FreeBSD.org>
Date: Sun, 24 Mar 2024 15:29:34 UTC
The branch main has been updated by dch:

URL: https://cgit.FreeBSD.org/ports/commit/?id=e2adf7a4e8012a7eb5a4fc93c4cb945698f6afc1

commit e2adf7a4e8012a7eb5a4fc93c4cb945698f6afc1
Author:     Dave Cottlehuber <dch@FreeBSD.org>
AuthorDate: 2024-03-24 15:29:30 +0000
Commit:     Dave Cottlehuber <dch@FreeBSD.org>
CommitDate: 2024-03-24 15:29:30 +0000

    net/zerotier: fix local auth, add internal webserver
    
    PR:             277735
---
 net/zerotier/Makefile                              |   1 +
 net/zerotier/files/patch-README.md                 |  26 ++++
 .../patch-controller_EmbeddedNetworkController.cpp |  26 ++++
 net/zerotier/files/patch-node_InetAddress.cpp      |  23 ++++
 net/zerotier/files/patch-service_OneService.cpp    | 138 +++++++++++++++++++++
 5 files changed, 214 insertions(+)

diff --git a/net/zerotier/Makefile b/net/zerotier/Makefile
index 31625cd8b1c4..b66ebf553035 100644
--- a/net/zerotier/Makefile
+++ b/net/zerotier/Makefile
@@ -1,5 +1,6 @@
 PORTNAME=	zerotier
 DISTVERSION=	1.12.2
+PORTREVISION=	1
 CATEGORIES=	net
 
 MAINTAINER=	dch@FreeBSD.org
diff --git a/net/zerotier/files/patch-README.md b/net/zerotier/files/patch-README.md
new file mode 100644
index 000000000000..2ef84861cac6
--- /dev/null
+++ b/net/zerotier/files/patch-README.md
@@ -0,0 +1,26 @@
+--- README.md.orig	2023-09-14 19:09:26 UTC
++++ README.md
+@@ -169,3 +169,23 @@ If there are other metrics you'd like to see tracked, 
+ | zt_peer_packet_errors | node_id | Counter | number of incoming packet errors from a peer |
+ 
+ If there are other metrics you'd like to see tracked, ask us in an Issue or send us a Pull Request!
++
++### HTTP / App server
++
++There is a static http file server suitable for hosting Single Page Apps at http://localhost:9993/app/<app-path>
++
++Use `zerotier-cli info -j` to find your zerotier-one service's homeDir
++
++``` sh
++cd $ZT_HOME
++sudo mkdir -p app/app1
++sudo mkdir -p app/appB
++echo '<html><meta charset=utf-8><title>appA</title><body><h1>hello world A' | sudo tee app/appA/index.html 
++echo '<html><meta charset=utf-8><title>app2</title><body><h1>hello world 2' | sudo tee app/app2/index.html 
++curl -sL http://localhost:9993/app/appA http://localhost:9993/app/app2 
++```
++
++Then visit [http://localhost:9993/app/app1/](http://localhost:9993/app/app1/) and [http://localhost:9993/app/appB/](http://localhost:9993/app/appB/)
++
++Requests to paths don't exist return the app root index.html, as is customary for SPAs. 
++If you want, you can write some javascript that talks to the service or controller [api](https://docs.zerotier.com/service/v1).
diff --git a/net/zerotier/files/patch-controller_EmbeddedNetworkController.cpp b/net/zerotier/files/patch-controller_EmbeddedNetworkController.cpp
new file mode 100644
index 000000000000..1f037774fcfb
--- /dev/null
+++ b/net/zerotier/files/patch-controller_EmbeddedNetworkController.cpp
@@ -0,0 +1,26 @@
+--- controller/EmbeddedNetworkController.cpp.orig	2023-09-14 19:09:26 UTC
++++ controller/EmbeddedNetworkController.cpp
+@@ -874,6 +874,7 @@ void EmbeddedNetworkController::configureHTTPControlPl
+ 	std::string memberListPath = "/controller/network/([0-9a-fA-F]{16})/member";
+ 	std::string memberPath = "/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})";
+ 
++
+ 	auto controllerGet = [&, setContent](const httplib::Request &req, httplib::Response &res) {
+ 		char tmp[4096];
+ 		const bool dbOk = _db.isReady();
+@@ -885,11 +886,11 @@ void EmbeddedNetworkController::configureHTTPControlPl
+ 			(unsigned long long)OSUtils::now(),
+ 			dbOk ? "true" : "false");
+ 
+-			if (!dbOk) {
+-				res.status = 503;
+-			}
++		if (!dbOk) {
++			res.status = 503;
++		}
+ 
+-			setContent(req, res, tmp);
++		setContent(req, res, tmp);
+ 	};
+ 	s.Get(controllerPath, controllerGet);
+ 	sv6.Get(controllerPath, controllerGet);
diff --git a/net/zerotier/files/patch-node_InetAddress.cpp b/net/zerotier/files/patch-node_InetAddress.cpp
new file mode 100644
index 000000000000..2b1b7a6e4b4a
--- /dev/null
+++ b/net/zerotier/files/patch-node_InetAddress.cpp
@@ -0,0 +1,23 @@
+--- node/InetAddress.cpp.orig	2023-09-14 19:09:26 UTC
++++ node/InetAddress.cpp
+@@ -132,7 +132,20 @@ InetAddress::IpScope InetAddress::ipScope() const
+ 					return IP_SCOPE_PRIVATE;        // fc00::/7
+ 				}
+ 			}
++
++			// :::ffff:127.0.0.1
++			// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1
+ 			unsigned int k = 0;
++			while ((!ip[k])&&(k < 9)) {
++				++k;
++			}
++			if (k == 9) {
++				if (ip[10] == 0xff && ip[11] == 0xff && ip[12] == 0x7f) {
++					return IP_SCOPE_LOOPBACK;
++				}
++			}
++
++			k = 0;
+ 			while ((!ip[k])&&(k < 15)) {
+ 				++k;
+ 			}
diff --git a/net/zerotier/files/patch-service_OneService.cpp b/net/zerotier/files/patch-service_OneService.cpp
new file mode 100644
index 000000000000..884b42e88a65
--- /dev/null
+++ b/net/zerotier/files/patch-service_OneService.cpp
@@ -0,0 +1,138 @@
+--- service/OneService.cpp.orig	2023-09-14 19:09:26 UTC
++++ service/OneService.cpp
+@@ -795,6 +795,7 @@ class OneServiceImpl : public OneService (public)
+     bool _allowTcpFallbackRelay;
+ 	bool _forceTcpRelay;
+ 	bool _allowSecondaryPort;
++	bool _enableWebServer;
+ 
+ 	unsigned int _primaryPort;
+ 	unsigned int _secondaryPort;
+@@ -1557,6 +1558,7 @@ class OneServiceImpl : public OneService (public)
+ 
+         std::vector<std::string> noAuthEndpoints { "/sso", "/health" };
+ 
++
+ 		auto setContent = [=] (const httplib::Request &req, httplib::Response &res, std::string content) {
+ 			if (req.has_param("jsonp")) {
+ 				if (content.length() > 0) {
+@@ -1573,8 +1575,98 @@ class OneServiceImpl : public OneService (public)
+ 			}
+ 		};
+ 
++		//
++		// static file server for app ui'
++		//
++		if (_enableWebServer) {
++			static std::string appUiPath = "/app";
++			static char appUiDir[16384];
++			sprintf(appUiDir,"%s%s",_homePath.c_str(),appUiPath.c_str());
+ 
+-        auto authCheck = [=] (const httplib::Request &req, httplib::Response &res) {
++			auto ret = _controlPlane.set_mount_point(appUiPath, appUiDir);
++			_controlPlaneV6.set_mount_point(appUiPath, appUiDir);
++			if (!ret) {
++				fprintf(stderr, "Mounting app directory failed. Creating it. Path: %s - Dir: %s\n", appUiPath.c_str(), appUiDir);
++				if (!OSUtils::mkdir(appUiDir)) {
++					fprintf(stderr, "Could not create app directory either. Path: %s - Dir: %s\n", appUiPath.c_str(), appUiDir);
++				} else {
++					ret = _controlPlane.set_mount_point(appUiPath, appUiDir);
++					_controlPlaneV6.set_mount_point(appUiPath, appUiDir);
++					if (!ret) {
++						fprintf(stderr, "Really could not create and mount directory. Path: %s - Dir: %s\nWeb apps won't work.\n", appUiPath.c_str(), appUiDir);
++					}
++				}
++			}
++
++			if (ret) {
++				// fallback to /index.html for paths that don't exist for SPAs
++				auto indexFallbackGet = [](const httplib::Request &req, httplib::Response &res) {
++					// fprintf(stderr, "fallback \n");
++
++					auto match = req.matches[1];
++					if (match.matched) {
++
++						// fallback
++						char indexHtmlPath[16384];
++						sprintf(indexHtmlPath,"%s/%s/%s", appUiDir, match.str().c_str(), "index.html");
++						// fprintf(stderr, "fallback path %s\n", indexHtmlPath);
++
++						std::string indexHtml;
++
++						if (!OSUtils::readFile(indexHtmlPath, indexHtml)) {
++							res.status = 500;
++							return;
++						}
++
++						res.set_content(indexHtml.c_str(), "text/html");
++					} else {
++						res.status = 500;
++						return;
++					}
++				};
++
++				auto slashRedirect = [](const httplib::Request &req, httplib::Response &res) {
++					// fprintf(stderr, "redirect \n");
++
++					// add .html
++					std::string htmlFile;
++					char htmlPath[16384];
++					sprintf(htmlPath,"%s%s%s", appUiDir, (req.path).substr(appUiPath.length()).c_str(), ".html");
++					// fprintf(stderr, "path: %s\n", htmlPath);
++					if (OSUtils::readFile(htmlPath, htmlFile)) {
++						res.set_content(htmlFile.c_str(), "text/html");
++						return;
++					} else {
++						res.status = 301;
++						res.set_header("location", req.path + "/");
++					}
++				};
++
++				// auto missingAssetGet = [&, setContent](const httplib::Request &req, httplib::Response &res) {
++				// 	fprintf(stderr, "missing \n");
++				// 	res.status = 404;
++				// 	std::string html = "oops";
++				// 	res.set_content(html, "text/plain");
++				// 	res.set_header("Content-Type", "text/plain");
++				// 	return;
++				// };
++
++				// auto fix no trailing slash by adding .html or redirecting to path/
++				_controlPlane.Get(appUiPath + R"((/[\w|-]+)+$)", slashRedirect);
++				_controlPlaneV6.Get(appUiPath + R"((/[\w|-]+)+$)", slashRedirect);
++
++				// // 404 missing assets for *.ext paths
++				//   s.Get(appUiPath + R"(/\.\w+$)", missingAssetGet);
++				// sv6.Get(appUiPath + R"(/\.\w+$)", missingAssetGet);
++
++				// fallback to index.html for unknown paths/files
++				_controlPlane.Get(appUiPath + R"((/[\w|-]+)(/[\w|-]+)*/$)", indexFallbackGet);
++				_controlPlaneV6.Get(appUiPath + R"((/[\w|-]+)(/[\w|-]+)*/$)", indexFallbackGet);
++
++			}
++		}
++
++		auto authCheck = [=] (const httplib::Request &req, httplib::Response &res) {
+ 			if (req.path == "/metrics") {
+ 
+ 				if (req.has_header("x-zt1-auth")) {
+@@ -1624,6 +1716,11 @@ class OneServiceImpl : public OneService (public)
+ 						isAuth = true;
+ 					}
+ 
++					// Web Apps base path
++					if (req.path.rfind("/app", 0) == 0) { //starts with /app
++						isAuth = true;
++					}
++
+ 					if (!isAuth) {
+ 						// check auth token
+ 						if (req.has_header("x-zt1-auth")) {
+@@ -2429,6 +2526,7 @@ class OneServiceImpl : public OneService (public)
+ 		// bondingPolicy cannot be used with allowTcpFallbackRelay
+ 		_allowTcpFallbackRelay = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],true);
+ 		_forceTcpRelay = OSUtils::jsonBool(settings["forceTcpRelay"],false);
++		_enableWebServer = (OSUtils::jsonBool(settings["enableWebServer"],false));
+ 
+ #ifdef ZT_TCP_FALLBACK_RELAY
+ 		_fallbackRelayAddress = InetAddress(OSUtils::jsonString(settings["tcpFallbackRelay"], ZT_TCP_FALLBACK_RELAY).c_str());