diff --git a/client/core/configurators/openVpnConfigurator.cpp b/client/core/configurators/openVpnConfigurator.cpp index 02bbd602e6..1a7273bac3 100644 --- a/client/core/configurators/openVpnConfigurator.cpp +++ b/client/core/configurators/openVpnConfigurator.cpp @@ -63,16 +63,18 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co return connData; } - connData.caCert = - m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode); - connData.clientCert = m_sshSession->getTextFileFromContainer( - container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode); - + const QStringList certPaths = { + QString::fromLatin1(amnezia::protocols::openvpn::caCertPath), + QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), + QString::fromLatin1(amnezia::protocols::openvpn::taKeyPath) + }; + const QList certs = m_sshSession->getTextFilesFromContainer(container, credentials, certPaths, errorCode); if (errorCode != ErrorCode::NoError) { return connData; } - - connData.taKey = m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode); + connData.caCert = certs.value(0); + connData.clientCert = certs.value(1); + connData.taKey = certs.value(2); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { errorCode = ErrorCode::SshScpFailureError; diff --git a/client/core/configurators/wireguardConfigurator.cpp b/client/core/configurators/wireguardConfigurator.cpp index 43a486c154..cdb8fef21e 100644 --- a/client/core/configurators/wireguardConfigurator.cpp +++ b/client/core/configurators/wireguardConfigurator.cpp @@ -165,20 +165,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon connData.clientIP = nextIp.toString(); // Get keys - connData.serverPubKey = - m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode); - connData.serverPubKey.replace("\n", ""); + const QList keys = + m_sshSession->getTextFilesFromContainer(container, credentials, {m_serverPublicKeyPath, m_serverPskKeyPath}, errorCode); if (errorCode != ErrorCode::NoError) { return connData; } - - connData.pskKey = m_sshSession->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode); + connData.serverPubKey = keys.value(0); + connData.serverPubKey.replace("\n", ""); + connData.pskKey = keys.value(1); connData.pskKey.replace("\n", ""); - if (errorCode != ErrorCode::NoError) { - return connData; - } - // Add client to config QString configPart = QString("[Peer]\n" "PublicKey = %1\n" diff --git a/client/core/controllers/selfhosted/installController.cpp b/client/core/controllers/selfhosted/installController.cpp index abf3174416..2929e4739f 100644 --- a/client/core/controllers/selfhosted/installController.cpp +++ b/client/core/controllers/selfhosted/installController.cpp @@ -90,10 +90,9 @@ InstallController::~InstallController() } ErrorCode InstallController::setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, - bool isUpdate) + SshSession &sshSession, bool isUpdate) { qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container); - SshSession sshSession(this); ErrorCode e = ErrorCode::NoError; e = isUserInSudo(credentials, sshSession); @@ -199,7 +198,7 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont ErrorCode errorCode = ErrorCode::NoError; if (reinstallRequired) { - errorCode = setupContainer(credentials, container, newConfig, true); + errorCode = setupContainer(credentials, container, newConfig, sshSession, true); } else { errorCode = configureContainerWorker(credentials, container, newConfig, sshSession); if (errorCode == ErrorCode::NoError) { @@ -1033,10 +1032,10 @@ ContainerConfig InstallController::generateConfig(DockerContainer container, int } ErrorCode InstallController::installContainer(const ServerCredentials &credentials, DockerContainer container, int port, - TransportProto transportProto, ContainerConfig &config) + TransportProto transportProto, ContainerConfig &config, SshSession &sshSession) { config = generateConfig(container, port, transportProto); - return setupContainer(credentials, container, config, false); + return setupContainer(credentials, container, config, sshSession, false); } @@ -1142,7 +1141,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials, wasContainerInstalled = false; if (!installedContainers.contains(container)) { ContainerConfig config; - errorCode = installContainer(credentials, container, port, transportProto, config); + errorCode = installContainer(credentials, container, port, transportProto, config, sshSession); if (errorCode) { return errorCode; } @@ -1212,7 +1211,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon wasContainerInstalled = false; if (!installedContainers.contains(container)) { ContainerConfig config; - errorCode = installContainer(credentials, container, port, transportProto, config); + errorCode = installContainer(credentials, container, port, transportProto, config, sshSession); if (errorCode) { return errorCode; } diff --git a/client/core/controllers/selfhosted/installController.h b/client/core/controllers/selfhosted/installController.h index 6aec8ce863..4c1be3ce59 100644 --- a/client/core/controllers/selfhosted/installController.h +++ b/client/core/controllers/selfhosted/installController.h @@ -33,7 +33,7 @@ class InstallController : public QObject QObject *parent = nullptr); ~InstallController(); - ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false); + ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession, bool isUpdate = false); ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig); ErrorCode rebootServer(const QString &serverId); @@ -55,7 +55,7 @@ class InstallController : public QObject ErrorCode scanServerForInstalledContainers(const QString &serverId); - ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config); + ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config, SshSession &sshSession); ErrorCode installServer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, bool &wasContainerInstalled); diff --git a/client/core/utils/selfhosted/sshClient.cpp b/client/core/utils/selfhosted/sshClient.cpp index 5e854ab4b7..55edba0d3b 100644 --- a/client/core/utils/selfhosted/sshClient.cpp +++ b/client/core/utils/selfhosted/sshClient.cpp @@ -48,6 +48,9 @@ namespace libssh { ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str()); ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity); + long connectTimeoutSec = 30; + ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &connectTimeoutSec); + QFutureWatcher watcher; QFuture future = QtConcurrent::run([this]() { return ssh_connect(m_session); @@ -61,7 +64,9 @@ namespace libssh { int connectionResult = watcher.result(); if (connectionResult != SSH_OK) { - return fromLibsshErrorCode(); + ErrorCode errorCode = fromLibsshErrorCode(); + disconnectFromHost(); + return errorCode; } std::string authUsername = credentials.userName.toStdString(); @@ -95,14 +100,20 @@ namespace libssh { if (errorCode == ErrorCode::NoError) { errorCode = ErrorCode::SshPrivateKeyFormatError; } + disconnectFromHost(); return errorCode; } } else { authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str()); if (authResult != SSH_OK) { - return fromLibsshErrorCode(); + ErrorCode errorCode = fromLibsshErrorCode(); + disconnectFromHost(); + return errorCode; } } + + long sessionTimeoutSec = 86400; + ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &sessionTimeoutSec); } return ErrorCode::NoError; } diff --git a/client/core/utils/selfhosted/sshSession.cpp b/client/core/utils/selfhosted/sshSession.cpp index 363745fd5a..093160b9bf 100644 --- a/client/core/utils/selfhosted/sshSession.cpp +++ b/client/core/utils/selfhosted/sshSession.cpp @@ -59,6 +59,7 @@ ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString sc qDebug() << "SshSession::Run script"; QString totalLine; + QStringList statements; const QStringList &lines = script.split("\n", Qt::SkipEmptyParts); for (int i = 0; i < lines.count(); i++) { QString currentLine = lines.at(i); @@ -69,24 +70,31 @@ ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString sc totalLine = totalLine + "\n" + currentLine; } - QString lineToExec; if (currentLine.endsWith("\\")) { continue; - } else { - lineToExec = totalLine; - totalLine.clear(); } + QString lineToExec = totalLine; + totalLine.clear(); + if (lineToExec.startsWith("#")) { continue; } - qDebug().noquote() << lineToExec; + statements << lineToExec; + } - error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); - if (error != ErrorCode::NoError) { - return error; - } + if (statements.isEmpty()) { + qDebug().noquote() << "SshSession::runScript finished (no statements)\n"; + return ErrorCode::NoError; + } + + const QString batchedScript = statements.join("\n"); + qDebug().noquote() << batchedScript; + + error = m_sshClient.executeCommand(batchedScript, cbReadStdOut, cbReadStdErr); + if (error != ErrorCode::NoError) { + return error; } qDebug().noquote() << "SshSession::runScript finished\n"; @@ -97,30 +105,25 @@ ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, D const std::function &cbReadStdOut, const std::function &cbReadStdErr) { - QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; - - ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); - if (e) - return e; - const bool useSh = container == DockerContainer::Socks5Proxy || container == DockerContainer::MtProxy || container == DockerContainer::Telemt; - QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, useSh ? "sh" : "bash"); - e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr); + const QString shell = useSh ? QStringLiteral("sh") : QStringLiteral("bash"); + const QString b64 = QString::fromLatin1(script.toUtf8().toBase64()); - QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName); - runScript(credentials, replaceVars(remover, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr); + const QString command = QStringLiteral("printf '%s' '%1' | base64 -d | sudo docker exec -i $CONTAINER_NAME %2") + .arg(b64, shell); - return e; + return runScript(credentials, + replaceVars(command, amnezia::genBaseVars(credentials, container, QString(), QString())), + cbReadStdOut, cbReadStdErr); } ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, libssh::ScpOverwriteMode overwriteMode) { - ErrorCode e = ErrorCode::NoError; - QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); - e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName); - if (e) - return e; + if (overwriteMode != libssh::ScpOverwriteMode::ScpOverwriteExisting + && overwriteMode != libssh::ScpOverwriteMode::ScpAppendToExisting) { + return ErrorCode::NotImplementedError; + } QString stdOut; auto cbReadStd = [&](const QString &data, libssh::Client &) { @@ -128,45 +131,26 @@ ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const return ErrorCode::NoError; }; - // mkdir - QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path); + auto baseVars = amnezia::genBaseVars(credentials, container, QString(), QString()); + + const QString b64 = QString::fromLatin1(file.toUtf8().toBase64()); + const QString dir = QFileInfo(path).path(); + const QString redirect = (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) + ? QStringLiteral(">>") + : QStringLiteral(">"); - e = runScript(credentials, replaceVars(mkdir, amnezia::genBaseVars(credentials, container, QString(), QString()))); + const QString command = QStringLiteral("printf '%s' '%1' | base64 -d | " + "sudo docker exec -i $CONTAINER_NAME sh -c 'mkdir -p \"%2\" && cat %3 \"%4\"'") + .arg(b64, dir, redirect, path); + + ErrorCode e = runScript(credentials, replaceVars(command, baseVars), cbReadStd, cbReadStd); if (e) return e; - if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) { - e = runScript(credentials, - replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path), - amnezia::genBaseVars(credentials, container, QString(), QString())), - cbReadStd, cbReadStd); - - if (e) - return e; - } else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) { - e = runScript(credentials, - replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName), - amnezia::genBaseVars(credentials, container, QString(), QString())), - cbReadStd, cbReadStd); - - if (e) - return e; - - e = runScript(credentials, - replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path), - amnezia::genBaseVars(credentials, container, QString(), QString())), - cbReadStd, cbReadStd); - - if (e) - return e; - } else - return ErrorCode::NotImplementedError; - if (stdOut.contains("Error") && stdOut.contains("No such container")) { return ErrorCode::ServerContainerMissingError; } - runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), amnezia::genBaseVars(credentials, container, QString(), QString()))); return e; } @@ -188,6 +172,38 @@ QByteArray SshSession::getTextFileFromContainer(DockerContainer container, const return QByteArray::fromHex(stdOut.toUtf8()); } +QList SshSession::getTextFilesFromContainer(DockerContainer container, const ServerCredentials &credentials, + const QStringList &paths, ErrorCode &errorCode) +{ + errorCode = ErrorCode::NoError; + QList result; + if (paths.isEmpty()) { + return result; + } + + const QString sep = QStringLiteral("ZZAMNSEPZZ"); + QString inner; + for (const QString &path : paths) { + inner += QStringLiteral("xxd -p '%1'; echo '%2'; ").arg(path, sep); + } + QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"%2\"") + .arg(ContainerUtils::containerToString(container), inner); + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data; + return ErrorCode::NoError; + }; + errorCode = runScript(credentials, script, cbReadStdOut); + + const QStringList parts = stdOut.split(sep); + for (int i = 0; i < paths.size(); ++i) { + const QString hex = (i < parts.size()) ? parts.at(i) : QString(); + result.append(QByteArray::fromHex(hex.toUtf8())); + } + return result; +} + ErrorCode SshSession::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, libssh::ScpOverwriteMode overwriteMode) { diff --git a/client/core/utils/selfhosted/sshSession.h b/client/core/utils/selfhosted/sshSession.h index 2a738cb959..0d1797e2ea 100644 --- a/client/core/utils/selfhosted/sshSession.h +++ b/client/core/utils/selfhosted/sshSession.h @@ -28,6 +28,8 @@ class SshSession : public QObject libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, ErrorCode &errorCode); + QList getTextFilesFromContainer(DockerContainer container, const ServerCredentials &credentials, + const QStringList &paths, ErrorCode &errorCode); static QString replaceVars(const QString &script, const Vars &vars); diff --git a/client/server_scripts/openvpn/configure_container.sh b/client/server_scripts/openvpn/configure_container.sh index 5ec0163fbc..82cd54a025 100644 --- a/client/server_scripts/openvpn/configure_container.sh +++ b/client/server_scripts/openvpn/configure_container.sh @@ -5,7 +5,8 @@ dev tun ca /opt/amnezia/openvpn/ca.crt cert /opt/amnezia/openvpn/AmneziaReq.crt key /opt/amnezia/openvpn/AmneziaReq.key -dh /opt/amnezia/openvpn/dh.pem +dh none +ecdh-curve secp384r1 server $OPENVPN_SUBNET_IP $OPENVPN_SUBNET_MASK ifconfig-pool-persist ipp.txt duplicate-cn diff --git a/client/server_scripts/openvpn/run_container.sh b/client/server_scripts/openvpn/run_container.sh index bb19c46ad6..385981c361 100644 --- a/client/server_scripts/openvpn/run_container.sh +++ b/client/server_scripts/openvpn/run_container.sh @@ -7,6 +7,8 @@ sudo docker run -d \ -p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO \ --name $CONTAINER_NAME $CONTAINER_NAME +amn_i=0; while [ "$(sudo docker inspect -f '{{.State.Running}}' $CONTAINER_NAME 2>/dev/null)" != "true" ] && [ $amn_i -lt 30 ]; do sleep 0.5; amn_i=$((amn_i+1)); done + sudo docker network connect amnezia-dns-net $CONTAINER_NAME # Create tun device if not exist @@ -18,8 +20,7 @@ sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS ne # OpenVPN config sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \ cd /opt/amnezia/openvpn && easyrsa init-pki; \ -cd /opt/amnezia/openvpn && easyrsa gen-dh; \ -cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\ +cd /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\ cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\ cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\ cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\