diff options
-rw-r--r-- | controller/EmbeddedNetworkController.cpp | 44 | ||||
-rw-r--r-- | controller/EmbeddedNetworkController.hpp | 8 | ||||
-rw-r--r-- | controller/README.md | 3 | ||||
-rw-r--r-- | node/CertificateOfMembership.hpp | 5 | ||||
-rw-r--r-- | node/Membership.hpp | 4 | ||||
-rw-r--r-- | node/NetworkConfig.hpp | 16 |
6 files changed, 67 insertions, 13 deletions
diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 738fea9a..4db219c9 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -413,7 +413,7 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( lrt = now; } - json network(_readJson(_networkJP(nwid,false))); + const json network(_readJson(_networkJP(nwid,false))); if (!network.size()) return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND; @@ -458,7 +458,11 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( // If member already has an authorized field, leave it alone. That way its state is // preserved if the user toggles the network back to private. Otherwise set it to // true by default for new members of public nets. - if (!member.count("authorized")) member["authorized"] = true; + if (!member.count("authorized")) { + member["authorized"] = true; + member["lastAuthorizedTime"] = now; + member["lastAuthorizedBy"] = authorizedBy; + } } else if (_jB(member["authorized"],false)) { authorizedBy = "memberIsAuthorized"; } else { @@ -476,6 +480,8 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( if ( ((expires == 0ULL)||(expires > now)) && (tok.length() > 0) && (tok == atok) ) { authorizedBy = "token"; member["authorized"] = true; // tokens actually change member authorization state + member["lastAuthorizedTime"] = now; + member["lastAuthorizedBy"] = authorizedBy; break; } } @@ -517,14 +523,28 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( return NetworkController::NETCONF_QUERY_ACCESS_DENIED; } + // ------------------------------------------------------------------------- // If we made it this far, they are authorized. + // ------------------------------------------------------------------------- _NetworkMemberInfo nmi; _getNetworkMemberInfo(now,nwid,nmi); + // Compute credential TTL. This is the "moving window" for COM agreement and + // the global TTL for Capability and Tag objects. (The same value is used + // for both.) This is computed by reference to the last time we deauthorized + // a member, since within the time period since this event any temporal + // differences are not particularly relevant. + uint64_t credentialTtl = ZT_NETWORKCONFIG_DEFAULT_MIN_CREDENTIAL_TTL; + if (now > nmi.mostRecentDeauthTime) + credentialTtl += (now - nmi.mostRecentDeauthTime); + if (credentialTtl > ZT_NETWORKCONFIG_DEFAULT_MAX_CREDENTIAL_TTL) + credentialTtl = ZT_NETWORKCONFIG_DEFAULT_MAX_CREDENTIAL_TTL; + nc.networkId = nwid; nc.type = _jB(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; nc.timestamp = now; + nc.credentialTimeToLive = credentialTtl; nc.revision = _jI(network["revision"],0ULL); nc.issuedTo = identity.address(); if (_jB(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; @@ -777,7 +797,7 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( } if (_jB(network["private"],true)) { - CertificateOfMembership com(now,ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA,nwid,identity.address()); + CertificateOfMembership com(now,credentialTtl,nwid,identity.address()); if (com.sign(signingId)) { nc.com = com; } else { @@ -976,10 +996,24 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( _initMember(member); try { - if (b.count("authorized")) member["authorized"] = _jB(b["authorized"],false); if (b.count("activeBridge")) member["activeBridge"] = _jB(b["activeBridge"],false); if ((b.count("identity"))&&(!member.count("identity"))) member["identity"] = _jS(b["identity"],""); // allow identity to be populated only if not already known + if (b.count("authorized")) { + const bool newAuth = _jB(b["authorized"],false); + const bool oldAuth = _jB(member["authorized"],false); + if (newAuth != oldAuth) { + if (newAuth) { + member["authorized"] = true; + member["lastAuthorizedTime"] = now; + member["lastAuthorizedBy"] = "user"; + } else { + member["authorized"] = false; + member["lastDeauthorizedTime"] = now; + } + } + } + if (b.count("ipAssignments")) { auto ipa = b["ipAssignments"]; if (ipa.is_array()) { @@ -1476,6 +1510,8 @@ void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid nmi.allocatedIps.insert(mip); } } + } else { + nmi.mostRecentDeauthTime = std::max(nmi.mostRecentDeauthTime,_jI(nm->second["lastDeauthorizedTime"],0ULL)); } } } diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index e6b4bb59..3613e0ef 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -141,12 +141,13 @@ private: // This does lock _networkMemberCache_m struct _NetworkMemberInfo { - _NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0) {} + _NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {} std::set<Address> activeBridges; std::set<InetAddress> allocatedIps; unsigned long authorizedMemberCount; unsigned long activeMemberCount; unsigned long totalMemberCount; + uint64_t mostRecentDeauthTime; }; void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi); @@ -154,7 +155,10 @@ private: inline void _initMember(nlohmann::json &member) { if (!member.count("authorized")) member["authorized"] = false; - if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array(); + if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL; + if (!member.count("lastAuthorizedBy")) member["lastAuthorizedBy"] = ""; + if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL; + if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array(); if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array(); if (!member.count("activeBridge")) member["activeBridge"] = false; if (!member.count("tags")) member["tags"] = nlohmann::json::array(); diff --git a/controller/README.md b/controller/README.md index 339adb31..189fcdbd 100644 --- a/controller/README.md +++ b/controller/README.md @@ -208,6 +208,9 @@ This returns an object containing all currently online members and the most rece | nwid | string | 16-digit network ID | no | | clock | integer | Current clock, ms since epoch | no | | authorized | boolean | Is member authorized? (for private networks) | YES | +| lastAuthorizedTime | integer | Time 'authorized' was last set to 'true' | no | +| lastAuthorizedBy | string | What last set 'authorized' to 'true'? | no | +| lastDeauthorizedTime | integer | Time 'authorized' was last set to 'false' | no | | activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES | | identity | string | Member's public ZeroTier identity (if known) | no | | ipAssignments | array[string] | Managed IP address assignments | YES | diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index 304111d6..2d7c2cb3 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -34,11 +34,6 @@ #include "Utils.hpp" /** - * Default window of time for certificate agreement - */ -#define ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA (ZT_NETWORK_AUTOCONF_DELAY * 5) - -/** * Maximum number of qualifiers allowed in a COM (absolute max: 65535) */ #define ZT_NETWORK_COM_MAX_QUALIFIERS 8 diff --git a/node/Membership.hpp b/node/Membership.hpp index 92bd7ebf..a845b992 100644 --- a/node/Membership.hpp +++ b/node/Membership.hpp @@ -32,10 +32,10 @@ #include "NetworkConfig.hpp" // Expiration time for capability and tag cache -#define ZT_MEMBERSHIP_STATE_EXPIRATION_TIME (ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA * 4) +#define ZT_MEMBERSHIP_STATE_EXPIRATION_TIME 600000 // Expiration time for Memberships (used in Peer::clean()) -#define ZT_MEMBERSHIP_EXPIRATION_TIME (ZT_MEMBERSHIP_STATE_EXPIRATION_TIME * 4) +#define ZT_MEMBERSHIP_EXPIRATION_TIME (ZT_MEMBERSHIP_STATE_EXPIRATION_TIME * 2) namespace ZeroTier { diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index a853d020..e1a4e302 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -41,6 +41,22 @@ #include "Identity.hpp" /** + * Default maximum credential TTL and maxDelta for COM timestamps + * + * The current value is two hours, providing ample time for a controller to + * experience fail-over, etc. + */ +#define ZT_NETWORKCONFIG_DEFAULT_MAX_CREDENTIAL_TTL 7200000ULL + +/** + * Default minimum credential TTL and maxDelta for COM timestamps + * + * This is just slightly over three minutes and provides three retries for + * all currently online members to refresh. + */ +#define ZT_NETWORKCONFIG_DEFAULT_MIN_CREDENTIAL_TTL 185000ULL + +/** * Flag: allow passive bridging (experimental) */ #define ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING 0x0000000000000001ULL |