diff --git a/api/classes/beadm.rst b/api/classes/beadm.rst index b280685..8424ea3 100644 --- a/api/classes/beadm.rst +++ b/api/classes/beadm.rst @@ -20,7 +20,7 @@ Every beadm class request contains the following parameters: | namespace | sysadm | | | | | | +---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+ -| action | | supported actions include "listbes", "renamebe" | +| action | | supported actions include "listbes", "renamebe", "activatebe", "createbe" | | | | | +---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+ @@ -137,4 +137,105 @@ The "renamebe" action renames the specified boot environment. When using this ac "id": "fooid", "name": "response", "namespace": "sysadm" + } + +.. index:: activatebe, beadm + +.. _Activate Boot Environment: + +Activate Boot Environment +========================= + +The "activatebe" action activates the specified boot environment (target) so that it will be the default at next boot. + + +**REST Request** + +.. code-block:: json + + PUT /sysadm/beadm + { + "target" : "bootthingy", + "action" : "activatebe" + } + +**WebSocket Request** + +.. code-block:: json + + { + "name" : "beadm", + "args" : { + "action" : "activatebe", + "target" : "bootthingy" + }, + "namespace" : "sysadm", + "id" : "fooid" + } + +**Response** + +.. code-block:: json + + { + "args": { + "activatebe": { + "target": "bootthingy" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + +.. index:: createbe, beadm + +.. _Create Boot Environment: + +Create Boot Environment +======================= + +The "create" action creates a new boot environment. Specify the name of the boot environment as the "newbe". By default, this action clones the active boot environment. +To specify another, inactive boot environment, also include "clonefrom" to specify which boot environment to clone from. + +**REST Request** + +.. code-block:: json + + PUT /sysadm/beadm + { + "action" : "createbe", + "newbe" : "red", + "clonefrom" : "green" + } + +**WebSocket Request** + +.. code-block:: json + + { + "id" : "fooid", + "args" : { + "newbe" : "red", + "clonefrom" : "green", + "action" : "createbe" + }, + "namespace" : "sysadm", + "name" : "beadm" + } + +**Response** + +.. code-block:: json + + { + "args": { + "createbe": { + "clonefrom": "green", + "newbe": "red" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" } \ No newline at end of file diff --git a/api/classes/iocage.rst b/api/classes/iocage.rst index 4708288..6e211ac 100644 --- a/api/classes/iocage.rst +++ b/api/classes/iocage.rst @@ -21,7 +21,8 @@ Every iocage class request contains the following parameters: | | | | +---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+ | action | | supported actions include "getdefaultsettings", "listjails", "getjailsettings", "startjail", "stopjail", | -| | | "capjail", "cleanjails", "cleanreleases", "cleantemplates", "cleanall", "activatepool", and "deactivatepool" | +| | | "capjail", "clonejail", "cleanjails", "cleanreleases", "cleantemplates", "cleanall", "activatepool", and | +| | | "deactivatepool" | | | | | +---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+ @@ -839,6 +840,108 @@ the jail. "namespace": "sysadm" } +.. index:: clonejail, iocage + +.. _Clone a Jail: + +Clone a Jail +============ + +The "clonejail" action clones the specified "jail". By default, the clone will inherit that jail's properties. Use "props" to specify any properties that should differ. All available +properties are described in `iocage(8) `_. + +In this example, the "tag" property is specified so that the new jail has a different name than the jail it was cloned from. + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iocage + { + "props" : "tag=newtest", + "jail" : "test", + "action" : "clonejail" + } + +**WebSocket Request** + +.. code-block:: json + + { + "namespace" : "sysadm", + "name" : "iocage", + "args" : { + "action" : "clonejail", + "jail" : "test", + "props" : "tag=newtest" + }, + "id" : "fooid" + } + +**Response** + +.. code-block:: json + + { + "args": { + "clonejail": { + "jail": "test", + "props": "tag=newtest", + "success": { + "Successfully created": " 5e1fe97e-cfba-11e5-8209-d05099728dbf (newtest)" + } + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + +In this example, no properties are specified so iocage populates its own values and the props returned in the response is empty: + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iocage + { + "action" : "clonejail", + "jail" : "test" + } + +**WebSocket Request** + +.. code-block:: json + + { + "args" : { + "jail" : "test", + "action" : "clonejail" + }, + "name" : "iocage", + "namespace" : "sysadm", + "id" : "fooid" + } + +**Response** + +.. code-block:: json + + { + "args": { + "clonejail": { + "jail": "test", + "props": "", + "success": { + "Successfully created": " 89e78032-cfba-11e5-8209-d05099728dbf (2016-02-09@23" + } + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + .. index:: cleanjails, iocage .. _Clean Jails: diff --git a/api/classes/iohyve.rst b/api/classes/iohyve.rst index a944590..8343c85 100644 --- a/api/classes/iohyve.rst +++ b/api/classes/iohyve.rst @@ -20,7 +20,8 @@ Every iohyve class request contains the following parameters: | namespace | sysadm | | | | | | +---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+ -| action | | supported actions include "listvms", "fetchiso", "renameiso", "rmiso" | +| action | | supported actions include "listvms", "fetchiso", "renameiso", "rmiso", "setup", "issetup", "create", "install", | +| | | "start", "stop" | | | | | +---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+ @@ -244,4 +245,300 @@ The "rmiso" action is used to to remove an existing ISO file from disk. Specify "id": "fooid", "name": "response", "namespace": "sysadm" + } + +.. index:: setup, iohyve + +.. _Setup iohyve: + +Setup iohyve +============ + +The "setup" action performs the initial setup of iohyve. It is mandatory to specify the FreeBSD device name of the "nic" and the ZFS "pool" to use. + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iohyve + { + "nic" : "re0", + "pool" : "tank", + "action" : "setup" + } + +**WebSocket Request** + +.. code-block:: json + + { + "id" : "fooid", + "name" : "iohyve", + "args" : { + "pool" : "tank", + "nic" : "re0", + "action" : "setup" + }, + "namespace" : "sysadm" + } + +**Response** + +.. code-block:: json + + { + "args": { + "setup": { + "nic": "re0", + "pool": "tank" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + +.. index:: issetup, iohyve + +.. _Determine iohyve Setup: + +Determine iohyve Setup +====================== + +The "issetup" action queries if iohyve has been setup and returns either "true" or "false". + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iohyve + { + "action" : "issetup" + } + +**WebSocket Request** + +.. code-block:: json + + { + "id" : "fooid", + "namespace" : "sysadm", + "args" : { + "action" : "issetup" + }, + "name" : "iohyve" + } + +**Response** + +.. code-block:: json + + { + "args": { + "issetup": { + "setup": "true" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + +.. index:: create, iohyve + +.. _Create Guest: + +Create Guest +============ + +The "create" action creates a new iohyve guest of the specified "name" and "size". + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iohyve + { + "action" : "create", + "name" : "bsdguest", + "size" : "10G" + } + +**WebSocket Request** + +.. code-block:: json + + { + "name" : "iohyve", + "namespace" : "sysadm", + "id" : "fooid", + "args" : { + "name" : "bsdguest", + "action" : "create", + "size" : "10G" + } + } + +**Response** + +.. code-block:: json + + { + "args": { + "create": { + "name": "bsdguest", + "size": "10G" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + +.. index:: install, iohyve + +.. _Install Guest: + +Install Guest +============= + +The "install" action starts the iohyve installation of the specified guest from the specified ISO. This action only boots the VM with the ISO; to do the actual installation, +run :command:`iohyve console ` from the system. + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iohyve + { + "name" : "bsdguest", + "iso" : "FreeBSD-10.2-RELEASE-amd64-disc1.iso", + "action" : "install" + } + +**WebSocket Request** + +.. code-block:: json + + { + "namespace" : "sysadm", + "name" : "iohyve", + "id" : "fooid", + "args" : { + "action" : "install", + "iso" : "FreeBSD-10.2-RELEASE-amd64-disc1.iso", + "name" : "bsdguest" + } + } + +**Response** + +.. code-block:: json + + { + "args": { + "install": { + "iso": "FreeBSD-10.2-RELEASE-amd64-disc1.iso", + "name": "bsdguest" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + +.. index:: start, iohyve + +.. _Start VM: + +Start VM +======== + +The "start" action starts the specified VM. + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iohyve + { + "action" : "start", + "name" : "bsdguest" + } + +**WebSocket Request** + +.. code-block:: json + + { + "name" : "iohyve", + "id" : "fooid", + "args" : { + "action" : "start", + "name" : "bsdguest" + }, + "namespace" : "sysadm" + } + +**Response** + +.. code-block:: json + + { + "args": { + "start": { + "name": "bsdguest" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" + } + +.. index:: stop, iohyve + +.. _Stop VM: + +Stop VM +======= + +The "stop" action stops the specified VM. + +**REST Request** + +.. code-block:: json + + PUT /sysadm/iohyve + { + "action" : "stop", + "name" : "bsdguest" + } + +**WebSocket Request** + +.. code-block:: json + + { + "id" : "fooid", + "args" : { + "action" : "stop", + "name" : "bsdguest" + }, + "name" : "iohyve", + "namespace" : "sysadm" + } + +**Response** + +.. code-block:: json + + { + "args": { + "stop": { + "name": "bsdguest" + } + }, + "id": "fooid", + "name": "response", + "namespace": "sysadm" } \ No newline at end of file diff --git a/src/server/AuthorizationManager.cpp b/src/server/AuthorizationManager.cpp index 9429419..2a20164 100644 --- a/src/server/AuthorizationManager.cpp +++ b/src/server/AuthorizationManager.cpp @@ -68,6 +68,52 @@ 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; +} + +void AuthorizationManager::ListCertificates(QString token, QJsonObject *out){ + QStringList keys; //Format: "RegisteredCerts//" + 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; iinsert(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()){ out->insert(username, user); } +} + +//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 @@ -89,14 +135,14 @@ QString AuthorizationManager::LoginUP(QHostAddress host, QString user, QString p return ""; //user not allowed access if not in either of the wheel/operator groups } }else{ isOperator = true; } - qDebug() << "Check username/password" << user << pass << localhost; + //qDebug() << "Check username/password" << user << pass << localhost; //Need to run the full username/password through PAM if(!localhost || user=="root" || user=="toor"){ ok = pam_checkPW(user,pass); }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,12 +154,55 @@ 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 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; icontains("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) ); + bool localhost = ( (host== QHostAddress::LocalHost) || (host== QHostAddress::LocalHostIPv6) || (host.toString()=="::ffff:127.0.0.1") ); //Login a particular automated service qDebug() << "Service Login Attempt:" << service << " Success:" << localhost; @@ -136,13 +225,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 certs); //Login w/ username & SSL certificate QString LoginService(QHostAddress host, QString service); //Login a particular automated service private: QHash HASH; QHash IPFAIL; - QString generateNewToken(bool isOperator); + QString generateNewToken(bool isOperator, QString name); QStringList getUserGroups(QString user); //Failure count management diff --git a/src/server/EventWatcher.cpp b/src/server/EventWatcher.cpp index 34756e7..4e5aee4 100644 --- a/src/server/EventWatcher.cpp +++ b/src/server/EventWatcher.cpp @@ -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); diff --git a/src/server/SslServer.h b/src/server/SslServer.h index 021d4ec..54e8b5b 100644 --- a/src/server/SslServer.h +++ b/src/server/SslServer.h @@ -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); diff --git a/src/server/WebBackend.cpp b/src/server/WebBackend.cpp index c422284..e6933f7 100644 --- a/src/server/WebBackend.cpp +++ b/src/server/WebBackend.cpp @@ -31,6 +31,8 @@ RestOutputStruct::ExitCode WebSocket::AvailableSubsystems(bool allaccess, QJsonO : , } */ + // - server settings (always available) + out->insert("sysadm/settings","read/write"); // - syscache if(QFile::exists("/var/run/syscache.pipe")){ @@ -41,7 +43,7 @@ RestOutputStruct::ExitCode WebSocket::AvailableSubsystems(bool allaccess, QJsonO if(QFile::exists("/usr/local/sbin/beadm")){ out->insert("sysadm/beadm", "read/write"); } - + // - dispatcher (Internal to server - always available) //"read" is the event notifications, "write" is the ability to queue up jobs @@ -95,7 +97,9 @@ RestOutputStruct::ExitCode WebSocket::EvaluateBackendRequest(const RestInputStru } //Go through and forward this request to the appropriate sub-system - if(namesp=="rpc" && name=="dispatcher"){ + if(namesp=="sysadm" && name=="settings"){ + return EvaluateSysadmSettingsRequest(IN.args, out); + }else if(namesp=="rpc" && name=="dispatcher"){ return EvaluateDispatcherRequest(IN.fullaccess, IN.args, out); }else if(namesp=="sysadm" && name=="beadm"){ return EvaluateSysadmBEADMRequest(IN.args, out); @@ -119,6 +123,40 @@ RestOutputStruct::ExitCode WebSocket::EvaluateBackendRequest(const RestInputStru } +// === SYSADM SETTINGS === +RestOutputStruct::ExitCode WebSocket::EvaluateSysadmSettingsRequest(const QJsonValue in_args, QJsonObject *out){ + if(!in_args.isObject()){ return RestOutputStruct::BADREQUEST; } + QJsonObject argsO = in_args.toObject(); + QStringList keys = argsO.keys(); + if(!keys.contains("action")){ return RestOutputStruct::BADREQUEST; } + QString act = argsO.value("action").toString(); + bool ok = false; + if(act=="register_ssl_cert" && keys.contains("pub_key")){ + //Additional arguments: "pub_key" (String), and the cert with that key must already be loaded into the connection + QString pub_key = argsO.value("pub_key").toString();\ + //Now find the currently-loaded certificate with the given public key + QList certs; + if(SOCKET!=0){ certs = SOCKET->sslConfiguration().peerCertificateChain(); } + else if(TSOCKET!=0){ certs = TSOCKET->peerCertificateChain(); } + for(int i=0; iRegisterCertificate(SockAuthToken, certs[i]); + } + } + }else if(act=="list_ssl_certs"){ + AUTHSYSTEM->ListCertificates(SockAuthToken, out); + ok = true; //always works for current user (even if nothing found) + }else if(act=="revoke_ssl_cert" && keys.contains("pub_key") ){ + //Additional arguments: "user" (optional), "pub_key" (String) + QString user; if(keys.contains("user")){ user = argsO.value("user").toString(); } + ok = AUTHSYSTEM->RevokeCertificate(SockAuthToken,argsO.value("pub_key").toString(), user); + } + + if(ok){ return RestOutputStruct::OK; } + else{ return RestOutputStruct::BADREQUEST; } +} + //==== SYSCACHE ==== RestOutputStruct::ExitCode WebSocket::EvaluateSyscacheRequest(const QJsonValue in_args, QJsonObject *out){ //syscache only needs a list of sub-commands at the moment (might change later) @@ -436,6 +474,26 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSysadmIocageRequest(const QJsonVal bool ok = false; if(keys.contains("action")){ QString act = JsonValueToString(in_args.toObject().value("action")); + if(act=="execjail"){ + ok = true; + out->insert("execjail", sysadm::Iocage::execJail(in_args.toObject())); + } + if(act=="df"){ + ok = true; + out->insert("df", sysadm::Iocage::df()); + } + if(act=="destroyjail"){ + ok = true; + out->insert("destroyjail", sysadm::Iocage::destroyJail(in_args.toObject())); + } + if(act=="createjail"){ + ok = true; + out->insert("createjail", sysadm::Iocage::createJail(in_args.toObject())); + } + if(act=="clonejail"){ + ok = true; + out->insert("clonejail", sysadm::Iocage::cloneJail(in_args.toObject())); + } if(act=="cleanall"){ ok = true; out->insert("cleanall", sysadm::Iocage::cleanAll()); @@ -504,6 +562,10 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSysadmIohyveRequest(const QJsonVal bool ok = false; if(keys.contains("action")){ QString act = JsonValueToString(in_args.toObject().value("action")); + if(act=="create"){ + ok = true; + out->insert("create", sysadm::Iohyve::createGuest(in_args.toObject())); + } if(act=="listvms"){ ok = true; out->insert("listvms", sysadm::Iohyve::listVMs()); @@ -512,6 +574,14 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSysadmIohyveRequest(const QJsonVal ok = true; out->insert("fetchiso", sysadm::Iohyve::fetchISO(in_args.toObject())); } + if(act=="install"){ + ok = true; + out->insert("install", sysadm::Iohyve::installGuest(in_args.toObject())); + } + if(act=="issetup"){ + ok = true; + out->insert("issetup", sysadm::Iohyve::isSetup()); + } if(act=="renameiso"){ ok = true; out->insert("renameiso", sysadm::Iohyve::renameISO(in_args.toObject())); @@ -520,6 +590,18 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSysadmIohyveRequest(const QJsonVal ok = true; out->insert("rmiso", sysadm::Iohyve::rmISO(in_args.toObject())); } + if(act=="setup"){ + ok = true; + out->insert("setup", sysadm::Iohyve::setupIohyve(in_args.toObject())); + } + if(act=="start"){ + ok = true; + out->insert("start", sysadm::Iohyve::startGuest(in_args.toObject())); + } + if(act=="stop"){ + ok = true; + out->insert("stop", sysadm::Iohyve::stopGuest(in_args.toObject())); + } } //end of "action" key usage //If nothing done - return the proper code diff --git a/src/server/WebSocket.cpp b/src/server/WebSocket.cpp index 31b9580..c58b79c 100644 --- a/src/server/WebSocket.cpp +++ b/src/server/WebSocket.cpp @@ -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 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"){ diff --git a/src/server/WebSocket.h b/src/server/WebSocket.h index 4547a69..b30d837 100644 --- a/src/server/WebSocket.h +++ b/src/server/WebSocket.h @@ -43,8 +43,14 @@ private: RestOutputStruct::ExitCode AvailableSubsystems(bool fullaccess, QJsonObject *out); // -- Main subsystem parser RestOutputStruct::ExitCode EvaluateBackendRequest(const RestInputStruct&, QJsonObject *out); + + // -- Individual subsystems + // -- Server Settings Modification API + RestOutputStruct::ExitCode EvaluateSysadmSettingsRequest(const QJsonValue in_args, QJsonObject *out); + // -- rpc syscache API RestOutputStruct::ExitCode EvaluateSyscacheRequest(const QJsonValue in_args, QJsonObject *out); + // -- rpc dispatcher API RestOutputStruct::ExitCode EvaluateDispatcherRequest(bool allaccess, const QJsonValue in_args, QJsonObject *out); // -- sysadm beadm API RestOutputStruct::ExitCode EvaluateSysadmBEADMRequest(const QJsonValue in_args, QJsonObject *out); diff --git a/src/server/library/sysadm-iocage.cpp b/src/server/library/sysadm-iocage.cpp index 36c35fa..97ec8b0 100644 --- a/src/server/library/sysadm-iocage.cpp +++ b/src/server/library/sysadm-iocage.cpp @@ -12,6 +12,207 @@ using namespace sysadm; //PLEASE: Keep the functions in the same order as listed in pcbsd-general.h +// Execute a process in a jail on the box +QJsonObject Iocage::execJail(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("jail") + || ! keys.contains("user") + || ! keys.contains("command") ) { + retObject.insert("error", "Missing required keys"); + return retObject; + } + + // Get the key values + QString jail = jsin.value("jail").toString(); + QString user = jsin.value("user").toString(); + QString command = jsin.value("command").toString(); + + QStringList output; + + output = General::RunCommand("iocage exec -U " + user + " " + jail + " " + command).split("\n"); + + QJsonObject vals; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).isEmpty() ) + break; + + if ( output.at(i).indexOf("execvp:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } else { + QString key = output.at(i).simplified().section(":", 0, 0); + QString value = output.at(i).simplified().section(":", 1, 1); + + vals.insert(key, value); + } + } + + retObject.insert("success", vals); + + return retObject; +} + +// Show resource usage for jails on the box +QJsonObject Iocage::df() { + QJsonObject retObject; + + // Get the key values + QStringList output = General::RunCommand("iocage df").split("\n"); + QJsonObject vals; + + for ( int i = 0; i < output.size(); i++) + { + // Null output at first + if ( output.at(i).isEmpty() ) + continue; + + QJsonObject jail; + QString line = output.at(i).simplified(); + QString uuid = line.section(" ", 0, 0); + + // Otherwise we get a list of what we already know. + if ( line.section(" ", 0, 0) == "UUID" ) + continue; + + jail.insert("crt", line.section(" ", 1, 1)); + jail.insert("res", line.section(" ", 2, 2)); + jail.insert("qta", line.section(" ", 3, 3)); + jail.insert("use", line.section(" ", 4, 4)); + jail.insert("ava", line.section(" ", 5, 5)); + jail.insert("tag", line.section(" ", 6, 6)); + + retObject.insert(uuid, jail); + } + + return retObject; +} + +// Destroy a jail on the box +QJsonObject Iocage::destroyJail(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("jail") ) { + retObject.insert("error", "Missing required keys"); + return retObject; + } + + // Get the key values + QString jail = jsin.value("jail").toString(); + QStringList output; + + output = General::RunCommand("iocage destroy -f " + jail).split("\n"); + + QJsonObject vals; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).isEmpty() ) + break; + + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } else { + QString key = output.at(i).simplified().section(":", 0, 0); + QString value = output.at(i).simplified().section(":", 1, 1); + + vals.insert(key, value); + } + } + + retObject.insert("success", vals); + + return retObject; +} + +// Create a jail on the box +QJsonObject Iocage::createJail(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + + // Get the key values + QString switches = jsin.value("switches").toString(); + QString props = jsin.value("props").toString(); + QStringList output; + + if ( keys.contains("switches" ) ) { + output = General::RunCommand("iocage create " + switches + " " + props).split("\n"); + } else { + output = General::RunCommand("iocage create " + props).split("\n"); + } + + QJsonObject vals; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).isEmpty() ) + break; + + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } else { + QString key = output.at(i).simplified().section(":", 0, 0); + QString value = output.at(i).simplified().section(":", 1, 1); + + if ( keys.contains("switches" ) ) { + vals.insert("uuid", key); + } else { + vals.insert(key, value); + } + } + } + + retObject.insert("switches", switches); + retObject.insert("props", props); + retObject.insert("success", vals); + + return retObject; +} + +// Clone a jail on the box +QJsonObject Iocage::cloneJail(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("jail") ) { + retObject.insert("error", "Missing required keys"); + return retObject; + } + + // Get the key values + QString jail = jsin.value("jail").toString(); + QString props = jsin.value("props").toString(); + + QStringList output = General::RunCommand("iocage clone " + jail + " " + props).split("\n"); + + QJsonObject vals; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).isEmpty() ) + break; + + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } else { + QString key = output.at(i).simplified().section(":", 0, 0); + QString value = output.at(i).simplified().section(":", 1, 1); + + vals.insert(key, value); + } + } + + retObject.insert("jail", jail); + retObject.insert("props", props); + retObject.insert("success", vals); + + return retObject; +} + // Clean everything iocage related on a box QJsonObject Iocage::cleanAll() { QJsonObject retObject; @@ -291,17 +492,33 @@ QJsonObject Iocage::getDefaultSettings() { // Return all of the jail settings QJsonObject Iocage::getJailSettings(QJsonObject jsin) { QJsonObject retObject; - - QStringList keys = jsin.keys(); - if (! keys.contains("jail") ) { - retObject.insert("error", "Missing required keys"); - return retObject; - } + QStringList output; // Get the key values QString jail = jsin.value("jail").toString(); + QString prop = jsin.value("prop").toString(); - QStringList output = General::RunCommand("iocage get all " + jail).split("\n"); + QString switches = jsin.value("switches").toString(); + + QStringList keys = jsin.keys(); + if (! keys.contains("jail") + && keys.contains("prop") + && keys.contains("switches") ) { + output = General::RunCommand("iocage get " + switches + " " + prop).split("\n"); + } else if ( ! keys.contains("jail") + && ! keys.contains("prop") + && ! keys.contains("switches") ){ + retObject.insert("error", "Missing required keys"); + return retObject; + } + + if ( ! keys.contains("prop") + && ! keys.contains("switches") ) { + output = General::RunCommand("iocage get all " + jail).split("\n"); + } else if ( keys.contains("prop") + && ! keys.contains("switches") ) { + output = General::RunCommand("iocage get " + prop + " " + jail).split("\n"); + } QJsonObject vals; for ( int i = 0; i < output.size(); i++) @@ -312,13 +529,42 @@ QJsonObject Iocage::getJailSettings(QJsonObject jsin) { if ( output.at(i).isEmpty() ) break; + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } else { QString key = output.at(i).simplified().section(":", 0, 0); QString value = output.at(i).simplified().section(":", 1, 1); + if ( keys.contains("switches" ) ) { + QString line = output.at(i).simplified(); + + // Otherwise we get a list of what we already know. + if ( line.section(" ", 0, 0) == "UUID" ) + continue; + + QJsonObject jail; + QString uuid = line.section(" ", 0, 0); + + jail.insert("TAG", line.section(" ", 1, 1)); + jail.insert(prop, line.section(" ", 2, 2)); + retObject.insert(uuid, jail); + continue; + } + + if ( keys.contains("prop" ) + && ! keys.contains("switches") + && prop != "all") { + vals.insert(prop, key); + retObject.insert(jail, vals); + continue; + } + vals.insert(key, value); + retObject.insert(jail, vals); + } } - retObject.insert(jail, vals); return retObject; } diff --git a/src/server/library/sysadm-iocage.h b/src/server/library/sysadm-iocage.h index 0497f16..6eb59f0 100644 --- a/src/server/library/sysadm-iocage.h +++ b/src/server/library/sysadm-iocage.h @@ -14,6 +14,11 @@ namespace sysadm{ class Iocage{ public: + static QJsonObject execJail(QJsonObject); + static QJsonObject df(); + static QJsonObject destroyJail(QJsonObject); + static QJsonObject createJail(QJsonObject); + static QJsonObject cloneJail(QJsonObject); static QJsonObject cleanAll(); static QJsonObject cleanTemplates(); static QJsonObject cleanReleases(); diff --git a/src/server/library/sysadm-iohyve.cpp b/src/server/library/sysadm-iohyve.cpp index c28d393..a90d165 100644 --- a/src/server/library/sysadm-iohyve.cpp +++ b/src/server/library/sysadm-iohyve.cpp @@ -13,6 +13,36 @@ using namespace sysadm; //PLEASE: Keep the functions in the same order as listed in pcbsd-general.h + +// Create a new guest VM +QJsonObject Iohyve::createGuest(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("name") || !keys.contains("size") ) { + retObject.insert("error", "Missing required key(s) 'name/size'"); + return retObject; + } + + // Get the key values + QString name = jsin.value("name").toString(); + QString size = jsin.value("size").toString(); + + QStringList output = General::RunCommand("iohyve create " + name + " " + size).split("\n"); + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("cannot create") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Return some details to user that the action was queued + retObject.insert("name", name); + retObject.insert("size", size); + return retObject; +} + // Queue the fetch of an ISO QJsonObject Iohyve::fetchISO(QJsonObject jsin) { QJsonObject retObject; @@ -39,6 +69,49 @@ QJsonObject Iohyve::fetchISO(QJsonObject jsin) { return retObject; } +// Create a new guest VM +QJsonObject Iohyve::installGuest(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("name") || !keys.contains("iso") ) { + retObject.insert("error", "Missing required key(s) 'name/iso'"); + return retObject; + } + + // Get the key values + QString name = jsin.value("name").toString(); + QString iso = jsin.value("iso").toString(); + + QStringList output = General::RunCommand("iohyve install " + name + " " + iso).split("\n"); + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("Could not open") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Return some details to user that the action was queued + retObject.insert("name", name); + retObject.insert("iso", iso); + return retObject; +} + +// Return if iohyve is setup on the box +QJsonObject Iohyve::isSetup() { + QJsonObject retObject; + + // Check if iohyve is setup on this box + // We check the flags variable, enabling / disabling is done via service mgmt + QString ioflags = General::getConfFileValue("/etc/rc.conf", "iohyve_flags=", 1); + if ( ioflags.isEmpty() ) + retObject.insert("setup", "false"); + else + retObject.insert("setup", "true"); + + return retObject; +} // List the VMs on the box QJsonObject Iohyve::listVMs() { @@ -156,3 +229,65 @@ QJsonObject Iohyve::setupIohyve(QJsonObject jsin) { retObject.insert("nic", nic); return retObject; } + +// Start a guest +QJsonObject Iohyve::startGuest(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("name") ) { + retObject.insert("error", "Missing required key 'name'"); + return retObject; + } + + // Get the key values + QString name = jsin.value("name").toString(); + + // Do the setup right now + QStringList output = General::RunCommand("iohyve start " + name).split("\n"); + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("Not a valid") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + retObject.insert("name", name); + return retObject; +} + +// Stop a guest +QJsonObject Iohyve::stopGuest(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("name") ) { + retObject.insert("error", "Missing required key 'name'"); + return retObject; + } + + // Get the key values + QString name = jsin.value("name").toString(); + + QString stoparg = "stop"; + if (! keys.contains("force") ) { + if ( jsin.value("force").toString() == "true" ) { + stoparg = "forcekill"; + } + } + + // Do the stop right now + QStringList output = General::RunCommand("iohyve " + stoparg + " " + name).split("\n"); + for ( int i = 0; i < output.size(); i++) + { + // This doesn't work, iohyve doesn't return error message right now + if ( output.at(i).indexOf("No such guest") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + retObject.insert("name", name); + return retObject; +} diff --git a/src/server/library/sysadm-iohyve.h b/src/server/library/sysadm-iohyve.h index ecd5051..8878b34 100644 --- a/src/server/library/sysadm-iohyve.h +++ b/src/server/library/sysadm-iohyve.h @@ -14,11 +14,16 @@ namespace sysadm{ class Iohyve{ public: + static QJsonObject createGuest(QJsonObject); static QJsonObject fetchISO(QJsonObject); + static QJsonObject installGuest(QJsonObject); + static QJsonObject isSetup(); static QJsonObject listVMs(); static QJsonObject renameISO(QJsonObject); static QJsonObject rmISO(QJsonObject); static QJsonObject setupIohyve(QJsonObject); + static QJsonObject startGuest(QJsonObject); + static QJsonObject stopGuest(QJsonObject); }; } //end of pcbsd namespace diff --git a/src/server/main.cpp b/src/server/main.cpp index e4ee487..44c1550 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -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);