openvpnconfigurator
This commit is contained in:
parent
a2a5cafc5f
commit
05a89ed4c9
4 changed files with 475 additions and 0 deletions
159
client/core/openvpnconfigurator.cpp
Normal file
159
client/core/openvpnconfigurator.cpp
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
#include "openvpnconfigurator.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QString>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
QString OpenVpnConfigurator::getRandomString(int len)
|
||||||
|
{
|
||||||
|
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||||
|
|
||||||
|
QString randomString;
|
||||||
|
for(int i=0; i<len; ++i) {
|
||||||
|
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
|
||||||
|
QChar nextChar = possibleCharacters.at(index);
|
||||||
|
randomString.append(nextChar);
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenVpnConfigurator::getEasyRsaShPath()
|
||||||
|
{
|
||||||
|
QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa";
|
||||||
|
easyRsaShPath.replace(":", "");
|
||||||
|
easyRsaShPath.replace("\\", "/");
|
||||||
|
easyRsaShPath.prepend("/");
|
||||||
|
|
||||||
|
return easyRsaShPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcessEnvironment OpenVpnConfigurator::prepareEnv()
|
||||||
|
{
|
||||||
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||||
|
QString pathEnvVar = env.value("PATH");
|
||||||
|
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\bin;");
|
||||||
|
|
||||||
|
env.insert("PATH", pathEnvVar);
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenVpnConfigurator::initPKI(const QString &path)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QProcess p;
|
||||||
|
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
p.setProcessEnvironment(prepareEnv());
|
||||||
|
|
||||||
|
QString command = QString("sh.exe");
|
||||||
|
|
||||||
|
p.setNativeArguments(getEasyRsaShPath() + " init-pki");
|
||||||
|
|
||||||
|
p.setWorkingDirectory(path);
|
||||||
|
|
||||||
|
p.start(command);
|
||||||
|
p.waitForFinished();
|
||||||
|
qDebug().noquote() << p.readAll();
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenVpnConfigurator::genReq(const QString &path, const QString &clientId)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QProcess p;
|
||||||
|
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
p.setProcessEnvironment(prepareEnv());
|
||||||
|
|
||||||
|
QString command = QString("sh.exe");
|
||||||
|
|
||||||
|
p.setNativeArguments(getEasyRsaShPath() + " gen-req " + clientId + " nopass");
|
||||||
|
|
||||||
|
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(command);
|
||||||
|
p.waitForFinished();
|
||||||
|
// qDebug().noquote() << p.readAll();
|
||||||
|
|
||||||
|
return "";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||||
|
{
|
||||||
|
OpenVpnConfigurator::ConnectionData connData;
|
||||||
|
connData.clientId = getRandomString(32);
|
||||||
|
|
||||||
|
QTemporaryDir dir;
|
||||||
|
// if (dir.isValid()) {
|
||||||
|
// // dir.path() returns the unique directory path
|
||||||
|
// }
|
||||||
|
|
||||||
|
QString path = dir.path();
|
||||||
|
|
||||||
|
initPKI(path);
|
||||||
|
genReq(path, connData.clientId);
|
||||||
|
|
||||||
|
|
||||||
|
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 QSsh::SshConnectionParameters &sshParams)
|
||||||
|
{
|
||||||
|
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||||
|
connData.host = sshParams.host;
|
||||||
|
|
||||||
|
QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId);
|
||||||
|
ServerController::uploadTextFileToContainer(sshParams, connData.request, reqFileName);
|
||||||
|
|
||||||
|
ServerController::signCert(sshParams, connData.clientId);
|
||||||
|
|
||||||
|
connData.caCert = ServerController::getTextFileFromContainer(sshParams, QString("/opt/amneziavpn_data/pki/ca.crt"));
|
||||||
|
connData.clientCert = ServerController::getTextFileFromContainer(sshParams, QString("/opt/amneziavpn_data/pki/issued/%1.crt").arg(connData.clientId));
|
||||||
|
connData.taKey = ServerController::getTextFileFromContainer(sshParams, QString("/opt/amneziavpn_data/ta.key"));
|
||||||
|
|
||||||
|
|
||||||
|
return connData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenVpnConfigurator::genOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams)
|
||||||
|
{
|
||||||
|
QFile configTemplFile(":/server_scripts/template.ovpn");
|
||||||
|
configTemplFile.open(QIODevice::ReadOnly);
|
||||||
|
QString config = configTemplFile.readAll();
|
||||||
|
|
||||||
|
ConnectionData connData = prepareOpenVpnConfig(sshParams);
|
||||||
|
|
||||||
|
config.replace("$PROTO", "udp");
|
||||||
|
config.replace("$REMOTE_HOST", connData.host);
|
||||||
|
config.replace("$REMOTE_PORT", "1194");
|
||||||
|
config.replace("$CA_CERT", connData.caCert);
|
||||||
|
config.replace("$CLIENT_CERT", connData.clientCert);
|
||||||
|
config.replace("$PRIV_KEY", connData.privKey);
|
||||||
|
config.replace("$TA_KEY", connData.taKey);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
39
client/core/openvpnconfigurator.h
Normal file
39
client/core/openvpnconfigurator.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#ifndef OPENVPNCONFIGURATOR_H
|
||||||
|
#define OPENVPNCONFIGURATOR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProcessEnvironment>
|
||||||
|
#include "servercontroller.h"
|
||||||
|
|
||||||
|
|
||||||
|
class OpenVpnConfigurator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct ConnectionData {
|
||||||
|
QString clientId;
|
||||||
|
QString request; // certificate request
|
||||||
|
QString privKey; // client private key
|
||||||
|
QString clientCert; // client signed certificate
|
||||||
|
QString caCert; // server certificate
|
||||||
|
QString taKey; // tls-auth key
|
||||||
|
QString host; // host ip
|
||||||
|
};
|
||||||
|
|
||||||
|
static QString genOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString getRandomString(int len);
|
||||||
|
static QString getEasyRsaShPath();
|
||||||
|
|
||||||
|
static QProcessEnvironment prepareEnv();
|
||||||
|
static void initPKI(const QString &path);
|
||||||
|
static QString genReq(const QString &path, const QString &clientId);
|
||||||
|
|
||||||
|
static ConnectionData createCertRequest();
|
||||||
|
|
||||||
|
static ConnectionData prepareOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // OPENVPNCONFIGURATOR_H
|
245
client/core/servercontroller.cpp
Normal file
245
client/core/servercontroller.cpp
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
#include "servercontroller.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
//#include "sshclient.h"
|
||||||
|
//#include "sshprocess.h"
|
||||||
|
|
||||||
|
#include "sshconnectionmanager.h"
|
||||||
|
#include "sshremoteprocess.h"
|
||||||
|
|
||||||
|
using namespace QSsh;
|
||||||
|
|
||||||
|
bool ServerController::runScript(const SshConnectionParameters &sshParams, QString script)
|
||||||
|
{
|
||||||
|
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||||
|
|
||||||
|
SshConnection *client = connectToHost(sshParams);
|
||||||
|
|
||||||
|
script.replace("\r", "");
|
||||||
|
|
||||||
|
qDebug() << "Run script";
|
||||||
|
|
||||||
|
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
|
||||||
|
for (int i = 0; i < lines.count(); i++) {
|
||||||
|
const QString &line = lines.at(i);
|
||||||
|
if (line.startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug().noquote() << "EXEC" << line;
|
||||||
|
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(line.toUtf8());
|
||||||
|
|
||||||
|
if (!proc) {
|
||||||
|
qCritical() << "Failed to create SshRemoteProcess, breaking.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
|
||||||
|
qDebug() << "Command started";
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
|
||||||
|
qDebug() << "Remote process exited with status" << status;
|
||||||
|
wait.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||||
|
qDebug().noquote() << proc->readAllStandardOutput();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||||
|
qDebug().noquote() << proc->readAllStandardError();
|
||||||
|
});
|
||||||
|
|
||||||
|
proc->start();
|
||||||
|
if (i < lines.count() - 1) {
|
||||||
|
wait.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "ServerController::runScript finished";
|
||||||
|
|
||||||
|
// client->disconnectFromHost();
|
||||||
|
|
||||||
|
// client->deleteLater();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerController::uploadTextFileToContainer(const SshConnectionParameters &sshParams,
|
||||||
|
QString &file, const QString &path)
|
||||||
|
{
|
||||||
|
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||||
|
|
||||||
|
QString script = QString("docker exec -i amneziavpn sh -c \"echo \'%1\' > %2\"").
|
||||||
|
arg(file).arg(path);
|
||||||
|
|
||||||
|
qDebug().noquote() << script;
|
||||||
|
|
||||||
|
SshConnection *client = connectToHost(sshParams);
|
||||||
|
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
|
||||||
|
|
||||||
|
if (!proc) {
|
||||||
|
qCritical() << "Failed to create SshRemoteProcess, breaking.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
|
||||||
|
qDebug() << "Command started";
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
|
||||||
|
qDebug() << "Remote process exited with status" << status;
|
||||||
|
wait.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||||
|
qDebug().noquote() << proc->readAllStandardOutput();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||||
|
qDebug().noquote() << proc->readAllStandardError();
|
||||||
|
});
|
||||||
|
|
||||||
|
proc->start();
|
||||||
|
wait.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ServerController::getTextFileFromContainer(const SshConnectionParameters &sshParams, const QString &path)
|
||||||
|
{
|
||||||
|
QString script = QString("docker exec -i amneziavpn sh -c \"cat \'%1\'\"").
|
||||||
|
arg(path);
|
||||||
|
|
||||||
|
SshConnection *client = connectToHost(sshParams);
|
||||||
|
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
|
||||||
|
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int ){
|
||||||
|
wait.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
proc->start();
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
return proc->readAllStandardOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerController::signCert(const SshConnectionParameters &sshParams, QString clientId)
|
||||||
|
{
|
||||||
|
QString script_import = QString("docker exec -i amneziavpn bash -c \"cd /opt/amneziavpn_data && "
|
||||||
|
"easyrsa import-req /opt/amneziavpn_data/clients/%1.req %1 &>/dev/null\"")
|
||||||
|
.arg(clientId);
|
||||||
|
|
||||||
|
QString script_sign = QString("docker exec -i amneziavpn bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
|
||||||
|
"easyrsa sign-req client %1 &>/dev/null\"")
|
||||||
|
.arg(clientId);
|
||||||
|
|
||||||
|
QStringList script {script_import, script_sign};
|
||||||
|
|
||||||
|
return runScript(sshParams, script.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerController::removeServer(const SshConnectionParameters &sshParams, ServerController::ServerType sType)
|
||||||
|
{
|
||||||
|
QString scriptFileName;
|
||||||
|
|
||||||
|
if (sType == OpenVPN) {
|
||||||
|
scriptFileName = ":/server_scripts/remove_openvpn_server.sh";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString scriptData;
|
||||||
|
|
||||||
|
QFile file(scriptFileName);
|
||||||
|
if (! file.open(QIODevice::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptData = file.readAll();
|
||||||
|
if (scriptData.isEmpty()) return false;
|
||||||
|
|
||||||
|
return runScript(sshParams, scriptData);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerController::setupServer(const SshConnectionParameters &sshParams, ServerController::ServerType sType)
|
||||||
|
{
|
||||||
|
QString scriptFileName;
|
||||||
|
|
||||||
|
if (sType == OpenVPN) {
|
||||||
|
scriptFileName = ":/server_scripts/setup_openvpn_server.sh";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString scriptData;
|
||||||
|
|
||||||
|
QFile file(scriptFileName);
|
||||||
|
if (! file.open(QIODevice::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptData = file.readAll();
|
||||||
|
if (scriptData.isEmpty()) return false;
|
||||||
|
|
||||||
|
return runScript(sshParams, scriptData);
|
||||||
|
}
|
||||||
|
|
||||||
|
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)
|
||||||
|
{
|
||||||
|
SshConnection *client = acquireConnection(sshParams);
|
||||||
|
//QPointer<SshConnection> client = new SshConnection(serverInfo);
|
||||||
|
|
||||||
|
QEventLoop waitssh;
|
||||||
|
QObject::connect(client, &SshConnection::connected, &waitssh, [&]() {
|
||||||
|
qDebug() << "Server connected by ssh";
|
||||||
|
waitssh.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(client, &SshConnection::disconnected, &waitssh, [&]() {
|
||||||
|
qDebug() << "Server disconnected by ssh";
|
||||||
|
waitssh.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(client, &SshConnection::error, &waitssh, [&](QSsh::SshError error) {
|
||||||
|
qCritical() << "Ssh error:" << error << client->errorString();
|
||||||
|
waitssh.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// QObject::connect(client, &SshConnection::dataAvailable, [&](const QString &message) {
|
||||||
|
// qCritical() << "Ssh message:" << message;
|
||||||
|
// });
|
||||||
|
|
||||||
|
//qDebug() << "Connection state" << client->state();
|
||||||
|
|
||||||
|
if (client->state() == SshConnection::State::Unconnected) {
|
||||||
|
client->connectToHost();
|
||||||
|
waitssh.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// QObject::connect(&client, &SshClient::sshDataReceived, [&](){
|
||||||
|
// qDebug().noquote() << "Data received";
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
// if(client.sshState() != SshClient::SshState::Ready) {
|
||||||
|
// qCritical() << "Can't connect to server";
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// qDebug() << "SSh connection established";
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// QObject::connect(proc, &SshProcess::finished, &wait, &QEventLoop::quit);
|
||||||
|
// QObject::connect(proc, &SshProcess::failed, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
32
client/core/servercontroller.h
Normal file
32
client/core/servercontroller.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef SERVERCONTROLLER_H
|
||||||
|
#define SERVERCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include "sshconnection.h"
|
||||||
|
|
||||||
|
class ServerController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum ServerType {
|
||||||
|
OpenVPN,
|
||||||
|
ShadowSocks,
|
||||||
|
WireGuard
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool removeServer(const QSsh::SshConnectionParameters &sshParams, ServerType sType);
|
||||||
|
static bool setupServer(const QSsh::SshConnectionParameters &sshParams, ServerType sType);
|
||||||
|
|
||||||
|
static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
|
||||||
|
static bool runScript(const QSsh::SshConnectionParameters &sshParams, QString script);
|
||||||
|
|
||||||
|
static void uploadTextFileToContainer(const QSsh::SshConnectionParameters &sshParams, QString &file, const QString &path);
|
||||||
|
static QString getTextFileFromContainer(const QSsh::SshConnectionParameters &sshParams, const QString &path);
|
||||||
|
|
||||||
|
static bool signCert(const QSsh::SshConnectionParameters &sshParams, QString clientId);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SERVERCONTROLLER_H
|
Loading…
Add table
Add a link
Reference in a new issue