mirror of
https://github.com/outbackdingo/sysadm.git
synced 2026-01-27 10:20:26 +00:00
Add a full chain of SSL Certificate management fucntions to the authorization manager, and setup the main "auth" API call to use the cert system if no password was supplied for authorization.
While here, also move the location of the server config file to /usr/local/etc/sysadm.conf, and print out that location in the main server log file.
This commit is contained in:
@@ -68,6 +68,55 @@ bool AuthorizationManager::hasFullAccess(QString token){
|
||||
return ok;
|
||||
}
|
||||
|
||||
//SSL Certificate register/revoke/list
|
||||
bool AuthorizationManager::RegisterCertificate(QString token, QSslCertificate cert){
|
||||
if(!checkAuth(token)){ return false; }
|
||||
QString user = hashID(token).section("::::",2,2); //get the user name from the currently-valid token
|
||||
CONFIG->setValue("RegisteredCerts/"+user+"/"+QString(cert.publicKey().toPem()), cert.toText());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuthorizationManager::RevokeCertificate(QString token, QString key, QString user){
|
||||
//user will be the current user if not empty - cannot touch other user's certs without full perms on current session
|
||||
QString cuser = hashID(token).section("::::",2,2);
|
||||
if(user.isEmpty()){ user = cuser; } //only probe current user
|
||||
if(user !=cuser){
|
||||
//Check permissions for this cross-user action
|
||||
if(!hasFullAccess(token)){ return false; }
|
||||
}
|
||||
//Check that the given cert exists first
|
||||
if( !CONFIG->contains("RegisteredCerts/"+user+"/"+key) ){ return false; }
|
||||
CONFIG->remove("RegisteredCerts/"+user+"/"+key);
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject AuthorizationManager::ListCertificates(QString token){
|
||||
QJsonObject obj;
|
||||
QStringList keys; //Format: "RegisteredCerts/<user>/<key>"
|
||||
if( hasFullAccess(token) ){
|
||||
//Read all user's certs
|
||||
keys = CONFIG->allKeys().filter("RegisteredCerts/");
|
||||
}else{
|
||||
//Only list certs for current user
|
||||
QString cuser = hashID(token).section("::::",2,2);
|
||||
keys = CONFIG->allKeys().filter("RegisteredCerts/"+cuser+"/");
|
||||
}
|
||||
keys.sort();
|
||||
//Now put the known keys into the output structure arranged by username/key
|
||||
QJsonObject user; QString username;
|
||||
for(int i=0; i<keys.length(); i++){
|
||||
if(username!=keys[i].section("/",1,1)){
|
||||
if(!user.isEmpty()){ obj.insert(username, user); user = QJsonObject(); } //save the current info to the output
|
||||
username = keys[i].section("/",1,1); //save the new username for later
|
||||
}
|
||||
user.insert(keys[i].section("/",2,3000), CONFIG->value(keys[i]).toString() ); //just in case the key has additional "/" in it
|
||||
}
|
||||
if(!user.isEmpty() && !username.isEmpty()){ obj.insert(username, user); }
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
//Generic functions
|
||||
int AuthorizationManager::checkAuthTimeoutSecs(QString token){
|
||||
//Return the number of seconds that a token is valid for
|
||||
if(!HASH.contains(token)){ return 0; } //invalid token
|
||||
@@ -96,7 +145,7 @@ QString AuthorizationManager::LoginUP(QHostAddress host, QString user, QString p
|
||||
}else{
|
||||
ok = true; //allow local access for users without password
|
||||
}
|
||||
|
||||
|
||||
qDebug() << "User Login Attempt:" << user << " Success:" << ok << " IP:" << host.toString();
|
||||
LogManager::log(LogManager::HOST, QString("User Login Attempt: ")+user+" Success: "+(ok?"true":"false")+" IP: "+host.toString() );
|
||||
if(!ok){
|
||||
@@ -108,10 +157,53 @@ QString AuthorizationManager::LoginUP(QHostAddress host, QString user, QString p
|
||||
}else{
|
||||
//valid login - generate a new token for it
|
||||
ClearHostFail(host.toString());
|
||||
return generateNewToken(isOperator);
|
||||
return generateNewToken(isOperator, user);
|
||||
}
|
||||
}
|
||||
|
||||
QString AuthorizationManager::LoginUC(QHostAddress host, QString user, QList<QSslCertificate> certs){
|
||||
//Login w/ username & SSL certificate
|
||||
bool localhost = ( (host== QHostAddress::LocalHost) || (host== QHostAddress::LocalHostIPv6) || (host.toString()=="::ffff:127.0.0.1") );
|
||||
bool ok = false;
|
||||
//First check that the user is valid on the system and part of the operator group
|
||||
bool isOperator = false;
|
||||
if(user!="root" && user!="toor"){
|
||||
QStringList groups = getUserGroups(user);
|
||||
if(groups.contains("wheel")){ isOperator = true; } //full-access user
|
||||
else if(!groups.contains("operator")){
|
||||
return ""; //user not allowed access if not in either of the wheel/operator groups
|
||||
}
|
||||
}else{ isOperator = true; }
|
||||
qDebug() << "Check username/certificate combination" << user << localhost;
|
||||
|
||||
//Need to check the registered certificates for the designated user
|
||||
if(!localhost || user=="root" || user=="toor"){
|
||||
for(int i=0; i<certs.length() && !ok; i++){
|
||||
if(CONFIG->contains("RegisteredCerts/"+user+"/"+QString(certs[i].publicKey().toPem()) ) ){
|
||||
//Cert was registered - check expiration info
|
||||
// TO-DO
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
ok = true; //allow local access for users without password
|
||||
}
|
||||
|
||||
qDebug() << "User Login Attempt:" << user << " Success:" << ok << " IP:" << host.toString();
|
||||
LogManager::log(LogManager::HOST, QString("User Login Attempt: ")+user+" Success: "+(ok?"true":"false")+" IP: "+host.toString() );
|
||||
if(!ok){
|
||||
//invalid login
|
||||
//Bump the fail count for this host
|
||||
bool overlimit = BumpFailCount(host.toString());
|
||||
if(overlimit){ emit BlockHost(host); }
|
||||
return (overlimit ? "REFUSED" : "");
|
||||
}else{
|
||||
//valid login - generate a new token for it
|
||||
ClearHostFail(host.toString());
|
||||
return generateNewToken(isOperator, user);
|
||||
}
|
||||
}
|
||||
|
||||
QString AuthorizationManager::LoginService(QHostAddress host, QString service){
|
||||
bool localhost = ( (host== QHostAddress::LocalHost) || (host== QHostAddress::LocalHostIPv6) );
|
||||
|
||||
@@ -136,13 +228,13 @@ QString AuthorizationManager::LoginService(QHostAddress host, QString service){
|
||||
}else{
|
||||
return "";
|
||||
}
|
||||
}else{ return generateNewToken(false); }//services are never given operator privileges
|
||||
}else{ return generateNewToken(false, service); }//services are never given operator privileges
|
||||
}
|
||||
|
||||
// =========================
|
||||
// PRIVATE
|
||||
// =========================
|
||||
QString AuthorizationManager::generateNewToken(bool isOp){
|
||||
QString AuthorizationManager::generateNewToken(bool isOp, QString user){
|
||||
QString tok;
|
||||
for(int i=0; i<TOKENLENGTH; i++){
|
||||
tok.append( AUTHCHARS.at( qrand() % AUTHCHARS.length() ) );
|
||||
@@ -150,10 +242,10 @@ QString AuthorizationManager::generateNewToken(bool isOp){
|
||||
|
||||
if( !hashID(tok).isEmpty() ){
|
||||
//Just in case the randomizer came up with something identical - re-run it
|
||||
tok = generateNewToken(isOp);
|
||||
tok = generateNewToken(isOp, user);
|
||||
}else{
|
||||
//unique token created - add it to the hash with the current time (+timeout)
|
||||
QString id = tok + "::::"+(isOp ? "operator" : "user"); //append operator status to auth key
|
||||
QString id = tok + "::::"+(isOp ? "operator" : "user")+"::::"+user; //append operator status to auth key
|
||||
HASH.insert(id, QDateTime::currentDateTime().addSecs(TIMEOUTSECS) );
|
||||
}
|
||||
return tok;
|
||||
|
||||
@@ -19,17 +19,23 @@ public:
|
||||
bool checkAuth(QString token); //see if the given token is valid
|
||||
bool hasFullAccess(QString token); //see if the token is associated with a full-access account
|
||||
|
||||
//SSL Certificate register/revoke/list
|
||||
bool RegisterCertificate(QString token, QSslCertificate cert); //if token is valid, register the given cert for future logins
|
||||
bool RevokeCertificate(QString token, QString key, QString user=""); //user will be the current user if not empty - cannot touch other user's certs without full perms on current session
|
||||
QJsonObject ListCertificates(QString token);
|
||||
|
||||
int checkAuthTimeoutSecs(QString token); //Return the number of seconds that a token is valid for
|
||||
|
||||
// == Token Generation functions
|
||||
QString LoginUP(QHostAddress host, QString user, QString pass); //Login w/ username & password
|
||||
QString LoginUC(QHostAddress host, QString user, QList<QSslCertificate> certs); //Login w/ username & SSL certificate
|
||||
QString LoginService(QHostAddress host, QString service); //Login a particular automated service
|
||||
|
||||
private:
|
||||
QHash<QString, QDateTime> HASH;
|
||||
QHash <QString, QDateTime> IPFAIL;
|
||||
|
||||
QString generateNewToken(bool isOperator);
|
||||
QString generateNewToken(bool isOperator, QString name);
|
||||
QStringList getUserGroups(QString user);
|
||||
|
||||
//Failure count management
|
||||
|
||||
@@ -25,7 +25,6 @@ EventWatcher::~EventWatcher(){
|
||||
}
|
||||
|
||||
void EventWatcher::start(){
|
||||
// - DISPATCH Events
|
||||
starting = true;
|
||||
// - Life Preserver Events
|
||||
WatcherUpdate(LPLOG); //load it initially (will also add it to the watcher);
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
protected:
|
||||
void incomingConnection(qintptr socketDescriptor){
|
||||
QSslSocket *serverSocket = new QSslSocket(this);
|
||||
qDebug() << "New Ssl Connection:";
|
||||
//qDebug() << "New Ssl Connection:";
|
||||
//setup any supported encruption types here
|
||||
serverSocket->setSslConfiguration(QSslConfiguration::defaultConfiguration());
|
||||
serverSocket->setProtocol(SSLVERSION);
|
||||
|
||||
@@ -153,11 +153,20 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){
|
||||
if(DEBUG){ qDebug() << "Authenticate Peer:" << SOCKET->peerAddress().toString(); }
|
||||
//Now do the auth
|
||||
if(out.in_struct.name=="auth" && out.in_struct.args.isObject() ){
|
||||
//username/password authentication
|
||||
//username/[password/cert] authentication
|
||||
QString user, pass;
|
||||
if(out.in_struct.args.toObject().contains("username")){ user = JsonValueToString(out.in_struct.args.toObject().value("username")); }
|
||||
if(out.in_struct.args.toObject().contains("password")){ pass = JsonValueToString(out.in_struct.args.toObject().value("password")); }
|
||||
SockAuthToken = AUTHSYSTEM->LoginUP(host, user, pass);
|
||||
if(!pass.isEmpty()){
|
||||
//Use the given password
|
||||
SockAuthToken = AUTHSYSTEM->LoginUP(host, user, pass);
|
||||
}else{
|
||||
//No password - use the current SSL certificates instead
|
||||
QList<QSslCertificate> certs;
|
||||
if(SOCKET!=0){ certs = SOCKET->sslConfiguration().peerCertificateChain(); }
|
||||
else if(TSOCKET!=0){ certs = TSOCKET->peerCertificateChain(); }
|
||||
SockAuthToken = AUTHSYSTEM->LoginUC(host, user, certs);
|
||||
}
|
||||
}else if(out.in_struct.name == "auth_token" && out.in_struct.args.isObject()){
|
||||
SockAuthToken = JsonValueToString(out.in_struct.args.toObject().value("token"));
|
||||
}else if(out.in_struct.name == "auth_clear"){
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#define DEBUG 0
|
||||
|
||||
//Create any global classes
|
||||
QSettings *CONFIG = new QSettings("PCBSD","sysadm");
|
||||
QSettings *CONFIG = new QSettings("/usr/local/etc/sysadm.conf", QSettings::IniFormat);
|
||||
EventWatcher *EVENTS = new EventWatcher();
|
||||
Dispatcher *DISPATCHER = new Dispatcher();
|
||||
bool WS_MODE = false;
|
||||
@@ -91,6 +91,7 @@ int main( int argc, char ** argv )
|
||||
//Start the daemon
|
||||
int ret = 1; //error return value
|
||||
if( w->startServer(port, websocket) ){
|
||||
qDebug() << " - Configuration File:" << CONFIG->fileName();
|
||||
QThread TBACK, TBACK2;
|
||||
EVENTS->moveToThread(&TBACK);
|
||||
DISPATCHER->moveToThread(&TBACK2);
|
||||
|
||||
Reference in New Issue
Block a user