diff --git a/controller/DB.cpp b/controller/DB.cpp index 8a86ae376..80cdb7fa1 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -67,6 +67,8 @@ void DB::initMember(nlohmann::json &member) if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL; if (!member.count("lastAuthorizedCredentialType")) member["lastAuthorizedCredentialType"] = nlohmann::json(); if (!member.count("lastAuthorizedCredential")) member["lastAuthorizedCredential"] = nlohmann::json(); + if (!member.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = -1LL; + if (!member.count("authenticationURL")) member["authenticationURL"] = nlohmann::json(); if (!member.count("vMajor")) member["vMajor"] = -1; if (!member.count("vMinor")) member["vMinor"] = -1; if (!member.count("vRev")) member["vRev"] = -1; diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 1b2da4c56..174916fec 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -466,6 +466,14 @@ EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPa _db(this), _rc(rc) { + memset(_ssoPsk, 0, sizeof(_ssoPsk)); + char *const ssoPskHex = getenv("ZT_SSO_PSK"); + if (ssoPskHex) { + // SECURITY: note that ssoPskHex will always be null-terminated if libc acatually + // returns something non-NULL. If the hex encodes something shorter than 48 bytes, + // it will be padded at the end with zeroes. If longer, it'll be truncated. + Utils::unhex(ssoPskHex, _ssoPsk, sizeof(_ssoPsk)); + } } EmbeddedNetworkController::~EmbeddedNetworkController() @@ -1248,7 +1256,7 @@ void EmbeddedNetworkController::_request( Utils::hex(nwid,nwids); _db.get(nwid,network,identity.address().toInt(),member,ns); if ((!network.is_object())||(network.empty())) { - _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND); + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND, nullptr, 0); return; } const bool newMember = ((!member.is_object())||(member.empty())); @@ -1262,11 +1270,11 @@ void EmbeddedNetworkController::_request( // known member. try { if (Identity(haveIdStr.c_str()) != identity) { - _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); return; } } catch ( ... ) { - _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); return; } } else { @@ -1348,16 +1356,30 @@ void EmbeddedNetworkController::_request( ms.identity = identity; } } + + const int64_t authenticationExpiryTime = member["authenticationExpiryTime"]; + if ((authenticationExpiryTime >= 0)&&(authenticationExpiryTime < now)) { + const std::string authenticationURL = member["authenticationURL"]; + if (authenticationURL.empty()) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED, nullptr, 0); + return; + } else { + Dictionary<1024> authInfo; + authInfo.add("aU", authenticationURL.c_str()); + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED, authInfo.data(), authInfo.sizeBytes()); + return; + } + } } else { // If they are not authorized, STOP! DB::cleanMember(member); _db.save(member,true); - _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); return; } // ------------------------------------------------------------------------- - // If we made it this far, they are authorized. + // If we made it this far, they are authorized (and authenticated). // ------------------------------------------------------------------------- int64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; @@ -1734,7 +1756,7 @@ void EmbeddedNetworkController::_request( if (com.sign(_signingId)) { nc->com = com; } else { - _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR); + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR, nullptr, 0); return; } diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index e499dd647..326bdce87 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -140,6 +140,7 @@ private: Identity _signingId; std::string _signingIdAddressString; NetworkController::Sender *_sender; + uint8_t _ssoPsk[48]; DBMirrorSet _db; BlockingQueue< _RQEntry * > _queue; diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 83c4a4787..cb4474c72 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -820,7 +820,12 @@ enum ZT_VirtualNetworkStatus /** * ZeroTier core version too old */ - ZT_NETWORK_STATUS_CLIENT_TOO_OLD = 5 + ZT_NETWORK_STATUS_CLIENT_TOO_OLD = 5, + + /** + * External authentication is required (e.g. SSO) + */ + ZT_NETWORK_STATUS_AUTHENTICATION_REQUIRED = 6 }; /** @@ -1339,6 +1344,16 @@ typedef struct * Network specific DNS configuration */ ZT_VirtualNetworkDNS dns; + + /** + * If the status us AUTHENTICATION_REQUIRED, this may contain a URL for authentication. + */ + char authenticationURL[256]; + + /** + * Time that current authentication expires or -1 if external authentication is not required. + */ + int64_t authenticationExpiryTime; } ZT_VirtualNetworkConfig; /** diff --git a/node/Network.cpp b/node/Network.cpp index 914c96bc6..32e4bcec4 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -1429,6 +1429,9 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const } memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS)); + + Utils::scopy(ec->authenticationURL, sizeof(ec->authenticationURL), _config.authenticationURL); + ec->authenticationExpiryTime = _config.authenticationExpiryTime; } void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup) diff --git a/node/Network.hpp b/node/Network.hpp index b20d8b66b..38933a4d7 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -220,6 +220,16 @@ public: _netconfFailure = NETCONF_FAILURE_NOT_FOUND; } + /** + * Set netconf failure to 'authentication required' possibly with an authorization URL + */ + inline void setAuthenticationRequired(const char *url) + { + Mutex::Lock _l(_lock); + _netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED; + _authorizationURL = (url) ? url : ""; + } + /** * Causes this network to request an updated configuration from its master node now * @@ -435,9 +445,11 @@ private: NETCONF_FAILURE_NONE, NETCONF_FAILURE_ACCESS_DENIED, NETCONF_FAILURE_NOT_FOUND, - NETCONF_FAILURE_INIT_FAILED + NETCONF_FAILURE_INIT_FAILED, + NETCONF_FAILURE_AUTHENTICATION_REQUIRED } _netconfFailure; int _portError; // return value from port config callback + std::string _authorizationURL; Hashtable
_memberships; diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 5259a3e32..2673311f7 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -182,6 +182,13 @@ bool NetworkConfig::toDictionary(Dictionary