From b270d527f4ad7dbdba520def574201a0cb26f2ed Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 24 May 2021 22:58:17 -0400 Subject: [PATCH] Basic plumbing for authentication requirement and piping through of URL information. --- controller/DB.cpp | 2 ++ controller/EmbeddedNetworkController.cpp | 34 +++++++++++++++++++----- controller/EmbeddedNetworkController.hpp | 1 + include/ZeroTierOne.h | 17 +++++++++++- node/Network.cpp | 3 +++ node/Network.hpp | 14 +++++++++- node/NetworkConfig.cpp | 14 ++++++++++ node/NetworkConfig.hpp | 14 ++++++++++ node/NetworkController.hpp | 10 +++++-- node/Node.cpp | 13 ++++++++- node/Node.hpp | 2 +- node/Packet.hpp | 11 +++++++- service/OneService.cpp | 2 ++ 13 files changed, 124 insertions(+), 13 deletions(-) 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 &d,b if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false; } + if (this->authenticationURL[0]) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false; + } + if (this->authenticationExpiryTime >= 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) return false; + } + delete tmp; } catch ( ... ) { delete tmp; @@ -365,6 +372,13 @@ bool NetworkConfig::fromDictionary(const DictionaryauthenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) { + this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0; // ensure null terminated + } else { + this->authenticationURL[0] = 0; + } + this->authenticationExpiryTime = d.getI(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, -1); } //printf("~~~\n%s\n~~~\n",d.data()); diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 06e0127fe..3f49ba50f 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -178,6 +178,10 @@ namespace ZeroTier { #define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP "COO" // dns (binary blobs) #define ZT_NETWORKCONFIG_DICT_KEY_DNS "DNS" +// authentication URL +#define ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL "aurl" +// authentication expiry +#define ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME "aexpt" // Legacy fields -- these are obsoleted but are included when older clients query @@ -604,6 +608,16 @@ public: * ZT pushed DNS configuration */ ZT_VirtualNetworkDNS dns; + + /** + * Authentication URL if authentication is required + */ + char authenticationURL[256]; + + /** + * Time current authentication expires or -1 if external authentication is disabled + */ + int64_t authenticationExpiryTime; }; } // namespace ZeroTier diff --git a/node/NetworkController.hpp b/node/NetworkController.hpp index 29a2d8f17..3bf570c97 100644 --- a/node/NetworkController.hpp +++ b/node/NetworkController.hpp @@ -38,7 +38,8 @@ public: NC_ERROR_NONE = 0, NC_ERROR_OBJECT_NOT_FOUND = 1, NC_ERROR_ACCESS_DENIED = 2, - NC_ERROR_INTERNAL_SERVER_ERROR = 3 + NC_ERROR_INTERNAL_SERVER_ERROR = 3, + NC_ERROR_AUTHENTICATION_REQUIRED = 4 }; /** @@ -69,12 +70,17 @@ public: /** * Send a network configuration request error * + * If errorData/errorDataSize are provided they must point to a valid serialized + * Dictionary containing error data. They can be null/zero if not specified. + * * @param nwid Network ID * @param requestPacketId Request packet ID or 0 if none * @param destination Destination peer Address * @param errorCode Error code + * @param errorData Data associated with error or NULL if none + * @param errorDataSize Size of errorData in bytes */ - virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode) = 0; + virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode, const void *errorData, unsigned int errorDataSize) = 0; }; NetworkController() {} diff --git a/node/Node.cpp b/node/Node.cpp index 05f5a247b..3e4c65c0f 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -731,7 +731,7 @@ void Node::ncSendRevocation(const Address &destination,const Revocation &rev) } } -void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode) +void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode, const void *errorData, unsigned int errorDataSize) { if (destination == RR->identity.address()) { SharedPtr n(network(nwid)); @@ -744,6 +744,9 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des case NetworkController::NC_ERROR_ACCESS_DENIED: n->setAccessDenied(); break; + case NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED: { + } + break; default: break; } @@ -760,8 +763,16 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des case NetworkController::NC_ERROR_ACCESS_DENIED: outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); break; + case NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED: + outp.append((unsigned char)Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED); + break; } + outp.append(nwid); + + if ((errorData)&&(errorDataSize > 0)) + outp.append(errorData, errorDataSize); + RR->sw->send((void *)0,outp,true); } // else we can't send an ERROR() in response to nothing, so discard } diff --git a/node/Node.hpp b/node/Node.hpp index 2bbd3b47f..913bc7142 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -245,7 +245,7 @@ public: virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig); virtual void ncSendRevocation(const Address &destination,const Revocation &rev); - virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode); + virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode, const void *errorData, unsigned int errorDataSize); inline const Address &remoteTraceTarget() const { return _remoteTraceTarget; } inline Trace::Level remoteTraceLevel() const { return _remoteTraceLevel; } diff --git a/node/Packet.hpp b/node/Packet.hpp index 78846ecdd..7219a3310 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -792,6 +792,12 @@ public: * * ERROR response payload: * <[8] 64-bit network ID> + * <[2] 16-bit length of error-related data (optional)> + * <[...] error-related data (optional)> + * + * Error related data is a Dictionary containing things like a URL + * for authentication or a human-readable error message, and is + * optional and may be absent or empty. */ VERB_NETWORK_CONFIG_REQUEST = 0x0b, @@ -1076,7 +1082,10 @@ public: ERROR_NETWORK_ACCESS_DENIED_ = 0x07, /* extra _ at end to avoid Windows name conflict */ /* Multicasts to this group are not wanted */ - ERROR_UNWANTED_MULTICAST = 0x08 + ERROR_UNWANTED_MULTICAST = 0x08, + + /* Network requires external or 2FA authentication (e.g. SSO). */ + ERROR_NETWORK_AUTHENTICATION_REQUIRED = 0x09 }; template diff --git a/service/OneService.cpp b/service/OneService.cpp index 6f75dbdff..7b5ee57b6 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -251,6 +251,8 @@ static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc, } nj["dns"] = m; + nj["authenticationURL"] = nc->authenticationURL; + nj["authenticationExpiryTime"] = nc->authenticationExpiryTime; } static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer)