Initial MicroService Tests

This commit is contained in:
stephb9959
2021-07-04 09:09:34 -07:00
parent 7aa9751379
commit c4f04f5f8d
11 changed files with 170 additions and 112 deletions

View File

@@ -1 +1,36 @@
# ucentralsec
uCentralSec is the Authentication & Resource Policy Access service for the uCentral system. In order to use the uCentral system
you must have at least 1 uCentralSec. uCentralSec is the first point of contact for the entire architecture. We strongly recommend using Docker
to deploy all the uCentral services. If you would like to develop and play with the source, please do.
## OpenAPI
Like all other uCentral services, uCentralSec is defined through an OpenAPI. You can use this API to build your own applications or integration modules
into your own systems. If all you need it to access the uCentralGW for example (the service that manages the APs), you will need to:
- get a token (`/oauth2`)
- find the endpoints on the system (`/systemEndpoints`)
- choose one to manage (pick an endpoint that matches what you are trying to do by looking at its `type`. For the gateway, type = ucentrtalgw)
- make your calls (use the PublicEndPoint of the corresponding entry to make your calls, do not forget to add `/api/v1` as the root os the call)
The CLI for the [uCentralGW](https://github.com/telecominfraproject/wlan-cloud-ucentralgw/blob/main/test_scripts/curl/cli) has a very good example of this. Loog for the `setgateway`
function.
## Firewall Considerations
The entire uCentral systems uses several MicroServices. In order for the whole system to work, you should provide the following port
access
- Security
- Public: 16001
- Private: 17001
- ALB: 16101
- Gateway:
- Public: 16002
- Private: 17002
- ALB: 16102
- Firmware:
- Public: 16004
- Private: 17004
- ALB: 16104

View File

@@ -57,7 +57,7 @@ namespace uCentral {
Logger_.notice("Stopping...");
}
bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::WebToken & UserInfo )
bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo )
{
if(!Secure_)
return true;
@@ -81,24 +81,23 @@ namespace uCentral {
if(CallToken.empty())
return false;
auto Client = Tokens_.find(CallToken);
auto Client = UserCache_.find(CallToken);
if( Client == Tokens_.end() )
return ValidateToken(CallToken, CallToken, UserInfo);
if( Client == UserCache_.end() )
return ValidateToken(CallToken, CallToken, UInfo);
if((Client->second.created_ + Client->second.expires_in_) > time(nullptr)) {
if((Client->second.webtoken.created_ + Client->second.webtoken.expires_in_) > time(nullptr)) {
SessionToken = CallToken;
UserInfo = Client->second ;
UInfo = Client->second ;
return true;
}
Tokens_.erase(CallToken);
UserCache_.erase(CallToken);
return false;
}
void AuthService::Logout(const std::string &token) {
SubMutexGuard Guard(Mutex_);
Tokens_.erase(token);
UserCache_.erase(token);
try {
Poco::JSON::Object Obj;
@@ -134,7 +133,7 @@ namespace uCentral {
return JWT;
}
bool AuthService::ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::WebToken & UserInfo ) {
bool AuthService::ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ) {
SubMutexGuard Guard(Mutex_);
Poco::JWT::Token DecryptedToken;
@@ -146,26 +145,26 @@ namespace uCentral {
auto IssuedAt = DecryptedToken.getIssuedAt();
auto Subject = DecryptedToken.getSubject();
UserInfo.access_token_ = Token;
UserInfo.refresh_token_= Token;
UserInfo.username_ = Identity;
UserInfo.id_token_ = Token;
UserInfo.token_type_ = "Bearer";
UserInfo.created_ = IssuedAt.epochTime();
UserInfo.expires_in_ = Expires.epochTime() - IssuedAt.epochTime();
UserInfo.idle_timeout_ = 5*60;
UInfo.webtoken.access_token_ = Token;
UInfo.webtoken.refresh_token_= Token;
UInfo.webtoken.username_ = Identity;
UInfo.webtoken.id_token_ = Token;
UInfo.webtoken.token_type_ = "Bearer";
UInfo.webtoken.created_ = IssuedAt.epochTime();
UInfo.webtoken.expires_in_ = Expires.epochTime() - IssuedAt.epochTime();
UInfo.webtoken.idle_timeout_ = 5*60;
if(Storage()->GetIdentityRights(Identity, UserInfo.acl_template_)) {
if(Storage()->GetIdentityRights(Identity, UInfo.webtoken.acl_template_)) {
} else {
// we can get in but we have no given rights... something is very wrong
UserInfo.acl_template_.Read_ = true ;
UserInfo.acl_template_.ReadWriteCreate_ =
UserInfo.acl_template_.ReadWrite_ =
UserInfo.acl_template_.Delete_ = false;
UserInfo.acl_template_.PortalLogin_ = true;
UInfo.webtoken.acl_template_.Read_ = true ;
UInfo.webtoken.acl_template_.ReadWriteCreate_ =
UInfo.webtoken.acl_template_.ReadWrite_ =
UInfo.webtoken.acl_template_.Delete_ = false;
UInfo.webtoken.acl_template_.PortalLogin_ = true;
}
Tokens_[UserInfo.access_token_] = UserInfo;
UserCache_[UInfo.webtoken.access_token_] = UInfo;
return true;
}
@@ -176,27 +175,24 @@ namespace uCentral {
return false;
}
void AuthService::CreateToken(const std::string & UserName, SecurityObjects::WebToken & UserInfo, SecurityObjects::AclTemplate & ACL)
void AuthService::CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo)
{
SubMutexGuard Guard(Mutex_);
std::string Token = GenerateToken(UserName,USERNAME,30);
UInfo.webtoken.expires_in_ = 30 * 24 * 60 * 60 ;
UInfo.webtoken.idle_timeout_ = 5 * 60;
UInfo.webtoken.token_type_ = "Bearer";
UInfo.webtoken.access_token_ = Token;
UInfo.webtoken.id_token_ = Token;
UInfo.webtoken.refresh_token_ = Token;
UInfo.webtoken.created_ = time(nullptr);
UInfo.webtoken.username_ = UserName;
UserInfo.acl_template_ = ACL;
UserInfo.expires_in_ = 30 * 24 * 60 * 60 ;
UserInfo.idle_timeout_ = 5 * 60;
UserInfo.token_type_ = "Bearer";
UserInfo.access_token_ = Token;
UserInfo.id_token_ = Token;
UserInfo.refresh_token_ = Token;
UserInfo.created_ = time(nullptr);
UserInfo.username_ = UserName;
Tokens_[UserInfo.access_token_] = UserInfo;
UserCache_[Token] = UInfo;
}
bool AuthService::Authorize( const std::string & UserName, const std::string & Password, SecurityObjects::WebToken & ResultToken )
bool AuthService::Authorize( const std::string & UserName, const std::string & Password, SecurityObjects::UserInfoAndPolicy & UInfo )
{
SubMutexGuard Guard(Mutex_);
SecurityObjects::AclTemplate ACL;
@@ -206,7 +202,11 @@ namespace uCentral {
if(((UserName == DefaultUserName_) && (DefaultPassword_== ComputePasswordHash(UserName,Password))) || !Secure_)
{
ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true;
CreateToken(UserName, ResultToken, ACL);
UInfo.webtoken.acl_template_ = ACL;
UInfo.userinfo.email = DefaultUserName_;
UInfo.userinfo.currentPassword = DefaultPassword_;
UInfo.userinfo.name = DefaultUserName_;
CreateToken(UserName, UInfo );
return true;
}
} else if (Mechanism_=="db") {
@@ -214,7 +214,7 @@ namespace uCentral {
std::string TUser{UserName};
if(Storage()->GetIdentity(TUser,PasswordHash,USERNAME,ACL)) {
CreateToken(UserName, ResultToken, ACL);
CreateToken(UserName, UInfo);
return true;
}
}

View File

@@ -24,7 +24,6 @@ namespace uCentral{
class AuthService : public SubSystemServer {
public:
typedef std::map<std::string, SecurityObjects::WebToken> WebTokenMap;
enum ACCESS_TYPE {
USERNAME,
SERVER,
@@ -43,10 +42,14 @@ namespace uCentral{
int Start() override;
void Stop() override;
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::WebToken & UserInfo );
void CreateToken(const std::string & UserName, SecurityObjects::WebToken & ResultToken, SecurityObjects::AclTemplate & ACL);
bool Authorize( const std::string & UserName, const std::string & Password, SecurityObjects::WebToken & ResultToken );
[[nodiscard]] bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo );
[[nodiscard]] bool Authorize( const std::string & UserName, const std::string & Password, SecurityObjects::UserInfoAndPolicy & UInfo );
void CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo);
[[nodiscard]] bool ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UserInfo );
void Logout(const std::string &token);
[[nodiscard]] bool IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo);
[[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request);
[[nodiscard]] std::string GenerateToken(const std::string & UserName, ACCESS_TYPE Type, int NumberOfDays);
@@ -54,15 +57,16 @@ namespace uCentral{
[[nodiscard]] std::string ComputePasswordHash(const std::string &UserName, const std::string &Password);
[[nodiscard]] bool UpdatePassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword);
[[nodiscard]] std::string ResetPassword(const std::string &Admin, const std::string &UserName);
private:
static AuthService *instance_;
WebTokenMap Tokens_;
bool Secure_ = false ;
std::string DefaultUserName_;
std::string DefaultPassword_;
std::string Mechanism_;
Poco::JWT::Signer Signer_;
Poco::SHA2Engine SHA2_;
SecurityObjects::UserInfoCache UserCache_;
AuthService() noexcept:
SubSystemServer("Authentication", "AUTH-SVR", "authentication")

View File

@@ -13,5 +13,24 @@ namespace uCentral::KafkaTopics {
static const std::string ALERTS{"alerts"};
static const std::string COMMAND{"command"};
static const std::string SERVICE_EVENTS{"service_events"};
namespace ServiceEvents {
static const std::string EVENT_JOIN{"join"};
static const std::string EVENT_LEAVE{"leave"};
static const std::string EVENT_KEEP_ALIVE{"keep-alive"};
static const std::string EVENT_REMOVE_TOKEN{"remove-token"};
namespace Fields {
static const std::string EVENT{"event"};
static const std::string ID{"id"};
static const std::string TYPE{"type"};
static const std::string PUBLIC{"publicEndPoint"};
static const std::string PRIVATE{"privateEndPoint"};
static const std::string KEY{"key"};
static const std::string VERSION{"version"};
static const std::string TOKEN{"token"};
}
}
}
#endif // UCENTRALGW_KAFKA_TOPICS_H

View File

@@ -56,57 +56,61 @@ namespace uCentral {
std::exit(Reason);
}
void MicroService::BusMessageReceived(std::string Key, std::string Message) {
void MicroService::BusMessageReceived(const std::string &Key, const std::string & Message) {
SubMutexGuard G(InfraMutex_);
// std::cout << "Message arrived:" << Key << " ," << Message << std::endl;
try {
Poco::JSON::Parser P;
auto Object = P.parse(Message).extract<Poco::JSON::Object::Ptr>();
if (Object->has("id")) {
uint64_t ID = Object->get("id");
if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) &&
Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) {
uint64_t ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID);
auto Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString();
if (ID != ID_) {
if (Object->has("event") && Object->has("type") &&
Object->has("publicEndPoint") && Object->has("privateEndPoint") &&
Object->has("version") && Object->has("key")) {
auto Event = Object->get("event").toString();
if( Event==KafkaTopics::ServiceEvents::EVENT_JOIN ||
Event==KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE ||
Event==KafkaTopics::ServiceEvents::EVENT_LEAVE ) {
if( Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) &&
Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) &&
Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) &&
Object->has(KafkaTopics::ServiceEvents::Fields::VERSION) &&
Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) {
if (Event == "keep-alive" && Services_.find(ID) != Services_.end()) {
// std::cout << "Keep-alive from " << ID << std::endl;
if (Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE && Services_.find(ID) != Services_.end()) {
Services_[ID].LastUpdate = std::time(nullptr);
} else if (Event == "leave") {
} else if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) {
Services_.erase(ID);
std::cout << "Leave from " << ID << std::endl;
} else if (Event == "join" || Event == "keep-alive") {
std::cout << "Join from " << ID << std::endl;
logger().information(Poco::format("Service %s ID=%Lu leaving system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
} else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN || Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) {
logger().information(Poco::format("Service %s ID=%Lu joining system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
Services_[ID] = MicroServiceMeta{
.Id = ID,
.Type = Poco::toLower(Object->get("type").toString()),
.PrivateEndPoint = Object->get("privateEndPoint").toString(),
.PublicEndPoint = Object->get("publicEndPoint").toString(),
.AccessKey = Object->get("key").toString(),
.Version = Object->get("version").toString(),
.Type = Poco::toLower(Object->get(KafkaTopics::ServiceEvents::Fields::TYPE).toString()),
.PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),
.PublicEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PUBLIC).toString(),
.AccessKey = Object->get(KafkaTopics::ServiceEvents::Fields::KEY).toString(),
.Version = Object->get(KafkaTopics::ServiceEvents::Fields::VERSION).toString(),
.LastUpdate = (uint64_t)std::time(nullptr)};
for (const auto &[Id, Svc] : Services_)
std::cout << "ID:" << Id << " Type:" << Svc.Type
<< " EndPoint:" << Svc.PublicEndPoint << std::endl;
} else {
std::cout << "Bad packet 2 ..." << Event << std::endl;
logger().error(Poco::format("Malformed event from device %Lu, event=%s",
ID, Event));
for (const auto &[Id, Svc] : Services_) {
logger().information(Poco::format("ID: %Lu Type: %s EndPoint: %s",Id,Svc.Type,Svc.PrivateEndPoint));
}
} else if (Object->has("event") &&
Object->get("event").toString() == "remove-token" &&
Object->has("token")) {
}
} else {
logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing a field.",Event));
}
} else if (Event==KafkaTopics::ServiceEvents::EVENT_REMOVE_TOKEN) {
if(Object->has(KafkaTopics::ServiceEvents::Fields::TOKEN)) {
#ifndef TIP_SECURITY_SERVICE
AuthClient()->RemovedCachedToken(Object->get("token").toString());
AuthClient()->RemovedCachedToken(Object->get(KafkaTopics::ServiceEvents::Fields::TOKEN).toString());
#endif
} else
std::cout << "Bad packet 1 ..." << std::endl;
logger().error(Poco::format("Malformed event from device %Lu", ID));
}
} else {
// std::cout << "Ignoring my own messages..." << std::endl;
logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing token",Event));
}
} else {
logger().error(Poco::format("Unknown Event: %s Source: %Lu", Event, ID));
}
}
} else {
logger().error("Bad bus message.");
}
} catch (const Poco::Exception &E) {
logger().log(E);
@@ -396,13 +400,13 @@ namespace uCentral {
std::string MicroService::MakeSystemEventMessage( const std::string & Type ) const {
Poco::JSON::Object Obj;
Obj.set("event",Type);
Obj.set("id",ID_);
Obj.set("type",Poco::toLower(DAEMON_APP_NAME));
Obj.set("publicEndPoint",MyPublicEndPoint_);
Obj.set("privateEndPoint",MyPrivateEndPoint_);
Obj.set("key",MyHash_);
Obj.set("version",Version_);
Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT,Type);
Obj.set(KafkaTopics::ServiceEvents::Fields::ID,ID_);
Obj.set(KafkaTopics::ServiceEvents::Fields::TYPE,Poco::toLower(DAEMON_APP_NAME));
Obj.set(KafkaTopics::ServiceEvents::Fields::PUBLIC,MyPublicEndPoint_);
Obj.set(KafkaTopics::ServiceEvents::Fields::PRIVATE,MyPrivateEndPoint_);
Obj.set(KafkaTopics::ServiceEvents::Fields::KEY,MyHash_);
Obj.set(KafkaTopics::ServiceEvents::Fields::VERSION,Version_);
std::stringstream ResultText;
Poco::JSON::Stringifier::stringify(Obj, ResultText);
return ResultText.str();
@@ -410,17 +414,16 @@ namespace uCentral {
void BusEventManager::run() {
Running_ = true;
auto Msg = Daemon()->MakeSystemEventMessage("join");
auto Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN);
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
while(Running_) {
Poco::Thread::trySleep((unsigned long)Daemon()->DaemonBusTimer());
if(!Running_)
break;
// std::cout << "Sending keep-alive:" << Daemon()->DaemonBusTimer() << std::endl;
auto Msg = Daemon()->MakeSystemEventMessage("keep-alive");
auto Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE);
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
}
Msg = Daemon()->MakeSystemEventMessage("leave");
Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_LEAVE);
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
};

View File

@@ -124,7 +124,7 @@ namespace uCentral {
[[nodiscard]] std::string MakeSystemEventMessage( const std::string & Type ) const ;
inline uint64_t DaemonBusTimer() const { return DAEMON_BUS_TIMER; };
void BusMessageReceived( std::string Key, std::string Message);
void BusMessageReceived( const std::string & Key, const std::string & Message);
[[nodiscard]] MicroServiceMetaVec GetServices(const std::string & type);
[[nodiscard]] MicroServiceMetaVec GetServices();
[[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request);

View File

@@ -114,6 +114,9 @@ namespace uCentral::SecurityObjects {
void to_json(Poco::JSON::Object &Obj) const;
bool from_json(const Poco::JSON::Object::Ptr &Obj);
};
typedef std::map<std::string,SecurityObjects::UserInfoAndPolicy> UserInfoCache;
}
#endif //UCENTRAL_RESTAPI_SECURITYOBJECTS_H

View File

@@ -283,7 +283,7 @@ namespace uCentral {
#else
if (AuthClient()->IsAuthorized(Request, SessionToken_, UserInfo_)) {
#endif
UserName = UserInfo_.username_;
UserName = UserInfo_.webtoken.username_;
return true;
} else {
UnAuthorized(Request, Response);

View File

@@ -88,7 +88,7 @@ namespace uCentral {
Poco::URI::QueryParameters Parameters_;
Poco::Logger &Logger_;
std::string SessionToken_;
SecurityObjects::WebToken UserInfo_;
SecurityObjects::UserInfoAndPolicy UserInfo_;
std::vector<std::string> Methods_;
QueryBlock QB_;
};

View File

@@ -31,11 +31,11 @@ namespace uCentral {
auto password = GetS(uCentral::RESTAPI::Protocol::PASSWORD, Obj);
Poco::toLowerInPlace(userId);
SecurityObjects::WebToken Token;
SecurityObjects::UserInfoAndPolicy UInfo;
if (AuthService()->Authorize(userId, password, Token)) {
if (AuthService()->Authorize(userId, password, UInfo)) {
Poco::JSON::Object ReturnObj;
Token.to_json(ReturnObj);
UInfo.webtoken.to_json(ReturnObj);
ReturnObject(Request, ReturnObj, Response);
} else {
UnAuthorized(Request, Response);

View File

@@ -29,15 +29,9 @@ ucentral.internal.restapi.host.0.key.password = mypassword
# NLB Support
#
alb.enable = true
alb.port = 15017
alb.port = 16101
authentication.enabled = true
authentication.default.username = tip@ucentral.com
authentication.default.password = openwifi
authentication.default.access = master
authentication.service.type = internal
system.directory.data = $UCENTRALSEC_ROOT/data
ucentral.service.key = $UCENTRALSEC_ROOT/certs/restapi-key.pem
ucentral.system.debug = true
ucentral.system.uri = https://localhost:16002
@@ -45,7 +39,7 @@ ucentral.system.commandchannel = /tmp/app.ucentralsec
mailer.hostname = smtp.gmail.com
mailer.username = no-reply@arilia.com
mailer.password = pink-elephants-play-hockey
mailer.password = **************************
mailer.loginmethod = login
mailer.port = 587