From fa151cd320c7906968420c7c82299f9b9a7c4fc2 Mon Sep 17 00:00:00 2001 From: pokamest Date: Mon, 4 Oct 2021 19:07:49 +0300 Subject: [PATCH] Ikev2 support --- client/client.pro | 4 + client/configurators/ikev2_configurator.cpp | 67 +++++ client/configurators/ikev2_configurator.h | 30 ++ client/configurators/openvpn_configurator.cpp | 9 +- client/configurators/vpn_configurator.cpp | 15 + client/configurators/vpn_configurator.h | 2 + .../configurators/wireguard_configurator.cpp | 2 +- client/containers/containers_defs.cpp | 23 +- client/containers/containers_defs.h | 1 + client/core/defs.h | 1 + client/core/scripts_registry.cpp | 2 + client/core/servercontroller.cpp | 84 ++++-- client/core/servercontroller.h | 8 +- client/protocols/ikev2_vpn_protocol.cpp | 57 ++++ client/protocols/ikev2_vpn_protocol.h | 32 +++ client/protocols/openvpnprotocol.cpp | 2 +- client/protocols/openvpnprotocol.h | 2 +- client/protocols/protocols_defs.h | 2 +- client/protocols/vpnprotocol.cpp | 20 ++ client/protocols/vpnprotocol.h | 5 + client/resources.qrc | 4 + client/server_scripts/ipsec/Dockerfile | 4 + .../ipsec/configure_container.sh | 257 ++++++++++++++++++ client/server_scripts/ipsec/run_container.sh | 7 + client/server_scripts/ipsec/start.sh | 0 client/server_scripts/wireguard/template.conf | 2 +- client/vpnconnection.cpp | 74 ++--- 27 files changed, 626 insertions(+), 90 deletions(-) create mode 100644 client/configurators/ikev2_configurator.cpp create mode 100644 client/configurators/ikev2_configurator.h create mode 100644 client/protocols/ikev2_vpn_protocol.cpp create mode 100644 client/protocols/ikev2_vpn_protocol.h create mode 100644 client/server_scripts/ipsec/Dockerfile create mode 100644 client/server_scripts/ipsec/configure_container.sh create mode 100644 client/server_scripts/ipsec/run_container.sh create mode 100644 client/server_scripts/ipsec/start.sh diff --git a/client/client.pro b/client/client.pro index 8448c11f..4762461d 100644 --- a/client/client.pro +++ b/client/client.pro @@ -19,6 +19,7 @@ include ("3rd/SortFilterProxyModel/SortFilterProxyModel.pri") HEADERS += \ ../ipc/ipc.h \ configurators/cloak_configurator.h \ + configurators/ikev2_configurator.h \ configurators/shadowsocks_configurator.h \ configurators/ssh_configurator.h \ configurators/vpn_configurator.h \ @@ -34,6 +35,7 @@ HEADERS += \ debug.h \ defines.h \ managementserver.h \ + protocols/ikev2_vpn_protocol.h \ protocols/openvpnovercloakprotocol.h \ protocols/protocols_defs.h \ protocols/shadowsocksvpnprotocol.h \ @@ -73,6 +75,7 @@ HEADERS += \ SOURCES += \ configurators/cloak_configurator.cpp \ + configurators/ikev2_configurator.cpp \ configurators/shadowsocks_configurator.cpp \ configurators/ssh_configurator.cpp \ configurators/vpn_configurator.cpp \ @@ -87,6 +90,7 @@ SOURCES += \ debug.cpp \ main.cpp \ managementserver.cpp \ + protocols/ikev2_vpn_protocol.cpp \ protocols/openvpnovercloakprotocol.cpp \ protocols/protocols_defs.cpp \ protocols/shadowsocksvpnprotocol.cpp \ diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp new file mode 100644 index 00000000..4c01cbdc --- /dev/null +++ b/client/configurators/ikev2_configurator.cpp @@ -0,0 +1,67 @@ +#include "ikev2_configurator.h" +#include +#include +#include +#include +#include +#include +#include + +#include "sftpdefs.h" + +#include "core/server_defs.h" +#include "containers/containers_defs.h" +#include "core/scripts_registry.h" +#include "utils.h" + +Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, + DockerContainer container, ErrorCode *errorCode) +{ + Ikev2Configurator::ConnectionData connData; + connData.host = credentials.hostName; + connData.clientId = Utils::getRandomString(16); + connData.password = Utils::getRandomString(16); + + QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12"; + + QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\ + "-S -c \"IKEv2 VPN CA\" -n \"%1\" "\ + "-s \"O=IKEv2 VPN,CN=%1\" "\ + "-k rsa -g 3072 -v 120 "\ + "-d sql:/etc/ipsec.d -t \",,\" "\ + "--keyUsage digitalSignature,keyEncipherment "\ + "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") + .arg(connData.clientId); + + ErrorCode e = ServerController::runContainerScript(credentials, container, scriptCreateCert); + + QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"") + .arg(connData.password) + .arg(connData.clientId) + .arg(certFileName); + e = ServerController::runContainerScript(credentials, container, scriptExportCert); + + connData.cert = ServerController::getTextFileFromContainer(container, credentials, certFileName, &e); + qDebug() << "Ikev2Configurator::ConnectionData cert size:" << connData.cert.size(); + + return connData; +} + +QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +{ + ConnectionData connData = prepareIkev2Config(credentials, container, errorCode); + if (errorCode && *errorCode) { + return ""; + } + + + QJsonObject config; + config[config_key::hostName] = connData.host; + config[config_key::userName] = connData.clientId; + config[config_key::cert] = QString(connData.cert.toBase64()); + config[config_key::password] = connData.password; + + return QJsonDocument(config).toJson(); +} + diff --git a/client/configurators/ikev2_configurator.h b/client/configurators/ikev2_configurator.h new file mode 100644 index 00000000..5257fc7f --- /dev/null +++ b/client/configurators/ikev2_configurator.h @@ -0,0 +1,30 @@ +#ifndef IKEV2_CONFIGURATOR_H +#define IKEV2_CONFIGURATOR_H + +#include +#include + +#include "core/defs.h" +#include "core/servercontroller.h" + +class Ikev2Configurator +{ +public: + + struct ConnectionData { + QByteArray cert; // p12 client cert + QString clientId; + QString password; // certificate password + QString host; // host ip + }; + + static QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + + +private: + static ConnectionData prepareIkev2Config(const ServerCredentials &credentials, + DockerContainer container, ErrorCode *errorCode = nullptr); +}; + +#endif // IKEV2_CONFIGURATOR_H diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 7ea9f901..5dac8cf0 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -248,10 +248,6 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia 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().routeMode() != Settings::VpnAllSites) { config.replace("redirect-gateway def1 bypass-dhcp", ""); } @@ -277,9 +273,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config) QString OpenVpnConfigurator::processConfigWithExportSettings(QString config) { - config.replace("$PRIMARY_DNS", m_settings().primaryDns()); - config.replace("$SECONDARY_DNS", m_settings().secondaryDns()); - if(!config.contains("redirect-gateway def1 bypass-dhcp")) { config.append("redirect-gateway def1 bypass-dhcp\n"); } @@ -308,5 +301,5 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, QStringList scriptList {script_import, script_sign}; QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container)); - return ServerController::runScript(ServerController::sshParams(credentials), script); + return ServerController::runScript(credentials, script); } diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index 245e4d6f..0b4d9170 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -3,6 +3,7 @@ #include "cloak_configurator.h" #include "shadowsocks_configurator.h" #include "wireguard_configurator.h" +#include "ikev2_configurator.h" #include #include @@ -10,6 +11,11 @@ #include "containers/containers_defs.h" +Settings &VpnConfigurator::m_settings() +{ + static Settings s; + return s; +} QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode) @@ -27,6 +33,9 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia case Protocol::WireGuard: return WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); + case Protocol::Ikev2: + return Ikev2Configurator::genIkev2Config(credentials, container, containerConfig, errorCode); + default: return ""; } @@ -34,6 +43,9 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia QString VpnConfigurator::processConfigWithLocalSettings(DockerContainer container, Protocol proto, QString config) { + config.replace("$PRIMARY_DNS", m_settings().primaryDns()); + config.replace("$SECONDARY_DNS", m_settings().secondaryDns()); + if (proto == Protocol::OpenVpn) { return OpenVpnConfigurator::processConfigWithLocalSettings(config); } @@ -42,6 +54,9 @@ QString VpnConfigurator::processConfigWithLocalSettings(DockerContainer containe QString VpnConfigurator::processConfigWithExportSettings(DockerContainer container, Protocol proto, QString config) { + config.replace("$PRIMARY_DNS", m_settings().primaryDns()); + config.replace("$SECONDARY_DNS", m_settings().secondaryDns()); + if (proto == Protocol::OpenVpn) { return OpenVpnConfigurator::processConfigWithExportSettings(config); } diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h index 32c030d7..930a6715 100644 --- a/client/configurators/vpn_configurator.h +++ b/client/configurators/vpn_configurator.h @@ -7,6 +7,7 @@ #include "settings.h" #include "core/servercontroller.h" +// Retrieve connection settings from server class VpnConfigurator { public: @@ -21,6 +22,7 @@ public: static void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, const QString &stdOut); + static Settings &m_settings(); }; #endif // VPN_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 6f180f03..c5c01d7c 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -130,7 +130,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return connData; } - e = ServerController::runScript(ServerController::sshParams(credentials), + e = ServerController::runScript(credentials, ServerController::replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'", ServerController::genVarsForScript(credentials, container))); diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 33e403b5..b56145bf 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -42,6 +42,9 @@ QVector ContainerProps::protocolsForContainer(amnezia::Docker case DockerContainer::Cloak: return { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak }; + case DockerContainer::Ipsec: + return { Protocol::Ikev2, Protocol::L2tp }; + case DockerContainer::Dns: return { }; @@ -69,6 +72,8 @@ QMap ContainerProps::containerHumanNames() {DockerContainer::ShadowSocks, "OpenVpn over ShadowSocks"}, {DockerContainer::Cloak, "OpenVpn over Cloak"}, {DockerContainer::WireGuard, "WireGuard"}, + {DockerContainer::Ipsec, QObject::tr("IPsec container")}, + {DockerContainer::TorWebSite, QObject::tr("Web site in TOR network")}, {DockerContainer::Dns, QObject::tr("DNS Service")}, {DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, @@ -84,6 +89,8 @@ QMap ContainerProps::containerDescriptions() {DockerContainer::Cloak, QObject::tr("Container with OpenVpn and ShadowSocks protocols " "configured with traffic masking by Cloak plugin")}, {DockerContainer::WireGuard, QObject::tr("WireGuard container")}, + {DockerContainer::Ipsec, QObject::tr("IPsec container")}, + {DockerContainer::TorWebSite, QObject::tr("Web site in TOR network")}, {DockerContainer::Dns, QObject::tr("DNS Service")}, {DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, @@ -99,15 +106,29 @@ amnezia::ServiceType ContainerProps::containerService(DockerContainer c) case DockerContainer::Cloak : return ServiceType::Vpn; case DockerContainer::ShadowSocks : return ServiceType::Vpn; case DockerContainer::WireGuard : return ServiceType::Vpn; + case DockerContainer::Ipsec : return ServiceType::Vpn; case DockerContainer::TorWebSite : return ServiceType::Other; case DockerContainer::Dns : return ServiceType::Other; case DockerContainer::FileShare : return ServiceType::Other; + case DockerContainer::Sftp : return ServiceType::Other; default: return ServiceType::Other; } } Protocol ContainerProps::defaultProtocol(DockerContainer c) { - return static_cast(c); + switch (c) { + case DockerContainer::None : return Protocol::Any; + case DockerContainer::OpenVpn : return Protocol::OpenVpn; + case DockerContainer::Cloak : return Protocol::Cloak; + case DockerContainer::ShadowSocks : return Protocol::ShadowSocks; + case DockerContainer::WireGuard : return Protocol::WireGuard; + case DockerContainer::Ipsec : return Protocol::Ikev2; + + case DockerContainer::TorWebSite : return Protocol::TorWebSite; + case DockerContainer::Dns : return Protocol::Dns; + case DockerContainer::FileShare : return Protocol::FileShare; + case DockerContainer::Sftp : return Protocol::Sftp; + } } diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 02ac5528..b27b61be 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -18,6 +18,7 @@ enum DockerContainer { ShadowSocks, Cloak, WireGuard, + Ipsec, //non-vpn TorWebSite, diff --git a/client/core/defs.h b/client/core/defs.h index 02d6cad6..99a7fc40 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -74,6 +74,7 @@ const char key_openvpn_config_path[] = "openvpn_config_path"; const char key_shadowsocks_config_data[] = "shadowsocks_config_data"; const char key_cloak_config_data[] = "cloak_config_data"; const char key_wireguard_config_data[] = "wireguard_config_data"; +const char key_ikev2_config_data[] = "ikev2_config_data"; } diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 080bc36c..7644f012 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -11,6 +11,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); + case DockerContainer::Ipsec: return QLatin1String("ipsec"); + case DockerContainer::TorWebSite: return QLatin1String("website_tor"); case DockerContainer::Dns: return QLatin1String("dns"); case DockerContainer::FileShare: return QLatin1String("file_share"); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 782c4fec..802bbb69 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -24,11 +24,11 @@ using namespace QSsh; -ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script, +ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script, const std::function)> &cbReadStdOut, const std::function)> &cbReadStdErr) { - SshConnection *client = connectToHost(sshParams); + SshConnection *client = connectToHost(sshParams(credentials)); if (client->state() == SshConnection::State::Connecting) { qDebug() << "ServerController::runScript aborted, connectToHost in progress"; return ErrorCode::SshTimeoutError; @@ -121,6 +121,26 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, return ErrorCode::NoError; } +ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, + DockerContainer container, QString script, + 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; + + QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName); + e = runScript(credentials, + replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + +// QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName); +// runScript(credentials, +// replaceVars(remover, genVarsForScript(credentials, container))); + + return e; +} + ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, QSsh::SftpOverwriteMode overwriteMode) @@ -136,20 +156,20 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, }; if (overwriteMode == QSsh::SftpOverwriteMode::SftpOverwriteExisting) { - e = runScript(sshParams(credentials), + e = runScript(credentials, replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), genVarsForScript(credentials, container)), cbReadStd, cbReadStd); if (e) return e; } else if (overwriteMode == QSsh::SftpOverwriteMode::SftpAppendToExisting) { - e = runScript(sshParams(credentials), + e = runScript(credentials, replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), genVarsForScript(credentials, container)), cbReadStd, cbReadStd); if (e) return e; - e = runScript(sshParams(credentials), + e = runScript(credentials, replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), genVarsForScript(credentials, container)), cbReadStd, cbReadStd); @@ -162,23 +182,23 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, return ErrorCode::ServerContainerMissingError; } - runScript(sshParams(credentials), + runScript(credentials, replaceVars(QString("sudo shred %1").arg(tmpFileName), genVarsForScript(credentials, container))); - runScript(sshParams(credentials), + runScript(credentials, replaceVars(QString("sudo rm %1").arg(tmpFileName), genVarsForScript(credentials, container))); return e; } -QString ServerController::getTextFileFromContainer(DockerContainer container, +QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode) { if (errorCode) *errorCode = ErrorCode::NoError; - QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\""). + QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\""). arg(ContainerProps::containerToString(container)).arg(path); qDebug().noquote() << "Copy file from container\n" << script; @@ -186,14 +206,14 @@ QString ServerController::getTextFileFromContainer(DockerContainer container, SshConnection *client = connectToHost(sshParams(credentials)); if (client->state() != SshConnection::State::Connected) { if (errorCode) *errorCode = fromSshConnectionErrorCode(client->errorState()); - return QString(); + return {}; } QSharedPointer proc = client->createRemoteProcess(script.toUtf8()); if (!proc) { qCritical() << "Failed to create SshRemoteProcess, breaking."; if (errorCode) *errorCode = ErrorCode::SshRemoteProcessCreationError; - return QString(); + return {}; } QEventLoop wait; @@ -221,7 +241,7 @@ QString ServerController::getTextFileFromContainer(DockerContainer container, } if (errorCode) *errorCode = ErrorCode::NoError; - return proc->readAllStandardOutput(); + return QByteArray::fromHex(proc->readAllStandardOutput()); } ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials) @@ -352,13 +372,13 @@ SshConnectionParameters ServerController::sshParams(const ServerCredentials &cre ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials) { - return runScript(sshParams(credentials), + return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); } ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container) { - return runScript(sshParams(credentials), + return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::remove_container), genVarsForScript(credentials, container))); } @@ -482,7 +502,7 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent stdOut += data + "\n"; }; - ErrorCode e = runScript(sshParams(credentials), + ErrorCode e = runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); @@ -495,7 +515,7 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) { // create folder on host - return runScript(sshParams(credentials), + return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container))); } @@ -515,7 +535,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden // stdOut += data + "\n"; // }; - return runScript(sshParams(credentials), + return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config))); } @@ -530,7 +550,7 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti stdOut += data + "\n"; }; - ErrorCode e = runScript(sshParams(credentials), + ErrorCode e = runScript(credentials, replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), genVarsForScript(credentials, container, config)), cbReadStdOut, cbReadStdErr); @@ -553,11 +573,12 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr }; - ErrorCode e = runScript(sshParams(credentials), + ErrorCode e = runContainerScript(credentials, container, replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), genVarsForScript(credentials, container, config)), cbReadStdOut, cbReadStdErr); + VpnConfigurator::updateContainerConfigAfterInstallation(container, config, stdOut); return e; @@ -576,7 +597,7 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred "/opt/amnezia/start.sh"); if (e) return e; - return runScript(sshParams(credentials), + return runScript(credentials, replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"", genVarsForScript(credentials, container, config))); } @@ -634,6 +655,25 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({{"$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) }}); + // IPsec vars + vars.append({{"$IPSEC_VPN_L2TP_NET", "192.168.42.0/24"}}); + vars.append({{"$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250"}}); + vars.append({{"$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1"}}); + + vars.append({{"$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24"}}); + vars.append({{"$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250"}}); + + vars.append({{"$IPSEC_VPN_SHA2_TRUNCBUG", "yes"}}); + + vars.append({{"$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes"}}); + vars.append({{"$IPSEC_VPN_DISABLE_IKEV2", "no"}}); + vars.append({{"$IPSEC_VPN_DISABLE_L2TP", "no"}}); + vars.append({{"$IPSEC_VPN_DISABLE_XAUTH", "no"}}); + + vars.append({{"$IPSEC_VPN_C2C_TRAFFIC", "no"}}); + + + // Sftp vars vars.append({{"$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Protocol::Sftp))) }}); vars.append({{"$SFTP_USER", sftpConfig.value(config_key::userName).toString() }}); @@ -661,7 +701,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential stdOut += data + "\n"; }; - ErrorCode e = runScript(sshParams(credentials), + ErrorCode e = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); if (errorCode) *errorCode = e; @@ -731,7 +771,7 @@ void ServerController::disconnectFromHost(const ServerCredentials &credentials) ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) { - return runScript(sshParams(credentials), + return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials))); } diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index 7116df41..30e86ff4 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -46,14 +46,18 @@ public: const ServerCredentials &credentials, const QString &file, const QString &path, QSsh::SftpOverwriteMode overwriteMode = QSsh::SftpOverwriteMode::SftpOverwriteExisting); - static QString getTextFileFromContainer(DockerContainer container, + static QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); static ErrorCode setupServerFirewall(const ServerCredentials &credentials); static QString replaceVars(const QString &script, const Vars &vars); - static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script, + static ErrorCode runScript(const ServerCredentials &credentials, QString script, + const std::function)> &cbReadStdOut = nullptr, + const std::function)> &cbReadStdErr = nullptr); + + static ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, const std::function)> &cbReadStdOut = nullptr, const std::function)> &cbReadStdErr = nullptr); diff --git a/client/protocols/ikev2_vpn_protocol.cpp b/client/protocols/ikev2_vpn_protocol.cpp new file mode 100644 index 00000000..df143cf5 --- /dev/null +++ b/client/protocols/ikev2_vpn_protocol.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "ikev2_vpn_protocol.h" +#include "utils.h" + +Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) : + VpnProtocol(configuration, parent) +{ + //m_configFile.setFileTemplate(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); + readIkev2Configuration(configuration); +} + +Ikev2Protocol::~Ikev2Protocol() +{ + qDebug() << "IpsecProtocol::~IpsecProtocol()"; + Ikev2Protocol::stop(); + QThread::msleep(200); +} + +void Ikev2Protocol::stop() +{ +#ifndef Q_OS_IOS + +#endif +} + +void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) +{ + m_config = configuration.value(config::key_ikev2_config_data).toObject(); +} + + + +ErrorCode Ikev2Protocol::start() +{ +#ifndef Q_OS_IOS + + QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()); + qDebug() << "Ikev2Protocol::start()" << cert; + + QTemporaryFile certFile; + certFile.open(); + certFile.write(cert); + certFile.close(); + + + return ErrorCode::NoError; + +#endif +} + diff --git a/client/protocols/ikev2_vpn_protocol.h b/client/protocols/ikev2_vpn_protocol.h new file mode 100644 index 00000000..8e256114 --- /dev/null +++ b/client/protocols/ikev2_vpn_protocol.h @@ -0,0 +1,32 @@ +#ifndef IPSEC_PROTOCOL_H +#define IPSEC_PROTOCOL_H + +#include +#include +#include +#include +#include + +#include "vpnprotocol.h" +#include "core/ipcclient.h" + +class Ikev2Protocol : public VpnProtocol +{ + Q_OBJECT + +public: + explicit Ikev2Protocol(const QJsonObject& configuration, QObject* parent = nullptr); + virtual ~Ikev2Protocol() override; + + ErrorCode start() override; + void stop() override; + +private: + void readIkev2Configuration(const QJsonObject &configuration); + + +private: + QJsonObject m_config; +}; + +#endif // IPSEC_PROTOCOL_H diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index be7e84e6..cb8c7807 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -57,7 +57,7 @@ void OpenVpnProtocol::stop() } } -ErrorCode OpenVpnProtocol::checkAndSetupTapDriver() +ErrorCode OpenVpnProtocol::prepare() { if (!IpcClient::Interface()) { return ErrorCode::AmneziaServiceConnectionFailed; diff --git a/client/protocols/openvpnprotocol.h b/client/protocols/openvpnprotocol.h index 854c2574..5fbddd67 100644 --- a/client/protocols/openvpnprotocol.h +++ b/client/protocols/openvpnprotocol.h @@ -21,7 +21,7 @@ public: ErrorCode start() override; void stop() override; - ErrorCode checkAndSetupTapDriver(); + ErrorCode prepare() override; static QString defaultConfigFileName(); static QString defaultConfigPath(); diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 98f16f3f..770fed26 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -16,6 +16,7 @@ constexpr char port[] = "port"; constexpr char local_port[] = "local_port"; constexpr char description[] = "description"; +constexpr char cert[] = "cert"; constexpr char containers[] = "containers"; @@ -23,7 +24,6 @@ constexpr char container[] = "container"; constexpr char defaultContainer[] = "defaultContainer"; constexpr char protocols[] = "protocols"; -//constexpr char protocol[] = "protocol"; constexpr char remote[] = "remote"; constexpr char transport_proto[] = "transport_proto"; diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 2a6cac4b..8a5a78d6 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -4,6 +4,13 @@ #include "vpnprotocol.h" #include "core/errorstrings.h" +#include "openvpnprotocol.h" +#include "shadowsocksvpnprotocol.h" +#include "openvpnovercloakprotocol.h" +#include "wireguardprotocol.h" +#include "ikev2_vpn_protocol.h" + + VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) : QObject(parent), m_connectionState(ConnectionState::Unknown), @@ -88,6 +95,19 @@ QString VpnProtocol::vpnGateway() const return m_vpnGateway; } +VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& configuration) +{ + switch (container) { + case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration); + case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); + case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); + case DockerContainer::WireGuard: return new WireguardProtocol(configuration); + case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); + + default: return nullptr; + } +} + QString VpnProtocol::routeGateway() const { return m_routeGateway; diff --git a/client/protocols/vpnprotocol.h b/client/protocols/vpnprotocol.h index 59c1278d..c6bfc3b2 100644 --- a/client/protocols/vpnprotocol.h +++ b/client/protocols/vpnprotocol.h @@ -6,6 +6,8 @@ #include #include "core/defs.h" +#include "containers/containers_defs.h" + using namespace amnezia; class QTimer; @@ -23,6 +25,7 @@ public: static QString textConnectionState(ConnectionState connectionState); + virtual ErrorCode prepare() { return ErrorCode::NoError; } virtual bool isConnected() const; virtual bool isDisconnected() const; @@ -37,6 +40,8 @@ public: QString routeGateway() const; QString vpnGateway() const; + static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration); + signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void connectionStateChanged(VpnProtocol::ConnectionState state); diff --git a/client/resources.qrc b/client/resources.qrc index 202b2060..e16f82c6 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -122,5 +122,9 @@ server_scripts/sftp/run_container.sh ui/qml/Pages/Protocols/PageProtoSftp.qml ui/qml/Pages/Protocols/PageProtoTorWebSite.qml + server_scripts/ipsec/configure_container.sh + server_scripts/ipsec/Dockerfile + server_scripts/ipsec/run_container.sh + server_scripts/ipsec/start.sh diff --git a/client/server_scripts/ipsec/Dockerfile b/client/server_scripts/ipsec/Dockerfile new file mode 100644 index 00000000..1fcecdad --- /dev/null +++ b/client/server_scripts/ipsec/Dockerfile @@ -0,0 +1,4 @@ +FROM amneziavpn/ipsec-server:latest + +RUN mkdir -p /opt/amnezia +LABEL maintainer="AmneziaVPN" diff --git a/client/server_scripts/ipsec/configure_container.sh b/client/server_scripts/ipsec/configure_container.sh new file mode 100644 index 00000000..0d22a7da --- /dev/null +++ b/client/server_scripts/ipsec/configure_container.sh @@ -0,0 +1,257 @@ +#!/bin/bash + +export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +if [ ! -e /dev/ppp ]; then +cat <<'EOF' + +Warning: /dev/ppp is missing, and IPsec/L2TP mode may not work. Please use + IKEv2 (https://git.io/ikev2docker) or IPsec/XAuth mode to connect. +EOF +fi + +NET_IFACE=$(route 2>/dev/null | grep -m 1 '^default' | grep -o '[^ ]*$') +[ -z "$NET_IFACE" ] && NET_IFACE=$(ip -4 route list 0/0 2>/dev/null | grep -m 1 -Po '(?<=dev )(\S+)') +[ -z "$NET_IFACE" ] && NET_IFACE=eth0 + + +mkdir -p /opt/src +mkdir -p /opt/amnezia/ikev2/clients + + +# Create IPsec config +cat > /etc/ipsec.conf <> /etc/ipsec.conf <<'EOF' +conn l2tp-psk + auto=add + leftprotoport=17/1701 + rightprotoport=17/%any + type=transport + also=shared + +EOF +fi + +if [ "$IPSEC_VPN_DISABLE_XAUTH" != "yes" ]; then +cat >> /etc/ipsec.conf <> /etc/ipsec.conf <<'EOF' +include /etc/ipsec.d/*.conf +EOF + +if uname -r | grep -qi 'coreos'; then + sed -i '/phase2alg/s/,aes256-sha2_512//' /etc/ipsec.conf +fi + +if grep -qs ike-frag /etc/ipsec.d/ikev2.conf; then + sed -i 's/^[[:space:]]\+ike-frag=/ fragmentation=/' /etc/ipsec.d/ikev2.conf +fi + + +# Create xl2tpd config +cat > /etc/xl2tpd/xl2tpd.conf < /etc/ppp/options.xl2tpd </dev/null +$syt kernel.msgmax=65536 2>/dev/null +$syt net.ipv4.ip_forward=1 2>/dev/null +$syt net.ipv4.conf.all.accept_redirects=0 2>/dev/null +$syt net.ipv4.conf.all.send_redirects=0 2>/dev/null +$syt net.ipv4.conf.all.rp_filter=0 2>/dev/null +$syt net.ipv4.conf.default.accept_redirects=0 2>/dev/null +$syt net.ipv4.conf.default.send_redirects=0 2>/dev/null +$syt net.ipv4.conf.default.rp_filter=0 2>/dev/null +$syt "net.ipv4.conf.$NET_IFACE.send_redirects=0" 2>/dev/null +$syt "net.ipv4.conf.$NET_IFACE.rp_filter=0" 2>/dev/null + +# Create IPTables rules +ipi='iptables -I INPUT' +ipf='iptables -I FORWARD' +ipp='iptables -t nat -I POSTROUTING' +res='RELATED,ESTABLISHED' +if ! iptables -t nat -C POSTROUTING -s "$IPSEC_VPN_L2TP_NET" -o "$NET_IFACE" -j MASQUERADE 2>/dev/null; then + $ipi 1 -p udp --dport 1701 -m policy --dir in --pol none -j DROP + $ipi 2 -m conntrack --ctstate INVALID -j DROP + $ipi 3 -m conntrack --ctstate "$res" -j ACCEPT + $ipi 4 -p udp -m multiport --dports 500,4500 -j ACCEPT + $ipi 5 -p udp --dport 1701 -m policy --dir in --pol ipsec -j ACCEPT + $ipi 6 -p udp --dport 1701 -j DROP + $ipf 1 -m conntrack --ctstate INVALID -j DROP + $ipf 2 -i "$NET_IFACE" -o ppp+ -m conntrack --ctstate "$res" -j ACCEPT + $ipf 3 -i ppp+ -o "$NET_IFACE" -j ACCEPT + $ipf 4 -i ppp+ -o ppp+ -j ACCEPT + $ipf 5 -i "$NET_IFACE" -d "$IPSEC_VPN_XAUTH_NET" -m conntrack --ctstate "$res" -j ACCEPT + $ipf 6 -s "$IPSEC_VPN_XAUTH_NET" -o "$NET_IFACE" -j ACCEPT + $ipf 7 -s "$IPSEC_VPN_XAUTH_NET" -o ppp+ -j ACCEPT + + if [ "$IPSEC_VPN_VPN_ANDROID_MTU_FIX" = "yes" ]; then + # Client-to-client traffic is allowed by default. To *disallow* such traffic, + # uncomment below and restart the Docker container. + $ipf 2 -i ppp+ -o ppp+ -s "$IPSEC_VPN_L2TP_NET" -d "$IPSEC_VPN_L2TP_NET" -j DROP + $ipf 3 -s "$IPSEC_VPN_XAUTH_NET" -d "$IPSEC_VPN_XAUTH_NET" -j DROP + $ipf 4 -i ppp+ -d "$IPSEC_VPN_XAUTH_NET" -j DROP + $ipf 5 -s "$IPSEC_VPN_XAUTH_NET" -o ppp+ -j DROP + fi + + iptables -A FORWARD -j DROP + $ipp -s "$IPSEC_VPN_XAUTH_NET" -o "$NET_IFACE" -m policy --dir out --pol none -j MASQUERADE + $ipp -s "$IPSEC_VPN_L2TP_NET" -o "$NET_IFACE" -j MASQUERADE +fi + + +if [ "$IPSEC_VPN_VPN_ANDROID_MTU_FIX" = "yes" ]; then + echo "Applying fix for Android MTU/MSS issues..." + iptables -t mangle -A FORWARD -m policy --pol ipsec --dir in \ + -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 \ + -j TCPMSS --set-mss 1360 + iptables -t mangle -A FORWARD -m policy --pol ipsec --dir out \ + -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 \ + -j TCPMSS --set-mss 1360 + +fi + +# Update file attributes +touch /etc/ipsec.secrets /etc/ppp/chap-secrets /etc/ipsec.d/passwd +chmod 600 /etc/ipsec.secrets /etc/ppp/chap-secrets /etc/ipsec.d/passwd + + +echo +echo "Starting IPsec service..." +mkdir -p /run/pluto /var/run/pluto +rm -f /run/pluto/pluto.pid /var/run/pluto/pluto.pid + +ipsec initnss >/dev/null +ipsec pluto --config /etc/ipsec.conf + + +# Start xl2tpd +mkdir -p /var/run/xl2tpd +rm -f /var/run/xl2tpd.pid +/usr/sbin/xl2tpd -c /etc/xl2tpd/xl2tpd.conf + + +################# IKEV2 ################## +if [ "$IPSEC_VPN_DISABLE_IKEV2" != "yes" ]; then +printf "y\n\nN\n" | certutil -z <(head -c 1024 /dev/urandom) \ + -S -x -n "IKEv2 VPN CA" \ + -s "O=IKEv2 VPN,CN=IKEv2 VPN CA" \ + -k rsa -g 3072 -v 120 \ + -d sql:/etc/ipsec.d -t "CT,," -2 + +certutil -z <(head -c 1024 /dev/urandom) \ + -S -c "IKEv2 VPN CA" -n "$SERVER_IP_ADDRESS" \ + -s "O=IKEv2 VPN,CN=$SERVER_IP_ADDRESS" \ + -k rsa -g 3072 -v 120 \ + -d sql:/etc/ipsec.d -t ",," \ + --keyUsage digitalSignature,keyEncipherment \ + --extKeyUsage serverAuth \ + --extSAN "ip:$SERVER_IP_ADDRESS,dns:$SERVER_IP_ADDRESS" + +cat > /etc/ipsec.d/ikev2.conf < VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig) { QMap configs; - for (Protocol proto: { Protocol::OpenVpn, - Protocol::ShadowSocks, - Protocol::Cloak, - Protocol::WireGuard}) { + for (Protocol proto: ProtocolProps::allProtocols()) { QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString(); @@ -242,6 +239,13 @@ ErrorCode VpnConnection::createVpnConfiguration(int serverIndex, m_vpnConfiguration.insert(config::key_wireguard_config_data, wgConfigData); } + if (container == DockerContainer::Ipsec) { + QString ikev2ConfigData = createVpnConfigurationForProto( + serverIndex, credentials, container, containerConfig, Protocol::Ikev2, &errorCode); + + m_vpnConfiguration.insert(config::key_ikev2_config_data, ikev2ConfigData); + } + //qDebug().noquote() << "VPN config" << QJsonDocument(m_vpnConfiguration).toJson(); return ErrorCode::NoError; } @@ -261,63 +265,29 @@ ErrorCode VpnConnection::connectToVpn(int serverIndex, m_vpnProtocol.reset(); } - if (container == DockerContainer::None || container == DockerContainer::OpenVpn) { - ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpn, containerConfig); - if (e) { - emit connectionStateChanged(VpnProtocol::Error); - return e; - } - - m_vpnProtocol.reset(new OpenVpnProtocol(m_vpnConfiguration)); - e = static_cast(m_vpnProtocol.data())->checkAndSetupTapDriver(); - if (e) { - emit connectionStateChanged(VpnProtocol::Error); - return e; - } + ErrorCode e = createVpnConfiguration(serverIndex, credentials, container, containerConfig); + if (e) { + emit connectionStateChanged(VpnProtocol::Error); + return e; } - else if (container == DockerContainer::ShadowSocks) { - ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::ShadowSocks, containerConfig); - if (e) { - emit connectionStateChanged(VpnProtocol::Error); - return e; - } - m_vpnProtocol.reset(new ShadowSocksVpnProtocol(m_vpnConfiguration)); - e = static_cast(m_vpnProtocol.data())->checkAndSetupTapDriver(); - if (e) { - emit connectionStateChanged(VpnProtocol::Error); - return e; - } + +#ifndef Q_OS_ANDROID + + m_vpnProtocol.reset(VpnProtocol::factory(container, containerConfig)); + if (!m_vpnProtocol) { + return ErrorCode::InternalError; } - else if (container == DockerContainer::Cloak) { - ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::Cloak, containerConfig); - if (e) { - emit connectionStateChanged(VpnProtocol::Error); - return e; - } - m_vpnProtocol.reset(new OpenVpnOverCloakProtocol(m_vpnConfiguration)); - e = static_cast(m_vpnProtocol.data())->checkAndSetupTapDriver(); - if (e) { - emit connectionStateChanged(VpnProtocol::Error); - return e; - } - } - else if (container == DockerContainer::WireGuard) { - ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::WireGuard, containerConfig); - if (e) { - emit connectionStateChanged(VpnProtocol::Error); - return e; - } + m_vpnProtocol->prepare(); -#ifdef Q_OS_ANDROID + +#else AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(Protocol::WireGuard, m_vpnConfiguration); androidVpnProtocol->initialize(); m_vpnProtocol.reset(androidVpnProtocol); -#else - m_vpnProtocol.reset(new WireguardProtocol(m_vpnConfiguration)); #endif - } + connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState)));