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;