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/README.md b/README.md index d0e1af6..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,275 +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 - - -# 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 new file mode 100644 index 0000000..c50791f --- /dev/null +++ b/client/client.pro @@ -0,0 +1,18 @@ +include(../nymea-remoteproxy.pri) +include(../libnymea-remoteproxyclient/libnymea-remoteproxyclient.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..ab20d0c --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +#include "proxyclient.h" + +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); + + 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 new file mode 100644 index 0000000..91d1001 --- /dev/null +++ b/client/proxyclient.cpp @@ -0,0 +1,34 @@ +#include "proxyclient.h" + + +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 new file mode 100644 index 0000000..74c54f9 --- /dev/null +++ b/client/proxyclient.h @@ -0,0 +1,39 @@ +#ifndef PROXYCLIENT_H +#define PROXYCLIENT_H + +#include + +#include "remoteproxyconnection.h" + +using namespace remoteproxyclient; + +class ProxyClient : public QObject +{ + Q_OBJECT +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/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/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..e33ab80 --- /dev/null +++ b/debian/control @@ -0,0 +1,42 @@ +Source: nymea-remoteproxy +Section: utils +Priority: options +Maintainer: Simon Stürz +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: 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}, + ${misc:Depends}, +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 +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: The nymea remote proxy server tests + The nymea remote proxy server tests 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-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 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.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 new file mode 100755 index 0000000..b21fc88 --- /dev/null +++ b/debian/rules @@ -0,0 +1,15 @@ +#!/usr/bin/make -f + +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 $@ 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/authentication/authenticationprocess.cpp b/libnymea-remoteproxy/authentication/authenticationprocess.cpp new file mode 100644 index 0000000..8f64df8 --- /dev/null +++ b/libnymea-remoteproxy/authentication/authenticationprocess.cpp @@ -0,0 +1,171 @@ +#include "authenticationprocess.h" +#include "loggingcategories.h" + +#include +#include +#include +#include + +namespace remoteproxy { + +AuthenticationProcess::AuthenticationProcess(QNetworkAccessManager *manager, QObject *parent) : + 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) +{ + 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(dcAuthenticationProcess()) << "Authentication process already running. Killing the running process and restart."; + m_process->kill(); + } + + // 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); + } + + // FIXME: check how to clean this up properly + m_resultFileName = "/tmp/" + QUuid::createUuid().toString().remove("{").remove("}").remove("-") + ".json"; + + 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::fromUtf8(QJsonDocument::fromVariant(request).toJson()), + m_resultFileName }); + +} + +void AuthenticationProcess::onDynamicCredentialsReady() +{ + QNetworkReply *reply = static_cast(sender()); + reply->deleteLater(); + + if (reply->error()) { + qCWarning(dcAuthenticationProcess()) << "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(dcAuthenticationProcess()) << "Failed to parse dynamic credentials reply data" << data << ":" << error.errorString(); + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + QVariantMap response = jsonDoc.toVariant().toMap(); + qCDebug(dcAuthentication()) << "-->" << 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) +{ + + 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(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 new file mode 100644 index 0000000..e0f0d38 --- /dev/null +++ b/libnymea-remoteproxy/authentication/authenticationprocess.h @@ -0,0 +1,50 @@ +#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(); + void cleanUp(); + +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..b6bb389 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,19 +41,25 @@ void AuthenticationReply::setError(Authenticator::AuthenticationError error) void AuthenticationReply::setFinished() { - emit finished(); + m_timer->stop(); + // emit in next event loop + QTimer::singleShot(0, this, &AuthenticationReply::finished); } 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 5b601a7..d088e40 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,20 +6,47 @@ namespace remoteproxy { AwsAuthenticator::AwsAuthenticator(QObject *parent) : - Authenticator(parent) + Authenticator(parent), + m_manager(new QNetworkAccessManager(this)) { } +AwsAuthenticator::~AwsAuthenticator() +{ + qCDebug(dcAuthentication()) << "Shutting down" << name(); +} + 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(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); + 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 06da9ee..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 { @@ -13,10 +15,17 @@ class AwsAuthenticator : public Authenticator Q_OBJECT public: explicit AwsAuthenticator(QObject *parent = nullptr); - ~AwsAuthenticator() override = default; + ~AwsAuthenticator() override; 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/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/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 ec62305..462cc69 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,9 @@ HEADERS += \ authentication/awsauthenticator.h \ authentication/authenticationreply.h \ proxyconfiguration.h \ - tunnelconnection.h + tunnelconnection.h \ + authentication/authenticationprocess.h \ + authentication/dummyauthenticator.h SOURCES += \ engine.cpp \ @@ -37,7 +45,9 @@ SOURCES += \ authentication/awsauthenticator.cpp \ authentication/authenticationreply.cpp \ proxyconfiguration.cpp \ - tunnelconnection.cpp + tunnelconnection.cpp \ + authentication/authenticationprocess.cpp \ + authentication/dummyauthenticator.cpp # install header file with relative subdirectory @@ -47,3 +57,6 @@ for(header, HEADERS) { eval(headers_$${path}.path = $${path}) eval(INSTALLS *= headers_$${path}) } + +target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH') +INSTALLS += target 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/libnymea-remoteproxy/proxyconfiguration.cpp b/libnymea-remoteproxy/proxyconfiguration.cpp index 84cfc55..f2d739a 100644 --- a/libnymea-remoteproxy/proxyconfiguration.cpp +++ b/libnymea-remoteproxy/proxyconfiguration.cpp @@ -1,10 +1,151 @@ +#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()) << "Configuration: Could not find configuration file" << fileName; + return false; + } + + if (!fileInfo.isReadable()) { + 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()); + 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; +} + +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 24329b5..d8cde2f 100644 --- a/libnymea-remoteproxy/proxyconfiguration.h +++ b/libnymea-remoteproxy/proxyconfiguration.h @@ -2,6 +2,8 @@ #define PROXYCONFIGURATION_H #include +#include +#include namespace remoteproxy { @@ -11,12 +13,54 @@ 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; }; +QDebug operator<< (QDebug debug, ProxyConfiguration *configuration); + } #endif // PROXYCONFIGURATION_H diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri index 6b0ba7c..2a7edce 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri @@ -1,14 +1,14 @@ INCLUDEPATH += $${PWD} HEADERS += \ - $${PWD}/jsonrpcclient.h \ + $${PWD}/proxyjsonrpcclient.h \ $${PWD}/jsonreply.h \ $${PWD}/remoteproxyconnection.h \ $${PWD}/proxyconnection.h \ $${PWD}/websocketconnection.h SOURCES += \ - $${PWD}/jsonrpcclient.cpp \ + $${PWD}/proxyjsonrpcclient.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..d9a3f1d 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pro @@ -2,13 +2,11 @@ include(../nymea-remoteproxy.pri) TEMPLATE = lib TARGET = nymea-remoteproxyclient +target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH') 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/ + +INSTALLS += target installheaders diff --git a/libnymea-remoteproxyclient/jsonrpcclient.cpp b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp similarity index 99% rename from libnymea-remoteproxyclient/jsonrpcclient.cpp rename to libnymea-remoteproxyclient/proxyjsonrpcclient.cpp index 94fd7ec..b421642 100644 --- a/libnymea-remoteproxyclient/jsonrpcclient.cpp +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp @@ -1,4 +1,4 @@ -#include "jsonrpcclient.h" +#include "proxyjsonrpcclient.h" #include "proxyconnection.h" #include diff --git a/libnymea-remoteproxyclient/jsonrpcclient.h b/libnymea-remoteproxyclient/proxyjsonrpcclient.h similarity index 100% rename from libnymea-remoteproxyclient/jsonrpcclient.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 0872f02..ed625c8 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -8,14 +8,14 @@ #include #include -#include "jsonrpcclient.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 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.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/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 71e28f8..da79df0 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -19,7 +19,9 @@ #include "engine.h" #include "loggingcategories.h" +#include "proxyconfiguration.h" #include "authentication/awsauthenticator.h" +#include "authentication/dummyauthenticator.h" using namespace remoteproxy; @@ -95,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 @@ -115,30 +117,33 @@ 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 mockAuthenticatorOption(QStringList() << "m" << "mock-authenticator", "Start the server using a mock authenticator which returns always true."); + parser.addOption(mockAuthenticatorOption); - QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate used for " - "this proxy server.", "certificate"); - parser.addOption(certOption); - - QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key " - "used for this proxy server.", "certificate-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 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); 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; @@ -146,104 +151,80 @@ 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); } - // 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()) << "=========================================================="; qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion(); - qCDebug(dcApplication()) << "=============================================="; + qCDebug(dcApplication()) << "=========================================================="; + + if (parser.isSet(developmentOption)) { + qCWarning(dcApplication()) << "##########################################################"; + qCWarning(dcApplication()) << "# DEVELOPMENT MODE #"; + qCWarning(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()->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()->setAuthenticationServerUrl(authenticationServerUrl); + Engine::instance()->setAuthenticator(authenticator); + Engine::instance()->start(); return application.exec(); 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); 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;