#include "openvpn_configurator.h" #include #include #include #include #include #include #include #include "core/server_defs.h" #include "protocols/protocols_defs.h" #include "core/scripts_registry.h" QString OpenVpnConfigurator::getEasyRsaShPath() { #ifdef Q_OS_WIN // easyrsa sh path should looks like // "/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa" QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa"; easyRsaShPath = "\"" + easyRsaShPath + "\""; qDebug().noquote() << "EasyRsa sh path" << easyRsaShPath; return easyRsaShPath; #else return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa"; #endif } QProcessEnvironment OpenVpnConfigurator::prepareEnv() { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString pathEnvVar = env.value("PATH"); #ifdef Q_OS_WIN pathEnvVar.clear(); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\i386;"); #else pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); #endif env.insert("PATH", pathEnvVar); //qDebug().noquote() << "ENV PATH" << pathEnvVar; return env; } ErrorCode OpenVpnConfigurator::initPKI(const QString &path) { QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); #ifdef Q_OS_WIN p.setProcessEnvironment(prepareEnv()); p.setProgram("cmd.exe"); p.setNativeArguments(QString("/C \"ash.exe %1\"").arg(getEasyRsaShPath() + " init-pki")); qDebug().noquote() << "EasyRsa tmp path" << path; qDebug().noquote() << "EasyRsa args" << p.nativeArguments(); #else p.setProgram(getEasyRsaShPath()); p.setArguments(QStringList() << "init-pki"); #endif p.setWorkingDirectory(path); QObject::connect(&p, &QProcess::channelReadyRead, [&](){ qDebug().noquote() << "Init PKI" << p.readAll(); }); p.start(); p.waitForFinished(); if (p.exitCode() == 0) return ErrorCode::NoError; else return ErrorCode::EasyRsaError; } ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &clientId) { QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); #ifdef Q_OS_WIN p.setProcessEnvironment(prepareEnv()); p.setProgram("cmd.exe"); p.setNativeArguments(QString("/C \"ash.exe %1\"").arg(getEasyRsaShPath() + " gen-req " + clientId + " nopass")); qDebug().noquote() << "EasyRsa args" << p.nativeArguments(); #else p.setArguments(QStringList() << "gen-req" << clientId << "nopass"); p.setProgram(getEasyRsaShPath()); #endif p.setWorkingDirectory(path); QObject::connect(&p, &QProcess::channelReadyRead, [&](){ QString data = p.readAll(); qDebug().noquote() << data; if (data.contains("Common Name (eg: your user, host, or server name)")) { p.write("\n"); } }); p.start(); p.waitForFinished(); if (p.exitCode() == 0) return ErrorCode::NoError; else return ErrorCode::EasyRsaError; } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() { OpenVpnConfigurator::ConnectionData connData; connData.clientId = Utils::getRandomString(32); QTemporaryDir dir; // if (dir.isValid()) { // // dir.path() returns the unique directory path // } QString path = dir.path(); initPKI(path); ErrorCode errorCode = genReq(path, connData.clientId); Q_UNUSED(errorCode) QFile req(path + "/pki/reqs/" + connData.clientId + ".req"); req.open(QIODevice::ReadOnly); connData.request = req.readAll(); QFile key(path + "/pki/private/" + connData.clientId + ".key"); key.open(QIODevice::ReadOnly); connData.privKey = key.readAll(); // qDebug().noquote() << connData.request; // qDebug().noquote() << connData.privKey; return connData; } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, ErrorCode *errorCode) { OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); connData.host = credentials.hostName; if (connData.privKey.isEmpty() || connData.request.isEmpty()) { if (errorCode) *errorCode = ErrorCode::EasyRsaExecutableMissing; return connData; } QString reqFileName = QString("%1/%2.req"). arg(amnezia::protocols::openvpn::clientsDirPath). arg(connData.clientId); ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (e) { if (errorCode) *errorCode = e; return connData; } e = signCert(container, credentials, connData.clientId); if (e) { if (errorCode) *errorCode = e; return connData; } connData.caCert = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e); connData.clientCert = ServerController::getTextFileFromContainer(container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); if (e) { if (errorCode) *errorCode = e; return connData; } connData.taKey = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError; } return connData; } Settings &OpenVpnConfigurator::m_settings() { static Settings s; return s; } QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { QString config = ServerController::replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), ServerController::genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); if (errorCode && *errorCode) { return ""; } config.replace("$OPENVPN_CA_CERT", connData.caCert); config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert); config.replace("$OPENVPN_PRIV_KEY", connData.privKey); config.replace("$OPENVPN_TA_KEY", connData.taKey); #ifdef Q_OS_MAC config.replace("block-outside-dns", ""); #endif //qDebug().noquote() << config; return processConfigWithLocalSettings(config); } QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config) { // TODO replace DNS if it already set config.replace("$PRIMARY_DNS", m_settings().primaryDns()); config.replace("$SECONDARY_DNS", m_settings().secondaryDns()); if (m_settings().customRouting()) { config.replace("redirect-gateway def1 bypass-dhcp", ""); } #ifdef Q_OS_MAC config.replace("block-outside-dns", ""); #endif return config; } QString OpenVpnConfigurator::convertOpenSShKey(const QString &key) { QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); QTemporaryFile tmp; #ifdef QT_DEBUG tmp.setAutoRemove(false); #endif tmp.open(); tmp.write(key.toUtf8()); tmp.close(); // ssh-keygen -p -P "" -N "" -m pem -f id_ssh #ifdef Q_OS_WIN p.setProcessEnvironment(prepareEnv()); p.setProgram("cmd.exe"); p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName())); #else p.setProgram("ssh-keygen"); p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName()); #endif p.start(); p.waitForFinished(); qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll(); tmp.open(); return tmp.readAll(); } ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId) { QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && " "easyrsa import-req %2/%3.req %3\"") .arg(amnezia::containerToString(container)) .arg(amnezia::protocols::openvpn::clientsDirPath) .arg(clientId); QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && " "easyrsa sign-req client %2\"") .arg(amnezia::containerToString(container)) .arg(clientId); QStringList scriptList {script_import, script_sign}; QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container)); return ServerController::runScript(ServerController::sshParams(credentials), script); }