diff --git a/README.md b/README.md index 37309bc3..327ca8f7 100644 --- a/README.md +++ b/README.md @@ -112,18 +112,18 @@ Additional help can be found in our [knowledge base](https://zerotier.atlassian. ### Prometheus Metrics -Prometheus Metrics are available at the `/metrics` API endpoint. This endpoint is protected by an API key stored in `authtoken.secret` because of the possibility of information leakage. Information that could be gleaned from the metrics include joined networks and peers your instance is talking to. +Prometheus Metrics are available at the `/metrics` API endpoint. This endpoint is protected by an API key stored in `metricstoken.secret` to prevent unwanted information leakage. Information that could be gleaned from the metrics include joined networks and peers your instance is talking to. -Access control is via the ZeroTier control interface itself and `authtoken.secret`. This can be sent as the `X-ZT1-Auth` HTTP header field or appended to the URL as `?auth=`. You can see the current metrics via `cURL` with the following command: +Access control is via the ZeroTier control interface itself and `metricstoken.secret`. This can be sent as a bearer auth token, via the `X-ZT1-Auth` HTTP header field, or appended to the URL as `?auth=`. You can see the current metrics via `cURL` with the following command: // Linux - curl -H "X-ZT1-Auth: $(sudo cat /var/lib/zerotier-one/authtoken.secret)" http://localhost:9993/metrics + curl -H "X-ZT1-Auth: $(sudo cat /var/lib/zerotier-one/metricstoken.secret)" http://localhost:9993/metrics // macOS - curl -H "X-XT1-Auth: $(sudo cat /Library/Application\ Support/ZeroTier/One/authtoken.secret)" http://localhost:9993/metrics + curl -H "X-XT1-Auth: $(sudo cat /Library/Application\ Support/ZeroTier/One/metricstoken.secret)" http://localhost:9993/metrics // Windows PowerShell (Admin) - Invoke-RestMethod -Headers @{'X-ZT1-Auth' = "$(Get-Content C:\ProgramData\ZeroTier\One\authtoken.secret)"; } -Uri http://localhost:9993/metrics + Invoke-RestMethod -Headers @{'X-ZT1-Auth' = "$(Get-Content C:\ProgramData\ZeroTier\One\metricstoken.secret)"; } -Uri http://localhost:9993/metrics To configure a scrape job in Prometheus on the machine ZeroTier is running on, add this to your Prometheus `scrape_config`: @@ -136,24 +136,23 @@ To configure a scrape job in Prometheus on the machine ZeroTier is running on, a - 127.0.0.1:9993 labels: group: zerotier-one - params: - auth: - - $YOUR_AUTHTOKEN_SECRET - -If your Prometheus instance is remote from the machine ZeroTier instance, you'll have to edit your `local.conf` file to allow remote access to the API control port. If your local lan is `10.0.0.0/24`, edit your `local.conf` as follows: - - { - "settings": { - "allowManagementFrom:" ["10.0.0.0/24"] - } - } - -Substitute your actual network IP ranges as necessary. - -It's also possible to access the metrics & control port over the ZeroTier network itself via the same method shown above. Just add the address range of your ZeroTier network to the list. NOTE: Using this method means that anyone with your auth token can control your ZeroTier instance, including leaving & joining other networks. + node_id: $YOUR_10_CHARACTER_NODE_ID + authorization: + credentials: $YOUR_METRICS_TOKEN_SECRET If neither of these methods are desirable, it is probably possible to distribute metrics via [Prometheus Proxy](https://github.com/pambrose/prometheus-proxy) or some other tool. Note: We have not tested this internally, but will probably work with the correct configuration. +Metrics are also available on disk in ZeroTier's working directory: + + // Linux + /var/lib/zerotier-one/metrics.prom + + // macOS + /Library/Application\ Support/ZeroTier/One/metrics.prom + + //Windows + C:\ProgramData\ZeroTier\One\metrics.prom + #### Available Metrics | Metric Name | Labels | Metric Type | Description | diff --git a/service/OneService.cpp b/service/OneService.cpp index 683db6e2..e063bded 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -201,6 +201,26 @@ std::string ssoResponseTemplate = R"""( )"""; +bool bearerTokenValid(const std::string authHeader, const std::string &checkToken) { + std::vector tokens = OSUtils::split(authHeader.c_str(), " ", NULL, NULL); + if (tokens.size() != 2) { + return false; + } + + std::string bearer = tokens[0]; + std::string token = tokens[1]; + std::transform(bearer.begin(), bearer.end(), bearer.begin(), [](unsigned char c){return std::tolower(c);}); + if (bearer != "bearer") { + return false; + } + + if (token != checkToken) { + return false; + } + + return true; +} + #if ZT_DEBUG==1 std::string dump_headers(const httplib::Headers &headers) { std::string s; @@ -753,6 +773,7 @@ public: const std::string _homePath; std::string _authToken; + std::string _metricsToken; std::string _controllerDbPath; const std::string _networksPath; const std::string _moonsPath; @@ -950,6 +971,26 @@ public: _authToken = _trimString(_authToken); } + { + const std::string metricsTokenPath(_homePath + ZT_PATH_SEPARATOR_S "metricstoken.secret"); + if (!OSUtils::readFile(metricsTokenPath.c_str(),_metricsToken)) { + unsigned char foo[24]; + Utils::getSecureRandom(foo,sizeof(foo)); + _metricsToken = ""; + for(unsigned int i=0;icontainsAddress(remoteAddr)) { - ipAllowed = true; - break; - } - } - } + setContent(req, res, "{}"); + res.status = 401; + return httplib::Server::HandlerResponse::Handled; + } else { + std::string r = req.remote_addr; + InetAddress remoteAddr(r.c_str()); + + bool ipAllowed = false; + bool isAuth = false; + // If localhost, allow + if (remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK) { + ipAllowed = true; + } + + if (!ipAllowed) { + for (auto i = _allowManagementFrom.begin(); i != _allowManagementFrom.end(); ++i) { + if (i->containsAddress(remoteAddr)) { + ipAllowed = true; + break; + } + } + } - if (ipAllowed) { - // auto-pass endpoints in `noAuthEndpoints`. No auth token required - if (std::find(noAuthEndpoints.begin(), noAuthEndpoints.end(), req.path) != noAuthEndpoints.end()) { - isAuth = true; - } + if (ipAllowed) { + // auto-pass endpoints in `noAuthEndpoints`. No auth token required + if (std::find(noAuthEndpoints.begin(), noAuthEndpoints.end(), req.path) != noAuthEndpoints.end()) { + isAuth = true; + } - if (!isAuth) { - // check auth token - if (req.has_header("x-zt1-auth")) { - std::string token = req.get_header_value("x-zt1-auth"); - if (token == _authToken) { - isAuth = true; - } - } else if (req.has_param("auth")) { - std::string token = req.get_param_value("auth"); - if (token == _authToken) { - isAuth = true; - } - } - } - } + if (!isAuth) { + // check auth token + if (req.has_header("x-zt1-auth")) { + std::string token = req.get_header_value("x-zt1-auth"); + if (token == _authToken) { + isAuth = true; + } + } else if (req.has_param("auth")) { + std::string token = req.get_param_value("auth"); + if (token == _authToken) { + isAuth = true; + } + } else if (req.has_header("authorization")) { + std::string auth = req.get_header_value("authorization"); + isAuth = bearerTokenValid(auth, _authToken); + } + } + } - if (ipAllowed && isAuth) { - return httplib::Server::HandlerResponse::Unhandled; - } - setContent(req, res, "{}"); - res.status = 401; - return httplib::Server::HandlerResponse::Handled; + if (ipAllowed && isAuth) { + return httplib::Server::HandlerResponse::Unhandled; + } + setContent(req, res, "{}"); + res.status = 401; + return httplib::Server::HandlerResponse::Handled; + } };