This commit is contained in:
stephb9959
2021-06-10 23:26:24 -07:00
parent fe8cb4e5ba
commit 2ad6cadea1
24 changed files with 2821 additions and 0 deletions

65
CMakeLists.txt Normal file
View File

@@ -0,0 +1,65 @@
cmake_minimum_required(VERSION 3.19)
project(ucentralsec)
set(CMAKE_CXX_STANDARD 17)
if(UNIX AND APPLE)
set(OPENSSL_ROOT_DIR /usr/local/opt/openssl)
set(MYSQL_ROOT_DIR /usr/local/opt/mysql-client)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
endif()
if(UNIX AND NOT APPLE)
set(PostgreSQL_TYPE_INCLUDE_DIR /usr/include/postgresql)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
endif()
if(SMALL_BUILD)
add_definitions(-DSMALL_BUILD)
endif()
# Auto build increment. You must define BUILD_INCREMENT with cmake -DBUILD_INCREMENT=1
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build)
file(READ build BUILD_NUM)
if(BUILD_INCREMENT)
MATH(EXPR BUILD_NUM "${BUILD_NUM}+1")
file(WRITE build ${BUILD_NUM})
endif()
else()
set(BUILD_NUM 1)
file(WRITE build ${BUILD_NUM})
endif()
set(BUILD_SHARED_LIBS 1)
add_definitions(-DAPP_VERSION="${CMAKE_PROJECT_VERSION}" -DBUILD_NUMBER="${BUILD_NUM}")
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF)
find_package(Boost REQUIRED system)
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
find_package(CppKafka REQUIRED)
find_package(PostgreSQL REQUIRED)
find_package(MySQL REQUIRED)
find_package(ODBC REQUIRED)
find_package(Poco REQUIRED COMPONENTS Crypto JWT Net Util NetSSL Data DataSQLite DataPostgreSQL DataMySQL DataODBC)
include_directories(/usr/local/include /usr/local/opt/openssl/include src include/kafka)
add_executable( ucentralsec build
src/Daemon.h src/Daemon.cpp
src/SubSystemServer.cpp src/SubSystemServer.h
src/RESTAPI_unknownRequestHandler.h src/RESTAPI_unknownRequestHandler.cpp
src/RESTAPI_oauth2Handler.h src/RESTAPI_oauth2Handler.cpp
src/RESTAPI_handler.h src/RESTAPI_handler.cpp
src/RESTAPI_server.cpp src/RESTAPI_server.h
src/RESTAPI_objects.cpp src/RESTAPI_objects.h
src/RESTAPI_protocol.h
src/AuthService.h src/AuthService.cpp
)
target_link_libraries(ucentralsec PUBLIC
${Poco_LIBRARIES} ${Boost_LIBRARIES} ${ZLIB_LIBRARIES} ${AWSSDK_LINK_LIBRARIES} CppKafka::cppkafka )

1
build Normal file
View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,274 @@
openapi: 3.0.1
info:
title: uCentral Security API
description: A process to manage security logins
version: 0.0.1
license:
name: BSD3
url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
contact:
name: Arilia Support
email: ucentralsupport@arilia.com
url: https://www.ucentral.info/support
servers:
- url: 'https://localhost:16001/api/v1'
security:
- bearerAuth: []
- ApiKeyAuth: []
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-KEY
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
responses:
NotFound:
description: The specified resource was not found.
content:
application/json:
schema:
$ref: '#/components/schemas/GenericErrorResponse'
Unauthorized:
description: The requested does not have sufficient rights to perform the operation.
content:
application/json:
schema:
$ref: '#/components/schemas/GenericErrorResponse'
Success:
description: The requested operation was performed.
content:
application/json:
schema:
$ref: '#/components/schemas/GenericGoodAnswer'
CommandSubmitSuccess:
description: The command was submitted succesfully.
content:
application/json:
schema:
properties:
serialNumber:
type: string
UUID:
type: string
format: uuid
schemas:
GenericErrorResponse:
description: Typical error response
properties:
ErrorCode:
type: integer
ErrorDetails:
type: string
ErrorDescription:
type: string
GenericGoodAnswer:
description: used for all succesful responses.
properties:
Operation:
type: string
Details:
type: string
Code:
type: integer
WebTokenRequest:
description: User Id and password.
type: object
required:
- userId
- password
properties:
userId:
type: string
default: support@example.com
password:
type: string
default: support
refreshToken:
type: string
example:
userId: support@example.com
password: support
WebTokenResult:
description: Login and Refresh Tokens to be used in subsequent API calls.
type: object
properties:
access_token:
type: string
refresh_token:
type: string
token_type:
type: string
expires_in:
type: integer
format: int32
idle_timeout:
type: integer
format: int32
username:
type: string
created:
type: integer
format: int64
aclTemplate:
$ref: '#/components/schemas/WebTokenAclTemplate'
WebTokenAclTemplate:
type: object
properties:
aclTemplate:
$ref: '#/components/schemas/AclTemplate'
ApiKeyCreationRequest:
type: object
properties:
name:
type: string
description:
type: string
expiresOn:
type: integer
format: int64
rights:
$ref: '#/components/schemas/AclTemplate'
ApiKeyCreationAnswer:
type: object
properties:
UUID:
type: string
format: uuid
name:
type: string
created:
type: integer
format: int64
expiresOn:
type: integer
format: int64
apiKey:
type: string
rights:
$ref: '#/components/schemas/AclTemplate'
AclTemplate:
type: object
properties:
Read:
type: boolean
ReadWrite:
type: boolean
ReadWriteCreate:
type: boolean
Delete:
type: boolean
PortalLogin:
type: boolean
SystemEndpoint:
type: object
properties:
name:
type: string
id:
type: integer
vendor:
type: string
uri:
type: string
format: uri
authenticationType:
type: string
SystemEndpointList:
type: object
properties:
endpoints:
type: array
items:
$ref: '#/components/schemas/SystemEndpoint'
paths:
/oauth2:
post:
tags:
- Authentication
summary: Get access token - to be used as Bearer token header for all other API requests.
operationId: getAccessToken
requestBody:
description: User id and password
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/WebTokenRequest'
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/WebTokenResult'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
/oauth2/{token}:
delete:
tags:
- Authentication
summary: Revoke a token.
operationId: removeAccessToken
parameters:
- in: path
name: token
schema:
type:
string
required: true
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/GenericGoodAnswer'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
/systemInfo:
get:
tags:
- Authentication
summary: retrieve the system layout
operationId: getSystemInfo
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SystemEndpointList'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'

101
src/ALBHealthCheckServer.h Normal file
View File

@@ -0,0 +1,101 @@
//
// Created by stephane bourque on 2021-06-04.
//
#ifndef UCENTRALGW_ALBHEALTHCHECKSERVER_H
#define UCENTRALGW_ALBHEALTHCHECKSERVER_H
#include <memory>
#include <iostream>
#include <fstream>
#include <sstream>
#include "Poco/Thread.h"
#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Logger.h"
#include "Daemon.h"
namespace uCentral::ALBHealthCheck {
class ALBRequestHandler: public Poco::Net::HTTPRequestHandler
/// Return a HTML document with the current date and time.
{
public:
ALBRequestHandler(Poco::Logger & L)
: Logger_(L)
{
}
void handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response)
{
Logger_.information(Poco::format("ALB-REQUEST(%s): New ALB request.",Request.clientAddress().toString()));
Response.setChunkedTransferEncoding(true);
Response.setContentType("text/html");
Response.setDate(Poco::Timestamp());
Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
Response.setKeepAlive(true);
Response.set("Connection","keep-alive");
Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
std::ostream &Answer = Response.send();
Answer << "uCentralGW Alive and kicking!" ;
}
private:
Poco::Logger & Logger_;
};
class ALBRequestHandlerFactory: public Poco::Net::HTTPRequestHandlerFactory
{
public:
explicit ALBRequestHandlerFactory(Poco::Logger & L):
Logger_(L)
{
}
ALBRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request)
{
if (request.getURI() == "/")
return new ALBRequestHandler(Logger_);
else
return nullptr;
}
private:
Poco::Logger &Logger_;
};
class Service {
public:
explicit Service(Poco::Logger &L)
: Logger_(L) {};
int Start() {
if(uCentral::ServiceConfig::GetBool("nlb.enable",false)) {
Port_ = (int)uCentral::ServiceConfig::GetInt("nlb.port",15015);
Socket_ = std::make_unique<Poco::Net::ServerSocket>(Port_);
auto Params = new Poco::Net::HTTPServerParams;
Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger_), *Socket_, Params);
Server_->start();
}
return 0;
}
void Stop() {
if(Server_)
Server_->stop();
}
private:
std::unique_ptr<Poco::Net::HTTPServer> Server_;
std::unique_ptr<Poco::Net::ServerSocket> Socket_;
int Port_ = 0;
Poco::Logger &Logger_;
};
}
#endif // UCENTRALGW_ALBHEALTHCHECKSERVER_H

235
src/AuthService.cpp Normal file
View File

@@ -0,0 +1,235 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include <ctime>
#include "Poco/Net/OAuth20Credentials.h"
#include "Poco/JWT/Token.h"
#include "Poco/JWT/Signer.h"
#include "Daemon.h"
#include "RESTAPI_handler.h"
#include "AuthService.h"
#include "uStorageService.h"
#include "uUtils.h"
namespace uCentral::Auth {
Service *Service::instance_ = nullptr;
ACCESS_TYPE IntToAccessType(int C) {
switch (C) {
case 1: return USERNAME;
case 2: return SERVER;
case 3: return CUSTOM;
default:
return USERNAME;
}
}
int AccessTypeToInt(ACCESS_TYPE T) {
switch (T) {
case USERNAME: return 1;
case SERVER: return 2;
case CUSTOM: return 3;
}
return 1; // some compilers complain...
}
Service::Service() noexcept: SubSystemServer("Authentication", "AUTH-SVR", "authentication")
{
std::string E{"SHA512"};
}
int Start() {
return Service::instance()->Start();
}
void Stop() {
Service::instance()->Stop();
}
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo ) {
return Service::instance()->IsAuthorized(Request,SessionToken, UserInfo);
}
bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken ) {
return Service::instance()->Authorize(UserName,Password,ResultToken);
}
void Logout(const std::string &Token) {
Service::instance()->Logout(Token);
}
int Service::Start() {
Signer_.setRSAKey(uCentral::instance()->Key());
Signer_.addAllAlgorithms();
Logger_.notice("Starting...");
Secure_ = uCentral::ServiceConfig::GetBool(SubSystemConfigPrefix_+".enabled",true);
DefaultPassword_ = uCentral::ServiceConfig::GetString(SubSystemConfigPrefix_+".default.password","");
DefaultUserName_ = uCentral::ServiceConfig::GetString(SubSystemConfigPrefix_+".default.username","");
Mechanism_ = uCentral::ServiceConfig::GetString(SubSystemConfigPrefix_+".service.type","internal");
return 0;
}
void Service::Stop() {
Logger_.notice("Stopping...");
}
bool Service::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, struct uCentral::Objects::WebToken & UserInfo )
{
if(!Secure_)
return true;
SubMutexGuard Guard(Mutex_);
std::string CallToken;
try {
Poco::Net::OAuth20Credentials Auth(Request);
if (Auth.getScheme() == "Bearer") {
CallToken = Auth.getBearerToken();
}
} catch(const Poco::Exception &E) {
}
if(CallToken.empty())
CallToken = Request.get("X-API-KEY ", "");
if(CallToken.empty())
return false;
auto Client = Tokens_.find(CallToken);
if( Client == Tokens_.end() )
return ValidateToken(CallToken, CallToken, UserInfo);
if((Client->second.created_ + Client->second.expires_in_) > time(nullptr)) {
SessionToken = CallToken;
UserInfo = Client->second ;
return true;
}
Tokens_.erase(CallToken);
return false;
}
void Service::Logout(const std::string &token) {
SubMutexGuard Guard(Mutex_);
Tokens_.erase(token);
}
std::string Service::GenerateToken(const std::string & Identity, ACCESS_TYPE Type, int NumberOfDays) {
SubMutexGuard Guard(Mutex_);
Poco::JWT::Token T;
T.setType("JWT");
switch(Type) {
case USERNAME: T.setSubject("usertoken"); break;
case SERVER: T.setSubject("servertoken"); break;
case CUSTOM: T.setSubject("customtoken"); break;
}
T.payload().set("identity", Identity);
T.setIssuedAt(Poco::Timestamp());
T.setExpiration(Poco::Timestamp() + Poco::Timespan(NumberOfDays,0,0,0,0));
std::string JWT = Signer_.sign(T,Poco::JWT::Signer::ALGO_RS256);
return JWT;
}
bool Service::ValidateToken(const std::string & Token, std::string & SessionToken, struct uCentral::Objects::WebToken & UserInfo ) {
SubMutexGuard Guard(Mutex_);
Poco::JWT::Token DecryptedToken;
try {
if (Signer_.tryVerify(Token, DecryptedToken)) {
auto Expires = DecryptedToken.getExpiration();
if (Expires > Poco::Timestamp()) {
auto Identity = DecryptedToken.payload().get("identity").toString();
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;
if(uCentral::Storage::GetIdentityRights(Identity, UserInfo.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;
}
Tokens_[UserInfo.access_token_] = UserInfo;
return true;
}
}
} catch (const Poco::Exception &E ) {
Logger_.log(E);
}
return false;
}
void Service::CreateToken(const std::string & UserName, uCentral::Objects::WebToken & UserInfo, uCentral::Objects::AclTemplate & ACL)
{
SubMutexGuard Guard(Mutex_);
std::string Token = GenerateToken(UserName,USERNAME,30);
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;
}
bool Service::Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken )
{
SubMutexGuard Guard(Mutex_);
uCentral::Objects::AclTemplate ACL;
if(Mechanism_=="internal")
{
if(((UserName == DefaultUserName_) && (Password == DefaultPassword_)) || !Secure_)
{
ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true;
CreateToken(UserName, ResultToken, ACL);
return true;
}
} else if (Mechanism_=="db") {
SHA2_.update(Password + UserName);
auto EncryptedPassword = uCentral::Utils::ToHex(SHA2_.digest());
std::string TUser{UserName};
if(uCentral::Storage::GetIdentity(TUser,EncryptedPassword,USERNAME,ACL)) {
CreateToken(UserName, ResultToken, ACL);
return true;
}
}
return false;
}
} // end of namespace

81
src/AuthService.h Normal file
View File

@@ -0,0 +1,81 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRAL_UAUTHSERVICE_H
#define UCENTRAL_UAUTHSERVICE_H
#include "SubSystemServer.h"
#include "Poco/JSON/Object.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/JWT/Signer.h"
#include "Poco/SHA2Engine.h"
#include "RESTAPI_objects.h"
namespace uCentral::Auth {
enum ACCESS_TYPE {
USERNAME,
SERVER,
CUSTOM
};
ACCESS_TYPE IntToAccessType(int C);
int AccessTypeToInt(ACCESS_TYPE T);
int Start();
void Stop();
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo );
bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken );
void Logout(const std::string &token);
class Service : public SubSystemServer {
public:
Service() noexcept;
friend int Start();
friend void Stop();
static Service *instance() {
if (instance_ == nullptr) {
instance_ = new Service;
}
return instance_;
}
friend bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo );
friend bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken );
[[nodiscard]] std::string GenerateToken(const std::string & UserName, ACCESS_TYPE Type, int NumberOfDays);
[[nodiscard]] bool ValidateToken(const std::string & Token, std::string & SessionToken, struct uCentral::Objects::WebToken & UserInfo );
friend void Logout(const std::string &token);
private:
static Service *instance_;
std::map<std::string,uCentral::Objects::WebToken> Tokens_;
bool Secure_ = false ;
std::string DefaultUserName_;
std::string DefaultPassword_;
std::string Mechanism_;
bool AutoProvisioning_ = false ;
Poco::JWT::Signer Signer_;
Poco::SHA2Engine SHA2_;
int Start() override;
void Stop() override;
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo );
void CreateToken(const std::string & UserName, uCentral::Objects::WebToken & ResultToken, uCentral::Objects::AclTemplate & ACL);
bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken );
void Logout(const std::string &token);
};
} // end of namespace
#endif //UCENTRAL_UAUTHSERVICE_H

277
src/Daemon.cpp Normal file
View File

@@ -0,0 +1,277 @@
//
// Created by stephane bourque on 2021-06-10.
//
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include <cstdlib>
#include <boost/algorithm/string.hpp>
#include "Poco/Util/Application.h"
#include "Poco/Util/ServerApplication.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Environment.h"
#include "Poco/Path.h"
#include "Poco/Net/SSLManager.h"
#include "uUtils.h"
#include "ALBHealthCheckServer.h"
#include "Daemon.h"
namespace uCentral {
Daemon *Daemon::instance_ = nullptr;
Daemon * instance() { return uCentral::Daemon::instance(); }
void MyErrorHandler::exception(const Poco::Exception & E) {
Poco::Thread * CurrentThread = Poco::Thread::current();
instance()->logger().log(E);
instance()->logger().error(Poco::format("Exception occurred in %s",CurrentThread->getName()));
}
void MyErrorHandler::exception(const std::exception & E) {
Poco::Thread * CurrentThread = Poco::Thread::current();
instance()->logger().warning(Poco::format("std::exception on %s",CurrentThread->getName()));
}
void MyErrorHandler::exception() {
Poco::Thread * CurrentThread = Poco::Thread::current();
instance()->logger().warning(Poco::format("exception on %s",CurrentThread->getName()));
}
void Daemon::Exit(int Reason) {
std::exit(Reason);
}
void Daemon::initialize(Application &self) {
Poco::Net::initializeSSL();
std::string Location = Poco::Environment::get(uCentral::DAEMON_CONFIG_ENV_VAR,".");
Poco::Path ConfigFile;
ConfigFile = ConfigFileName_.empty() ? Location + "/" + uCentral::DAEMON_PROPERTIES_FILENAME : ConfigFileName_;
if(!ConfigFile.isFile())
{
std::cerr << uCentral::DAEMON_APP_NAME << ": Configuration " << ConfigFile.toString() << " does not seem to exist. Please set "
<< uCentral::DAEMON_ROOT_ENV_VAR << " env variable the path of the "
<< uCentral::DAEMON_PROPERTIES_FILENAME << " file." << std::endl;
std::exit(Poco::Util::Application::EXIT_CONFIG);
}
static const char * LogFilePathKey = "logging.channels.c2.path";
loadConfiguration(ConfigFile.toString());
if(LogDir_.empty()) {
std::string OriginalLogFileValue = config().getString(LogFilePathKey);
std::string RealLogFileValue = Poco::Path::expand(OriginalLogFileValue);
config().setString(LogFilePathKey, RealLogFileValue);
} else {
config().setString(LogFilePathKey, LogDir_);
}
Poco::Path DataDir(config().getString("system.directory.data"));
try {
DataDir.makeDirectory();
DataDir_ = DataDir.toString();
} catch(...) {
}
std::string KeyFile = Poco::Path::expand(config().getString("ucentral.service.key"));
AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, ""));
ServerApplication::initialize(self);
logger().information("Starting...");
if(!DebugMode_)
DebugMode_ = config().getBool("ucentral.system.debug",false);
ID_ = config().getInt64("ucentral.system.id",1);
}
[[nodiscard]] std::string Daemon::IdentifyDevice(const std::string & Id ) const {
for(const auto &[Type,List]:DeviceTypeIdentifications_)
{
if(List.find(Id)!=List.end())
return Type;
}
return "AP";
}
void Daemon::uninitialize() {
// add your own uninitialization code here
ServerApplication::uninitialize();
}
void Daemon::reinitialize(Poco::Util::Application &self) {
ServerApplication::reinitialize(self);
// add your own reinitialization code here
}
void Daemon::defineOptions(Poco::Util::OptionSet &options) {
ServerApplication::defineOptions(options);
options.addOption(
Poco::Util::Option("help", "", "display help information on command line arguments")
.required(false)
.repeatable(false)
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleHelp)));
options.addOption(
Poco::Util::Option("file", "", "specify the configuration file")
.required(false)
.repeatable(false)
.argument("file")
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleConfig)));
options.addOption(
Poco::Util::Option("debug", "", "to run in debug, set to true")
.required(false)
.repeatable(false)
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleDebug)));
options.addOption(
Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)")
.required(false)
.repeatable(false)
.argument("dir")
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleLogs)));
options.addOption(
Poco::Util::Option("version", "", "get the version and quit.")
.required(false)
.repeatable(false)
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleVersion)));
}
std::string Daemon::Version() {
std::string V = APP_VERSION;
std::string B = BUILD_NUMBER;
return V + "(" + B + ")";
}
void Daemon::handleHelp(const std::string &name, const std::string &value) {
HelpRequested_ = true;
displayHelp();
stopOptionsProcessing();
}
void Daemon::handleVersion(const std::string &name, const std::string &value) {
HelpRequested_ = true;
std::cout << Version() << std::endl;
stopOptionsProcessing();
}
void Daemon::handleDebug(const std::string &name, const std::string &value) {
if(value == "true")
DebugMode_ = true ;
}
void Daemon::handleLogs(const std::string &name, const std::string &value) {
LogDir_ = value;
}
void Daemon::handleConfig(const std::string &name, const std::string &value) {
ConfigFileName_ = value;
}
void Daemon::displayHelp() {
Poco::Util::HelpFormatter helpFormatter(options());
helpFormatter.setCommand(commandName());
helpFormatter.setUsage("OPTIONS");
helpFormatter.setHeader("A " + std::string(uCentral::DAEMON_APP_NAME) + " implementation for TIP.");
helpFormatter.format(std::cout);
}
std::string Daemon::CreateUUID() {
return UUIDGenerator_.create().toString();
}
int Daemon::main(const ArgVec &args) {
Poco::ErrorHandler::set(&AppErrorHandler_);
if (!HelpRequested_) {
Poco::Logger &logger = Poco::Logger::get(uCentral::DAEMON_APP_NAME );
logger.notice(Poco::format("Starting %s version %s.",std::string(uCentral::DAEMON_APP_NAME), Version()));
if(Poco::Net::Socket::supportsIPv6())
logger.information("System supports IPv6.");
else
logger.information("System does NOT support IPv6.");
if (config().getBool("application.runAsDaemon", false))
{
logger.information("Starting as a daemon.");
}
uCentral::ALBHealthCheck::Service NLB(logger);
NLB.Start();
instance()->waitForTerminationRequest();
NLB.Stop();
logger.notice(Poco::format("Stopped %s...",std::string(uCentral::DAEMON_APP_NAME)));
}
return Application::EXIT_OK;
}
namespace ServiceConfig {
uint64_t GetInt(const std::string &Key,uint64_t Default) {
return (uint64_t) instance()->config().getInt64(Key,Default);
}
uint64_t GetInt(const std::string &Key) {
return instance()->config().getInt(Key);
}
uint64_t GetBool(const std::string &Key,bool Default) {
return instance()->config().getBool(Key,Default);
}
uint64_t GetBool(const std::string &Key) {
return instance()->config().getBool(Key);
}
std::string GetString(const std::string &Key,const std::string & Default) {
std::string R = instance()->config().getString(Key, Default);
return Poco::Path::expand(R);
}
std::string GetString(const std::string &Key) {
std::string R = instance()->config().getString(Key);
return Poco::Path::expand(R);
}
}
}
int main(int argc, char **argv) {
try {
auto App = uCentral::Daemon::instance();
auto ExitCode = App->run(argc, argv);
delete App;
return ExitCode;
} catch (Poco::Exception &exc) {
std::cerr << exc.displayText() << std::endl;
return Poco::Util::Application::EXIT_SOFTWARE;
}
}
// end of namespace

99
src/Daemon.h Normal file
View File

@@ -0,0 +1,99 @@
//
// Created by stephane bourque on 2021-06-10.
//
#ifndef UCENTRALSEC_DAEMON_H
#define UCENTRALSEC_DAEMON_H
#include <iostream>
#include <cstdlib>
#include <vector>
#include <set>
#include "Poco/Util/Application.h"
#include "Poco/Util/ServerApplication.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/UUIDGenerator.h"
#include "Poco/ErrorHandler.h"
#include "Poco/Crypto/RSAKey.h"
using Poco::Util::ServerApplication;
namespace uCentral {
static const char * DAEMON_PROPERTIES_FILENAME = "ucentralsec.properties";
static const char * DAEMON_ROOT_ENV_VAR = "UCENTRALSEC_ROOT";
static const char * DAEMON_CONFIG_ENV_VAR = "UCENTRALSEC_CONFIG";
static const char * DAEMON_APP_NAME = "uCentralSec";
class MyErrorHandler : public Poco::ErrorHandler {
public:
void exception(const Poco::Exception & E) override;
void exception(const std::exception & E) override;
void exception() override;
private:
};
class Daemon : public ServerApplication {
public:
int main(const ArgVec &args) override;
void initialize(Application &self) override;
void uninitialize() override;
void reinitialize(Application &self) override;
void defineOptions(Poco::Util::OptionSet &options) override;
void handleHelp(const std::string &name, const std::string &value);
void handleVersion(const std::string &name, const std::string &value);
void handleDebug(const std::string &name, const std::string &value);
void handleLogs(const std::string &name, const std::string &value);
void handleConfig(const std::string &name, const std::string &value);
void displayHelp();
std::string CreateUUID();
[[nodiscard]] std::string IdentifyDevice(const std::string & Compatible) const;
bool AutoProvisioning() const { return AutoProvisioning_ ; }
bool Debug() const { return DebugMode_; }
uint64_t ID() const { return ID_; }
static bool SetSubsystemLogLevel(const std::string & SubSystem, const std::string & Level);
static std::string Version();
const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; }
void Exit(int Reason);
[[nodiscard]] inline const std::string & DataDir() { return DataDir_; }
static Daemon *instance() {
if (instance_ == nullptr) {
instance_ = new Daemon;
}
return instance_;
}
private:
static Daemon *instance_;
bool HelpRequested_ = false;
bool AutoProvisioning_ = false;
std::map<std::string,std::set<std::string>> DeviceTypeIdentifications_;
std::string ConfigFileName_;
std::string LogDir_;
bool DebugMode_ = false;
uint64_t ID_ = 1;
Poco::UUIDGenerator UUIDGenerator_;
MyErrorHandler AppErrorHandler_;
Poco::SharedPtr<Poco::Crypto::RSAKey> AppKey_ = nullptr;
std::string DataDir_;
};
namespace ServiceConfig {
uint64_t GetInt(const std::string &Key,uint64_t Default);
uint64_t GetInt(const std::string &Key);
std::string GetString(const std::string &Key,const std::string & Default);
std::string GetString(const std::string &Key);
uint64_t GetBool(const std::string &Key,bool Default);
uint64_t GetBool(const std::string &Key);
}
Daemon * instance();
}
#endif //UCENTRALSEC_DAEMON_H

319
src/RESTAPI_handler.cpp Normal file
View File

@@ -0,0 +1,319 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include <cctype>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <future>
#include <numeric>
#include <chrono>
#include "Poco/URI.h"
#include "Poco/DateTimeParser.h"
#include "RESTAPI_handler.h"
#include "AuthService.h"
#include "RESTAPI_protocol.h"
#include "uUtils.h"
#define DBG std::cout << __LINE__ << " " __FILE__ << std::endl;
namespace uCentral::RESTAPI {
bool RESTAPIHandler::ParseBindings(const std::string & Request, const std::string & Path, BindingMap &bindings) {
std::string Param, Value;
bindings.clear();
std::vector<std::string> PathItems = uCentral::Utils::Split(Path,'/');
std::vector<std::string> ParamItems = uCentral::Utils::Split(Request,'/');
if(PathItems.size()!=ParamItems.size())
return false;
for(auto i=0;i!=PathItems.size();i++) {
if (PathItems[i] != ParamItems[i]) {
if (PathItems[i][0] == '{') {
auto ParamName = PathItems[i].substr(1, PathItems[i].size() - 2);
bindings[ParamName] = ParamItems[i];
} else
return false;
}
}
return true;
}
void RESTAPIHandler::PrintBindings() {
for (auto &[key, value] : Bindings_)
std::cout << "Key = " << key << " Value= " << value << std::endl;
}
void RESTAPIHandler::ParseParameters(Poco::Net::HTTPServerRequest &request) {
Poco::URI uri(request.getURI());
Parameters_ = uri.getQueryParameters();
}
static bool is_number(const std::string &s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
}
static bool is_bool(const std::string &s) {
if (s == "true" || s == "false")
return true;
return false;
}
uint64_t RESTAPIHandler::GetParameter(const std::string &Name, const uint64_t Default) {
for (const auto &i : Parameters_) {
if (i.first == Name) {
if (!is_number(i.second))
return Default;
return std::stoi(i.second);
}
}
return Default;
}
bool RESTAPIHandler::GetBoolParameter(const std::string &Name, bool Default) {
for (const auto &i : Parameters_) {
if (i.first == Name) {
if (!is_bool(i.second))
return Default;
return i.second == "true";
}
}
return Default;
}
std::string RESTAPIHandler::GetParameter(const std::string &Name, const std::string &Default) {
for (const auto &i : Parameters_) {
if (i.first == Name)
return i.second;
}
return Default;
}
const std::string &RESTAPIHandler::GetBinding(const std::string &Name, const std::string &Default) {
auto E = Bindings_.find(Name);
if (E == Bindings_.end())
return Default;
return E->second;
}
static std::string MakeList(const std::vector<std::string> &L) {
std::string Return;
for (const auto &i : L)
if (Return.empty())
Return = i;
else
Return += ", " + i;
return Return;
}
void RESTAPIHandler::AddCORS(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
auto Origin = Request.find("Origin");
if (Origin != Request.end()) {
Response.set("Access-Control-Allow-Origin", Origin->second);
Response.set("Vary", "Origin");
} else {
Response.set("Access-Control-Allow-Origin", "*");
}
Response.set("Access-Control-Allow-Headers", "*");
Response.set("Access-Control-Allow-Methods", MakeList(Methods_));
Response.set("Access-Control-Max-Age", "86400");
}
void RESTAPIHandler::SetCommonHeaders(Poco::Net::HTTPServerResponse &Response, bool CloseConnection) {
Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
Response.setChunkedTransferEncoding(true);
Response.setContentType("application/json");
if(CloseConnection) {
Response.set("Connection", "close");
Response.setKeepAlive(false);
} else {
Response.setKeepAlive(true);
Response.set("Connection", "Keep-Alive");
Response.set("Keep-Alive", "timeout=5, max=1000");
}
}
void RESTAPIHandler::ProcessOptions(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
AddCORS(Request, Response);
SetCommonHeaders(Response);
Response.setContentLength(0);
Response.set("Access-Control-Allow-Credentials", "true");
Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
Response.set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
/* std::cout << "RESPONSE:" << std::endl;
for(const auto &[f,s]:Response)
std::cout << "First: " << f << " second:" << s << std::endl;
*/
Response.send();
}
void RESTAPIHandler::PrepareResponse(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response,
Poco::Net::HTTPResponse::HTTPStatus Status,
bool CloseConnection) {
Response.setStatus(Status);
AddCORS(Request, Response);
SetCommonHeaders(Response, CloseConnection);
}
void RESTAPIHandler::BadRequest(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
Response.send();
}
void RESTAPIHandler::UnAuthorized(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_FORBIDDEN);
Response.send();
}
void RESTAPIHandler::NotFound(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
Response.send();
}
void RESTAPIHandler::OK(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
PrepareResponse(Request, Response);
Response.send();
}
void RESTAPIHandler::SendFile(Poco::File & File, const std::string & UUID, Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
Response.set("Content-Type","application/octet-stream");
Response.set("Content-Disposition", "attachment; filename=" + UUID );
Response.set("Content-Transfer-Encoding","binary");
Response.set("Accept-Ranges", "bytes");
Response.set("Cache-Control", "private");
Response.set("Pragma", "private");
Response.set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
Response.set("Content-Length", std::to_string(File.getSize()));
AddCORS(Request, Response);
Response.sendFile(File.path(),"application/octet-stream");
}
void RESTAPIHandler::ReturnStatus(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response,
Poco::Net::HTTPResponse::HTTPStatus Status,
bool CloseConnection) {
PrepareResponse(Request, Response, Status, CloseConnection);
if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) {
Response.setContentLength(0);
Response.erase("Content-Type");
Response.setChunkedTransferEncoding(false);
}
Response.send();
}
bool RESTAPIHandler::ContinueProcessing(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
if (Request.getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
/* std::cout << "REQUEST:" << std::endl;
for(const auto &[f,s]:Request)
std::cout << "First: " << f << " second:" << s << std::endl;
*/
ProcessOptions(Request, Response);
return false;
} else if (std::find(Methods_.begin(), Methods_.end(), Request.getMethod()) == Methods_.end()) {
BadRequest(Request, Response);
return false;
}
return true;
}
bool RESTAPIHandler::IsAuthorized(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
if (uCentral::Auth::IsAuthorized(Request, SessionToken_, UserInfo_)) {
return true;
} else {
UnAuthorized(Request, Response);
}
return false;
}
bool RESTAPIHandler::IsAuthorized(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response, std::string &UserName) {
if (uCentral::Auth::IsAuthorized(Request, SessionToken_, UserInfo_)) {
UserName = UserInfo_.username_;
return true;
} else {
UnAuthorized(Request, Response);
}
return false;
}
bool RESTAPIHandler::ValidateAPIKey(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
auto Key = Request.get("X-API-KEY", "");
if (Key.empty())
return false;
return true;
}
void RESTAPIHandler::ReturnObject(Poco::Net::HTTPServerRequest &Request, Poco::JSON::Object &Object,
Poco::Net::HTTPServerResponse &Response) {
PrepareResponse(Request, Response);
std::ostream &Answer = Response.send();
Poco::JSON::Stringifier::stringify(Object, Answer);
}
void RESTAPIHandler::InitQueryBlock() {
QB_.SerialNumber = GetParameter(uCentral::RESTAPI::Protocol::SERIALNUMBER, "");
QB_.StartDate = GetParameter(uCentral::RESTAPI::Protocol::STARTDATE, 0);
QB_.EndDate = GetParameter(uCentral::RESTAPI::Protocol::ENDDATE, 0);
QB_.Offset = GetParameter(uCentral::RESTAPI::Protocol::OFFSET, 0);
QB_.Limit = GetParameter(uCentral::RESTAPI::Protocol::LIMIT, 100);
QB_.Filter = GetParameter(uCentral::RESTAPI::Protocol::FILTER, "");
QB_.Select = GetParameter(uCentral::RESTAPI::Protocol::SELECT, "");
QB_.Lifetime = GetBoolParameter(uCentral::RESTAPI::Protocol::LIFETIME,false);
QB_.LogType = GetParameter(uCentral::RESTAPI::Protocol::LOGTYPE,0);
QB_.LastOnly = GetBoolParameter(uCentral::RESTAPI::Protocol::LASTONLY,false);
QB_.Newest = GetBoolParameter(uCentral::RESTAPI::Protocol::NEWEST,false);
}
[[nodiscard]] uint64_t RESTAPIHandler::Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default){
if(Obj->has(Parameter))
return Obj->get(Parameter);
return Default;
}
[[nodiscard]] std::string RESTAPIHandler::GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default){
if(Obj->has(Parameter))
return Obj->get(Parameter).toString();
return Default;
}
[[nodiscard]] bool RESTAPIHandler::GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default){
if(Obj->has(Parameter))
return Obj->get(Parameter).toString()=="true";
return Default;
}
[[nodiscard]] uint64_t RESTAPIHandler::GetWhen(const Poco::JSON::Object::Ptr &Obj) {
return RESTAPIHandler::Get(uCentral::RESTAPI::Protocol::WHEN, Obj);
}
}

107
src/RESTAPI_handler.h Normal file
View File

@@ -0,0 +1,107 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRAL_RESTAPI_HANDLER_H
#define UCENTRAL_RESTAPI_HANDLER_H
#include "Poco/URI.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/NetException.h"
#include "Poco/Logger.h"
#include "Poco/File.h"
#include "Poco/JSON/Object.h"
#include "RESTAPI_objects.h"
#include "AuthService.h"
namespace uCentral::RESTAPI {
struct QueryBlock {
uint64_t StartDate = 0 , EndDate = 0 , Offset = 0 , Limit = 0, LogType = 0 ;
std::string SerialNumber, Filter, Select;
bool Lifetime=false, LastOnly=false, Newest=false;
};
class RESTAPIHandler : public Poco::Net::HTTPRequestHandler {
public:
typedef std::map<std::string, std::string> BindingMap;
RESTAPIHandler(BindingMap map, Poco::Logger &l, std::vector<std::string> Methods)
: Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)) {}
static bool ParseBindings(const std::string & Path, const std::string & Request, BindingMap &Keys);
void PrintBindings();
void ParseParameters(Poco::Net::HTTPServerRequest &request);
void AddCORS(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &response);
void SetCommonHeaders(Poco::Net::HTTPServerResponse &response, bool CloseConnection=false);
void ProcessOptions(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &response);
void
PrepareResponse(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &response,
Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK,
bool CloseConnection = false);
bool ContinueProcessing(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response);
bool IsAuthorized(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response);
bool IsAuthorized(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response, std::string &UserName);
uint64_t GetParameter(const std::string &Name, uint64_t Default);
std::string GetParameter(const std::string &Name, const std::string &Default);
bool GetBoolParameter(const std::string &Name, bool Default);
bool ValidateAPIKey(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response);
void BadRequest(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
void UnAuthorized(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response);
void ReturnObject(Poco::Net::HTTPServerRequest &Request, Poco::JSON::Object &Object,
Poco::Net::HTTPServerResponse &Response);
void NotFound(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
void OK(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
void ReturnStatus(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response,
Poco::Net::HTTPResponse::HTTPStatus Status,
bool CloseConnection=false);
void SendFile(Poco::File & File, const std::string & UUID,
Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
const std::string &GetBinding(const std::string &Name, const std::string &Default);
void InitQueryBlock();
[[nodiscard]] inline bool HasReadAccess() const {
return UserInfo_.acl_template_.Read_ || UserInfo_.acl_template_.ReadWrite_ ||
UserInfo_.acl_template_.ReadWriteCreate_;
}
[[nodiscard]] inline bool HasWriteAccess() const {
return UserInfo_.acl_template_.ReadWrite_ || UserInfo_.acl_template_.ReadWriteCreate_;
}
[[nodiscard]] inline bool HasCreateAccess() const {
return UserInfo_.acl_template_.ReadWriteCreate_;
}
[[nodiscard]] static uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0);
[[nodiscard]] static std::string GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default="");
[[nodiscard]] static bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false);
[[nodiscard]] static uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj);
protected:
BindingMap Bindings_;
Poco::URI::QueryParameters Parameters_;
Poco::Logger &Logger_;
std::string SessionToken_;
struct uCentral::Objects::WebToken UserInfo_;
std::vector<std::string> Methods_;
QueryBlock QB_;
};
}
#endif //UCENTRAL_RESTAPI_HANDLER_H

View File

@@ -0,0 +1,60 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include "Poco/JSON/Parser.h"
#include "RESTAPI_oauth2Handler.h"
#include "AuthService.h"
#include "RESTAPI_protocol.h"
void RESTAPI_oauth2Handler::handleRequest(Poco::Net::HTTPServerRequest & Request, Poco::Net::HTTPServerResponse & Response)
{
if(!ContinueProcessing(Request,Response))
return;
try {
if (Request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_POST) {
// Extract the info for login...
Poco::JSON::Parser parser;
Poco::JSON::Object::Ptr Obj = parser.parse(Request.stream()).extract<Poco::JSON::Object::Ptr>();
auto userId = GetS(uCentral::RESTAPI::Protocol::USERID, Obj);
auto password = GetS(uCentral::RESTAPI::Protocol::PASSWORD, Obj);
Poco::toLowerInPlace(userId);
uCentral::Objects::WebToken Token;
if (uCentral::Auth::Authorize(userId, password, Token)) {
Poco::JSON::Object ReturnObj;
Token.to_json(ReturnObj);
ReturnObject(Request, ReturnObj, Response);
} else {
UnAuthorized(Request, Response);
}
} else if (Request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_DELETE) {
if (!IsAuthorized(Request, Response)) {
return;
}
auto Token = GetBinding(uCentral::RESTAPI::Protocol::TOKEN, "...");
if (Token == SessionToken_) {
uCentral::Auth::Logout(Token);
ReturnStatus(Request, Response, Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true);
} else {
NotFound(Request,Response);
}
} else {
BadRequest(Request, Response);
}
return;
}
catch (const Poco::Exception &E) {
Logger_.warning(Poco::format( "%s: Failed with: %s" , std::string(__func__), E.displayText()));
}
BadRequest(Request, Response);
}

View File

@@ -0,0 +1,26 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRAL_RESTAPI_OAUTH2HANDLER_H
#define UCENTRAL_RESTAPI_OAUTH2HANDLER_H
#include "RESTAPI_handler.h"
class RESTAPI_oauth2Handler: public uCentral::RESTAPI::RESTAPIHandler
{
public:
RESTAPI_oauth2Handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L)
: RESTAPIHandler(bindings,L,
std::vector<std::string>
{ Poco::Net::HTTPRequest::HTTP_POST,
Poco::Net::HTTPRequest::HTTP_DELETE,
Poco::Net::HTTPRequest::HTTP_OPTIONS}) {}
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override;
};
#endif //UCENTRAL_RESTAPI_OAUTH2HANDLER_H

48
src/RESTAPI_objects.cpp Normal file
View File

@@ -0,0 +1,48 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include "Poco/JSON/Parser.h"
#include "Poco/JSON/Stringifier.h"
#include "Daemon.h"
#include "RESTAPI_handler.h"
#include "RESTAPI_objects.h"
#include "uUtils.h"
namespace uCentral::Objects {
void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr) {
std::string D = ObjStr.empty() ? "{}" : ObjStr;
Poco::JSON::Parser P;
Poco::Dynamic::Var result = P.parse(D);
const auto &DetailsObj = result.extract<Poco::JSON::Object::Ptr>();
Obj.set(ObjName, DetailsObj);
}
void AclTemplate::to_json(Poco::JSON::Object &Obj) const {
Obj.set("Read",Read_);
Obj.set("ReadWrite",ReadWrite_);
Obj.set("ReadWriteCreate",ReadWriteCreate_);
Obj.set("Delete",Delete_);
Obj.set("PortalLogin",PortalLogin_);
}
void WebToken::to_json(Poco::JSON::Object & Obj) const {
Poco::JSON::Object AclTemplateObj;
acl_template_.to_json(AclTemplateObj);
Obj.set("access_token",access_token_);
Obj.set("refresh_token",refresh_token_);
Obj.set("token_type",token_type_);
Obj.set("expires_in",expires_in_);
Obj.set("idle_timeout",idle_timeout_);
Obj.set("created",created_);
Obj.set("username",username_);
Obj.set("aclTemplate",AclTemplateObj);
}
}

39
src/RESTAPI_objects.h Normal file
View File

@@ -0,0 +1,39 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRAL_RESTAPI_OBJECTS_H
#define UCENTRAL_RESTAPI_OBJECTS_H
#include "Poco/JSON/Object.h"
namespace uCentral::Objects {
struct AclTemplate {
bool Read_ = true ;
bool ReadWrite_ = true ;
bool ReadWriteCreate_ = true ;
bool Delete_ = true ;
bool PortalLogin_ = true ;
void to_json(Poco::JSON::Object &Obj) const ;
};
struct WebToken {
std::string access_token_;
std::string refresh_token_;
std::string id_token_;
std::string token_type_;
std::string username_;
unsigned int expires_in_;
unsigned int idle_timeout_;
AclTemplate acl_template_;
uint64_t created_;
void to_json(Poco::JSON::Object &Obj) const ;
};
}
#endif //UCENTRAL_RESTAPI_OBJECTS_H

86
src/RESTAPI_protocol.h Normal file
View File

@@ -0,0 +1,86 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRALGW_RESTAPI_PROTOCOL_H
#define UCENTRALGW_RESTAPI_PROTOCOL_H
namespace uCentral::RESTAPI::Protocol {
static const char * CAPABILITIES = "capabilities";
static const char * LOGS = "logs";
static const char * HEALTHCHECKS = "healthchecks";
static const char * STATISTICS = "statistics";
static const char * STATUS = "status";
static const char * SERIALNUMBER = "serialNumber";
static const char * PERFORM = "perform";
static const char * CONFIGURE = "configure";
static const char * UPGRADE = "upgrade";
static const char * REBOOT = "reboot";
static const char * FACTORY = "factory";
static const char * LEDS = "leds";
static const char * TRACE = "trace";
static const char * REQUEST = "request";
static const char * WIFISCAN = "wifiscan";
static const char * EVENTQUEUE = "eventqueue";
static const char * RTTY = "rtty";
static const char * COMMAND = "command";
static const char * STARTDATE = "startDate";
static const char * ENDDATE = "endDate";
static const char * OFFSET = "offset";
static const char * LIMIT = "limit";
static const char * LIFETIME = "lifetime";
static const char * UUID = "UUID";
static const char * DATA = "data";
static const char * CONFIGURATION = "configuration";
static const char * WHEN = "when";
static const char * URI = "uri";
static const char * LOGTYPE = "logType";
static const char * VALUES = "values";
static const char * TYPES = "types";
static const char * PAYLOAD = "payload";
static const char * KEEPREDIRECTOR = "keepRedirector";
static const char * NETWORK = "network";
static const char * INTERFACE = "interface";
static const char * BANDS = "bands";
static const char * CHANNELS = "channels";
static const char * VERBOSE = "verbose";
static const char * MESSAGE = "message";
static const char * STATE = "state";
static const char * HEALTHCHECK = "healthcheck";
static const char * PCAP_FILE_TYPE = "pcap";
static const char * DURATION = "duration";
static const char * NUMBEROFPACKETS = "numberOfPackets";
static const char * FILTER = "filter";
static const char * SELECT = "select";
static const char * SERIALONLY = "serialOnly";
static const char * COUNTONLY = "countOnly";
static const char * DEVICEWITHSTATUS = "deviceWithStatus";
static const char * DEVICESWITHSTATUS = "devicesWithStatus";
static const char * DEVICES = "devices";
static const char * COUNT = "count";
static const char * SERIALNUMBERS = "serialNumbers";
static const char * CONFIGURATIONS = "configurations";
static const char * NAME = "name";
static const char * COMMANDS = "commands";
static const char * COMMANDUUID = "commandUUID";
static const char * FIRMWARES = "firmwares";
static const char * TOPIC = "topic";
static const char * REASON = "reason";
static const char * FILEUUID = "uuid";
static const char * USERID = "userId";
static const char * PASSWORD = "password";
static const char * TOKEN = "token";
static const char * SETLOGLEVEL = "setloglevel";
static const char * GETLOGLEVEL = "getloglevel";
static const char * PARAMETERS = "parameters";
static const char * VALUE = "value";
static const char * LASTONLY = "lastOnly";
static const char * NEWEST = "newest";
static const char * ACTIVESCAN = "activeScan";
}
#endif // UCENTRALGW_RESTAPI_PROTOCOL_H

89
src/RESTAPI_server.cpp Normal file
View File

@@ -0,0 +1,89 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include "Poco/URI.h"
#include "RESTAPI_server.h"
#include "RESTAPI_oauth2Handler.h"
#include "RESTAPI_unknownRequestHandler.h"
#include "uUtils.h"
namespace uCentral::RESTAPI {
Service *Service::instance_ = nullptr;
int Start() {
return Service::instance()->Start();
}
void Stop() {
Service::instance()->Stop();
}
Service::Service() noexcept: SubSystemServer("RESTAPIServer", "RESTAPIServer", "ucentral.restapi")
{
}
int Service::Start() {
Logger_.information("Starting.");
for(const auto & Svr: ConfigServersList_) {
Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()),
Svr.KeyFile(),Svr.CertFile()));
auto Sock{Svr.CreateSecureSocket(Logger_)};
// Sock.setReceiveTimeout(Poco::Timespan(10,0));
Svr.LogCert(Logger_);
if(!Svr.RootCA().empty())
Svr.LogCas(Logger_);
auto Params = new Poco::Net::HTTPServerParams;
Params->setMaxThreads(50);
Params->setMaxQueued(200);
Params->setKeepAlive(true);
// uint64_t T = 45000;
// Params->setKeepAliveTimeout(T);
// Params->setMaxKeepAliveRequests(200);
// Params->setTimeout();
auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new RequestHandlerFactory, Pool_, Sock, Params);
NewServer->start();
RESTServers_.push_back(std::move(NewServer));
}
return 0;
}
Poco::Net::HTTPRequestHandler *RequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) {
Logger_.debug(Poco::format("REQUEST(%s): %s %s", uCentral::Utils::FormatIPv6(Request.clientAddress().toString()), Request.getMethod(), Request.getURI()));
Poco::URI uri(Request.getURI());
const auto & Path = uri.getPath();
RESTAPIHandler::BindingMap bindings;
if (RESTAPIHandler::ParseBindings(Path, "/api/v1/oauth2/{token}", bindings)) {
return new RESTAPI_oauth2Handler(bindings, Logger_);
} else if (RESTAPIHandler::ParseBindings(Path, "/api/v1/oauth2", bindings)) {
return new RESTAPI_oauth2Handler(bindings, Logger_);
}
Logger_.error(Poco::format("INVALID-API-ENDPOINT: %s",Path));
return new RESTAPI_UnknownRequestHandler(bindings,Logger_);
}
void Service::Stop() {
Logger_.information("Stopping ");
for( const auto & svr : RESTServers_ )
svr->stop();
}
} // namespace

63
src/RESTAPI_server.h Normal file
View File

@@ -0,0 +1,63 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRAL_UCENTRALRESTAPISERVER_H
#define UCENTRAL_UCENTRALRESTAPISERVER_H
#include "SubSystemServer.h"
#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/NetException.h"
namespace uCentral::RESTAPI {
int Start();
void Stop();
class Service : public SubSystemServer {
public:
Service() noexcept;
friend int Start();
friend void Stop();
static Service *instance() {
if (instance_ == nullptr) {
instance_ = new Service;
}
return instance_;
}
private:
static Service *instance_;
int Start() override;
void Stop() override;
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
Poco::ThreadPool Pool_;
};
class RequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
public:
RequestHandlerFactory() :
Logger_(Service::instance()->Logger()){}
Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override;
private:
Poco::Logger & Logger_;
};
} // namespace
#endif //UCENTRAL_UCENTRALRESTAPISERVER_H

View File

@@ -0,0 +1,16 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include "RESTAPI_unknownRequestHandler.h"
void RESTAPI_UnknownRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response)
{
if(!IsAuthorized(Request,Response))
return;
BadRequest(Request, Response);
}

View File

@@ -0,0 +1,25 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRAL_RESTAPI_UNKNOWNREQUESTHANDLER_H
#define UCENTRAL_RESTAPI_UNKNOWNREQUESTHANDLER_H
#include "RESTAPI_handler.h"
class RESTAPI_UnknownRequestHandler: public uCentral::RESTAPI::RESTAPIHandler
{
public:
RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap & bindings,Poco::Logger & L)
: RESTAPIHandler(bindings,L,
std::vector<std::string>{}) {}
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override;
};
#endif //UCENTRAL_RESTAPI_UNKNOWNREQUESTHANDLER_H

265
src/SubSystemServer.cpp Normal file
View File

@@ -0,0 +1,265 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include "SubSystemServer.h"
#include "Daemon.h"
#include "Poco/Net/X509Certificate.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "openssl/ssl.h"
SubSystemServer::SubSystemServer( std::string Name,
const std::string & LoggingPrefix,
std::string SubSystemConfigPrefix )
: Name_(std::move(Name)),
Logger_(Poco::Logger::get(LoggingPrefix)),
SubSystemConfigPrefix_(std::move(SubSystemConfigPrefix))
{
Logger_.setLevel(Poco::Message::PRIO_NOTICE);
}
void SubSystemServer::initialize(Poco::Util::Application & self)
{
Logger_.notice("Initializing...");
auto i=0;
bool good=true;
while(good) {
std::string root{SubSystemConfigPrefix_ + ".host." + std::to_string(i) + "."};
std::string address{root + "address"};
if(uCentral::ServiceConfig::GetString(address,"").empty()) {
good = false;
}
else {
std::string port{root + "port"};
std::string key{root + "key"};
std::string key_password{root + "key.password"};
std::string cert{root + "cert"};
std::string name{root + "name"};
std::string backlog{root+"backlog"};
std::string rootca{root+"rootca"};
std::string issuer{root+"issuer"};
std::string clientcas(root+"clientcas");
std::string cas{root+"cas"};
std::string level{root+"security"};
Poco::Net::Context::VerificationMode M=Poco::Net::Context::VERIFY_RELAXED;
auto L = uCentral::ServiceConfig::GetString(level,"");
if(L=="strict") {
M=Poco::Net::Context::VERIFY_STRICT;
} else if(L=="none") {
M=Poco::Net::Context::VERIFY_NONE;
} else if(L=="relaxed") {
M=Poco::Net::Context::VERIFY_RELAXED;
} else if(L=="once")
M=Poco::Net::Context::VERIFY_ONCE;
PropertiesFileServerEntry entry( uCentral::ServiceConfig::GetString(address,""),
uCentral::ServiceConfig::GetInt(port,0),
uCentral::ServiceConfig::GetString(key,""),
uCentral::ServiceConfig::GetString(cert,""),
uCentral::ServiceConfig::GetString(rootca,""),
uCentral::ServiceConfig::GetString(issuer,""),
uCentral::ServiceConfig::GetString(clientcas,""),
uCentral::ServiceConfig::GetString(cas,""),
uCentral::ServiceConfig::GetString(key_password,""),
uCentral::ServiceConfig::GetString(name,""),
M,
(int) uCentral::ServiceConfig::GetInt(backlog,64));
ConfigServersList_.push_back(entry);
i++;
}
}
}
void SubSystemServer::uninitialize()
{
}
void SubSystemServer::reinitialize(Poco::Util::Application & self)
{
// add your own reinitialization code here
}
void SubSystemServer::defineOptions(Poco::Util::OptionSet& options)
{
}
Poco::Net::SecureServerSocket PropertiesFileServerEntry::CreateSecureSocket(Poco::Logger & L) const
{
Poco::Net::Context::Params P;
P.verificationMode = level_;
P.verificationDepth = 9;
P.loadDefaultCAs = root_ca_.empty();
P.cipherList = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH";
P.dhUse2048Bits = true;
P.caLocation = cas_;
auto Context = new Poco::Net::Context(Poco::Net::Context::TLS_SERVER_USE, P);
if(!cert_file_.empty() && !key_file_.empty()) {
Poco::Crypto::X509Certificate Cert(cert_file_);
Poco::Crypto::X509Certificate Root(root_ca_);
Context->useCertificate(Cert);
Context->addChainCertificate(Root);
Context->addCertificateAuthority(Root);
if (level_ == Poco::Net::Context::VERIFY_STRICT) {
if (issuer_cert_file_.empty()) {
L.fatal("In strict mode, you must supply ans issuer certificate");
}
if(client_cas_.empty()) {
L.fatal("In strict mode, client cas must be supplied");
}
Poco::Crypto::X509Certificate Issuing(issuer_cert_file_);
Context->addChainCertificate(Issuing);
Context->addCertificateAuthority(Issuing);
}
Poco::Crypto::RSAKey Key("", key_file_, "");
Context->usePrivateKey(Key);
SSL_CTX *SSLCtx = Context->sslContext();
if (!SSL_CTX_check_private_key(SSLCtx)) {
L.fatal(Poco::format("Wrong Certificate(%s) for Key(%s)", cert_file_, key_file_));
}
SSL_CTX_set_verify(SSLCtx, SSL_VERIFY_PEER, nullptr);
if(level_==Poco::Net::Context::VERIFY_STRICT) {
SSL_CTX_set_client_CA_list(SSLCtx, SSL_load_client_CA_file(client_cas_.c_str()));
}
SSL_CTX_enable_ct(SSLCtx, SSL_CT_VALIDATION_STRICT);
SSL_CTX_dane_enable(SSLCtx);
Context->enableSessionCache();
Context->setSessionCacheSize(0);
Context->setSessionTimeout(10);
Context->enableExtendedCertificateVerification(true);
Context->disableStatelessSessionResumption();
}
if (address_ == "*") {
Poco::Net::IPAddress Addr(Poco::Net::IPAddress::wildcard(Poco::Net::Socket::supportsIPv6() ? Poco::Net::AddressFamily::IPv6 : Poco::Net::AddressFamily::IPv4 ));
Poco::Net::SocketAddress SockAddr(Addr, port_);
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
}
else {
Poco::Net::IPAddress Addr(address_);
Poco::Net::SocketAddress SockAddr(Addr, port_);
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
}
}
void PropertiesFileServerEntry::LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C) {
L.information("=============================================================================================");
L.information(Poco::format("> Issuer: %s",C.issuerName()));
L.information("---------------------------------------------------------------------------------------------");
L.information(Poco::format("> Common Name: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
L.information(Poco::format("> Country: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
L.information(Poco::format("> Locality: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
L.information(Poco::format("> State/Prov: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
L.information(Poco::format("> Org name: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
L.information(Poco::format("> Org unit: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
L.information(Poco::format("> Email: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
L.information(Poco::format("> Serial#: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
L.information("---------------------------------------------------------------------------------------------");
L.information(Poco::format("> Subject: %s",C.subjectName()));
L.information("---------------------------------------------------------------------------------------------");
L.information(Poco::format("> Common Name: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
L.information(Poco::format("> Country: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
L.information(Poco::format("> Locality: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
L.information(Poco::format("> State/Prov: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
L.information(Poco::format("> Org name: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
L.information(Poco::format("> Org unit: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
L.information(Poco::format("> Email: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
L.information(Poco::format("> Serial#: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
L.information("---------------------------------------------------------------------------------------------");
L.information(Poco::format("> Signature Algo: %s",C.signatureAlgorithm()));
auto From = Poco::DateTimeFormatter::format(C.validFrom(),Poco::DateTimeFormat::HTTP_FORMAT);
L.information(Poco::format("> Valid from: %s",From));
auto Expires = Poco::DateTimeFormatter::format(C.expiresOn(),Poco::DateTimeFormat::HTTP_FORMAT);
L.information(Poco::format("> Expires on: %s",Expires));
L.information(Poco::format("> Version: %d",(int)C.version()));
L.information(Poco::format("> Serial #: %s",C.serialNumber()));
L.information("=============================================================================================");
}
void PropertiesFileServerEntry::LogCert(Poco::Logger & L) const {
try {
Poco::Crypto::X509Certificate C(cert_file_);
L.information("=============================================================================================");
L.information("=============================================================================================");
L.information(Poco::format("Certificate Filename: %s",cert_file_));
LogCertInfo(L,C);
L.information("=============================================================================================");
if(!issuer_cert_file_.empty()) {
Poco::Crypto::X509Certificate C1(issuer_cert_file_);
L.information("=============================================================================================");
L.information("=============================================================================================");
L.information(Poco::format("Issues Certificate Filename: %s",issuer_cert_file_));
LogCertInfo(L,C1);
L.information("=============================================================================================");
}
if(!client_cas_.empty()) {
std::vector<Poco::Crypto::X509Certificate> Certs=Poco::Net::X509Certificate::readPEM(client_cas_);
L.information("=============================================================================================");
L.information("=============================================================================================");
L.information(Poco::format("Client CAs Filename: %s",client_cas_));
L.information("=============================================================================================");
auto i=1;
for(const auto & C3 : Certs)
{
L.information(Poco::format(" Index: %d",i));
L.information("=============================================================================================");
LogCertInfo(L, C3);
i++;
}
L.information("=============================================================================================");
}
} catch( const Poco::Exception & E) {
L.log(E);
}
}
void PropertiesFileServerEntry::LogCas(Poco::Logger & L) const {
try {
std::vector<Poco::Crypto::X509Certificate> Certs=Poco::Net::X509Certificate::readPEM(root_ca_);
L.information("=============================================================================================");
L.information("=============================================================================================");
L.information(Poco::format("CA Filename: %s",root_ca_));
L.information("=============================================================================================");
auto i=1;
for(const auto & C : Certs)
{
L.information(Poco::format(" Index: %d",i));
L.information("=============================================================================================");
LogCertInfo(L, C);
i++;
}
L.information("=============================================================================================");
} catch ( const Poco::Exception & E ) {
L.log(E);
}
}

106
src/SubSystemServer.h Normal file
View File

@@ -0,0 +1,106 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRAL_SUBSYSTEMSERVER_H
#define UCENTRAL_SUBSYSTEMSERVER_H
#include <mutex>
#include "Poco/Util/Application.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Logger.h"
#include "Poco/Net/SecureServerSocket.h"
#include "Poco/Net/X509Certificate.h"
using SubMutex = std::recursive_mutex;
using SubMutexGuard = std::lock_guard<SubMutex>;
class PropertiesFileServerEntry {
public:
PropertiesFileServerEntry( std::string Address,
uint32_t port,
std::string Key_file,
std::string Cert_file,
std::string RootCa,
std::string Issuer,
std::string ClientCas,
std::string Cas,
std::string Key_file_password = "",
std::string Name="",
Poco::Net::Context::VerificationMode M=Poco::Net::Context::VerificationMode::VERIFY_RELAXED,
int backlog=64) :
address_(std::move(Address)),
port_(port),
key_file_(std::move(Key_file)),
cert_file_(std::move(Cert_file)),
root_ca_(std::move(RootCa)),
issuer_cert_file_(std::move(Issuer)),
client_cas_(std::move(ClientCas)),
cas_(std::move(Cas)),
key_file_password_(std::move(Key_file_password)),
name_(std::move(Name)),
level_(M),
backlog_(backlog){};
[[nodiscard]] const std::string & Address() const { return address_; };
[[nodiscard]] uint32_t Port() const { return port_; };
[[nodiscard]] const std::string & KeyFile() const { return key_file_; };
[[nodiscard]] const std::string & CertFile() const { return cert_file_; };
[[nodiscard]] const std::string & RootCA() const { return root_ca_; };
[[nodiscard]] const std::string & KeyFilePassword() const { return key_file_password_; };
[[nodiscard]] const std::string & IssuerCertFile() const { return issuer_cert_file_; };
[[nodiscard]] const std::string & Name() const { return name_; };
[[nodiscard]] Poco::Net::SecureServerSocket CreateSecureSocket(Poco::Logger &L) const;
[[nodiscard]] int Backlog() const { return backlog_; }
void LogCert( Poco::Logger & L ) const;
void LogCas( Poco::Logger & L ) const;
static void LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C);
private:
std::string address_;
std::string cert_file_;
std::string key_file_;
std::string root_ca_;
std::string key_file_password_;
std::string issuer_cert_file_;
std::string client_cas_;
std::string cas_;
uint32_t port_;
std::string name_;
int backlog_;
Poco::Net::Context::VerificationMode level_;
};
class SubSystemServer : public Poco::Util::Application::Subsystem {
public:
SubSystemServer(std::string Name, const std::string & LoggingName, std::string SubSystemPrefix );
void initialize(Poco::Util::Application &self) override;
void uninitialize() override;
void reinitialize(Poco::Util::Application & self) override;
void defineOptions(Poco::Util::OptionSet &options) override;
const char *name() const override { return Name_.c_str(); };
const PropertiesFileServerEntry & Host(int index) { return ConfigServersList_[index]; };
Poco::Logger & Logger() { return Logger_;};
void SetLoggingLevel(Poco::Message::Priority NewPriority) { Logger_.setLevel(NewPriority); }
virtual int Start() = 0;
virtual void Stop() = 0;
protected:
SubMutex Mutex_{};
Poco::Logger & Logger_;
std::string Name_;
std::vector<PropertiesFileServerEntry> ConfigServersList_;
std::string SubSystemConfigPrefix_;
};
#endif //UCENTRAL_SUBSYSTEMSERVER_H

266
src/uUtils.cpp Normal file
View File

@@ -0,0 +1,266 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#include <stdexcept>
#include "uUtils.h"
#include "Poco/Exception.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTime.h"
#include "Poco/DateTimeParser.h"
#include "Poco/StringTokenizer.h"
#include "uCentralProtocol.h"
namespace uCentral::Utils {
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial) {
return ((Serial.size() < uCentralProtocol::SERIAL_NUMBER_LENGTH) &&
std::all_of(Serial.begin(),Serial.end(),[](auto i){return std::isxdigit(i);}));
}
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter ) {
std::vector<std::string> ReturnList;
unsigned long P=0;
while(P<List.size())
{
unsigned long P2 = List.find_first_of(Delimiter, P);
if(P2==std::string::npos) {
ReturnList.push_back(List.substr(P));
break;
}
else
ReturnList.push_back(List.substr(P,P2-P));
P=P2+1;
}
return ReturnList;
}
[[nodiscard]] std::string FormatIPv6(const std::string & I )
{
if(I.substr(0,8) == "[::ffff:")
{
unsigned long PClosingBracket = I.find_first_of(']');
std::string ip = I.substr(8, PClosingBracket-8);
std::string port = I.substr(PClosingBracket+1);
return ip + port;
}
return I;
}
[[nodiscard]] std::string SerialToMAC(const std::string &Serial) {
std::string R = Serial;
if(R.size()<12)
padTo(R,12,'0');
else if (R.size()>12)
R = R.substr(0,12);
char buf[18];
buf[0] = R[0]; buf[1] = R[1] ; buf[2] = ':' ;
buf[3] = R[2] ; buf[4] = R[3]; buf[5] = ':' ;
buf[6] = R[4]; buf[7] = R[5] ; buf[8] = ':' ;
buf[9] = R[6] ; buf[10]= R[7]; buf[11] = ':';
buf[12] = R[8] ; buf[13]= R[9]; buf[14] = ':';
buf[15] = R[10] ; buf[16]= R[11];buf[17] = 0;
return buf;
}
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B) {
std::string R;
R.reserve(B.size()*2);
static const char hex[] = "0123456789abcdef";
for(const auto &i:B)
{
R += (hex[ (i & 0xf0) >> 4]);
R += (hex[ (i & 0x0f) ]);
}
return R;
}
inline static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
inline static const char kPadCharacter = '=';
std::string base64encode(const byte *input, unsigned long size) {
std::string encoded;
encoded.reserve(((size / 3) + (size % 3 > 0)) * 4);
std::uint32_t temp;
std::size_t i;
int ee = (int)(size/3);
for (i = 0; i < 3*ee; ++i) {
temp = input[i++] << 16;
temp += input[i++] << 8;
temp += input[i];
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
encoded.append(1, kEncodeLookup[(temp & 0x0000003F)]);
}
switch (size % 3) {
case 1:
temp = input[i] << 16;
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(2, kPadCharacter);
break;
case 2:
temp = input[i++] << 16;
temp += input[i] << 8;
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
encoded.append(1, kPadCharacter);
break;
}
return encoded;
}
std::vector<byte> base64decode(const std::string& input)
{
if(input.length() % 4)
throw std::runtime_error("Invalid base64 length!");
std::size_t padding{};
if(input.length())
{
if(input[input.length() - 1] == kPadCharacter) padding++;
if(input[input.length() - 2] == kPadCharacter) padding++;
}
std::vector<byte> decoded;
decoded.reserve(((input.length() / 4) * 3) - padding);
std::uint32_t temp{};
auto it = input.begin();
while(it < input.end())
{
for(std::size_t i = 0; i < 4; ++i)
{
temp <<= 6;
if (*it >= 0x41 && *it <= 0x5A) temp |= *it - 0x41;
else if(*it >= 0x61 && *it <= 0x7A) temp |= *it - 0x47;
else if(*it >= 0x30 && *it <= 0x39) temp |= *it + 0x04;
else if(*it == 0x2B) temp |= 0x3E;
else if(*it == 0x2F) temp |= 0x3F;
else if(*it == kPadCharacter)
{
switch(input.end() - it)
{
case 1:
decoded.push_back((temp >> 16) & 0x000000FF);
decoded.push_back((temp >> 8 ) & 0x000000FF);
return decoded;
case 2:
decoded.push_back((temp >> 10) & 0x000000FF);
return decoded;
default:
throw std::runtime_error("Invalid padding in base64!");
}
}
else throw std::runtime_error("Invalid character in base64!");
++it;
}
decoded.push_back((temp >> 16) & 0x000000FF);
decoded.push_back((temp >> 8 ) & 0x000000FF);
decoded.push_back((temp ) & 0x000000FF);
}
return decoded;
}
std::string to_RFC3339(uint64_t t)
{
if(t==0)
return "";
return Poco::DateTimeFormatter::format(Poco::DateTime(Poco::Timestamp::fromEpochTime(t)), Poco::DateTimeFormat::ISO8601_FORMAT);
}
uint64_t from_RFC3339(const std::string &TimeString)
{
if(TimeString.empty() || TimeString=="0")
return 0;
try {
int TZ;
Poco::DateTime DT = Poco::DateTimeParser::parse(Poco::DateTimeFormat::ISO8601_FORMAT,TimeString,TZ);
return DT.timestamp().epochTime();
}
catch( const Poco::Exception & E )
{
}
return 0;
}
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds) {
Poco::StringTokenizer TimeTokens(Time,":",Poco::StringTokenizer::TOK_TRIM);
Hours = Minutes = Hours = 0 ;
if(TimeTokens.count()==1) {
Hours = std::atoi(TimeTokens[0].c_str());
} else if(TimeTokens.count()==2) {
Hours = std::atoi(TimeTokens[0].c_str());
Minutes = std::atoi(TimeTokens[1].c_str());
} else if(TimeTokens.count()==3) {
Hours = std::atoi(TimeTokens[0].c_str());
Minutes = std::atoi(TimeTokens[1].c_str());
Seconds = std::atoi(TimeTokens[2].c_str());
} else
return false;
return true;
}
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day) {
Poco::StringTokenizer DateTokens(Time,"-",Poco::StringTokenizer::TOK_TRIM);
Year = Month = Day = 0 ;
if(DateTokens.count()==3) {
Year = std::atoi(DateTokens[0].c_str());
Month = std::atoi(DateTokens[1].c_str());
Day = std::atoi(DateTokens[2].c_str());
} else
return false;
return true;
}
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2) {
if(H1<H2)
return true;
if(H1>H2)
return false;
if(M1<M2)
return true;
if(M2>M1)
return false;
if(S1<=S2)
return true;
return false;
}
}

40
src/uUtils.h Normal file
View File

@@ -0,0 +1,40 @@
//
// License type: BSD 3-Clause License
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
//
// Created by Stephane Bourque on 2021-03-04.
// Arilia Wireless Inc.
//
#ifndef UCENTRALGW_UUTILS_H
#define UCENTRALGW_UUTILS_H
#include <vector>
#include <string>
namespace uCentral::Utils {
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter=',');
[[nodiscard]] std::string FormatIPv6(const std::string & I );
inline void padTo(std::string& str, size_t num, char paddingChar = '\0') {
str.append(num - str.length() % num, paddingChar);
}
[[nodiscard]] std::string SerialToMAC(const std::string &Serial);
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B);
using byte = std::uint8_t;
[[nodiscard]] std::string base64encode(const byte *input, unsigned long size);
std::vector<byte> base64decode(const std::string& input);
// [[nodiscard]] std::string to_RFC3339(uint64_t t);
// [[nodiscard]] uint64_t from_RFC3339(const std::string &t);
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds);
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day);
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2);
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial);
}
#endif // UCENTRALGW_UUTILS_H

133
ucentralsec.properties Normal file
View File

@@ -0,0 +1,133 @@
#
# uCentral protocol server for devices. This is where you point
# all your devices. You can replace the * for address by the specific
# address of one of your interfaces
#
#
# REST API access
#
ucentral.restapi.host.0.backlog = 100
ucentral.restapi.host.0.security = relaxed
ucentral.restapi.host.0.rootca = $UCENTRALSEC_ROOT/certs/restapi-ca.pem
ucentral.restapi.host.0.address = *
ucentral.restapi.host.0.port = 16001
ucentral.restapi.host.0.cert = $UCENTRALSEC_ROOT/certs/restapi-cert.pem
ucentral.restapi.host.0.key = $UCENTRALSEC_ROOT/certs/restapi-key.pem
ucentral.restapi.host.0.key.password = mypassword
#
# NLB Support
#
nlb.enable = true
nlb.port = 15017
authentication.enabled = true
authentication.default.username = tip@ucentral.com
authentication.default.password = openwifi
authentication.default.access = master
authentication.service.type = internal
firmware.autoupdate.policy.default = auto
system.directory.data = $UCENTRALSEC_ROOT/data
ucentral.service.key = $UCENTRALSEC_ROOT/certs/restapi-key.pem
ucentral.system.debug = true
ucentral.system.uri = https://localhost:16001
ucentral.system.id = 1
ucentral.system.commandchannel = /tmp/app.ucentralgw
#
# Kafka
#
ucentral.kafka.enable = false
ucentral.kafka.brokerlist = 127.0.0.1:9092
ucentral.kafka.auto.commit = false
ucentral.kafka.queue.buffering.max.ms = 50
#
# This section select which form of persistence you need
# Only one selected at a time. If you select multiple, this service will die if a horrible
# death and might make your beer flat.
#
storage.type = sqlite
#storage.type = postgresql
#storage.type = mysql
#storage.type = odbc
storage.type.sqlite.db = $UCENTRALSEC_ROOT/devices.db
storage.type.sqlite.idletime = 120
storage.type.sqlite.maxsessions = 128
storage.type.postgresql.maxsessions = 64
storage.type.postgresql.idletime = 60
storage.type.postgresql.host = localhost
storage.type.postgresql.username = stephb
storage.type.postgresql.password = snoopy99
storage.type.postgresql.database = ucentral
storage.type.postgresql.port = 5432
storage.type.postgresql.connectiontimeout = 60
storage.type.mysql.maxsessions = 64
storage.type.mysql.idletime = 60
storage.type.mysql.host = localhost
storage.type.mysql.username = stephb
storage.type.mysql.password = snoopy99
storage.type.mysql.database = ucentral
storage.type.mysql.port = 3306
storage.type.mysql.connectiontimeout = 60
#
# Authentication
#
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.system.debug = true
ucentral.system.uri = https://localhost:16001
ucentral.system.id = 1
ucentral.system.commandchannel = /tmp/app.ucentralgw
########################################################################
########################################################################
#
# Thw following sections apply to the uCentral service
#
# Logging: please leave as is for now.
#
########################################################################
########################################################################
logging.formatters.f1.class = PatternFormatter
logging.formatters.f1.pattern = %s: [%p] %t
logging.formatters.f1.times = UTC
logging.channels.c1.class = ConsoleChannel
logging.channels.c1.formatter = f1
logging.channels.c2.class = FileChannel
# This is where the logs will be written. This path MUST exist
logging.channels.c2.path = $UCENTRALSEC_ROOT/logs/sample.log
logging.channels.c2.formatter.class = PatternFormatter
logging.channels.c2.formatter.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t
logging.channels.c3.class = ConsoleChannel
logging.channels.c3.pattern = %s: [%p] %t
# External Channel
logging.loggers.root.channel = c2
logging.loggers.root.level = debug
# Inline Channel with PatternFormatter
# logging.loggers.l1.name = logger1
# logging.loggers.l1.channel.class = ConsoleChannel
# logging.loggers.l1.channel.pattern = %s: [%p] %t
# logging.loggers.l1.level = information
# SplitterChannel
# logging.channels.splitter.class = SplitterChannel
# logging.channels.splitter.channels = l1,l2
# logging.loggers.l2.name = logger2
# logging.loggers.l2.channel = splitter