From cac4a1491bfe3d26595282a580e328bfee07351c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 13 Aug 2018 17:49:49 +0200 Subject: [PATCH 1/8] Start authenticator work --- README.md | 32 +++++ .../authentication/awsauthenticator.cpp | 8 ++ .../authentication/awsauthenticator.h | 5 +- .../authentication/sigv4utils.cpp | 123 +++++++++++++++++ .../authentication/sigv4utils.h | 27 ++++ libnymea-remoteproxy/libnymea-remoteproxy.pro | 12 +- libnymea-remoteproxy/proxyconfiguration.cpp | 124 +++++++++++++++++- libnymea-remoteproxy/proxyconfiguration.h | 46 ++++++- nymea-remoteproxy.pri | 3 + server/main.cpp | 14 +- 10 files changed, 376 insertions(+), 18 deletions(-) create mode 100644 libnymea-remoteproxy/authentication/sigv4utils.cpp create mode 100644 libnymea-remoteproxy/authentication/sigv4utils.h diff --git a/README.md b/README.md index d0e1af6..aef9bb0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,38 @@ If you want to start the proxy server from the build directory, you need to expo $ ./server/nymea-remoteproxy -c ../nymea-remoteproxy/tests/test-certificate.crt -k ../nymea-remoteproxy/tests/test-certificate.key +## AWS SDK + +Get the latest source code and build dependecies + + $ apt update + $ apt install git build-essential cmake libcurl4-openssl-dev libssl-dev uuid-dev zlib1g-dev libpulse-dev + + $ git clone https://github.com/aws/aws-sdk-cpp.git + +Create the build and install folder + + $ cd aws-sdk-cpp + $ mkdir -p build/install + $ cd build + + $ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_ONLY="lambda" -DCMAKE_INSTALL_PREFIX=$(pwd)/install ../ + $ make -j$(nproc) + +Install build output into install directory + + $ make install + +#### Building debian package + + $ git clone https://github.com/aws/aws-sdk-cpp.git + $ cd aws-sdk-cpp + + $ git clone git@gitlab.guh.io:cloud/aws-sdk-cpp-debian.git debian + + $ crossbuilder + + # Install diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.cpp b/libnymea-remoteproxy/authentication/awsauthenticator.cpp index 5b601a7..cf95729 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.cpp +++ b/libnymea-remoteproxy/authentication/awsauthenticator.cpp @@ -10,6 +10,11 @@ AwsAuthenticator::AwsAuthenticator(QObject *parent) : } +AwsAuthenticator::~AwsAuthenticator() +{ + qCDebug(dcAuthenticator()) << "Shutting down" << name(); +} + QString AwsAuthenticator::name() const { return "AWS authenticator"; @@ -19,6 +24,9 @@ AuthenticationReply *AwsAuthenticator::authenticate(ProxyClient *proxyClient) { qCDebug(dcAuthenticator()) << name() << "Start authenticating" << proxyClient << "using token" << proxyClient->token(); AuthenticationReply *reply = createAuthenticationReply(proxyClient, this); + + // TODO: start authentication request + return reply; } diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.h b/libnymea-remoteproxy/authentication/awsauthenticator.h index 06da9ee..f5f921f 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.h +++ b/libnymea-remoteproxy/authentication/awsauthenticator.h @@ -13,10 +13,13 @@ class AwsAuthenticator : public Authenticator Q_OBJECT public: explicit AwsAuthenticator(QObject *parent = nullptr); - ~AwsAuthenticator() override = default; + ~AwsAuthenticator() override; QString name() const override; +private: + + public slots: AuthenticationReply *authenticate(ProxyClient *proxyClient) override; diff --git a/libnymea-remoteproxy/authentication/sigv4utils.cpp b/libnymea-remoteproxy/authentication/sigv4utils.cpp new file mode 100644 index 0000000..147ce9c --- /dev/null +++ b/libnymea-remoteproxy/authentication/sigv4utils.cpp @@ -0,0 +1,123 @@ +#include "sigv4utils.h" + +#include +#include +#include +#include +#include +#include + +SigV4Utils::SigV4Utils() +{ + +} + +QByteArray SigV4Utils::getCurrentDateTime() +{ + return QDateTime::currentDateTime().toUTC().toString("yyyyMMddThhmmssZ").toUtf8(); +} + +QByteArray SigV4Utils::getCanonicalQueryString(const QNetworkRequest &request, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray ®ion, const QByteArray &service, const QByteArray &payload) +{ + QByteArray algorithm = "AWS4-HMAC-SHA256"; + QByteArray dateTime = getCurrentDateTime(); + QByteArray credentialScope = getCredentialScope(algorithm, dateTime, region, service); + + QByteArray canonicalQueryString; + canonicalQueryString += "X-Amz-Algorithm=AWS4-HMAC-SHA256"; + canonicalQueryString += "&X-Amz-Credential=" + QByteArray(accessKeyId + '/' + credentialScope).toPercentEncoding(); + canonicalQueryString += "&X-Amz-Date=" + dateTime; + if (request.rawHeaderList().count() > 0){ + canonicalQueryString += "&X-Amz-SignedHeaders=" + request.rawHeaderList().join(';').toLower(); + } + + QByteArray canonicalRequest = getCanonicalRequest(QNetworkAccessManager::GetOperation, request, payload); + + QByteArray stringToSign = getStringToSign(canonicalRequest, dateTime, region, service); + QByteArray signature = getSignature(stringToSign, secretAccessKey, dateTime, region, service); + + canonicalQueryString += "&X-Amz-Signature=" + signature; + + if (!sessionToken.isEmpty()) { + canonicalQueryString += "&X-Amz-Security-Token=" + sessionToken.toPercentEncoding(); + } + + return canonicalQueryString; +} + +QByteArray SigV4Utils::getSignatureKey(const QByteArray &key, const QByteArray &date, const QByteArray ®ion, const QByteArray &service) +{ + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; + return QMessageAuthenticationCode::hash("aws4_request", + QMessageAuthenticationCode::hash(service, + QMessageAuthenticationCode::hash(region, + QMessageAuthenticationCode::hash(date, "AWS4"+key, + hashAlgorithm), hashAlgorithm), hashAlgorithm), hashAlgorithm); +} + +QByteArray SigV4Utils::getCanonicalRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, const QByteArray &payload) +{ + QByteArray canonicalRequest; + + QByteArray method; + switch (operation) { + case QNetworkAccessManager::GetOperation: + method = "GET"; + break; + case QNetworkAccessManager::PostOperation: + method = "POST"; + break; + default: + Q_ASSERT_X(false, "Network operation not implemented", "SigV4Utils"); + } + QByteArray uri = request.url().path().toUtf8(); + QUrlQuery query(request.url()); + QList > queryItems = query.queryItems(); + QStringList queryItemStrings; + for (int i = 0; i < queryItems.count(); i++) { + QPair queryItem = queryItems.at(i); + queryItemStrings.append(queryItem.first + '=' + queryItem.second); + } + queryItemStrings.sort(Qt::CaseInsensitive); + + QByteArray canonicalQueryString = queryItemStrings.join('&').toUtf8(); + + QByteArray canonicalHeaders; + foreach(const QByteArray &headerName, request.rawHeaderList()) { + canonicalHeaders += headerName.toLower() + ':' + request.rawHeader(headerName) + '\n'; + } + + QByteArray payloadHash = QCryptographicHash::hash(payload, QCryptographicHash::Sha256).toHex(); + + canonicalRequest = method + '\n' + uri + '\n' + canonicalQueryString + '\n' + canonicalHeaders + '\n' + request.rawHeaderList().join(';').toLower() + '\n' + payloadHash; + return canonicalRequest; +} + +QByteArray SigV4Utils::getCredentialScope(const QByteArray &algorithm, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service) +{ + Q_UNUSED(algorithm) + QByteArray credentialScope = dateTime.left(8) + '/' + region + '/' + service + "/aws4_request"; + return credentialScope; +} + +QByteArray SigV4Utils::getStringToSign(const QByteArray &canonicalRequest, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service) +{ + QByteArray algorithm = "AWS4-HMAC-SHA256"; + QByteArray credentialScope = getCredentialScope(algorithm, dateTime, region, service); + + QByteArray stringToSign = algorithm + '\n' + dateTime + '\n' + credentialScope + '\n' + QCryptographicHash::hash(canonicalRequest, QCryptographicHash::Sha256).toHex(); + return stringToSign; +} + +QByteArray SigV4Utils::getSignature(const QByteArray &stringToSign, const QByteArray &secretAccessKey, const QByteArray &dateTime, const QString ®ion, const QString &service) +{ + QByteArray signingKey = getSignatureKey(secretAccessKey, dateTime.left(8), region.toUtf8(), service.toUtf8()); + QByteArray signature = QMessageAuthenticationCode::hash(stringToSign, signingKey, QCryptographicHash::Sha256).toHex(); + return signature; +} + +QByteArray SigV4Utils::getAuthorizationHeader(const QByteArray &accessKeyId, const QByteArray &dateTime, const QString ®ion, const QString &service, const QNetworkRequest &request, const QByteArray &signature) +{ + QByteArray authHeader = "AWS4-HMAC-SHA256 Credential=" + accessKeyId + '/' + dateTime.left(8) + '/' + region.toUtf8() + '/' + service.toUtf8() + '/' + "aws4_request, SignedHeaders=" + request.rawHeaderList().join(';').toLower() + ", Signature=" + signature; + return authHeader; +} diff --git a/libnymea-remoteproxy/authentication/sigv4utils.h b/libnymea-remoteproxy/authentication/sigv4utils.h new file mode 100644 index 0000000..4861869 --- /dev/null +++ b/libnymea-remoteproxy/authentication/sigv4utils.h @@ -0,0 +1,27 @@ +#ifndef SIGV4UTILS_H +#define SIGV4UTILS_H + +#include +#include +#include + +class SigV4Utils +{ +public: + SigV4Utils(); + + static QByteArray getCurrentDateTime(); + + + static QByteArray getCanonicalQueryString(const QNetworkRequest &request, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray ®ion, const QByteArray &service, const QByteArray &payload); + static QByteArray getCanonicalRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, const QByteArray &payload); + static QByteArray getCanonicalHeaders(const QNetworkRequest &request); + static QByteArray getCredentialScope(const QByteArray &algorithm, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service); + static QByteArray getStringToSign(const QByteArray &canonicalRequest, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service); + static QByteArray getSignatureKey(const QByteArray &key, const QByteArray &date, const QByteArray ®ion, const QByteArray &service); + static QByteArray getSignature(const QByteArray &stringToSign, const QByteArray &secretAccessKey, const QByteArray &dateTime, const QString ®ion, const QString &service); + static QByteArray getAuthorizationHeader(const QByteArray &accessKeyId, const QByteArray &dateTime, const QString ®ion, const QString &service, const QNetworkRequest &request, const QByteArray &signature); + +}; + +#endif // SIGV4UTILS_H diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index ec62305..e73399c 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -3,6 +3,12 @@ include(../nymea-remoteproxy.pri) TEMPLATE = lib TARGET = nymea-remoteproxy +# -L/home/timon/guh/development/cloud/aws-sdk-cpp/build/install/lib +# -laws-cpp-sdk-access-management \ +# -laws-cpp-sdk-cognito-identity \ +# -laws-cpp-sdk-iam \ +# -laws-cpp-sdk-kinesis\ + HEADERS += \ engine.h \ loggingcategories.h \ @@ -19,7 +25,8 @@ HEADERS += \ authentication/awsauthenticator.h \ authentication/authenticationreply.h \ proxyconfiguration.h \ - tunnelconnection.h + tunnelconnection.h \ + authentication/sigv4utils.h SOURCES += \ engine.cpp \ @@ -37,7 +44,8 @@ SOURCES += \ authentication/awsauthenticator.cpp \ authentication/authenticationreply.cpp \ proxyconfiguration.cpp \ - tunnelconnection.cpp + tunnelconnection.cpp \ + authentication/sigv4utils.cpp # install header file with relative subdirectory diff --git a/libnymea-remoteproxy/proxyconfiguration.cpp b/libnymea-remoteproxy/proxyconfiguration.cpp index 84cfc55..e91665c 100644 --- a/libnymea-remoteproxy/proxyconfiguration.cpp +++ b/libnymea-remoteproxy/proxyconfiguration.cpp @@ -1,10 +1,132 @@ +#include "loggingcategories.h" #include "proxyconfiguration.h" +#include + namespace remoteproxy { -ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent) +ProxyConfiguration::ProxyConfiguration(QObject *parent) : + QObject(parent) { } +bool ProxyConfiguration::loadConfiguration(const QString &fileName) +{ + QFileInfo fileInfo(fileName); + + if (!fileInfo.exists()) { + qCWarning(dcApplication()) << "Could not find configuration file" << fileName; + return false; + } + + if (!fileInfo.isReadable()) { + qCWarning(dcApplication()) << "Cannot read configuration file" << fileName; + return false; + } + + QSettings settings(fileName, QSettings::IniFormat); + + settings.beginGroup("General"); + setWriteLogFile(settings.value("writeLogs", false).toBool()); + setLogFileName(settings.value("logFile", "/var/log/nymea-remoteproxy.log").toString()); + setSslCertificateFileName(settings.value("certificate", "/etc/ssl/certs/ssl-cert-snakeoil.pem").toString()); + setSslCertificateKeyFileName(settings.value("certificateKey", "/etc/ssl/private/ssl-cert-snakeoil.key").toString()); + settings.endGroup(); + + settings.beginGroup("WebSocketServer"); + setWebSocketServerHost(QHostAddress(settings.value("host", "127.0.0.1").toString())); + setWebSocketServerPort(static_cast(settings.value("port", 1212).toInt())); + settings.endGroup(); + + settings.beginGroup("TcpServer"); + setWebSocketServerHost(QHostAddress(settings.value("host", "127.0.0.1").toString())); + setWebSocketServerPort(static_cast(settings.value("port", 1213).toInt())); + settings.endGroup(); + + return true; +} + +bool ProxyConfiguration::writeLogFile() const +{ + return m_writeLogFile; +} + +void ProxyConfiguration::setWriteLogFile(bool enabled) +{ + m_writeLogFile = enabled; +} + +QString ProxyConfiguration::logFileName() const +{ + return m_logFileName; +} + +void ProxyConfiguration::setLogFileName(const QString &logFileName) +{ + m_logFileName = logFileName; +} + +QString ProxyConfiguration::sslCertificateFileName() const +{ + return m_sslCertificateFileName; +} + +void ProxyConfiguration::setSslCertificateFileName(const QString &fileName) +{ + m_logFileName = fileName; +} + +QString ProxyConfiguration::sslCertificateKeyFileName() const +{ + return m_sslCertificateKeyFileName; +} + +void ProxyConfiguration::setSslCertificateKeyFileName(const QString &fileName) +{ + m_sslCertificateKeyFileName = fileName; +} + +QHostAddress ProxyConfiguration::webSocketServerHost() const +{ + return m_webSocketServerHost; +} + +void ProxyConfiguration::setWebSocketServerHost(const QHostAddress &address) +{ + m_webSocketServerHost = address; +} + +quint16 ProxyConfiguration::webSocketServerPort() const +{ + return m_webSocketServerPort; +} + +void ProxyConfiguration::setWebSocketServerPort(quint16 port) +{ + m_webSocketServerPort = port; +} + +QHostAddress ProxyConfiguration::tcpServerHost() const +{ + return m_tcpServerHost; +} + +void ProxyConfiguration::setTcpServerHost(const QHostAddress &address) +{ + m_tcpServerHost = address; +} + +quint16 ProxyConfiguration::tcpServerPort() const +{ + return m_tcpServerPort; +} + +void ProxyConfiguration::setTcpServerPort(quint16 port) +{ + m_tcpServerPort = port; +} + + + } diff --git a/libnymea-remoteproxy/proxyconfiguration.h b/libnymea-remoteproxy/proxyconfiguration.h index 24329b5..2f203d9 100644 --- a/libnymea-remoteproxy/proxyconfiguration.h +++ b/libnymea-remoteproxy/proxyconfiguration.h @@ -2,6 +2,8 @@ #define PROXYCONFIGURATION_H #include +#include +#include namespace remoteproxy { @@ -11,9 +13,49 @@ class ProxyConfiguration : public QObject public: explicit ProxyConfiguration(QObject *parent = nullptr); -signals: + bool loadConfiguration(const QString &fileName); -public slots: + // General + bool writeLogFile() const; + void setWriteLogFile(bool enabled); + + QString logFileName() const; + void setLogFileName(const QString &logFileName); + + QString sslCertificateFileName() const; + void setSslCertificateFileName(const QString &fileName); + + QString sslCertificateKeyFileName() const; + void setSslCertificateKeyFileName(const QString &fileName); + + // WebSocketServer + QHostAddress webSocketServerHost() const; + void setWebSocketServerHost(const QHostAddress &address); + + quint16 webSocketServerPort() const; + void setWebSocketServerPort(quint16 port); + + // TcpServer + QHostAddress tcpServerHost() const; + void setTcpServerHost(const QHostAddress &address); + + quint16 tcpServerPort() const; + void setTcpServerPort(quint16 port); + +private: + // General + bool m_writeLogFile = false; + QString m_logFileName = "/var/log/nymea-remoteproxy.log"; + QString m_sslCertificateFileName = "/etc/ssl/certs/ssl-cert-snakeoil.pem"; + QString m_sslCertificateKeyFileName = "/etc/ssl/private/ssl-cert-snakeoil.key"; + + // WebSocketServer + QHostAddress m_webSocketServerHost = QHostAddress::LocalHost; + quint16 m_webSocketServerPort = 1212; + + // TcpServer + QHostAddress m_tcpServerHost = QHostAddress::LocalHost; + quint16 m_tcpServerPort = 1213; }; diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri index 2b077fc..d1914bd 100644 --- a/nymea-remoteproxy.pri +++ b/nymea-remoteproxy.pri @@ -16,6 +16,9 @@ CONFIG += c++11 console QMAKE_CXXFLAGS *= -Werror -std=c++11 -g QMAKE_LFLAGS *= -std=c++11 +INCLUDEPATH += /home/timon/guh/development/cloud/aws-sdk-cpp/build/install/include +LIBS += -L/home/timon/guh/development/cloud/aws-sdk-cpp/build/install/lib -laws-cpp-sdk-core -laws-cpp-sdk-lambda + top_srcdir=$$PWD top_builddir=$$shadowed($$PWD) diff --git a/server/main.cpp b/server/main.cpp index 71e28f8..329f48f 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -124,16 +124,14 @@ int main(int argc, char *argv[]) QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate used for " "this proxy server.", "certificate"); + certOption.setDefaultValue("/etc/ssl/certs/ssl-cert-snakeoil.pem"); parser.addOption(certOption); QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key " "used for this proxy server.", "certificate-key"); + certKeyOption.setDefaultValue("/etc/ssl/private/ssl-cert-snakeoil.key"); parser.addOption(certKeyOption); - QCommandLineOption authenticationUrlOption(QStringList() << "a" << "authentication-server", - "The server url of the AWS authentication server.", "url", "https://127.0.0.1"); - parser.addOption(authenticationUrlOption); - QCommandLineOption verboseOption(QStringList() << "v" << "verbose", "Print more verbose."); parser.addOption(verboseOption); @@ -221,13 +219,6 @@ int main(int argc, char *argv[]) exit(-1); } - // Authentication server url - QUrl authenticationServerUrl(parser.value(authenticationUrlOption)); - if (!authenticationServerUrl.isValid()) { - qCCritical(dcApplication()) << "Invalid authentication server url:" << parser.value(authenticationUrlOption); - exit(-1); - } - qCDebug(dcApplication()) << "=============================================="; qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion(); qCDebug(dcApplication()) << "=============================================="; @@ -243,7 +234,6 @@ int main(int argc, char *argv[]) Engine::instance()->setWebSocketServerHostAddress(serverHostAddress); Engine::instance()->setWebSocketServerPort(static_cast(port)); Engine::instance()->setSslConfiguration(sslConfiguration); - Engine::instance()->setAuthenticationServerUrl(authenticationServerUrl); Engine::instance()->start(); return application.exec(); From 935f6d1fa4918f5b93a5b81082850a8ca39bf52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 14 Aug 2018 17:26:57 +0200 Subject: [PATCH 2/8] Prepare client binary --- client/client.pro | 17 +++ client/main.cpp | 47 ++++++ client/proxyclient.cpp | 6 + client/proxyclient.h | 18 +++ .../authentication/authenticationprocess.cpp | 118 +++++++++++++++ .../authentication/authenticationprocess.h | 49 +++++++ .../authentication/authenticationreply.cpp | 9 ++ .../authentication/authenticationreply.h | 5 +- .../authentication/authenticator.h | 2 +- .../authentication/awsauthenticator.cpp | 24 +++- .../authentication/awsauthenticator.h | 6 + .../authentication/sigv4utils.cpp | 123 ---------------- .../authentication/sigv4utils.h | 27 ---- libnymea-remoteproxy/engine.cpp | 56 ++++---- libnymea-remoteproxy/engine.h | 13 +- libnymea-remoteproxy/libnymea-remoteproxy.pro | 4 +- libnymea-remoteproxy/proxyconfiguration.cpp | 25 +++- libnymea-remoteproxy/proxyconfiguration.h | 2 + nymea-remoteproxy.conf | 13 ++ nymea-remoteproxy.pro | 4 +- server/main.cpp | 136 ++++++++---------- tests/nymea-remoteproxy-tests.cpp | 14 +- tests/nymea-remoteproxy-tests.h | 4 + 23 files changed, 440 insertions(+), 282 deletions(-) create mode 100644 client/client.pro create mode 100644 client/main.cpp create mode 100644 client/proxyclient.cpp create mode 100644 client/proxyclient.h create mode 100644 libnymea-remoteproxy/authentication/authenticationprocess.cpp create mode 100644 libnymea-remoteproxy/authentication/authenticationprocess.h delete mode 100644 libnymea-remoteproxy/authentication/sigv4utils.cpp delete mode 100644 libnymea-remoteproxy/authentication/sigv4utils.h create mode 100644 nymea-remoteproxy.conf diff --git a/client/client.pro b/client/client.pro new file mode 100644 index 0000000..2a15c3d --- /dev/null +++ b/client/client.pro @@ -0,0 +1,17 @@ +include(../nymea-remoteproxy.pri) + +TARGET = nymea-remoteproxy-client +TEMPLATE = app + +INCLUDEPATH += ../libnymea-remoteproxy + +LIBS += -L$$top_builddir/libnymea-remoteproxyclient/ -lnymea-remoteproxyclient + +SOURCES += main.cpp \ + proxyclient.cpp + +target.path = /usr/bin +INSTALLS += target + +HEADERS += \ + proxyclient.h diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..224b8b9 --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + + QCoreApplication application(argc, argv); + application.setApplicationName(SERVER_NAME_STRING); + application.setOrganizationName("guh"); + application.setApplicationVersion(SERVER_VERSION_STRING); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + parser.setApplicationDescription(QString("\nThe nymea remote proxy server. This server allowes nymea-cloud users and " + "registered nymea deamons to establish a tunnel connection.\n\n" + "Server version: %1\n" + "API version: %2\n\n" + "Copyright %3 2018 Simon Stürz \n") + .arg(SERVER_VERSION_STRING) + .arg(API_VERSION_STRING) + .arg(QChar(0xA9))); + + + QCommandLineOption tokenOption(QStringList() << "t" << "token", "The AWS token for authentication.", "token"); + parser.addOption(tokenOption); + + + parser.process(application); + + + + return application.exec(); +} diff --git a/client/proxyclient.cpp b/client/proxyclient.cpp new file mode 100644 index 0000000..e02018e --- /dev/null +++ b/client/proxyclient.cpp @@ -0,0 +1,6 @@ +#include "proxyclient.h" + +ProxyClient::ProxyClient(QObject *parent) : QObject(parent) +{ + +} diff --git a/client/proxyclient.h b/client/proxyclient.h new file mode 100644 index 0000000..b5901a6 --- /dev/null +++ b/client/proxyclient.h @@ -0,0 +1,18 @@ +#ifndef PROXYCLIENT_H +#define PROXYCLIENT_H + +#include + + +class ProxyClient : public QObject +{ + Q_OBJECT +public: + explicit ProxyClient(QObject *parent = nullptr); + +signals: + +public slots: +}; + +#endif // PROXYCLIENT_H diff --git a/libnymea-remoteproxy/authentication/authenticationprocess.cpp b/libnymea-remoteproxy/authentication/authenticationprocess.cpp new file mode 100644 index 0000000..f0f9a43 --- /dev/null +++ b/libnymea-remoteproxy/authentication/authenticationprocess.cpp @@ -0,0 +1,118 @@ +#include "authenticationprocess.h" +#include "loggingcategories.h" + +#include +#include +#include + +namespace remoteproxy { + +AuthenticationProcess::AuthenticationProcess(QNetworkAccessManager *manager, QObject *parent) : + QObject(parent), + m_manager(manager) +{ + +} + +void AuthenticationProcess::useDynamicCredentials(bool dynamicCredentials) +{ + m_dynamicCredentials = dynamicCredentials; +} + +void AuthenticationProcess::requestDynamicCredentials() +{ + QNetworkReply *reply = m_manager->get(QNetworkRequest(QUrl("http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-Remote-Connection-Proxy-Role"))); + connect(reply, &QNetworkReply::finished, this, &AuthenticationProcess::onDynamicCredentialsReady); +} + +void AuthenticationProcess::startVerificationProcess() +{ + if (m_process->state() != QProcess::NotRunning) { + qCWarning(dcAuthenticator()) << "Authentication process already running. Killing the running process and restart."; + m_process->kill(); + m_process->deleteLater(); + m_process = nullptr; + } + + m_process = new QProcess(this); + connect(m_process, static_cast(&QProcess::finished), this, &AuthenticationProcess::onProcessFinished); + + // Create request map + QVariantMap request; + request.insert("token", m_token); + + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("AWS_DEFAULT_REGION", "eu-west-1"); + + if (m_dynamicCredentials) { + env.insert("AWS_ACCESS_KEY_ID", m_awsAccessKeyId); + env.insert("AWS_SECRET_ACCESS_KEY", m_awsSecretAccessKey); + env.insert("AWS_SESSION_TOKEN", m_awsSessionToken); + } + + // Output file name + m_resultFileName = "/tmp/" + QUuid::createUuid().toString().remove("{").remove("}").remove("-") + ".json"; + + qCDebug(dcAuthenticator()) << "Start authenticator process and store result in" << m_resultFileName; + + m_process->start("aws", { "lambda", "invoke", + "--function-name", "system-services-authorizer-dev-checkToken", + "--invocation-type", "RequestResponse", + "--payload", QString("'%1'").arg(QString::fromUtf8(QJsonDocument::fromVariant(request).toJson())), + m_resultFileName }); + +} + +void AuthenticationProcess::onDynamicCredentialsReady() +{ + QNetworkReply *reply = static_cast(sender()); + reply->deleteLater(); + + if (reply->error()) { + qCWarning(dcAuthenticator()) << "Dynamic credentials reply error: " << reply->errorString(); + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + QByteArray data = reply->readAll(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + + if(error.error != QJsonParseError::NoError) { + qCWarning(dcAuthenticator()) << "Failed to parse dynamic credentials reply data" << data << ":" << error.errorString(); + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + + QVariantMap response = jsonDoc.toVariant().toMap(); + qCDebug(dcAuthenticator()) << "-->" << response; + + m_awsAccessKeyId = response.value("AccessKeyId").toString(); + m_awsSecretAccessKey = response.value("SecretAccessKey").toString(); + m_awsSessionToken = response.value("Token").toString(); + + startVerificationProcess(); +} + +void AuthenticationProcess::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + qCDebug(dcAuthenticator()) << "Authentication process finished" << exitCode << exitStatus; +} + +void AuthenticationProcess::authenticate(const QString &token) +{ + qCDebug(dcAuthenticator()) << "Start authentication process for token"; + m_token = token; + + if (m_dynamicCredentials) { + // Request the access information + requestDynamicCredentials(); + } else { + startVerificationProcess(); + } +} + +} diff --git a/libnymea-remoteproxy/authentication/authenticationprocess.h b/libnymea-remoteproxy/authentication/authenticationprocess.h new file mode 100644 index 0000000..35a2192 --- /dev/null +++ b/libnymea-remoteproxy/authentication/authenticationprocess.h @@ -0,0 +1,49 @@ +#ifndef AUTHENTICATIONPROCESS_H +#define AUTHENTICATIONPROCESS_H + +#include +#include +#include + +#include "authenticator.h" + +namespace remoteproxy { + +class AuthenticationProcess : public QObject +{ + Q_OBJECT +public: + explicit AuthenticationProcess(QNetworkAccessManager *manager, QObject *parent = nullptr); + + void useDynamicCredentials(bool dynamicCredentials); + +private: + QString m_token; + QString m_resultFileName; + + bool m_dynamicCredentials = true; + QString m_awsAccessKeyId; + QString m_awsSecretAccessKey; + QString m_awsSessionToken; + + QNetworkAccessManager *m_manager = nullptr; + QProcess *m_process = nullptr; + + void requestDynamicCredentials(); + void startVerificationProcess(); + +signals: + void authenticationFinished(Authenticator::AuthenticationError error); + +private slots: + void onDynamicCredentialsReady(); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + +public slots: + void authenticate(const QString &token); + +}; + +} + +#endif // AUTHENTICATIONPROCESS_H diff --git a/libnymea-remoteproxy/authentication/authenticationreply.cpp b/libnymea-remoteproxy/authentication/authenticationreply.cpp index 4ee757e..6e0af1c 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.cpp +++ b/libnymea-remoteproxy/authentication/authenticationreply.cpp @@ -7,7 +7,11 @@ AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *pare QObject(parent), m_proxyClient(proxyClient) { + m_timer = new QTimer(this); + m_timer->setInterval(10000); + m_timer->setSingleShot(true); + m_process = new QProcess(this); } ProxyClient *AuthenticationReply::proxyClient() const @@ -37,6 +41,7 @@ void AuthenticationReply::setError(Authenticator::AuthenticationError error) void AuthenticationReply::setFinished() { + m_timer->stop(); emit finished(); } @@ -44,12 +49,16 @@ void AuthenticationReply::onTimeout() { m_timedOut = true; m_error = Authenticator::AuthenticationErrorTimeout; + m_timer->stop(); + m_process->kill(); emit finished(); } void AuthenticationReply::abort() { m_error = Authenticator::AuthenticationErrorAborted; + m_timer->stop(); + m_process->kill(); emit finished(); } diff --git a/libnymea-remoteproxy/authentication/authenticationreply.h b/libnymea-remoteproxy/authentication/authenticationreply.h index 4aa05a0..d0e4a6d 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.h +++ b/libnymea-remoteproxy/authentication/authenticationreply.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "authenticator.h" @@ -25,9 +26,9 @@ public: private: explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr); - ProxyClient *m_proxyClient = nullptr; - QTimer m_timer; + QTimer *m_timer = nullptr; + QProcess *m_process = nullptr; bool m_timedOut = false; bool m_finished = false; diff --git a/libnymea-remoteproxy/authentication/authenticator.h b/libnymea-remoteproxy/authentication/authenticator.h index cda3e84..736b0a4 100644 --- a/libnymea-remoteproxy/authentication/authenticator.h +++ b/libnymea-remoteproxy/authentication/authenticator.h @@ -19,7 +19,7 @@ public: AuthenticationErrorTimeout, AuthenticationErrorAborted, AuthenticationErrorAuthenticationFailed, - AuthenticationErrorAuthenticationServerNotResponding + AuthenticationErrorProxyError }; Q_ENUM(AuthenticationError) diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.cpp b/libnymea-remoteproxy/authentication/awsauthenticator.cpp index cf95729..86cc876 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.cpp +++ b/libnymea-remoteproxy/authentication/awsauthenticator.cpp @@ -1,3 +1,4 @@ +#include "engine.h" #include "proxyclient.h" #include "awsauthenticator.h" #include "loggingcategories.h" @@ -5,7 +6,8 @@ namespace remoteproxy { AwsAuthenticator::AwsAuthenticator(QObject *parent) : - Authenticator(parent) + Authenticator(parent), + m_manager(new QNetworkAccessManager(this)) { } @@ -20,13 +22,31 @@ QString AwsAuthenticator::name() const return "AWS authenticator"; } +void AwsAuthenticator::onAuthenticationProcessFinished(Authenticator::AuthenticationError error) +{ + AuthenticationProcess *process = static_cast(sender()); + AuthenticationReply *reply = m_runningProcesses.take(process); + + setReplyError(reply, error); + setReplyFinished(reply); + + qCDebug(dcAuthenticator()) << "" << error; +} + AuthenticationReply *AwsAuthenticator::authenticate(ProxyClient *proxyClient) { qCDebug(dcAuthenticator()) << name() << "Start authenticating" << proxyClient << "using token" << proxyClient->token(); AuthenticationReply *reply = createAuthenticationReply(proxyClient, this); - // TODO: start authentication request + AuthenticationProcess *process = new AuthenticationProcess(m_manager, this); + process->useDynamicCredentials(!Engine::instance()->developerMode()); + connect(process, &AuthenticationProcess::authenticationFinished, this, &AwsAuthenticator::onAuthenticationProcessFinished); + // Configure process + m_runningProcesses.insert(process, reply); + + // Start authentication process + process->authenticate(proxyClient->token()); return reply; } diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.h b/libnymea-remoteproxy/authentication/awsauthenticator.h index f5f921f..ca7cdcf 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.h +++ b/libnymea-remoteproxy/authentication/awsauthenticator.h @@ -2,9 +2,11 @@ #define AWSAUTHENTICATOR_H #include +#include #include "authenticator.h" #include "authenticationreply.h" +#include "authenticationprocess.h" namespace remoteproxy { @@ -18,7 +20,11 @@ public: QString name() const override; private: + QNetworkAccessManager *m_manager = nullptr; + QHash m_runningProcesses; +private slots: + void onAuthenticationProcessFinished(Authenticator::AuthenticationError error); public slots: AuthenticationReply *authenticate(ProxyClient *proxyClient) override; diff --git a/libnymea-remoteproxy/authentication/sigv4utils.cpp b/libnymea-remoteproxy/authentication/sigv4utils.cpp deleted file mode 100644 index 147ce9c..0000000 --- a/libnymea-remoteproxy/authentication/sigv4utils.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "sigv4utils.h" - -#include -#include -#include -#include -#include -#include - -SigV4Utils::SigV4Utils() -{ - -} - -QByteArray SigV4Utils::getCurrentDateTime() -{ - return QDateTime::currentDateTime().toUTC().toString("yyyyMMddThhmmssZ").toUtf8(); -} - -QByteArray SigV4Utils::getCanonicalQueryString(const QNetworkRequest &request, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray ®ion, const QByteArray &service, const QByteArray &payload) -{ - QByteArray algorithm = "AWS4-HMAC-SHA256"; - QByteArray dateTime = getCurrentDateTime(); - QByteArray credentialScope = getCredentialScope(algorithm, dateTime, region, service); - - QByteArray canonicalQueryString; - canonicalQueryString += "X-Amz-Algorithm=AWS4-HMAC-SHA256"; - canonicalQueryString += "&X-Amz-Credential=" + QByteArray(accessKeyId + '/' + credentialScope).toPercentEncoding(); - canonicalQueryString += "&X-Amz-Date=" + dateTime; - if (request.rawHeaderList().count() > 0){ - canonicalQueryString += "&X-Amz-SignedHeaders=" + request.rawHeaderList().join(';').toLower(); - } - - QByteArray canonicalRequest = getCanonicalRequest(QNetworkAccessManager::GetOperation, request, payload); - - QByteArray stringToSign = getStringToSign(canonicalRequest, dateTime, region, service); - QByteArray signature = getSignature(stringToSign, secretAccessKey, dateTime, region, service); - - canonicalQueryString += "&X-Amz-Signature=" + signature; - - if (!sessionToken.isEmpty()) { - canonicalQueryString += "&X-Amz-Security-Token=" + sessionToken.toPercentEncoding(); - } - - return canonicalQueryString; -} - -QByteArray SigV4Utils::getSignatureKey(const QByteArray &key, const QByteArray &date, const QByteArray ®ion, const QByteArray &service) -{ - QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; - return QMessageAuthenticationCode::hash("aws4_request", - QMessageAuthenticationCode::hash(service, - QMessageAuthenticationCode::hash(region, - QMessageAuthenticationCode::hash(date, "AWS4"+key, - hashAlgorithm), hashAlgorithm), hashAlgorithm), hashAlgorithm); -} - -QByteArray SigV4Utils::getCanonicalRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, const QByteArray &payload) -{ - QByteArray canonicalRequest; - - QByteArray method; - switch (operation) { - case QNetworkAccessManager::GetOperation: - method = "GET"; - break; - case QNetworkAccessManager::PostOperation: - method = "POST"; - break; - default: - Q_ASSERT_X(false, "Network operation not implemented", "SigV4Utils"); - } - QByteArray uri = request.url().path().toUtf8(); - QUrlQuery query(request.url()); - QList > queryItems = query.queryItems(); - QStringList queryItemStrings; - for (int i = 0; i < queryItems.count(); i++) { - QPair queryItem = queryItems.at(i); - queryItemStrings.append(queryItem.first + '=' + queryItem.second); - } - queryItemStrings.sort(Qt::CaseInsensitive); - - QByteArray canonicalQueryString = queryItemStrings.join('&').toUtf8(); - - QByteArray canonicalHeaders; - foreach(const QByteArray &headerName, request.rawHeaderList()) { - canonicalHeaders += headerName.toLower() + ':' + request.rawHeader(headerName) + '\n'; - } - - QByteArray payloadHash = QCryptographicHash::hash(payload, QCryptographicHash::Sha256).toHex(); - - canonicalRequest = method + '\n' + uri + '\n' + canonicalQueryString + '\n' + canonicalHeaders + '\n' + request.rawHeaderList().join(';').toLower() + '\n' + payloadHash; - return canonicalRequest; -} - -QByteArray SigV4Utils::getCredentialScope(const QByteArray &algorithm, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service) -{ - Q_UNUSED(algorithm) - QByteArray credentialScope = dateTime.left(8) + '/' + region + '/' + service + "/aws4_request"; - return credentialScope; -} - -QByteArray SigV4Utils::getStringToSign(const QByteArray &canonicalRequest, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service) -{ - QByteArray algorithm = "AWS4-HMAC-SHA256"; - QByteArray credentialScope = getCredentialScope(algorithm, dateTime, region, service); - - QByteArray stringToSign = algorithm + '\n' + dateTime + '\n' + credentialScope + '\n' + QCryptographicHash::hash(canonicalRequest, QCryptographicHash::Sha256).toHex(); - return stringToSign; -} - -QByteArray SigV4Utils::getSignature(const QByteArray &stringToSign, const QByteArray &secretAccessKey, const QByteArray &dateTime, const QString ®ion, const QString &service) -{ - QByteArray signingKey = getSignatureKey(secretAccessKey, dateTime.left(8), region.toUtf8(), service.toUtf8()); - QByteArray signature = QMessageAuthenticationCode::hash(stringToSign, signingKey, QCryptographicHash::Sha256).toHex(); - return signature; -} - -QByteArray SigV4Utils::getAuthorizationHeader(const QByteArray &accessKeyId, const QByteArray &dateTime, const QString ®ion, const QString &service, const QNetworkRequest &request, const QByteArray &signature) -{ - QByteArray authHeader = "AWS4-HMAC-SHA256 Credential=" + accessKeyId + '/' + dateTime.left(8) + '/' + region.toUtf8() + '/' + service.toUtf8() + '/' + "aws4_request, SignedHeaders=" + request.rawHeaderList().join(';').toLower() + ", Signature=" + signature; - return authHeader; -} diff --git a/libnymea-remoteproxy/authentication/sigv4utils.h b/libnymea-remoteproxy/authentication/sigv4utils.h deleted file mode 100644 index 4861869..0000000 --- a/libnymea-remoteproxy/authentication/sigv4utils.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SIGV4UTILS_H -#define SIGV4UTILS_H - -#include -#include -#include - -class SigV4Utils -{ -public: - SigV4Utils(); - - static QByteArray getCurrentDateTime(); - - - static QByteArray getCanonicalQueryString(const QNetworkRequest &request, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray ®ion, const QByteArray &service, const QByteArray &payload); - static QByteArray getCanonicalRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, const QByteArray &payload); - static QByteArray getCanonicalHeaders(const QNetworkRequest &request); - static QByteArray getCredentialScope(const QByteArray &algorithm, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service); - static QByteArray getStringToSign(const QByteArray &canonicalRequest, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service); - static QByteArray getSignatureKey(const QByteArray &key, const QByteArray &date, const QByteArray ®ion, const QByteArray &service); - static QByteArray getSignature(const QByteArray &stringToSign, const QByteArray &secretAccessKey, const QByteArray &dateTime, const QString ®ion, const QString &service); - static QByteArray getAuthorizationHeader(const QByteArray &accessKeyId, const QByteArray &dateTime, const QString ®ion, const QString &service, const QNetworkRequest &request, const QByteArray &signature); - -}; - -#endif // SIGV4UTILS_H diff --git a/libnymea-remoteproxy/engine.cpp b/libnymea-remoteproxy/engine.cpp index aba289e..abc9468 100644 --- a/libnymea-remoteproxy/engine.cpp +++ b/libnymea-remoteproxy/engine.cpp @@ -32,6 +32,10 @@ bool Engine::exists() void Engine::start() { + // Make sure an authenticator was registered + Q_ASSERT_X(m_authenticator != nullptr, "Engine", "There is no authenticator registerd."); + Q_ASSERT_X(m_configuration != nullptr, "Engine", "There is no configuration set."); + if (!m_running) qCDebug(dcEngine()) << "Start server engine"; @@ -44,15 +48,12 @@ void Engine::start() QUrl websocketServerUrl; websocketServerUrl.setScheme("wss"); - websocketServerUrl.setHost(m_webSocketServerHostAddress.toString()); - websocketServerUrl.setPort(m_webSocketServerPort); + websocketServerUrl.setHost(m_configuration->webSocketServerHost().toString()); + websocketServerUrl.setPort(m_configuration->webSocketServerPort()); m_webSocketServer->setServerUrl(websocketServerUrl); m_proxyServer->registerTransportInterface(m_webSocketServer); - // Make sure an authenticator was registered - Q_ASSERT_X(m_authenticator != nullptr, "Engine", "There is no authenticator registerd."); - qCDebug(dcEngine()) << "Starting proxy server"; m_proxyServer->startServer(); @@ -74,6 +75,11 @@ bool Engine::running() const return m_running; } +bool Engine::developerMode() const +{ + return m_developerMode; +} + QString Engine::serverName() const { return m_serverName; @@ -84,36 +90,24 @@ void Engine::setServerName(const QString &serverName) m_serverName = serverName; } -void Engine::setWebSocketServerHostAddress(const QHostAddress &hostAddress) +void Engine::setConfiguration(ProxyConfiguration *configuration) { - qCDebug(dcEngine()) << "Websocket server host address:" << hostAddress; - m_webSocketServerHostAddress = hostAddress; -} - -void Engine::setWebSocketServerPort(const quint16 &port) -{ - qCDebug(dcEngine()) << "Websocket server port:" << port; - m_webSocketServerPort = port; + m_configuration = configuration; + qCDebug(dcApplication()) << "Set configuration" << m_configuration; } void Engine::setSslConfiguration(const QSslConfiguration &configuration) { - qCDebug(dcEngine()) << "SSL certificate information:"; - qCDebug(dcEngine()) << " Common name:" << configuration.localCertificate().issuerInfo(QSslCertificate::CommonName); - qCDebug(dcEngine()) << " Organisation:" << configuration.localCertificate().issuerInfo(QSslCertificate::Organization); - qCDebug(dcEngine()) << " Organisation unit name:" << configuration.localCertificate().issuerInfo(QSslCertificate::OrganizationalUnitName); - qCDebug(dcEngine()) << " Country name:" << configuration.localCertificate().issuerInfo(QSslCertificate::CountryName); - qCDebug(dcEngine()) << " Locality name:" << configuration.localCertificate().issuerInfo(QSslCertificate::LocalityName); - qCDebug(dcEngine()) << " State/Province:" << configuration.localCertificate().issuerInfo(QSslCertificate::StateOrProvinceName); - qCDebug(dcEngine()) << " Email address:" << configuration.localCertificate().issuerInfo(QSslCertificate::EmailAddress); - m_sslConfiguration = configuration; -} -void Engine::setAuthenticationServerUrl(const QUrl &url) -{ - qCDebug(dcEngine()) << "Authentication server URL" << url.toString(); - m_authenticationServerUrl = url; + qCDebug(dcEngine()) << "SSL certificate information:"; + qCDebug(dcEngine()) << " Common name:" << m_sslConfiguration.localCertificate().issuerInfo(QSslCertificate::CommonName); + qCDebug(dcEngine()) << " Organisation:" << m_sslConfiguration.localCertificate().issuerInfo(QSslCertificate::Organization); + qCDebug(dcEngine()) << " Organisation unit name:" << m_sslConfiguration.localCertificate().issuerInfo(QSslCertificate::OrganizationalUnitName); + qCDebug(dcEngine()) << " Country name:" << m_sslConfiguration.localCertificate().issuerInfo(QSslCertificate::CountryName); + qCDebug(dcEngine()) << " Locality name:" << m_sslConfiguration.localCertificate().issuerInfo(QSslCertificate::LocalityName); + qCDebug(dcEngine()) << " State/Province:" << m_sslConfiguration.localCertificate().issuerInfo(QSslCertificate::StateOrProvinceName); + qCDebug(dcEngine()) << " Email address:" << m_sslConfiguration.localCertificate().issuerInfo(QSslCertificate::EmailAddress); } void Engine::setAuthenticator(Authenticator *authenticator) @@ -132,6 +126,11 @@ void Engine::setAuthenticator(Authenticator *authenticator) // FIXME: connect } +void Engine::setDeveloperModeEnabled(bool enabled) +{ + m_developerMode = enabled; +} + Authenticator *Engine::authenticator() const { return m_authenticator; @@ -172,7 +171,6 @@ void Engine::clean() } if (m_configuration) { - delete m_configuration; m_configuration = nullptr; } } diff --git a/libnymea-remoteproxy/engine.h b/libnymea-remoteproxy/engine.h index 1228de2..138123d 100644 --- a/libnymea-remoteproxy/engine.h +++ b/libnymea-remoteproxy/engine.h @@ -26,17 +26,17 @@ public: void stop(); bool running() const; + bool developerMode() const; QString serverName() const; void setServerName(const QString &serverName); - void setWebSocketServerHostAddress(const QHostAddress &hostAddress); - void setWebSocketServerPort(const quint16 &port); + void setConfiguration(ProxyConfiguration *configuration); void setSslConfiguration(const QSslConfiguration &configuration); - void setAuthenticationServerUrl(const QUrl &url); - void setAuthenticator(Authenticator *authenticator); + void setDeveloperModeEnabled(bool enabled); + Authenticator *authenticator() const; ProxyServer *proxyServer() const; WebSocketServer *webSocketServer() const; @@ -47,13 +47,10 @@ private: static Engine *s_instance; bool m_running = false; + bool m_developerMode = false; QString m_serverName; - quint16 m_webSocketServerPort = 1212; - QHostAddress m_webSocketServerHostAddress = QHostAddress::LocalHost; QSslConfiguration m_sslConfiguration; - QUrl m_authenticationServerUrl; - ProxyConfiguration *m_configuration = nullptr; Authenticator *m_authenticator = nullptr; ProxyServer *m_proxyServer = nullptr; diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index e73399c..8a6ce1c 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -26,7 +26,7 @@ HEADERS += \ authentication/authenticationreply.h \ proxyconfiguration.h \ tunnelconnection.h \ - authentication/sigv4utils.h + authentication/authenticationprocess.h SOURCES += \ engine.cpp \ @@ -45,7 +45,7 @@ SOURCES += \ authentication/authenticationreply.cpp \ proxyconfiguration.cpp \ tunnelconnection.cpp \ - authentication/sigv4utils.cpp + authentication/authenticationprocess.cpp # install header file with relative subdirectory diff --git a/libnymea-remoteproxy/proxyconfiguration.cpp b/libnymea-remoteproxy/proxyconfiguration.cpp index e91665c..f2d739a 100644 --- a/libnymea-remoteproxy/proxyconfiguration.cpp +++ b/libnymea-remoteproxy/proxyconfiguration.cpp @@ -14,18 +14,18 @@ ProxyConfiguration::ProxyConfiguration(QObject *parent) : bool ProxyConfiguration::loadConfiguration(const QString &fileName) { QFileInfo fileInfo(fileName); - if (!fileInfo.exists()) { - qCWarning(dcApplication()) << "Could not find configuration file" << fileName; + qCWarning(dcApplication()) << "Configuration: Could not find configuration file" << fileName; return false; } if (!fileInfo.isReadable()) { - qCWarning(dcApplication()) << "Cannot read configuration file" << fileName; + qCWarning(dcApplication()) << "Configuration: Cannot read configuration file" << fileName; return false; } QSettings settings(fileName, QSettings::IniFormat); + qCDebug(dcApplication()) << settings.childGroups(); settings.beginGroup("General"); setWriteLogFile(settings.value("writeLogs", false).toBool()); @@ -127,6 +127,25 @@ void ProxyConfiguration::setTcpServerPort(quint16 port) m_tcpServerPort = port; } +QDebug operator<<(QDebug debug, ProxyConfiguration *configuration) +{ + debug.nospace() << endl << "========== ProxyConfiguration ==========" << endl; + debug.nospace() << "General" << endl; + debug.nospace() << " - write logfile:" << configuration->writeLogFile() << endl; + debug.nospace() << " - logfile:" << configuration->logFileName() << endl; + debug.nospace() << " - certificate:" << configuration->sslCertificateFileName() << endl; + debug.nospace() << " - certificate key:" << configuration->sslCertificateKeyFileName() << endl; + debug.nospace() << "WebSocketServer" << endl; + debug.nospace() << " - host:" << configuration->webSocketServerHost().toString() << endl; + debug.nospace() << " - port:" << configuration->webSocketServerPort() << endl; + debug.nospace() << "TcpServer" << endl; + debug.nospace() << " - host:" << configuration->tcpServerHost().toString() << endl; + debug.nospace() << " - port:" << configuration->tcpServerPort() << endl; + debug.nospace() << "========== ProxyConfiguration ==========" << endl; + + return debug; return debug; +} + } diff --git a/libnymea-remoteproxy/proxyconfiguration.h b/libnymea-remoteproxy/proxyconfiguration.h index 2f203d9..d8cde2f 100644 --- a/libnymea-remoteproxy/proxyconfiguration.h +++ b/libnymea-remoteproxy/proxyconfiguration.h @@ -59,6 +59,8 @@ private: }; +QDebug operator<< (QDebug debug, ProxyConfiguration *configuration); + } #endif // PROXYCONFIGURATION_H diff --git a/nymea-remoteproxy.conf b/nymea-remoteproxy.conf new file mode 100644 index 0000000..244249f --- /dev/null +++ b/nymea-remoteproxy.conf @@ -0,0 +1,13 @@ +[General] +writeLogs=false +logFile=/var/log/nymea-remoteproxy.log +certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem +certificateKey=/etc/ssl/private/ssl-cert-snakeoil.key + +[WebSocketServer] +host=0.0.0.0 +port=443 + +[TcpServer] +host=0.0.0.0 +port=80 diff --git a/nymea-remoteproxy.pro b/nymea-remoteproxy.pro index 7f7c81e..3b3fc8b 100644 --- a/nymea-remoteproxy.pro +++ b/nymea-remoteproxy.pro @@ -1,11 +1,11 @@ include(nymea-remoteproxy.pri) TEMPLATE=subdirs -SUBDIRS += server libnymea-remoteproxy libnymea-remoteproxyclient tests +SUBDIRS += server client libnymea-remoteproxy libnymea-remoteproxyclient tests server.depends = libnymea-remoteproxy tests.depends = libnymea-remoteproxy libnymea-remoteproxyclient - +client.depends = libnymea-remoteproxyclient message("----------------------------------------------------------") message("Building nymea-remoteproxy $${SERVER_VERSION}") diff --git a/server/main.cpp b/server/main.cpp index 329f48f..530fdae 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -19,6 +19,7 @@ #include "engine.h" #include "loggingcategories.h" +#include "proxyconfiguration.h" #include "authentication/awsauthenticator.h" using namespace remoteproxy; @@ -115,28 +116,30 @@ int main(int argc, char *argv[]) "logfile", "/var/log/nymea-remoteproxy.log"); parser.addOption(logfileOption); - QCommandLineOption serverOption(QStringList() << "s" << "server", "The server address this proxy will listen on. " - "Default is 127.0.0.1", "hostaddress", "127.0.0.1"); - parser.addOption(serverOption); + QCommandLineOption developmentOption(QStringList() << "d" << "development", "Enable the development mode. This enabled the server " + "assumes there are static AWS credentials provided to aws-cli."); + parser.addOption(developmentOption); - QCommandLineOption portOption(QStringList() << "p" << "port", "The proxy server port. Default is 1212", "port", "1212"); - parser.addOption(portOption); - - QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate used for " - "this proxy server.", "certificate"); - certOption.setDefaultValue("/etc/ssl/certs/ssl-cert-snakeoil.pem"); - parser.addOption(certOption); - - QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key " - "used for this proxy server.", "certificate-key"); - certKeyOption.setDefaultValue("/etc/ssl/private/ssl-cert-snakeoil.key"); - parser.addOption(certKeyOption); + QCommandLineOption configOption(QStringList() << "c" <<"config", "The path to the proxy server configuration file. The default is /etc/nymea-remoteproxy/nymea-remoteproxy.conf", "configuration"); + configOption.setDefaultValue("/etc/nymea-remoteproxy/nymea-remoteproxy.conf"); + parser.addOption(configOption); QCommandLineOption verboseOption(QStringList() << "v" << "verbose", "Print more verbose."); parser.addOption(verboseOption); parser.process(application); + // Create a default configuration + ProxyConfiguration *configuration = new ProxyConfiguration(nullptr); + if (parser.isSet(configOption)) { + qCDebug(dcApplication()) << "Loading configuration file from" << parser.value(configOption); + if (!configuration->loadConfiguration(parser.value(configOption))) { + qCCritical(dcApplication()) << "Invalid configuration file passed" << parser.value(configOption); + exit(-1); + } + } + + if (parser.isSet(verboseOption)) { s_loggingFilters["Debug"] = true; s_loggingFilters["WebSocketServerTraffic"] = true; @@ -144,84 +147,62 @@ int main(int argc, char *argv[]) QLoggingCategory::installFilter(loggingCategoryFilter); - // Open the logfile, if any specified - if (parser.isSet(logfileOption)) { - QFileInfo fi(parser.value(logfileOption)); - QDir logDir(fi.absolutePath()); - if (!logDir.exists() && !logDir.mkpath(logDir.absolutePath())) { - qCCritical(dcApplication()) << "Error opening log file" << parser.value(logfileOption); - return 1; - } - s_logFile.setFileName(parser.value(logfileOption)); - if (!s_logFile.open(QFile::WriteOnly | QFile::Append)) { - qCCritical(dcApplication()) << "Error opening log file" << parser.value(logfileOption); - return 1; - } - s_loggingEnabled = true; - } - - // Proxy server host address - QHostAddress serverHostAddress = QHostAddress(parser.value(serverOption)); - if (serverHostAddress.isNull()) { - qCCritical(dcApplication()) << "Invalid hostaddress for the proxy server:" << parser.value(serverOption); + // Verify webserver configuration + if (configuration->webSocketServerHost().isNull()) { + qCCritical(dcApplication()) << "Invalid web socket host address passed."; exit(-1); } - // Port - bool ok = false; - uint port = parser.value(portOption).toUInt(&ok); - if (!ok) { - qCCritical(dcApplication()) << "Invalid port value:" << parser.value(portOption); - exit(-1); - } - - if (port > 65535) { - qCCritical(dcApplication()) << "Port value is out of range:" << parser.value(portOption); + // Verify tcp server configuration + if (configuration->tcpServerHost().isNull()) { + qCCritical(dcApplication()) << "Invalid TCP server host address passed."; exit(-1); } // SSL certificate QSslConfiguration sslConfiguration; - if (parser.isSet(certOption)) { - // Load certificate - QFile certFile(parser.value(certOption)); - if (!certFile.open(QIODevice::ReadOnly)) { - qCCritical(dcApplication()) << "Could not open certificate file:" << parser.value(certOption) << certFile.errorString(); - exit(-1); - } - - QSslCertificate certificate(&certFile, QSsl::Pem); - qCDebug(dcApplication()) << "Loaded successfully certificate" << parser.value(certOption); - certFile.close(); - - // Create SSL configuration - sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); - sslConfiguration.setLocalCertificate(certificate); - sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater); + // Load certificate + QFile certFile(configuration->sslCertificateFileName()); + if (!certFile.open(QIODevice::ReadOnly)) { + qCCritical(dcApplication()) << "Could not open certificate file" << configuration->sslCertificateFileName() << certFile.errorString(); + exit(-1); } + QSslCertificate certificate(&certFile, QSsl::Pem); + qCDebug(dcApplication()) << "Loaded successfully certificate" << configuration->sslCertificateFileName(); + certFile.close(); + + // Create SSL configuration + sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); + sslConfiguration.setLocalCertificate(certificate); + sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater); + // SSL key - if (parser.isSet(certKeyOption)) { - QFile certKeyFile(parser.value(certKeyOption)); - if (!certKeyFile.open(QIODevice::ReadOnly)) { - qCCritical(dcApplication()) << "Could not open certificate key file:" << parser.value(certKeyOption) << certKeyFile.errorString(); - exit(-1); - } - - QSslKey sslKey(&certKeyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); - qCDebug(dcApplication()) << "Loaded successfully certificate key" << parser.value(certKeyOption); - certKeyFile.close(); - sslConfiguration.setPrivateKey(sslKey); + QFile certKeyFile(configuration->sslCertificateKeyFileName()); + if (!certKeyFile.open(QIODevice::ReadOnly)) { + qCCritical(dcApplication()) << "Could not open certificate key file:" << configuration->sslCertificateKeyFileName() << certKeyFile.errorString(); + exit(-1); } + QSslKey sslKey(&certKeyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + qCDebug(dcApplication()) << "Loaded successfully certificate key" << configuration->sslCertificateKeyFileName(); + certKeyFile.close(); + sslConfiguration.setPrivateKey(sslKey); + if (sslConfiguration.isNull()) { qCCritical(dcApplication()) << "No SSL configuration specified. The server does not suppoert insecure connections."; exit(-1); } - qCDebug(dcApplication()) << "=============================================="; + if (parser.isSet(developmentOption)) { + qCWarning(dcApplication()) << "##########################################################"; + qCWarning(dcApplication()) << "# DEVELOPMENT MODE #"; + qCWarning(dcApplication()) << "##########################################################"; + } + + qCDebug(dcApplication()) << "=========================================================="; qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion(); - qCDebug(dcApplication()) << "=============================================="; + qCDebug(dcApplication()) << "=========================================================="; if (s_loggingEnabled) qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName(); @@ -230,10 +211,11 @@ int main(int argc, char *argv[]) AwsAuthenticator *authenticator = new AwsAuthenticator(nullptr); // Configure and start the engines - Engine::instance()->setAuthenticator(authenticator); - Engine::instance()->setWebSocketServerHostAddress(serverHostAddress); - Engine::instance()->setWebSocketServerPort(static_cast(port)); + Engine::instance()->setConfiguration(configuration); + Engine::instance()->setDeveloperModeEnabled(parser.isSet(developmentOption)); Engine::instance()->setSslConfiguration(sslConfiguration); + Engine::instance()->setAuthenticator(authenticator); + Engine::instance()->start(); return application.exec(); diff --git a/tests/nymea-remoteproxy-tests.cpp b/tests/nymea-remoteproxy-tests.cpp index b39c90b..7e9f4f6 100644 --- a/tests/nymea-remoteproxy-tests.cpp +++ b/tests/nymea-remoteproxy-tests.cpp @@ -13,6 +13,8 @@ RemoteProxyTests::RemoteProxyTests(QObject *parent) : QObject(parent) { + m_configuration = new ProxyConfiguration(this); + QFile certificateFile(":/test-certificate.crt"); if (!certificateFile.open(QIODevice::ReadOnly)) { qWarning() << "Could not open resource file" << certificateFile.fileName(); @@ -72,11 +74,10 @@ void RemoteProxyTests::startEngine() if (!Engine::exists()) { QString serverName = "nymea-remoteproxy-testserver"; Engine::instance()->setAuthenticator(m_authenticator); - Engine::instance()->setAuthenticationServerUrl(QUrl("https://localhost")); Engine::instance()->setServerName(serverName); - Engine::instance()->setWebSocketServerPort(m_port); - Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost); + Engine::instance()->setConfiguration(m_configuration); Engine::instance()->setSslConfiguration(m_sslConfiguration); + Engine::instance()->setDeveloperModeEnabled(true); QVERIFY(Engine::exists()); QVERIFY(Engine::instance()->serverName() == serverName); @@ -89,6 +90,8 @@ void RemoteProxyTests::startServer() if (!Engine::instance()->running()) { QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); + Engine::instance()->setConfiguration(m_configuration); + Engine::instance()->setDeveloperModeEnabled(true); Engine::instance()->start(); runningSpy.wait(); QVERIFY(runningSpy.count() == 1); @@ -217,9 +220,8 @@ void RemoteProxyTests::webserverConnectionBlocked() dummyServer.listen(QHostAddress::LocalHost, m_port); // Start proxy webserver - Engine::instance()->setWebSocketServerPort(m_port); + Engine::instance()->setConfiguration(m_configuration); Engine::instance()->setAuthenticator(m_authenticator); - Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost); Engine::instance()->setSslConfiguration(m_sslConfiguration); QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); @@ -324,7 +326,7 @@ void RemoteProxyTests::authenticate_data() << 100 << Authenticator::AuthenticationErrorAuthenticationFailed; QTest::newRow("not responding") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken - << 200 << Authenticator::AuthenticationErrorAuthenticationServerNotResponding; + << 200 << Authenticator::AuthenticationErrorProxyError; QTest::newRow("aborted") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << 100 << Authenticator::AuthenticationErrorAborted; diff --git a/tests/nymea-remoteproxy-tests.h b/tests/nymea-remoteproxy-tests.h index 5764d54..ceebea9 100644 --- a/tests/nymea-remoteproxy-tests.h +++ b/tests/nymea-remoteproxy-tests.h @@ -12,6 +12,7 @@ #include "jsonrpc/jsontypes.h" #include "mockauthenticator.h" +#include "proxyconfiguration.h" #include "remoteproxyconnection.h" using namespace remoteproxy; @@ -24,8 +25,11 @@ public: explicit RemoteProxyTests(QObject *parent = nullptr); private: + ProxyConfiguration *m_configuration = nullptr; + quint16 m_port = 1212; QHostAddress m_serverAddress = QHostAddress::LocalHost; + QSslConfiguration m_sslConfiguration; MockAuthenticator *m_authenticator = nullptr; QString m_testToken; From d28906faf011b8957613283583553526fe8b125c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 13 Aug 2018 22:52:52 +0200 Subject: [PATCH 3/8] add debian packaging --- debian/changelog | 5 +++ debian/compat | 1 + debian/control | 35 +++++++++++++++++++ debian/libnymea-remoteproxy.install | 1 + debian/libnymea-remoteproxyclient.install | 1 + debian/nymea-remoteproxy-tests.install | 1 + debian/nymea-remoteproxy.install | 1 + debian/rules | 17 +++++++++ debian/source/format | 1 + libnymea-remoteproxy/libnymea-remoteproxy.pro | 3 ++ .../{jsonrpcclient.cpp => jsonrpcclient2.cpp} | 2 +- .../{jsonrpcclient.h => jsonrpcclient2.h} | 0 .../libnymea-remoteproxyclient.pri | 4 +-- .../libnymea-remoteproxyclient.pro | 3 ++ .../remoteproxyconnection.h | 2 +- 15 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/libnymea-remoteproxy.install create mode 100644 debian/libnymea-remoteproxyclient.install create mode 100644 debian/nymea-remoteproxy-tests.install create mode 100644 debian/nymea-remoteproxy.install create mode 100755 debian/rules create mode 100644 debian/source/format rename libnymea-remoteproxyclient/{jsonrpcclient.cpp => jsonrpcclient2.cpp} (99%) rename libnymea-remoteproxyclient/{jsonrpcclient.h => jsonrpcclient2.h} (100%) diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..0eb9687 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +nymea-remoteproxy (0.0.1) xenial; urgency=medium + + * Initial release. + + -- Michael Zanetti Thu, 13 Aug 2018 21:33:51 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..0bf0738 --- /dev/null +++ b/debian/control @@ -0,0 +1,35 @@ +Source: nymea-remoteproxy +Section: utils +Priority: options +Maintainer: Michael Zanetti +Build-depends: debhelper (>= 0.0.0), + libqt5websockets5-dev, +Standards-Version: 3.9.3 + +Package: nymea-remoteproxy +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: The nymea remote proxy server + The nymea remote proxy server + +Package: libnymea-remoteproxy +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: The nymea remote proxy server + The nymea remote proxy server + +Package: libnymea-remoteproxyclient +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: The nymea remote proxy server + The nymea remote proxy server + +Package: nymea-remoteproxy-tests +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: The nymea remote proxy server tests + The nymea remote proxy server tests diff --git a/debian/libnymea-remoteproxy.install b/debian/libnymea-remoteproxy.install new file mode 100644 index 0000000..5833199 --- /dev/null +++ b/debian/libnymea-remoteproxy.install @@ -0,0 +1 @@ +usr/lib/libnymea-remoteproxy.so* diff --git a/debian/libnymea-remoteproxyclient.install b/debian/libnymea-remoteproxyclient.install new file mode 100644 index 0000000..894b860 --- /dev/null +++ b/debian/libnymea-remoteproxyclient.install @@ -0,0 +1 @@ +usr/lib/libnymea-remoteproxyclient.so* diff --git a/debian/nymea-remoteproxy-tests.install b/debian/nymea-remoteproxy-tests.install new file mode 100644 index 0000000..ef18480 --- /dev/null +++ b/debian/nymea-remoteproxy-tests.install @@ -0,0 +1 @@ +usr/bin/nymea-remoteproxy-tests diff --git a/debian/nymea-remoteproxy.install b/debian/nymea-remoteproxy.install new file mode 100644 index 0000000..3563d18 --- /dev/null +++ b/debian/nymea-remoteproxy.install @@ -0,0 +1 @@ +usr/bin/nymea-remoteproxy diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..6e863b9 --- /dev/null +++ b/debian/rules @@ -0,0 +1,17 @@ +#!/usr/bin/make -f + +DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) + +%: + dh $@ + +#override_dh_auto_build: +# JANUSP=/usr make -j1 + +#override_dh_auto_install: +# install build/janus_guhio.so debian/janus-guh-plugin/lib/$(DEB_HOST_MULTIARCH)/janus/plugins/ +# install -m 0644 conf/janus.plugin.guhio.cfg debian/janus-guh-plugin/etc/janus/ + +override_dh_auto_check: + LD_LIBRARY_PATH=libnymea-remoteproxy dh_auto_check + diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index 8a6ce1c..546b810 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -55,3 +55,6 @@ for(header, HEADERS) { eval(headers_$${path}.path = $${path}) eval(INSTALLS *= headers_$${path}) } + +target.path = /usr/lib/ +INSTALLS += target diff --git a/libnymea-remoteproxyclient/jsonrpcclient.cpp b/libnymea-remoteproxyclient/jsonrpcclient2.cpp similarity index 99% rename from libnymea-remoteproxyclient/jsonrpcclient.cpp rename to libnymea-remoteproxyclient/jsonrpcclient2.cpp index 94fd7ec..adb68be 100644 --- a/libnymea-remoteproxyclient/jsonrpcclient.cpp +++ b/libnymea-remoteproxyclient/jsonrpcclient2.cpp @@ -1,4 +1,4 @@ -#include "jsonrpcclient.h" +#include "jsonrpcclient2.h" #include "proxyconnection.h" #include diff --git a/libnymea-remoteproxyclient/jsonrpcclient.h b/libnymea-remoteproxyclient/jsonrpcclient2.h similarity index 100% rename from libnymea-remoteproxyclient/jsonrpcclient.h rename to libnymea-remoteproxyclient/jsonrpcclient2.h diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri index 6b0ba7c..949c4e2 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri @@ -1,14 +1,14 @@ INCLUDEPATH += $${PWD} HEADERS += \ - $${PWD}/jsonrpcclient.h \ + $${PWD}/jsonrpcclient2.h \ $${PWD}/jsonreply.h \ $${PWD}/remoteproxyconnection.h \ $${PWD}/proxyconnection.h \ $${PWD}/websocketconnection.h SOURCES += \ - $${PWD}/jsonrpcclient.cpp \ + $${PWD}/jsonrpcclient2.cpp \ $${PWD}/jsonreply.cpp \ $${PWD}/remoteproxyconnection.cpp \ $${PWD}/proxyconnection.cpp \ diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro index 5e25fe8..49167c4 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro @@ -12,3 +12,6 @@ for(header, HEADERS) { eval(headers_$${path}.path = $${path}) eval(INSTALLS *= headers_$${path}) } + +target.path = /usr/lib/ +INSTALLS += target diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.h b/libnymea-remoteproxyclient/remoteproxyconnection.h index 0872f02..bc1a3db 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -8,7 +8,7 @@ #include #include -#include "jsonrpcclient.h" +#include "jsonrpcclient2.h" #include "proxyconnection.h" Q_DECLARE_LOGGING_CATEGORY(dcRemoteProxyClientConnection) From 682e18e9df42b05ea76dbdf2751eb0d69647c9d4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 13 Aug 2018 23:11:04 +0200 Subject: [PATCH 4/8] only install relevant headers --- .../libnymea-remoteproxyclient.pro | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro index 49167c4..6b6172c 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro @@ -2,16 +2,11 @@ include(../nymea-remoteproxy.pri) TEMPLATE = lib TARGET = nymea-remoteproxyclient +target.path = /usr/lib/ include(libnymea-remoteproxyclient.pri) -# install header file with relative subdirectory -for(header, HEADERS) { - path = /usr/include/nymea-remoteproxyclient/$${dirname(header)} - eval(headers_$${path}.files += $${header}) - eval(headers_$${path}.path = $${path}) - eval(INSTALLS *= headers_$${path}) -} +installheaders.files = remoteproxyconnection.h +installheaders.path = /usr/include/nymea-remoteproxyclient/ -target.path = /usr/lib/ -INSTALLS += target +INSTALLS += target installheaders From 36f1fbd6ea1f23109be7e89b50a5f9c04dc56bda Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 13 Aug 2018 23:21:33 +0200 Subject: [PATCH 5/8] forward declare includes --- libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri | 4 ++-- .../{jsonrpcclient2.cpp => proxyjsonrpcclient.cpp} | 2 +- .../{jsonrpcclient2.h => proxyjsonrpcclient.h} | 0 libnymea-remoteproxyclient/remoteproxyconnection.cpp | 2 ++ libnymea-remoteproxyclient/remoteproxyconnection.h | 6 +++--- 5 files changed, 8 insertions(+), 6 deletions(-) rename libnymea-remoteproxyclient/{jsonrpcclient2.cpp => proxyjsonrpcclient.cpp} (99%) rename libnymea-remoteproxyclient/{jsonrpcclient2.h => proxyjsonrpcclient.h} (100%) diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri index 949c4e2..2a7edce 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri @@ -1,14 +1,14 @@ INCLUDEPATH += $${PWD} HEADERS += \ - $${PWD}/jsonrpcclient2.h \ + $${PWD}/proxyjsonrpcclient.h \ $${PWD}/jsonreply.h \ $${PWD}/remoteproxyconnection.h \ $${PWD}/proxyconnection.h \ $${PWD}/websocketconnection.h SOURCES += \ - $${PWD}/jsonrpcclient2.cpp \ + $${PWD}/proxyjsonrpcclient.cpp \ $${PWD}/jsonreply.cpp \ $${PWD}/remoteproxyconnection.cpp \ $${PWD}/proxyconnection.cpp \ diff --git a/libnymea-remoteproxyclient/jsonrpcclient2.cpp b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp similarity index 99% rename from libnymea-remoteproxyclient/jsonrpcclient2.cpp rename to libnymea-remoteproxyclient/proxyjsonrpcclient.cpp index adb68be..b421642 100644 --- a/libnymea-remoteproxyclient/jsonrpcclient2.cpp +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp @@ -1,4 +1,4 @@ -#include "jsonrpcclient2.h" +#include "proxyjsonrpcclient.h" #include "proxyconnection.h" #include diff --git a/libnymea-remoteproxyclient/jsonrpcclient2.h b/libnymea-remoteproxyclient/proxyjsonrpcclient.h similarity index 100% rename from libnymea-remoteproxyclient/jsonrpcclient2.h rename to libnymea-remoteproxyclient/proxyjsonrpcclient.h diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index 7780b13..b476013 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -1,5 +1,7 @@ #include "remoteproxyconnection.h" #include "websocketconnection.h" +#include "proxyjsonrpcclient.h" +#include "proxyconnection.h" Q_LOGGING_CATEGORY(dcRemoteProxyClientConnection, "RemoteProxyClientConnection") Q_LOGGING_CATEGORY(dcRemoteProxyClientConnectionTraffic, "RemoteProxyClientConnectionTraffic") diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.h b/libnymea-remoteproxyclient/remoteproxyconnection.h index bc1a3db..ed625c8 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -8,14 +8,14 @@ #include #include -#include "jsonrpcclient2.h" -#include "proxyconnection.h" - Q_DECLARE_LOGGING_CATEGORY(dcRemoteProxyClientConnection) Q_DECLARE_LOGGING_CATEGORY(dcRemoteProxyClientConnectionTraffic) namespace remoteproxyclient { +class JsonRpcClient; +class ProxyConnection; + class RemoteProxyConnection : public QObject { Q_OBJECT From b2118c8efc1daafd7b7912eb6f984c0614efc756 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 14 Aug 2018 17:16:57 +0200 Subject: [PATCH 6/8] fix debian packaging --- copyright | 24 +++++++++++++++++++ debian/control | 20 ++++++++-------- debian/libnymea-remoteproxy.install | 1 - debian/libnymea-remoteproxyclient.install | 1 - debian/libnymea-remoteproxyclient.install.in | 1 + debian/nymea-remoteproxy.install | 1 - debian/nymea-remoteproxy.install.in | 2 ++ debian/rules | 20 +++++++--------- libnymea-remoteproxy/libnymea-remoteproxy.pro | 2 +- .../libnymea-remoteproxyclient.pro | 2 +- 10 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 copyright delete mode 100644 debian/libnymea-remoteproxy.install delete mode 100644 debian/libnymea-remoteproxyclient.install create mode 100644 debian/libnymea-remoteproxyclient.install.in delete mode 100644 debian/nymea-remoteproxy.install create mode 100644 debian/nymea-remoteproxy.install.in diff --git a/copyright b/copyright new file mode 100644 index 0000000..a881845 --- /dev/null +++ b/copyright @@ -0,0 +1,24 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: nymea-remoteproxy +Upstream-Contact: Simon Stürz +Copyright: 2018, guh GmbH +Download: http://www.github.com/guh/nymea-remoteproxy +Source: https://github.com/guh/nymea-remoteproxy.git + + +License: GPL-2+ + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL-2'. + +License: LGPL-2.1 + On Debian systems, the complete text of the GNU Lesser General + Public License can be found in `/usr/share/common-licenses/LGPL-2.1'. + +License: LGPL-3 + On Debian systems, the complete text of the GNU Lesser General + Public License can be found in `/usr/share/common-licenses/LGPL-3'. + + +Files: * +License: GPL-2+ +Copyright: 2018, Simon Stürz diff --git a/debian/control b/debian/control index 0bf0738..f59b7ed 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: nymea-remoteproxy Section: utils Priority: options -Maintainer: Michael Zanetti +Maintainer: Simon Stürz Build-depends: debhelper (>= 0.0.0), libqt5websockets5-dev, Standards-Version: 3.9.3 @@ -13,19 +13,19 @@ Depends: ${shlibs:Depends}, Description: The nymea remote proxy server The nymea remote proxy server -Package: libnymea-remoteproxy -Architecture: any -Depends: ${shlibs:Depends}, - ${misc:Depends}, -Description: The nymea remote proxy server - The nymea remote proxy server - Package: libnymea-remoteproxyclient Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, -Description: The nymea remote proxy server - The nymea remote proxy server +Description: The nymea remote proxy server client lib + The nymea remote proxy server client lib + +Package: libnymea-remoteproxyclient-dev +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: The nymea remote proxy server client lib, development files + The nymea remote proxy server client lib, development files Package: nymea-remoteproxy-tests Architecture: any diff --git a/debian/libnymea-remoteproxy.install b/debian/libnymea-remoteproxy.install deleted file mode 100644 index 5833199..0000000 --- a/debian/libnymea-remoteproxy.install +++ /dev/null @@ -1 +0,0 @@ -usr/lib/libnymea-remoteproxy.so* diff --git a/debian/libnymea-remoteproxyclient.install b/debian/libnymea-remoteproxyclient.install deleted file mode 100644 index 894b860..0000000 --- a/debian/libnymea-remoteproxyclient.install +++ /dev/null @@ -1 +0,0 @@ -usr/lib/libnymea-remoteproxyclient.so* diff --git a/debian/libnymea-remoteproxyclient.install.in b/debian/libnymea-remoteproxyclient.install.in new file mode 100644 index 0000000..8067a26 --- /dev/null +++ b/debian/libnymea-remoteproxyclient.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/libnymea-remoteproxyclient.so.* diff --git a/debian/nymea-remoteproxy.install b/debian/nymea-remoteproxy.install deleted file mode 100644 index 3563d18..0000000 --- a/debian/nymea-remoteproxy.install +++ /dev/null @@ -1 +0,0 @@ -usr/bin/nymea-remoteproxy diff --git a/debian/nymea-remoteproxy.install.in b/debian/nymea-remoteproxy.install.in new file mode 100644 index 0000000..b0bd768 --- /dev/null +++ b/debian/nymea-remoteproxy.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/libnymea-remoteproxy.so* +usr/bin/nymea-remoteproxy diff --git a/debian/rules b/debian/rules index 6e863b9..b21fc88 100755 --- a/debian/rules +++ b/debian/rules @@ -2,16 +2,14 @@ DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) + +PREPROCESS_FILES := $(wildcard debian/*.in) + +$(PREPROCESS_FILES:.in=): %: %.in + sed 's,/@DEB_HOST_MULTIARCH@,$(DEB_HOST_MULTIARCH:%=/%),g' $< > $@ + +override_dh_install: $(PREPROCESS_FILES:.in=) + dh_install + %: dh $@ - -#override_dh_auto_build: -# JANUSP=/usr make -j1 - -#override_dh_auto_install: -# install build/janus_guhio.so debian/janus-guh-plugin/lib/$(DEB_HOST_MULTIARCH)/janus/plugins/ -# install -m 0644 conf/janus.plugin.guhio.cfg debian/janus-guh-plugin/etc/janus/ - -override_dh_auto_check: - LD_LIBRARY_PATH=libnymea-remoteproxy dh_auto_check - diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index 546b810..a0867aa 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -56,5 +56,5 @@ for(header, HEADERS) { eval(INSTALLS *= headers_$${path}) } -target.path = /usr/lib/ +target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH') INSTALLS += target diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro index 6b6172c..d9a3f1d 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro @@ -2,7 +2,7 @@ include(../nymea-remoteproxy.pri) TEMPLATE = lib TARGET = nymea-remoteproxyclient -target.path = /usr/lib/ +target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH') include(libnymea-remoteproxyclient.pri) From 2605e7a78abc705d65acd7abb060b22b87f9b8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 14 Aug 2018 19:00:28 +0200 Subject: [PATCH 7/8] Add first client version for testing --- README.md | 307 ------------------ client/client.pro | 1 + client/main.cpp | 19 +- client/proxyclient.cpp | 30 +- client/proxyclient.h | 21 ++ .../authentication/authenticationprocess.cpp | 87 ++++- .../authentication/authenticationprocess.h | 1 + .../authentication/authenticationreply.cpp | 3 +- .../authentication/awsauthenticator.cpp | 6 +- .../authentication/dummyauthenticator.cpp | 30 ++ .../authentication/dummyauthenticator.h | 27 ++ libnymea-remoteproxy/libnymea-remoteproxy.pro | 6 +- libnymea-remoteproxy/loggingcategories.cpp | 3 +- libnymea-remoteproxy/loggingcategories.h | 3 +- server/main.cpp | 25 +- tests/mockauthenticator.cpp | 4 +- 16 files changed, 219 insertions(+), 354 deletions(-) create mode 100644 libnymea-remoteproxy/authentication/dummyauthenticator.cpp create mode 100644 libnymea-remoteproxy/authentication/dummyauthenticator.h diff --git a/README.md b/README.md index aef9bb0..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,307 +0,0 @@ -# nymea remote proxy server ----------------------------------------------- - -The nymea remote proxy server is the meeting point of nymea servers and nymea clients in order to establishing a secure remote connection. - -# Build - -In order to build the proxy server you need to install the qt default package. - - apt install qt5-default - -Change into the source directory and run following commands - - cd nymea-remoteproxy - mkdir build - cd build - qmake ../ - make -j$(nproc) - -In the build directory you can find the resulting library and binary files. - -If you want to start the proxy server from the build directory, you need to export the library path before starting the application: - - - $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/libnymea-remoteproxy:$(pwd)/libnymea-remoteproxyclient - $ ./server/nymea-remoteproxy -c ../nymea-remoteproxy/tests/test-certificate.crt -k ../nymea-remoteproxy/tests/test-certificate.key - - -## AWS SDK - -Get the latest source code and build dependecies - - $ apt update - $ apt install git build-essential cmake libcurl4-openssl-dev libssl-dev uuid-dev zlib1g-dev libpulse-dev - - $ git clone https://github.com/aws/aws-sdk-cpp.git - -Create the build and install folder - - $ cd aws-sdk-cpp - $ mkdir -p build/install - $ cd build - - $ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_ONLY="lambda" -DCMAKE_INSTALL_PREFIX=$(pwd)/install ../ - $ make -j$(nproc) - -Install build output into install directory - - $ make install - -#### Building debian package - - $ git clone https://github.com/aws/aws-sdk-cpp.git - $ cd aws-sdk-cpp - - $ git clone git@gitlab.guh.io:cloud/aws-sdk-cpp-debian.git debian - - $ crossbuilder - - -# Install - - - -# Configure - - - -# Test - -In order to run the test, you can call `make check` in the build directory or run the resulting executable: - - $ nymea-remoteproxy-tests - - -## Test coverage report - -If you want to create a line coverage report from the tests simply run following command in the source directory: - - - $ apt install lcov gcovr - $ ./create-coverage-html.sh - -The resulting coverage report will be place in the `coverage-html` directory. - -# Usage - -In order to get information about the server you can start the command with the `--help` parameter. - - $ nymea-remoteproxy --help - - Usage: nymea-remoteproxy [options] - - The nymea remote proxy server. This server allowes nymea-cloud users and registered nymea deamons to establish a tunnel connection. - - Server version: 0.0.1 - API version: 0.1 - - Copyright © 2018 Simon Stürz - - - Options: - -h, --help Displays this help. - -v, --version Displays version information. - -l, --logging Write log file to the given logfile. - -s, --server The server address this proxy will - listen on. Default is 127.0.0.1 - -p, --port The proxy server port. Default is - 1212 - -c, --certificate The path to the SSL certificate used - for this proxy server. - -k, --certificate-key The path to the SSL certificate key - used for this proxy server. - -a, --authentication-server The server url of the AWS - authentication server. - - -# Server API - -Once a client connects to the proxy server, he must authenticate him self by passing the token received from the nymea-cloud mqtt connection request. - -## Message format - -#### Request - - { - "id": integer, - "method": "Namespace.Method", - "o:params" { } - } - -#### Response - - { - "id": integer, - "status": "string", - "o:params" { }, - "o:error": "string" - } - -#### Notification - - { - "id": integer, - "notification": "Namespace.Notification", - "o:params" { } - } - -## Say Hello - -#### Request - - { - "id": 0, - "method": "RemoteProxy.Hello" - } - - -#### Response - - { - "id": 0, - "params": { - "apiVersion": "0.1", - "name": "nymea-remoteproxy-testserver", - "server": "nymea-remoteproxy", - "version": "0.0.1" - }, - "status": "success" - } - -## Authenticate the connection - -The first data a client **must** send to the proxy server is the authentication request. This request contains the token which will be verified agains the nymea-cloud infrastructure. - -#### Request - - { - "id": 1, - "method": "Authentication.Authenticate", - "params": { - "id": "string", - "name": "string", - "token": "tokenstring" - } - } - -#### Response - -* **On Success**: If the token was authenticated successfully, the response will look like this: - - { - "id": 1, - "status": "success" - } - -* **On Failure** If the token was invalid, the response will look like this and the server will close the connection immediatly: - - { - "id": 1, - "status": "error", - "error": "Invalid token. You are not allowed to use this server." - } - -#### Tunnel established - -Once the other client is here and ready, the server will send a notification to the clients indicating that the tunnel has been established successfully. This message is the last data comming from the proxy server. - -> **Important:** Any data traffic following after this notification comes from the tunnel endpoint, __not__ from the __proxy server__ any more. - - { - "id": "0", - "notification": "RemoteProxy.TunnelEstablished", - "params": { - "name": "String", - "uuid": "String" - } - } - - -## Introspect the API - - -#### Request - - { - "id": 0, - "method": "RemoteProxy.Introspect" - } - -#### Response - - - { - "id": 0, - "params": { - "methods": { - "Authentication.Authenticate": { - "description": "Authenticate this connection. The returned AuthenticationError informs about the result. If the authentication was not successfull, the server will close the connection immediatly after sending the error response. The given id should be a unique id the other tunnel client can understand. Once the authentication was successfull, you can wait for the RemoteProxy.TunnelEstablished notification. If you send any data before getting this notification, the server will close the connection. If the tunnel client does not show up within 10 seconds, the server will close the connection.", - "params": { - "name": "String", - "token": "String", - "uuid": "String" - }, - "returns": { - "authenticationError": "$ref:AuthenticationError" - } - }, - "RemoteProxy.Hello": { - "description": "Once connected to this server, a client can get information about the server by saying Hello. The response informs the client about this proxy server.", - "params": { - }, - "returns": { - "apiVersion": "String", - "name": "String", - "server": "String", - "version": "String" - } - }, - "RemoteProxy.Introspect": { - "description": "Introspect this API.", - "params": { - }, - "returns": { - "methods": "Object", - "notifications": "Object", - "types": "Object" - } - } - }, - "notifications": { - "RemoteProxy.TunnelEstablished": { - "description": "Emitted whenever the tunnel has been established successfully. This is the last message from the remote proxy server! Any following data will be from the other tunnel client until the connection will be closed. The parameter contain some information about the other tunnel client.", - "params": { - "name": "String", - "uuid": "String" - } - } - }, - "types": { - "AuthenticationError": [ - "AuthenticationErrorNoError", - "AuthenticationErrorUnknown", - "AuthenticationErrorTimeout", - "AuthenticationErrorAborted", - "AuthenticationErrorAuthenticationFailed", - "AuthenticationErrorAuthenticationServerNotResponding" - ], - "BasicType": [ - "Uuid", - "String", - "Int", - "UInt", - "Double", - "Bool", - "Variant", - "Object" - ] - } - }, - "status": "success" - } - - -# License - -This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. diff --git a/client/client.pro b/client/client.pro index 2a15c3d..c50791f 100644 --- a/client/client.pro +++ b/client/client.pro @@ -1,4 +1,5 @@ include(../nymea-remoteproxy.pri) +include(../libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri) TARGET = nymea-remoteproxy-client TEMPLATE = app diff --git a/client/main.cpp b/client/main.cpp index 224b8b9..ab20d0c 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,19 +1,11 @@ -#include #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include #include #include +#include "proxyclient.h" + int main(int argc, char *argv[]) { @@ -38,10 +30,15 @@ int main(int argc, char *argv[]) QCommandLineOption tokenOption(QStringList() << "t" << "token", "The AWS token for authentication.", "token"); parser.addOption(tokenOption); - parser.process(application); + if (!parser.isSet(tokenOption)) { + qWarning() << "Please specify the token for authentication." << endl; + exit(-1); + } + ProxyClient client; + client.start(parser.value(tokenOption)); return application.exec(); } diff --git a/client/proxyclient.cpp b/client/proxyclient.cpp index e02018e..91d1001 100644 --- a/client/proxyclient.cpp +++ b/client/proxyclient.cpp @@ -1,6 +1,34 @@ #include "proxyclient.h" -ProxyClient::ProxyClient(QObject *parent) : QObject(parent) + +ProxyClient::ProxyClient(QObject *parent) : + QObject(parent) { + m_connection = new RemoteProxyConnection(QUuid::createUuid(), "nymea-remoteproxy-client", RemoteProxyConnection::ConnectionTypeWebSocket, this); + m_connection->setInsecureConnection(true); + connect(m_connection, &RemoteProxyConnection::ready, this, &ProxyClient::onClientReady); + connect(m_connection, &RemoteProxyConnection::authenticated, this, &ProxyClient::onAuthenticationFinished); + connect(m_connection, &RemoteProxyConnection::errorOccured, this, &ProxyClient::onErrorOccured); } + +void ProxyClient::onErrorOccured(RemoteProxyConnection::Error error) +{ + qDebug() << "Error occured" << error << m_connection->errorString(); +} + +void ProxyClient::onClientReady() +{ + m_connection->authenticate(m_token); +} + +void ProxyClient::onAuthenticationFinished() +{ + qDebug() << "Authentication finished."; +} + +void ProxyClient::start(const QString &token) +{ + m_token = token; + m_connection->connectServer(m_hostAddress, static_cast(m_port)); +} diff --git a/client/proxyclient.h b/client/proxyclient.h index b5901a6..74c54f9 100644 --- a/client/proxyclient.h +++ b/client/proxyclient.h @@ -3,6 +3,9 @@ #include +#include "remoteproxyconnection.h" + +using namespace remoteproxyclient; class ProxyClient : public QObject { @@ -10,9 +13,27 @@ class ProxyClient : public QObject public: explicit ProxyClient(QObject *parent = nullptr); + void setHostAddress(const QHostAddress &hostAddress); + void setPort(const int &port); + +private: + QString m_token; + QHostAddress m_hostAddress = QHostAddress::LocalHost; + int m_port = 1212; + + RemoteProxyConnection *m_connection = nullptr; + signals: + +private slots: + void onErrorOccured(RemoteProxyConnection::Error error); + void onClientReady(); + void onAuthenticationFinished(); + public slots: + void start(const QString &token); + }; #endif // PROXYCLIENT_H diff --git a/libnymea-remoteproxy/authentication/authenticationprocess.cpp b/libnymea-remoteproxy/authentication/authenticationprocess.cpp index f0f9a43..8f64df8 100644 --- a/libnymea-remoteproxy/authentication/authenticationprocess.cpp +++ b/libnymea-remoteproxy/authentication/authenticationprocess.cpp @@ -2,6 +2,7 @@ #include "loggingcategories.h" #include +#include #include #include @@ -11,7 +12,9 @@ AuthenticationProcess::AuthenticationProcess(QNetworkAccessManager *manager, QOb QObject(parent), m_manager(manager) { - + m_process = new QProcess(this); + m_process->setProcessChannelMode(QProcess::MergedChannels); + connect(m_process, static_cast(&QProcess::finished), this, &AuthenticationProcess::onProcessFinished); } void AuthenticationProcess::useDynamicCredentials(bool dynamicCredentials) @@ -28,20 +31,14 @@ void AuthenticationProcess::requestDynamicCredentials() void AuthenticationProcess::startVerificationProcess() { if (m_process->state() != QProcess::NotRunning) { - qCWarning(dcAuthenticator()) << "Authentication process already running. Killing the running process and restart."; + qCWarning(dcAuthenticationProcess()) << "Authentication process already running. Killing the running process and restart."; m_process->kill(); - m_process->deleteLater(); - m_process = nullptr; } - m_process = new QProcess(this); - connect(m_process, static_cast(&QProcess::finished), this, &AuthenticationProcess::onProcessFinished); - // Create request map QVariantMap request; request.insert("token", m_token); - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("AWS_DEFAULT_REGION", "eu-west-1"); @@ -51,15 +48,15 @@ void AuthenticationProcess::startVerificationProcess() env.insert("AWS_SESSION_TOKEN", m_awsSessionToken); } - // Output file name + // FIXME: check how to clean this up properly m_resultFileName = "/tmp/" + QUuid::createUuid().toString().remove("{").remove("}").remove("-") + ".json"; - qCDebug(dcAuthenticator()) << "Start authenticator process and store result in" << m_resultFileName; + qCDebug(dcAuthentication()) << "Start authenticator process and store result in" << m_resultFileName; m_process->start("aws", { "lambda", "invoke", "--function-name", "system-services-authorizer-dev-checkToken", "--invocation-type", "RequestResponse", - "--payload", QString("'%1'").arg(QString::fromUtf8(QJsonDocument::fromVariant(request).toJson())), + "--payload", QString::fromUtf8(QJsonDocument::fromVariant(request).toJson()), m_resultFileName }); } @@ -70,7 +67,7 @@ void AuthenticationProcess::onDynamicCredentialsReady() reply->deleteLater(); if (reply->error()) { - qCWarning(dcAuthenticator()) << "Dynamic credentials reply error: " << reply->errorString(); + qCWarning(dcAuthenticationProcess()) << "Dynamic credentials reply error: " << reply->errorString(); emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); return; } @@ -81,14 +78,13 @@ void AuthenticationProcess::onDynamicCredentialsReady() QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if(error.error != QJsonParseError::NoError) { - qCWarning(dcAuthenticator()) << "Failed to parse dynamic credentials reply data" << data << ":" << error.errorString(); + qCWarning(dcAuthenticationProcess()) << "Failed to parse dynamic credentials reply data" << data << ":" << error.errorString(); emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); return; } - QVariantMap response = jsonDoc.toVariant().toMap(); - qCDebug(dcAuthenticator()) << "-->" << response; + qCDebug(dcAuthentication()) << "-->" << response; m_awsAccessKeyId = response.value("AccessKeyId").toString(); m_awsSecretAccessKey = response.value("SecretAccessKey").toString(); @@ -99,18 +95,75 @@ void AuthenticationProcess::onDynamicCredentialsReady() void AuthenticationProcess::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { - qCDebug(dcAuthenticator()) << "Authentication process finished" << exitCode << exitStatus; + + if (exitStatus == QProcess::CrashExit) { + qCWarning(dcAuthenticationProcess()) << "Authentication process crashed:" << endl << qUtf8Printable(m_process->readAll()); + } + + if (exitCode != 0) { + qCWarning(dcAuthenticationProcess()) << "The authentication process finished with error" << exitCode << endl << qUtf8Printable(m_process->readAll()); + } + + qCDebug(dcAuthenticationProcess()) << "Finished successfully"; + QFile resultFile(m_resultFileName); + + if (!resultFile.exists()) { + qCWarning(dcAuthenticationProcess()) << "The process output file does not exist."; + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + if (!resultFile.open(QIODevice::ReadWrite)) { + qCWarning(dcAuthenticationProcess()) << "Could not open result file from process:" << resultFile.errorString(); + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + QByteArray resultData = resultFile.readAll(); + + resultFile.close(); + if (!resultFile.remove()) { + qCWarning(dcAuthenticationProcess()) << "Could not clean up result file from process:" << resultFile.errorString(); + return; + } + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(resultData, &error); + + if(error.error != QJsonParseError::NoError) { + qCWarning(dcAuthenticationProcess()) << "Failed to parse lambda invoke result data" << resultData << ":" << error.errorString(); + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + QVariantMap response = jsonDoc.toVariant().toMap(); + qCDebug(dcAuthenticationProcess()) << "-->" << response; + if (response.isEmpty()) { + qCWarning(dcAuthenticationProcess()) << "Received empty lambda result."; + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + bool isValid = response.value("isValid").toBool(); + + if (isValid) { + emit authenticationFinished(Authenticator::AuthenticationErrorNoError); + } else { + emit authenticationFinished(Authenticator::AuthenticationErrorAuthenticationFailed); + } + } void AuthenticationProcess::authenticate(const QString &token) { - qCDebug(dcAuthenticator()) << "Start authentication process for token"; + qCDebug(dcAuthenticationProcess()) << "Start authentication process for token" << token; m_token = token; if (m_dynamicCredentials) { // Request the access information requestDynamicCredentials(); } else { + // Direct call aws cli and assume the credentials will be provided static startVerificationProcess(); } } diff --git a/libnymea-remoteproxy/authentication/authenticationprocess.h b/libnymea-remoteproxy/authentication/authenticationprocess.h index 35a2192..e0f0d38 100644 --- a/libnymea-remoteproxy/authentication/authenticationprocess.h +++ b/libnymea-remoteproxy/authentication/authenticationprocess.h @@ -31,6 +31,7 @@ private: void requestDynamicCredentials(); void startVerificationProcess(); + void cleanUp(); signals: void authenticationFinished(Authenticator::AuthenticationError error); diff --git a/libnymea-remoteproxy/authentication/authenticationreply.cpp b/libnymea-remoteproxy/authentication/authenticationreply.cpp index 6e0af1c..b6bb389 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.cpp +++ b/libnymea-remoteproxy/authentication/authenticationreply.cpp @@ -42,7 +42,8 @@ void AuthenticationReply::setError(Authenticator::AuthenticationError error) void AuthenticationReply::setFinished() { m_timer->stop(); - emit finished(); + // emit in next event loop + QTimer::singleShot(0, this, &AuthenticationReply::finished); } void AuthenticationReply::onTimeout() diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.cpp b/libnymea-remoteproxy/authentication/awsauthenticator.cpp index 86cc876..d088e40 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.cpp +++ b/libnymea-remoteproxy/authentication/awsauthenticator.cpp @@ -14,7 +14,7 @@ AwsAuthenticator::AwsAuthenticator(QObject *parent) : AwsAuthenticator::~AwsAuthenticator() { - qCDebug(dcAuthenticator()) << "Shutting down" << name(); + qCDebug(dcAuthentication()) << "Shutting down" << name(); } QString AwsAuthenticator::name() const @@ -30,12 +30,12 @@ void AwsAuthenticator::onAuthenticationProcessFinished(Authenticator::Authentica setReplyError(reply, error); setReplyFinished(reply); - qCDebug(dcAuthenticator()) << "" << error; + qCDebug(dcAuthentication()) << "" << error; } AuthenticationReply *AwsAuthenticator::authenticate(ProxyClient *proxyClient) { - qCDebug(dcAuthenticator()) << name() << "Start authenticating" << proxyClient << "using token" << proxyClient->token(); + qCDebug(dcAuthentication()) << name() << "Start authenticating" << proxyClient << "using token" << proxyClient->token(); AuthenticationReply *reply = createAuthenticationReply(proxyClient, this); AuthenticationProcess *process = new AuthenticationProcess(m_manager, this); diff --git a/libnymea-remoteproxy/authentication/dummyauthenticator.cpp b/libnymea-remoteproxy/authentication/dummyauthenticator.cpp new file mode 100644 index 0000000..ac38e6e --- /dev/null +++ b/libnymea-remoteproxy/authentication/dummyauthenticator.cpp @@ -0,0 +1,30 @@ +#include "dummyauthenticator.h" +#include "loggingcategories.h" + +#include + +namespace remoteproxy { + +DummyAuthenticator::DummyAuthenticator(QObject *parent) : + Authenticator(parent) +{ + +} + +QString DummyAuthenticator::name() const +{ + return "Dummy authenticator"; +} + +AuthenticationReply *DummyAuthenticator::authenticate(ProxyClient *proxyClient) +{ + qCDebug(dcAuthentication()) << name() << "validate" << proxyClient; + qCWarning(dcAuthentication()) << "Attention: This authenticator will always succeed! This is a security risk and was enabled explitly!"; + AuthenticationReply *reply = createAuthenticationReply(proxyClient, this); + + setReplyError(reply, AuthenticationErrorNoError); + setReplyFinished(reply); + return reply; +} + +} diff --git a/libnymea-remoteproxy/authentication/dummyauthenticator.h b/libnymea-remoteproxy/authentication/dummyauthenticator.h new file mode 100644 index 0000000..2adf1d1 --- /dev/null +++ b/libnymea-remoteproxy/authentication/dummyauthenticator.h @@ -0,0 +1,27 @@ +#ifndef DUMMYAUTHENTICATOR_H +#define DUMMYAUTHENTICATOR_H + +#include + +#include "proxyclient.h" +#include "authenticator.h" + +namespace remoteproxy { + +class DummyAuthenticator : public Authenticator +{ + Q_OBJECT +public: + explicit DummyAuthenticator(QObject *parent = nullptr); + ~DummyAuthenticator() override = default; + + QString name() const override; + +public slots: + AuthenticationReply *authenticate(ProxyClient *proxyClient) override; + +}; + +} + +#endif // DUMMYAUTHENTICATOR_H diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index a0867aa..462cc69 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -26,7 +26,8 @@ HEADERS += \ authentication/authenticationreply.h \ proxyconfiguration.h \ tunnelconnection.h \ - authentication/authenticationprocess.h + authentication/authenticationprocess.h \ + authentication/dummyauthenticator.h SOURCES += \ engine.cpp \ @@ -45,7 +46,8 @@ SOURCES += \ authentication/authenticationreply.cpp \ proxyconfiguration.cpp \ tunnelconnection.cpp \ - authentication/authenticationprocess.cpp + authentication/authenticationprocess.cpp \ + authentication/dummyauthenticator.cpp # install header file with relative subdirectory diff --git a/libnymea-remoteproxy/loggingcategories.cpp b/libnymea-remoteproxy/loggingcategories.cpp index f728eab..60acc03 100644 --- a/libnymea-remoteproxy/loggingcategories.cpp +++ b/libnymea-remoteproxy/loggingcategories.cpp @@ -6,7 +6,8 @@ Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") Q_LOGGING_CATEGORY(dcJsonRpcTraffic, "JsonRpcTraffic") Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer") Q_LOGGING_CATEGORY(dcWebSocketServerTraffic, "WebSocketServerTraffic") -Q_LOGGING_CATEGORY(dcAuthenticator, "Authenticator") +Q_LOGGING_CATEGORY(dcAuthentication, "Authentication") +Q_LOGGING_CATEGORY(dcAuthenticationProcess, "AuthenticationProcess") Q_LOGGING_CATEGORY(dcProxyServer, "ProxyServer") Q_LOGGING_CATEGORY(dcProxyServerTraffic, "ProxyServerTraffic") diff --git a/libnymea-remoteproxy/loggingcategories.h b/libnymea-remoteproxy/loggingcategories.h index 6715c3e..b1a57ee 100644 --- a/libnymea-remoteproxy/loggingcategories.h +++ b/libnymea-remoteproxy/loggingcategories.h @@ -10,7 +10,8 @@ Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpcTraffic) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServerTraffic) -Q_DECLARE_LOGGING_CATEGORY(dcAuthenticator) +Q_DECLARE_LOGGING_CATEGORY(dcAuthentication) +Q_DECLARE_LOGGING_CATEGORY(dcAuthenticationProcess) Q_DECLARE_LOGGING_CATEGORY(dcProxyServer) Q_DECLARE_LOGGING_CATEGORY(dcProxyServerTraffic) diff --git a/server/main.cpp b/server/main.cpp index 530fdae..da79df0 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -21,6 +21,7 @@ #include "loggingcategories.h" #include "proxyconfiguration.h" #include "authentication/awsauthenticator.h" +#include "authentication/dummyauthenticator.h" using namespace remoteproxy; @@ -96,7 +97,7 @@ int main(int argc, char *argv[]) s_loggingFilters.insert("JsonRpcTraffic", true); s_loggingFilters.insert("WebSocketServer", true); s_loggingFilters.insert("WebSocketServerTraffic", false); - s_loggingFilters.insert("Authenticator", true); + s_loggingFilters.insert("Authentication", true); s_loggingFilters.insert("ProxyServer", true); // command line parser @@ -120,7 +121,10 @@ int main(int argc, char *argv[]) "assumes there are static AWS credentials provided to aws-cli."); parser.addOption(developmentOption); - QCommandLineOption configOption(QStringList() << "c" <<"config", "The path to the proxy server configuration file. The default is /etc/nymea-remoteproxy/nymea-remoteproxy.conf", "configuration"); + QCommandLineOption mockAuthenticatorOption(QStringList() << "m" << "mock-authenticator", "Start the server using a mock authenticator which returns always true."); + parser.addOption(mockAuthenticatorOption); + + QCommandLineOption configOption(QStringList() << "c" <<"configuration", "The path to the proxy server configuration file. The default is /etc/nymea-remoteproxy/nymea-remoteproxy.conf", "configuration"); configOption.setDefaultValue("/etc/nymea-remoteproxy/nymea-remoteproxy.conf"); parser.addOption(configOption); @@ -194,21 +198,26 @@ int main(int argc, char *argv[]) exit(-1); } + qCDebug(dcApplication()) << "=========================================================="; + qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion(); + qCDebug(dcApplication()) << "=========================================================="; + if (parser.isSet(developmentOption)) { qCWarning(dcApplication()) << "##########################################################"; qCWarning(dcApplication()) << "# DEVELOPMENT MODE #"; qCWarning(dcApplication()) << "##########################################################"; } - qCDebug(dcApplication()) << "=========================================================="; - qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion(); - qCDebug(dcApplication()) << "=========================================================="; - if (s_loggingEnabled) qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName(); - // Create default authenticator - AwsAuthenticator *authenticator = new AwsAuthenticator(nullptr); + Authenticator *authenticator = nullptr; + if (parser.isSet(mockAuthenticatorOption)) { + authenticator = qobject_cast(new DummyAuthenticator(nullptr)); + } else { + // Create default authenticator + authenticator = qobject_cast(new AwsAuthenticator(nullptr)); + } // Configure and start the engines Engine::instance()->setConfiguration(configuration); diff --git a/tests/mockauthenticator.cpp b/tests/mockauthenticator.cpp index 2bec341..0efb2cf 100644 --- a/tests/mockauthenticator.cpp +++ b/tests/mockauthenticator.cpp @@ -29,7 +29,7 @@ void MockAuthenticator::replyFinished() MockAuthenticationReply *reply = static_cast(sender()); reply->deleteLater(); - qCDebug(dcAuthenticator()) << name() << "Authentication finished."; + qCDebug(dcAuthentication()) << name() << "Authentication finished."; setReplyError(reply->authenticationReply(), reply->error()); setReplyFinished(reply->authenticationReply()); @@ -37,7 +37,7 @@ void MockAuthenticator::replyFinished() AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient) { - qCDebug(dcAuthenticator()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token(); + qCDebug(dcAuthentication()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token(); AuthenticationReply *authenticationReply = createAuthenticationReply(proxyClient, this); From 37499a314211319af86237c6eaaa2b1f7433daaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 14 Aug 2018 19:06:21 +0200 Subject: [PATCH 8/8] Add proxy client application to debian package --- .gitignore | 1 + debian/control | 7 +++++++ debian/nymea-remoteproxy-client.install.in | 1 + 3 files changed, 9 insertions(+) create mode 100644 debian/nymea-remoteproxy-client.install.in diff --git a/.gitignore b/.gitignore index 745c4dd..67809ed 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ Thumbs.db *.exe coverage-html +client/nymea-remoteproxy-client diff --git a/debian/control b/debian/control index f59b7ed..e33ab80 100644 --- a/debian/control +++ b/debian/control @@ -13,6 +13,13 @@ Depends: ${shlibs:Depends}, Description: The nymea remote proxy server The nymea remote proxy server +Package: nymea-remoteproxy-client +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: The nymea remote proxy client for testing + The nymea remote proxy client for testing + Package: libnymea-remoteproxyclient Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-remoteproxy-client.install.in b/debian/nymea-remoteproxy-client.install.in new file mode 100644 index 0000000..ad9aba1 --- /dev/null +++ b/debian/nymea-remoteproxy-client.install.in @@ -0,0 +1 @@ +usr/bin/nymea-remoteproxy-client