Prepare client binary

This commit is contained in:
Simon Stürz 2018-08-14 17:26:57 +02:00
parent 089c4ff743
commit 935f6d1fa4
23 changed files with 440 additions and 282 deletions

17
client/client.pro Normal file
View File

@ -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

47
client/main.cpp Normal file
View File

@ -0,0 +1,47 @@
#include <QDir>
#include <QUrl>
#include <QtDebug>
#include <QSslKey>
#include <QDateTime>
#include <QFileInfo>
#include <QTextStream>
#include <QMessageLogger>
#include <QSslCertificate>
#include <QCoreApplication>
#include <QCoreApplication>
#include <QLoggingCategory>
#include <QSslConfiguration>
#include <QCommandLineParser>
#include <QCommandLineOption>
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 <simon.stuerz@guh.io>\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();
}

6
client/proxyclient.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "proxyclient.h"
ProxyClient::ProxyClient(QObject *parent) : QObject(parent)
{
}

18
client/proxyclient.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef PROXYCLIENT_H
#define PROXYCLIENT_H
#include <QObject>
class ProxyClient : public QObject
{
Q_OBJECT
public:
explicit ProxyClient(QObject *parent = nullptr);
signals:
public slots:
};
#endif // PROXYCLIENT_H

View File

@ -0,0 +1,118 @@
#include "authenticationprocess.h"
#include "loggingcategories.h"
#include <QUrl>
#include <QNetworkReply>
#include <QJsonDocument>
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<void(QProcess::*)(int, QProcess::ExitStatus)>(&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<QNetworkReply *>(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();
}
}
}

View File

@ -0,0 +1,49 @@
#ifndef AUTHENTICATIONPROCESS_H
#define AUTHENTICATIONPROCESS_H
#include <QObject>
#include <QProcess>
#include <QNetworkAccessManager>
#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

View File

@ -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();
}

View File

@ -4,6 +4,7 @@
#include <QUuid>
#include <QTimer>
#include <QObject>
#include <QProcess>
#include <QElapsedTimer>
#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;

View File

@ -19,7 +19,7 @@ public:
AuthenticationErrorTimeout,
AuthenticationErrorAborted,
AuthenticationErrorAuthenticationFailed,
AuthenticationErrorAuthenticationServerNotResponding
AuthenticationErrorProxyError
};
Q_ENUM(AuthenticationError)

View File

@ -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<AuthenticationProcess *>(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;
}

View File

@ -2,9 +2,11 @@
#define AWSAUTHENTICATOR_H
#include <QObject>
#include <QNetworkAccessManager>
#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<AuthenticationProcess *, AuthenticationReply *> m_runningProcesses;
private slots:
void onAuthenticationProcessFinished(Authenticator::AuthenticationError error);
public slots:
AuthenticationReply *authenticate(ProxyClient *proxyClient) override;

View File

@ -1,123 +0,0 @@
#include "sigv4utils.h"
#include <QDateTime>
#include <QCryptographicHash>
#include <QMessageAuthenticationCode>
#include <QtDebug>
#include <QUrlQuery>
#include <QList>
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 &region, 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 &region, 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<QPair<QString, QString> > queryItems = query.queryItems();
QStringList queryItemStrings;
for (int i = 0; i < queryItems.count(); i++) {
QPair<QString, QString> 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 &region, 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 &region, 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 &region, 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 &region, 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;
}

View File

@ -1,27 +0,0 @@
#ifndef SIGV4UTILS_H
#define SIGV4UTILS_H
#include <QString>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
class SigV4Utils
{
public:
SigV4Utils();
static QByteArray getCurrentDateTime();
static QByteArray getCanonicalQueryString(const QNetworkRequest &request, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray &region, 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 &region, const QByteArray &service);
static QByteArray getStringToSign(const QByteArray &canonicalRequest, const QByteArray &dateTime, const QByteArray &region, const QByteArray &service);
static QByteArray getSignatureKey(const QByteArray &key, const QByteArray &date, const QByteArray &region, const QByteArray &service);
static QByteArray getSignature(const QByteArray &stringToSign, const QByteArray &secretAccessKey, const QByteArray &dateTime, const QString &region, const QString &service);
static QByteArray getAuthorizationHeader(const QByteArray &accessKeyId, const QByteArray &dateTime, const QString &region, const QString &service, const QNetworkRequest &request, const QByteArray &signature);
};
#endif // SIGV4UTILS_H

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -59,6 +59,8 @@ private:
};
QDebug operator<< (QDebug debug, ProxyConfiguration *configuration);
}
#endif // PROXYCONFIGURATION_H

13
nymea-remoteproxy.conf Normal file
View File

@ -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

View File

@ -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}")

View File

@ -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<quint16>(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();

View File

@ -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;

View File

@ -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;