Browse Source

plumbing full flow from controller -> client network

Grant Limberg 4 years ago
parent
commit
8d39c9a861

+ 25 - 1
controller/DB.hpp

@@ -40,6 +40,30 @@
 namespace ZeroTier
 {
 
+struct AuthInfo
+{
+public:
+	AuthInfo() 
+	: enabled(false)
+	, version(0)
+	, authenticationURL()
+	, authenticationExpiryTime(0)
+	, centralAuthURL()
+	, ssoNonce()
+	, ssoState()
+	, ssoClientID()
+	{}
+
+	bool enabled;
+	uint64_t version;
+	std::string authenticationURL;
+	uint64_t authenticationExpiryTime;
+	std::string centralAuthURL;
+	std::string ssoNonce;
+	std::string ssoState;
+	std::string ssoClientID;
+};
+
 /**
  * Base class with common infrastructure for all controller DB implementations
  */
@@ -108,7 +132,7 @@ public:
 	virtual void eraseMember(const uint64_t networkId,const uint64_t memberId) = 0;
 	virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) = 0;
 
-	virtual std::string getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL) { return ""; }
+	virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) { return AuthInfo(); }
 	virtual void networkMemberSSOHasExpired(uint64_t nwid, int64_t ts);
 
 	inline void addListener(DB::ChangeListener *const listener)

+ 5 - 5
controller/DBMirrorSet.cpp

@@ -125,16 +125,16 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vect
 	return false;
 }
 
-std::string DBMirrorSet::getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL) 
+AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) 
 {
 	std::lock_guard<std::mutex> l(_dbs_l);
 	for(auto d=_dbs.begin();d!=_dbs.end();++d) { 
-		std::string url = (*d)->getSSOAuthURL(member, redirectURL);
-		if (!url.empty()) {
-			return url;
+		AuthInfo info = (*d)->getSSOAuthInfo(member, redirectURL);
+		if (info.enabled) {
+			return info;
 		}
 	}
-	return "";
+	return AuthInfo();
 }
 
 void DBMirrorSet::networkMemberSSOHasExpired(uint64_t nwid, int64_t ts)

+ 1 - 1
controller/DBMirrorSet.hpp

@@ -51,7 +51,7 @@ public:
 	virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member);
 	virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId);
 
-	std::string getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL);
+	AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
 	void networkMemberSSOHasExpired(uint64_t nwid, int64_t ts);
 
 	inline void addDB(const std::shared_ptr<DB> &db)

+ 65 - 16
controller/EmbeddedNetworkController.cpp

@@ -1360,27 +1360,53 @@ void EmbeddedNetworkController::_request(
 	// Otherwise no, we use standard auth logic.
 	bool networkSSOEnabled = OSUtils::jsonBool(network["ssoEnabled"], false);
 	bool memberSSOExempt = OSUtils::jsonBool(member["ssoExempt"], false);
-	std::string authenticationURL;
-	if (networkSSOEnabled && !memberSSOExempt) {
-		authenticationURL = _db.getSSOAuthURL(member, _ssoRedirectURL);
+	AuthInfo info;
+	if (networkSSOEnabled && ! memberSSOExempt) {
+		info = _db.getSSOAuthInfo(member, _ssoRedirectURL);
+		assert(info.enabled == networkSSOEnabled);
+
 		std::string memberId = member["id"];
 		//fprintf(stderr, "ssoEnabled && !ssoExempt %s-%s\n", nwids, memberId.c_str());
 		uint64_t authenticationExpiryTime = (int64_t)OSUtils::jsonInt(member["authenticationExpiryTime"], 0);
 		//fprintf(stderr, "authExpiryTime: %lld\n", authenticationExpiryTime);
 		if (authenticationExpiryTime < now) {
-			if (!authenticationURL.empty()) {
-				_db.networkMemberSSOHasExpired(nwid, now);
-				onNetworkMemberDeauthorize(&_db, nwid, identity.address().toInt());
+			if (info.version == 0) {
+				if (!info.authenticationURL.empty()) {
+					_db.networkMemberSSOHasExpired(nwid, now);
+					onNetworkMemberDeauthorize(&_db, nwid, identity.address().toInt());
 
-				Dictionary<3072> authInfo;
-				authInfo.add("aU", authenticationURL.c_str());
-				//fprintf(stderr, "sending auth URL: %s\n", authenticationURL.c_str());
+					Dictionary<4096> authInfo;
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_VERSION, 0ULL);
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, info.authenticationURL.c_str());
+					//fprintf(stderr, "sending auth URL: %s\n", authenticationURL.c_str());
 
-				DB::cleanMember(member);
-				_db.save(member,true);
+					DB::cleanMember(member);
+					_db.save(member,true);
 
-				_sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED, authInfo.data(), authInfo.sizeBytes());
-				return;
+					_sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED, authInfo.data(), authInfo.sizeBytes());
+					return;
+				}
+			} else if (info.version == 1) {
+				if (!info.authenticationURL.empty()) {
+					_db.networkMemberSSOHasExpired(nwid, now);
+					onNetworkMemberDeauthorize(&_db, nwid, identity.address().toInt());
+
+					Dictionary<8192> authInfo;
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_VERSION, info.version);
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, info.authenticationURL.c_str());
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL, info.centralAuthURL.c_str());
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_NONCE, info.ssoNonce.c_str());
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_STATE, info.ssoState.c_str());
+					authInfo.add(ZT_AUTHINFO_DICT_KEY_CLIENT_ID, info.ssoClientID.c_str());
+
+					DB::cleanMember(member);
+					_db.save(member, true);
+
+					_sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED, authInfo.data(), authInfo.sizeBytes());
+					return;
+				}
+			} else {
+				fprintf(stderr, "invalid sso info.version %llu\n", info.version);
 			}
 		} else if (authorized) {
 			_db.memberWillExpire(authenticationExpiryTime, nwid, identity.address().toInt());
@@ -1452,9 +1478,32 @@ void EmbeddedNetworkController::_request(
 	nc->multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL);
 
 	nc->ssoEnabled = OSUtils::jsonBool(network["ssoEnabled"], false);
-	nc->authenticationExpiryTime = OSUtils::jsonInt(member["authenticationExpiryTime"], 0LL);
-	if (!authenticationURL.empty())
-		Utils::scopy(nc->authenticationURL, sizeof(nc->authenticationURL), authenticationURL.c_str());
+	nc->ssoVersion = info.version;
+
+	if (info.version == 0) {
+		nc->authenticationExpiryTime = OSUtils::jsonInt(member["authenticationExpiryTime"], 0LL);
+		if (!info.authenticationURL.empty()) {
+			Utils::scopy(nc->authenticationURL, sizeof(nc->authenticationURL), info.authenticationURL.c_str());
+		}
+	}
+	else if (info.version == 1) {
+		nc->authenticationExpiryTime = OSUtils::jsonInt(member["authenticationExpiryTime"], 0LL);
+		if (!info.authenticationURL.empty()) {
+			Utils::scopy(nc->authenticationURL, sizeof(nc->authenticationURL), info.authenticationURL.c_str());
+		}
+		if (!info.centralAuthURL.empty()) {
+			Utils::scopy(nc->centralAuthURL, sizeof(nc->centralAuthURL), info.centralAuthURL.c_str());
+		}
+		if (!info.ssoNonce.empty()) {
+			Utils::scopy(nc->ssoNonce, sizeof(nc->ssoNonce), info.ssoNonce.c_str());
+		}
+		if (!info.ssoState.empty()) {
+			Utils::scopy(nc->ssoState, sizeof(nc->ssoState), info.ssoState.c_str());
+		}
+		if (!info.ssoClientID.empty()) {
+			Utils::scopy(nc->ssoClientID, sizeof(nc->ssoClientID), info.ssoClientID.c_str());
+		}
+	}
 
 	std::string rtt(OSUtils::jsonString(member["remoteTraceTarget"],""));
 	if (rtt.length() == 10) {

+ 31 - 16
controller/PostgreSQL.cpp

@@ -336,7 +336,7 @@ void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId,
 	}
 }
 
-std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL)
+AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL)
 {
 	// NONCE is just a random character string.  no semantic meaning
 	// state = HMAC SHA384 of Nonce based on shared sso key
@@ -347,10 +347,12 @@ std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::s
 	// how do we tell when a nonce is used? if auth_expiration_time is set
 	std::string networkId = member["nwid"];
 	std::string memberId = member["id"];
-	char authenticationURL[4096] = {0};
 
-	//fprintf(stderr, "PostgreSQL::updateMemberOnLoad: %s-%s\n", networkId.c_str(), memberId.c_str());
-	bool have_auth = false;
+
+	char authenticationURL[4096] = {0};
+	AuthInfo info;
+	info.enabled = true;
+	// fprintf(stderr, "PostgreSQL::updateMemberOnLoad: %s-%s\n", networkId.c_str(), memberId.c_str());
 	try {
 		auto c = _pool->borrow();
 		pqxx::work w(*c->c);
@@ -390,38 +392,51 @@ std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::s
 				exit(6);
 			}
 
-			r = w.exec_params("SELECT org.client_id, org.authorization_endpoint "
+			r = w.exec_params("SELECT org.client_id, org.authorization_endpoint, org.sso_version "
 				"FROM ztc_network AS nw, ztc_org AS org "
 				"WHERE nw.id = $1 AND nw.sso_enabled = true AND org.owner_id = nw.owner_id", networkId);
 		
 			std::string client_id = "";
 			std::string authorization_endpoint = "";
+			uint64_t sso_version = 0;
 
 			if (r.size() == 1) {
 				client_id = r.at(0)[0].as<std::string>();
 				authorization_endpoint = r.at(0)[1].as<std::string>();
+				sso_version = r.at(0)[2].as<uint64_t>();
 			} else if (r.size() > 1) {
 				fprintf(stderr, "ERROR: More than one auth endpoint for an organization?!?!? NetworkID: %s\n", networkId.c_str());
 			} else {
 				fprintf(stderr, "No client or auth endpoint?!?\n");
 			}
-
+		
+			info.version = sso_version;
+			
 			// no catch all else because we don't actually care if no records exist here. just continue as normal.
 			if ((!client_id.empty())&&(!authorization_endpoint.empty())) {
-				have_auth = true;
-
+				
 				uint8_t state[48];
 				HMACSHA384(_ssoPsk, nonceBytes, sizeof(nonceBytes), state);
 				char state_hex[256];
 				Utils::hex(state, 48, state_hex);
 				
-				OSUtils::ztsnprintf(authenticationURL, sizeof(authenticationURL),
-					"%s?response_type=id_token&response_mode=form_post&scope=openid+email+profile&redirect_uri=%s&nonce=%s&state=%s&client_id=%s",
-					authorization_endpoint.c_str(),
-					redirectURL.c_str(),
-					nonce.c_str(),
-					state_hex,
-					client_id.c_str());
+				if (info.version == 0) {
+					char url[2048] = {0};
+					OSUtils::ztsnprintf(url, sizeof(authenticationURL),
+						"%s?response_type=id_token&response_mode=form_post&scope=openid+email+profile&redirect_uri=%s&nonce=%s&state=%s&client_id=%s",
+						authorization_endpoint.c_str(),
+						redirectURL.c_str(),
+						nonce.c_str(),
+						state_hex,
+						client_id.c_str());
+					info.authenticationURL = std::string(url);
+				} else if (info.version == 1) {
+					info.ssoClientID = client_id;
+					info.authenticationURL = authorization_endpoint;
+					info.ssoNonce = nonce;
+					info.ssoState = std::string(state_hex);
+					info.centralAuthURL = redirectURL;
+				}
 			}  else {
 				fprintf(stderr, "client_id: %s\nauthorization_endpoint: %s\n", client_id.c_str(), authorization_endpoint.c_str());
 			}
@@ -432,7 +447,7 @@ std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::s
 		fprintf(stderr, "ERROR: Error updating member on load: %s\n", e.what());
 	}
 
-	return std::string(authenticationURL);
+	return info; //std::string(authenticationURL);
 }
 
 void PostgreSQL::initializeNetworks()

+ 1 - 1
controller/PostgreSQL.hpp

@@ -107,7 +107,7 @@ public:
 	virtual void eraseNetwork(const uint64_t networkId);
 	virtual void eraseMember(const uint64_t networkId, const uint64_t memberId);
 	virtual void nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress);
-	virtual std::string getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL);
+	virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
 
 protected:
 	struct _PairHasher

+ 27 - 0
include/ZeroTierOne.h

@@ -1194,11 +1194,18 @@ typedef struct
 	 */
 	ZT_VirtualNetworkDNS dns;
 
+
+
 	/**
 	 * sso enabled
 	 */
 	bool ssoEnabled;
 
+	/**
+	 * SSO verison
+	 */
+	uint64_t ssoVersion;
+
 	/**
 	 * If the status us AUTHENTICATION_REQUIRED, this may contain a URL for authentication.
 	 */
@@ -1208,6 +1215,26 @@ typedef struct
 	 * Time that current authentication expires. only valid if ssoEnabled is true
 	 */
 	uint64_t authenticationExpiryTime;
+
+	/**
+	 * central base URL.
+	 */
+	char centralAuthURL[2048];
+
+	/**
+	 * sso nonce
+	 */
+	char ssoNonce[64];
+
+	/**
+	 * sso state
+	 */
+	char ssoState[128];
+
+	/**
+	 * oidc client id
+	 */
+	char ssoClientID[256];
 } ZT_VirtualNetworkConfig;
 
 /**

+ 9 - 5
make-mac.mk

@@ -1,6 +1,8 @@
 CC=clang
 CXX=clang++
-INCLUDES=
+TOPDIR=$(shell PWD)
+
+INCLUDES=-I$(shell PWD)/zeroidc/target
 DEFS=
 LIBS=
 ARCH_FLAGS=-arch x86_64 -arch arm64 
@@ -26,7 +28,7 @@ DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_BUIL
 
 include objects.mk
 ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o osdep/MacDNSHelper.o ext/http-parser/http_parser.o
-LIBS+=-framework CoreServices -framework SystemConfiguration -framework CoreFoundation
+LIBS+=-framework CoreServices -framework SystemConfiguration -framework CoreFoundation -framework Security
 
 # Official releases are signed with our Apple cert and apply software updates by default
 ifeq ($(ZT_OFFICIAL_RELEASE),1)
@@ -103,7 +105,7 @@ mac-agent: FORCE
 osdep/MacDNSHelper.o: osdep/MacDNSHelper.mm
 	$(CXX) $(CXXFLAGS) -c osdep/MacDNSHelper.mm -o osdep/MacDNSHelper.o 
 
-one:	$(CORE_OBJS) $(ONE_OBJS) one.o mac-agent zeroidc
+one:	zeroidc $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent 
 	$(CXX) $(CXXFLAGS) -o zerotier-one $(CORE_OBJS) $(ONE_OBJS) one.o $(LIBS) zeroidc/target/libzeroidc.a
 	# $(STRIP) zerotier-one
 	ln -sf zerotier-one zerotier-idtool
@@ -115,8 +117,8 @@ zerotier-one: one
 zeroidc: zeroidc/target/libzeroidc.a
 
 zeroidc/target/libzeroidc.a:
-	cd zeroidc && cargo build --target=x86_64-apple-darwin --release
-	cd zeroidc && cargo build --target=aarch64-apple-darwin --release
+	cd zeroidc && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build --target=x86_64-apple-darwin --release
+	cd zeroidc && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build --target=aarch64-apple-darwin --release
 	cd zeroidc && lipo -create target/x86_64-apple-darwin/release/libzeroidc.a target/aarch64-apple-darwin/release/libzeroidc.a -output target/libzeroidc.a
 
 central-controller:
@@ -126,6 +128,8 @@ zerotier-idtool: one
 
 zerotier-cli: one
 
+$(CORE_OBJS): zeroidc
+
 libzerotiercore.a:	$(CORE_OBJS)
 	ar rcs libzerotiercore.a $(CORE_OBJS)
 	ranlib libzerotiercore.a

+ 52 - 7
node/IncomingPacket.cpp

@@ -199,17 +199,62 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar
 					const uint16_t errorDataSize = at<uint16_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8);
 					s -= 2;
 					if (s >= (int)errorDataSize) {
-						Dictionary<3072> authInfo(((const char *)this->data()) + (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 10), errorDataSize);
-						char authenticationURL[2048];
-						if (authInfo.get("aU", authenticationURL, sizeof(authenticationURL)) > 0) {
-							authenticationURL[sizeof(authenticationURL) - 1] = 0; // ensure always zero terminated
-							network->setAuthenticationRequired(authenticationURL);
-							noUrl = false;
+						Dictionary<8192> authInfo(((const char *)this->data()) + (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 10), errorDataSize);
+
+						uint64_t authVer = authInfo.getUI(ZT_AUTHINFO_DICT_KEY_VERSION, 0ULL);
+
+						if (authVer == 0) {
+							char authenticationURL[2048];
+							
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, authenticationURL, sizeof(authenticationURL)) > 0) {
+								authenticationURL[sizeof(authenticationURL) - 1] = 0; // ensure always zero terminated
+								network->setAuthenticationRequired(authenticationURL);
+								noUrl = false;
+							}
+						} else if (authVer == 1) {
+							bool haveAuthURL = false;
+							char authenticationURL[2048] = { 0 };
+							bool haveCentralURL = false;
+							char centralAuthURL[2048] = { 0 };
+							bool haveNonce = false;
+							char ssoNonce[64] = { 0 };
+							bool haveState = false;
+							char ssoState[128] = {0};
+							bool haveClientID = false;
+							char ssoClientID[256] = { 0 };
+
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, authenticationURL, sizeof(authenticationURL)) > 0) {
+								authenticationURL[sizeof(authenticationURL) - 1] = 0;
+								haveAuthURL = true;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL, centralAuthURL, sizeof(centralAuthURL))>0) {
+								centralAuthURL[sizeof(centralAuthURL) - 1] = 0;
+								haveCentralURL = true;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_NONCE, ssoNonce, sizeof(ssoNonce)) > 0) {
+								ssoNonce[sizeof(ssoNonce) - 1] = 0;
+								haveNonce = true;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_STATE, ssoState, sizeof(ssoState)) > 0) {
+								ssoState[sizeof(ssoState) - 1] = 0;
+								haveState = true;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CLIENT_ID, ssoClientID, sizeof(ssoClientID)) > 0) {
+								ssoClientID[sizeof(ssoClientID) - 1] = 0;
+								haveClientID = true;
+							}
+
+							noUrl = ! (haveAuthURL && haveCentralURL && haveNonce && haveState && haveClientID);
+
+							if (!noUrl) {
+								network->setAuthenticationRequired(authenticationURL, centralAuthURL, ssoClientID, ssoNonce, ssoState);
+							}
 						}
 					}
 				}
-				if (noUrl)
+				if (noUrl) {
 					network->setAuthenticationRequired("");
+				}
 			}
 		}	break;
 

+ 46 - 1
node/Network.cpp

@@ -32,6 +32,7 @@
 #include "Node.hpp"
 #include "Peer.hpp"
 #include "Trace.hpp"
+#include "zeroidc.h"
 
 #include <set>
 
@@ -550,7 +551,8 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u
 	_lastConfigUpdate(0),
 	_destroyed(false),
 	_netconfFailure(NETCONF_FAILURE_NONE),
-	_portError(0)
+	_portError(0),
+	_idc(nullptr)
 {
 	for(int i=0;i<ZT_NETWORK_MAX_INCOMING_UPDATES;++i)
 		_incomingConfigChunks[i].ts = 0;
@@ -591,6 +593,12 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u
 		_portError = RR->node->configureVirtualNetworkPort(tPtr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
 		_portInitialized = true;
 	}
+
+	if (nconf->ssoEnabled) {
+		if (_config.ssoVersion == 1) {
+			// start sso handling for network
+		}
+	}
 }
 
 Network::~Network()
@@ -598,6 +606,12 @@ Network::~Network()
 	ZT_VirtualNetworkConfig ctmp;
 	_externalConfig(&ctmp);
 
+	if (_idc) {
+		zeroidc::zeroidc_stop(_idc);
+		zeroidc::zeroidc_delete(_idc);
+		_idc = nullptr;
+	}
+
 	if (_destroyed) {
 		// This is done in Node::leave() so we can pass tPtr properly
 		//RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
@@ -1434,8 +1448,13 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const
 	memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS));
 
 	Utils::scopy(ec->authenticationURL, sizeof(ec->authenticationURL), _authenticationURL.c_str());
+	ec->ssoVersion = _config.ssoVersion;
 	ec->authenticationExpiryTime = _config.authenticationExpiryTime;
 	ec->ssoEnabled = _config.ssoEnabled;
+	Utils::scopy(ec->centralAuthURL, sizeof(ec->centralAuthURL), _config.centralAuthURL);
+	Utils::scopy(ec->ssoNonce, sizeof(ec->ssoNonce), _config.ssoNonce);
+	Utils::scopy(ec->ssoState, sizeof(ec->ssoState), _config.ssoState);
+	Utils::scopy(ec->ssoClientID, sizeof(ec->ssoClientID), _config.ssoClientID);
 }
 
 void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup)
@@ -1542,4 +1561,30 @@ Membership &Network::_membership(const Address &a)
 	return _memberships[a];
 }
 
+void Network::setAuthenticationRequired(const char* authEndpoint, const char* centralEndpoint, const char* clientID, const char* nonce, const char* state)
+{
+	Mutex::Lock _l(_lock);
+	_netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
+	_config.ssoEnabled = true;
+	_config.ssoVersion = 1;
+
+	Utils::scopy(_config.authenticationURL, sizeof(_config.authenticationURL), authEndpoint);
+	Utils::scopy(_config.centralAuthURL, sizeof(_config.centralAuthURL), centralEndpoint);
+	Utils::scopy(_config.ssoClientID, sizeof(_config.ssoClientID), clientID);
+	Utils::scopy(_config.ssoNonce, sizeof(_config.ssoNonce), nonce);
+	Utils::scopy(_config.ssoState, sizeof(_config.ssoState), state);
+
+	// TODO: propaagte to full flow module
+	if (!this->_idc) {
+		this->_idc = zeroidc::zeroidc_new(_config.authenticationURL, _config.ssoClientID, _config.centralAuthURL, 9993);
+		zeroidc::zeroidc_start(this->_idc);
+	}
+
+	zeroidc::AuthInfo *info = zeroidc::zeroidc_get_auth_info(this->_idc, _config.ssoState, _config.ssoNonce);
+	const char* url = zeroidc::zeroidc_get_auth_url(info);
+	if (url != NULL) {
+		fprintf(stderr, "full auth URL: %s\n", url);
+	}
+}
+
 } // namespace ZeroTier

+ 15 - 2
node/Network.hpp

@@ -41,6 +41,10 @@
 #define ZT_NETWORK_MAX_INCOMING_UPDATES 3
 #define ZT_NETWORK_MAX_UPDATE_CHUNKS ((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1)
 
+namespace zeroidc {
+typedef struct ZeroIDC ZeroIDC;
+}
+
 namespace ZeroTier {
 
 class RuntimeEnvironment;
@@ -229,7 +233,14 @@ public:
 		_netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
 		_authenticationURL = (url) ? url : "";
 		_config.ssoEnabled = true;
-	}	
+		_config.ssoVersion = 0;
+	}
+
+	/**
+	 * set netconf failure to 'authentication required' along with info needed
+	 * for sso full flow authentication.
+	 */
+	void setAuthenticationRequired(const char* authEndpoint, const char* centralEndpoint, const char* clientID, const char* nonce, const char* state);
 
 	/**
 	 * Causes this network to request an updated configuration from its master node now
@@ -457,8 +468,10 @@ private:
 	Mutex _lock;
 
 	AtomicCounter __refCount;
+
+	zeroidc::ZeroIDC *_idc;
 };
 
-} // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 55 - 13
node/NetworkConfig.cpp

@@ -182,12 +182,24 @@ bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,b
 			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false;
 		}
 
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false;
-		if (this->ssoEnabled) {
-			if (this->authenticationURL[0]) {
-				if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
+		if (this->ssoVersion == 0) {
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false;
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false;
+
+			if (this->ssoEnabled) {
+				if (this->authenticationURL[0]) {
+					if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
+				}
+				if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) return false;
 			}
-			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) return false;
+		} else if(this->ssoVersion == 1) {
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false;
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false;
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) return false;
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) return false;
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) return false;
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) return false;
 		}
 
 		delete tmp;
@@ -374,18 +386,48 @@ bool NetworkConfig::fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACI
 				DNS::deserializeDNS(*tmp, p, &dns);
 			}
 
-
+			this->ssoVersion = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, 0ULL);
 			this->ssoEnabled = d.getB(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, false);
-			if (this->ssoEnabled) {
-				if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
-					this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0; // ensure null terminated
+
+			if (this->ssoVersion == 0) {
+				// implicit flow
+				if (this->ssoEnabled) {
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (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, 0);
 				} else {
 					this->authenticationURL[0] = 0;
+					this->authenticationExpiryTime = 0;
+				}
+			} else if (this->ssoVersion == 1) {
+				// full flow
+				if (this->ssoEnabled) {
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
+						this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL, (unsigned int)sizeof(this->centralAuthURL)) > 0) {
+						this->centralAuthURL[sizeof(this->centralAuthURL) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce, (unsigned int)sizeof(this->ssoNonce)) > 0) {
+						this->ssoNonce[sizeof(this->ssoNonce) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState, (unsigned int)sizeof(this->ssoState)) > 0) {
+						this->ssoState[sizeof(this->ssoState) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID, (unsigned int)sizeof(this->ssoClientID)) > 0) {
+						this->ssoClientID[sizeof(this->ssoClientID) - 1] = 0;
+					}
+				} else {
+					this->authenticationURL[0] = 0;
+					this->authenticationExpiryTime = 0;
+					this->centralAuthURL[0] = 0;
+					this->ssoNonce[0] = 0;
+					this->ssoState[0] = 0;
+					this->ssoClientID[0] = 0;
 				}
-				this->authenticationExpiryTime = d.getI(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, 0);
-			} else {
-				this->authenticationURL[0] = 0;
-				this->authenticationExpiryTime = 0;
 			}
 		}
 

+ 63 - 2
node/NetworkConfig.hpp

@@ -180,10 +180,35 @@ namespace ZeroTier {
 #define ZT_NETWORKCONFIG_DICT_KEY_DNS "DNS"
 // sso enabld
 #define ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED "ssoe"
+// so version
+#define ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION "ssov"
 // authentication URL
 #define ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL "aurl"
 // authentication expiry
 #define ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME "aexpt"
+// central endpoint
+#define ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL "ssoce"
+// nonce
+#define ZT_NETWORKCONFIG_DICT_KEY_NONCE "sson"
+// state
+#define ZT_NETWORKCONFIG_DICT_KEY_STATE "ssos"
+// client ID
+#define ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID "ssocid"
+
+// AuthInfo fields -- used by ncSendError for sso
+
+// AuthInfo Version
+#define ZT_AUTHINFO_DICT_KEY_VERSION "aV"
+// authenticaiton URL
+#define ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL "aU"
+// Central endpoint URL
+#define ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL "aCU"
+// Nonce
+#define ZT_AUTHINFO_DICT_KEY_NONCE "aN"
+// State
+#define ZT_AUTHINFO_DICT_KEY_STATE "aS"
+// Client ID
+#define ZT_AUTHINFO_DICT_KEY_CLIENT_ID "aCID"
 
 // Legacy fields -- these are obsoleted but are included when older clients query
 
@@ -242,7 +267,11 @@ public:
 		dnsCount(0),
 		ssoEnabled(false),
 		authenticationURL(),
-		authenticationExpiryTime(0)
+		authenticationExpiryTime(0),
+		centralAuthURL(),
+		ssoNonce(),
+		ssoState(),
+		ssoClientID()
 	{
 		name[0] = 0;
 		memset(specialists, 0, sizeof(uint64_t)*ZT_MAX_NETWORK_SPECIALISTS);
@@ -250,6 +279,11 @@ public:
 		memset(staticIps, 0, sizeof(InetAddress)*ZT_MAX_ZT_ASSIGNED_ADDRESSES);
 		memset(rules, 0, sizeof(ZT_VirtualNetworkRule)*ZT_MAX_NETWORK_RULES);
 		memset(&dns, 0, sizeof(ZT_VirtualNetworkDNS));
+		memset(authenticationURL, 0, sizeof(authenticationURL));
+		memset(centralAuthURL, 0, sizeof(centralAuthURL));
+		memset(ssoNonce, 0, sizeof(ssoNonce));
+		memset(ssoState, 0, sizeof(ssoState));
+		memset(ssoClientID, 0, sizeof(ssoClientID));
 	}
 
 	/**
@@ -619,15 +653,42 @@ public:
 	 */
 	bool ssoEnabled;
 
+	/**
+	 * SSO verison
+	 */
+	uint64_t ssoVersion;
+
 	/**
 	 * Authentication URL if authentication is required
 	 */
 	char authenticationURL[2048];
 
-	/**
+/**
 	 * Time current authentication expires or 0 if external authentication is disabled
+	 * 
+	 * Not used if authVersion >= 1
 	 */
 	uint64_t authenticationExpiryTime;
+
+	/**
+	 * central base URL.
+	 */
+	char centralAuthURL[2048];
+
+	/**
+	 * sso nonce
+	 */
+	char ssoNonce[64];
+
+	/**
+	 * sso state
+	 */
+	char ssoState[128];
+
+	/**
+	 * oidc client id
+	 */
+	char ssoClientID[256];
 };
 
 } // namespace ZeroTier

+ 5 - 0
zeroidc/.cargo/config.toml

@@ -0,0 +1,5 @@
+[target.x86_64-apple-darwin]
+rustflags=["-C", "link-arg=-mmacosx-version-min=10.13"]
+
+[target.aarch64-apple-darwin]
+rustflags=["-C", "link-arg=-mmacosx-version-min=10.13"]