Add native lambda invoke code

This commit is contained in:
Simon Stürz 2018-08-27 13:32:07 +02:00
parent c7dd9e72aa
commit c5f031796b
34 changed files with 572 additions and 241 deletions

8
debian/changelog vendored
View File

@ -1,4 +1,10 @@
nymea-remoteproxy (0.1.2) UNRELEASED; urgency=medium
nymea-remoteproxy (0.1.3) bionic; urgency=medium
* Add native AWS lambda invoke code
-- Simon Stürz <simon.stuerz@guh.io> Mon, 27 Aug 2018 13:31:18 +0200
nymea-remoteproxy (0.1.2) bionic; urgency=medium
* Many bug fixes
* Add more scurity mechanisms

View File

@ -29,12 +29,11 @@ AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *pare
QObject(parent),
m_proxyClient(proxyClient)
{
m_timer.setSingleShot(true);
connect(&m_timer, &QTimer::timeout, this, &AuthenticationReply::onTimeout);
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &AuthenticationReply::onTimeout);
m_process = new QProcess(this);
m_timer.start(Engine::instance()->configuration()->authenticationTimeout());
m_timer->start(Engine::instance()->configuration()->authenticationTimeout());
}
ProxyClient *AuthenticationReply::proxyClient() const
@ -64,7 +63,7 @@ void AuthenticationReply::setError(Authenticator::AuthenticationError error)
void AuthenticationReply::setFinished()
{
m_timer.stop();
m_timer->stop();
// emit in next event loop
QTimer::singleShot(0, this, &AuthenticationReply::finished);
}
@ -73,17 +72,13 @@ void AuthenticationReply::onTimeout()
{
m_timedOut = true;
m_error = Authenticator::AuthenticationErrorTimeout;
m_timer.stop();
m_process->kill();
QTimer::singleShot(0, this, &AuthenticationReply::finished);
setFinished();
}
void AuthenticationReply::abort()
{
m_error = Authenticator::AuthenticationErrorAborted;
m_timer.stop();
m_process->kill();
QTimer::singleShot(0, this, &AuthenticationReply::finished);
setFinished();
}
}

View File

@ -48,8 +48,7 @@ public:
private:
explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr);
ProxyClient *m_proxyClient = nullptr;
QTimer m_timer;
QProcess *m_process = nullptr;
QTimer *m_timer = nullptr;
bool m_timedOut = false;
bool m_finished = false;

View File

@ -32,28 +32,19 @@
namespace remoteproxy {
AuthenticationProcess::AuthenticationProcess(QNetworkAccessManager *manager, QObject *parent) :
AuthenticationProcess::AuthenticationProcess(QNetworkAccessManager *manager, const QString &accessKey, const QString &secretAccessKey, const QString &sessionToken, QObject *parent) :
QObject(parent),
m_manager(manager)
m_manager(manager),
m_accessKey(accessKey),
m_secretAccessKey(secretAccessKey),
m_sessionToken(sessionToken)
{
m_process = new QProcess(this);
m_process->setProcessChannelMode(QProcess::MergedChannels);
connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &AuthenticationProcess::onProcessFinished);
}
void AuthenticationProcess::useDynamicCredentials(bool dynamicCredentials)
{
m_dynamicCredentials = dynamicCredentials;
}
void AuthenticationProcess::requestDynamicCredentials()
{
m_requestTimer.start();
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::invokeLambdaFunction(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken)
void AuthenticationProcess::invokeLambdaFunction()
{
// Known configurations
QString region = "eu-west-1";
@ -63,23 +54,21 @@ void AuthenticationProcess::invokeLambdaFunction(const QString accessKey, const
// {'url_path': '/2015-03-31/functions/system-services-authorizer-dev-checkToken/invocations', 'query_string': {}, 'method': 'POST', 'headers': {'X-Amz-Invocation-Type': 'RequestResponse', 'User-Agent': 'aws-cli/1.14.44 Python/3.6.5 Linux/4.15.0-1019-aws botocore/1.8.48'}, 'body': b'{"token": "...."}', 'url': 'https://lambda.eu-west-1.amazonaws.com/2015-03-31/functions/system-services-authorizer-dev-checkToken/invocations', 'context': {'client_region': 'eu-west-1', 'client_config': <botocore.config.Config object at 0x7f44560f3128>, 'has_streaming_input': True, 'auth_type': None}}
// Create request map
QVariantMap requestMap;
requestMap.insert("token", m_token);
QByteArray payload = QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Compact);
QUrl requestUrl;
requestUrl.setScheme("https");
requestUrl.setHost(QString("lambda.%1.amazonaws.com").arg(region));
requestUrl.setPath(QString("/2015-03-31/functions/%1/invocations").arg(lambdaFunctionName));
QNetworkRequest request(requestUrl);
request.setRawHeader("User-Agent", QString("%1/%2 JSON-RPC/%3").arg(SERVER_NAME_STRING).arg(SERVER_VERSION_STRING).arg(API_VERSION_STRING).toUtf8());
request.setRawHeader("Content-Type", "application/json");
request.setRawHeader("host", requestUrl.host().toUtf8());
request.setRawHeader("x-amz-invocation-type", invocationType.toUtf8());
// Create request map
QVariantMap requestMap;
requestMap.insert("token", m_token);
QByteArray payload = QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Compact);
SigV4Utils::signRequest(QNetworkAccessManager::PostOperation, request, region, service, accessKey.toUtf8(), secretAccessKey.toUtf8(), sessionToken.toUtf8(), payload);
QNetworkRequest request(requestUrl);
//request.setRawHeader("User-Agent", QString("%1/%2 JSON-RPC/%3").arg(SERVER_NAME_STRING).arg(SERVER_VERSION_STRING).arg(API_VERSION_STRING).toUtf8());
//request.setRawHeader("Content-Type", "application/json");
request.setRawHeader("host", requestUrl.host().toUtf8());
SigV4Utils::signRequest(QNetworkAccessManager::PostOperation, request, region, service, invocationType, m_accessKey.toUtf8(), m_secretAccessKey.toUtf8(), m_sessionToken.toUtf8(), payload);
qCDebug(dcAuthenticationProcess()) << "Invoke lambda function" << lambdaFunctionName;
@ -87,19 +76,19 @@ void AuthenticationProcess::invokeLambdaFunction(const QString accessKey, const
qCDebug(dcAuthenticationProcess()) << request.url().toString();
foreach (const QByteArray &rawHeader, request.rawHeaderList()) {
qDebug(dcAuthenticationProcess()) << request.rawHeader(rawHeader);
qDebug(dcAuthenticationProcess()) << rawHeader << request.rawHeader(rawHeader);
}
qCDebug(dcAuthenticationProcess()) << payload;
qCDebug(dcAuthenticationProcess()) << "--------------------------------------------";
m_lambdaTimer.start();
QNetworkReply *reply = m_manager->get(request);
QNetworkReply *reply = m_manager->post(request, payload);
connect(reply, &QNetworkReply::finished, this, &AuthenticationProcess::onLambdaInvokeFunctionFinished);
}
void AuthenticationProcess::startVerificationProcess(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken)
void AuthenticationProcess::startVerificationProcess()
{
if (m_process->state() != QProcess::NotRunning) {
qCWarning(dcAuthenticationProcess()) << "Authentication process already running. Killing the running process and restart.";
@ -113,56 +102,31 @@ void AuthenticationProcess::startVerificationProcess(const QString accessKey, co
// Set environment
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("AWS_DEFAULT_REGION", "eu-west-1");
if (m_dynamicCredentials) {
qCDebug(dcAuthenticationProcess()) << "Using dynamic credentials" << accessKey << secretAccessKey << sessionToken;
env.insert("AWS_ACCESS_KEY_ID", accessKey);
env.insert("AWS_SECRET_ACCESS_KEY", secretAccessKey);
env.insert("AWS_SESSION_TOKEN", sessionToken);
if (m_fallback) {
qCDebug(dcAuthenticationProcess()) << "Using dynamic credentials" << m_accessKey << m_secretAccessKey << m_sessionToken;
env.insert("AWS_ACCESS_KEY_ID", m_accessKey);
env.insert("AWS_SECRET_ACCESS_KEY", m_secretAccessKey);
env.insert("AWS_SESSION_TOKEN", m_sessionToken);
}
m_process->setProcessEnvironment(env);
// FIXME: check how to clean this up properly
m_resultFileName = "/tmp/" + QUuid::createUuid().toString().remove("{").remove("}").remove("-") + ".json";
QStringList processParams = { "lambda", "invoke",
"--function-name", "system-services-authorizer-dev-checkToken",
"--invocation-type", "RequestResponse",
"--payload", QString::fromUtf8(QJsonDocument::fromVariant(request).toJson()),
m_resultFileName };
qCDebug(dcAuthenticationProcess()) << "Process environment" << env.toStringList();
qCDebug(dcAuthenticationProcess()) << "Process params" << processParams;
qCDebug(dcAuthentication()) << "Start authenticator process and store result in" << m_resultFileName;
m_processTimer.start();
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<QNetworkReply *>(sender());
reply->deleteLater();
qCDebug(dcAuthenticationProcess()) << "Dynamic credentials request finished (" << m_requestTimer.elapsed() << "[ms] )";
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;
startVerificationProcess(response.value("AccessKeyId").toString(),
response.value("SecretAccessKey").toString(),
response.value("Token").toString());
m_process->start("aws", processParams);
}
void AuthenticationProcess::onLambdaInvokeFunctionFinished()
@ -170,39 +134,58 @@ void AuthenticationProcess::onLambdaInvokeFunctionFinished()
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
qCDebug(dcAuthenticationProcess()) << "Lambda invoke request finished (" << m_lambdaTimer.elapsed() << "[ms] )";
if (reply->error()) {
qCWarning(dcAuthenticationProcess()) << "Dynamic credentials reply error: " << reply->errorString();
emit authenticationFinished(Authenticator::AuthenticationErrorProxyError);
return;
}
qCDebug(dcAuthenticationProcess()) << "Lambda invoke request finished (" << m_lambdaTimer.elapsed() << "[ms] )";
QByteArray data = reply->readAll();
qCDebug(dcAuthenticationProcess()) << "Invoke lambda function response ready";
qCDebug(dcAuthenticationProcess()) << "--------------------------------------------";
qCDebug(dcAuthenticationProcess()) << reply->request().url().toString();
foreach (const QByteArray &rawHeader, reply->rawHeaderList()) {
qDebug(dcAuthenticationProcess()) << reply->rawHeader(rawHeader);
qDebug(dcAuthenticationProcess()) << rawHeader << reply->rawHeader(rawHeader);
}
qCDebug(dcAuthenticationProcess()) << data;
qCDebug(dcAuthenticationProcess()) << qUtf8Printable(data);
qCDebug(dcAuthenticationProcess()) << "--------------------------------------------";
if (reply->error()) {
qCWarning(dcAuthenticationProcess()) << "Lambda invoke reply error: " << reply->errorString();
m_fallback = true;
return;
}
qCDebug(dcAuthenticationProcess()) << "Lambda function result ready" << qUtf8Printable(data);
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();
qCWarning(dcAuthenticationProcess()) << "Failed to parse lambda invoke result data" << data << ":" << error.errorString();
emit authenticationFinished(Authenticator::AuthenticationErrorProxyError);
return;
}
QVariantMap response = jsonDoc.toVariant().toMap();
qCDebug(dcAuthentication()) << "-->" << response;
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) {
QVariantMap verifiedDataMap = response.value("verifiedData").toMap();
QString vendorId = verifiedDataMap.value("vendorId").toString();
QString userPoolId = verifiedDataMap.value("userPoolId").toString();
QVariantMap verifiedParsedTokenMap = verifiedDataMap.value("verifiedParsedToken").toMap();
QString email = verifiedParsedTokenMap.value("email").toString();
QString cognitoUsername = verifiedParsedTokenMap.value("cognito:username").toString();
UserInformation userInformation(email, cognitoUsername, vendorId, userPoolId);
emit authenticationFinished(Authenticator::AuthenticationErrorNoError, userInformation);
} else {
emit authenticationFinished(Authenticator::AuthenticationErrorAuthenticationFailed);
}
}
void AuthenticationProcess::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
@ -281,15 +264,7 @@ 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 {
// FIXME:
// Direct call aws cli and assume the credentials will be provided static
// startVerificationProcess();
}
invokeLambdaFunction();
}
}

View File

@ -27,8 +27,8 @@
#include <QElapsedTimer>
#include <QNetworkAccessManager>
#include "authenticator.h"
#include "userinformation.h"
#include "authentication/authenticator.h"
namespace remoteproxy {
@ -36,32 +36,32 @@ class AuthenticationProcess : public QObject
{
Q_OBJECT
public:
explicit AuthenticationProcess(QNetworkAccessManager *manager, QObject *parent = nullptr);
void useDynamicCredentials(bool dynamicCredentials);
explicit AuthenticationProcess(QNetworkAccessManager *manager, const QString &accessKey, const QString &secretAccessKey, const QString &sessionToken, QObject *parent = nullptr);
private:
QNetworkAccessManager *m_manager = nullptr;
QString m_accessKey;
QString m_secretAccessKey;
QString m_sessionToken;
QString m_token;
QString m_resultFileName;
bool m_dynamicCredentials = true;
bool m_fallback = false;
QNetworkAccessManager *m_manager = nullptr;
QProcess *m_process = nullptr;
QElapsedTimer m_requestTimer;
QElapsedTimer m_lambdaTimer;
QElapsedTimer m_processTimer;
void requestDynamicCredentials();
void invokeLambdaFunction(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken);
void startVerificationProcess(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken);
void invokeLambdaFunction();
void startVerificationProcess();
void cleanUp();
signals:
void authenticationFinished(Authenticator::AuthenticationError error, const UserInformation &userInformation = UserInformation());
private slots:
void onDynamicCredentialsReady();
void onLambdaInvokeFunctionFinished();
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);

View File

@ -26,16 +26,18 @@
namespace remoteproxy {
AwsAuthenticator::AwsAuthenticator(QObject *parent) :
AwsAuthenticator::AwsAuthenticator(const QUrl &awsCredentialsUrl, QObject *parent) :
Authenticator(parent),
m_manager(new QNetworkAccessManager(this))
{
// TODO: verify if aws command is installed
m_credentialsProvider = new AwsCredentialProvider(m_manager, awsCredentialsUrl, this);
QMetaObject::invokeMethod(m_credentialsProvider, QString("enable").toLatin1().data(), Qt::QueuedConnection);
}
AwsAuthenticator::~AwsAuthenticator()
{
qCDebug(dcAuthentication()) << "Shutting down" << name();
m_credentialsProvider->disable();
}
QString AwsAuthenticator::name() const
@ -63,8 +65,18 @@ AuthenticationReply *AwsAuthenticator::authenticate(ProxyClient *proxyClient)
qCDebug(dcAuthentication()) << name() << "Start authenticating" << proxyClient;
AuthenticationReply *reply = createAuthenticationReply(proxyClient, this);
AuthenticationProcess *process = new AuthenticationProcess(m_manager, this);
process->useDynamicCredentials(!Engine::instance()->developerMode());
if (!m_credentialsProvider->isValid()) {
qCWarning(dcAuthentication()) << name() << "There are no credentials for authenticating.";
setReplyError(reply, AuthenticationErrorProxyError);
setReplyFinished(reply);
return reply;
}
AuthenticationProcess *process = new AuthenticationProcess(m_manager,
m_credentialsProvider->accessKey(),
m_credentialsProvider->secretAccessKey(),
m_credentialsProvider->sessionToken(), this);
connect(process, &AuthenticationProcess::authenticationFinished, this, &AwsAuthenticator::onAuthenticationProcessFinished);
// Configure process

View File

@ -25,9 +25,10 @@
#include <QObject>
#include <QNetworkAccessManager>
#include "authenticator.h"
#include "authenticationreply.h"
#include "awscredentialprovider.h"
#include "authenticationprocess.h"
#include "authentication/authenticator.h"
#include "authentication/authenticationreply.h"
namespace remoteproxy {
@ -35,13 +36,14 @@ class AwsAuthenticator : public Authenticator
{
Q_OBJECT
public:
explicit AwsAuthenticator(QObject *parent = nullptr);
explicit AwsAuthenticator(const QUrl &awsCredentialsUrl, QObject *parent = nullptr);
~AwsAuthenticator() override;
QString name() const override;
private:
QNetworkAccessManager *m_manager = nullptr;
AwsCredentialProvider *m_credentialsProvider = nullptr;
QHash<AuthenticationProcess *, AuthenticationReply *> m_runningProcesses;
private slots:

View File

@ -0,0 +1,148 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
* *
* This file is part of nymea-remoteproxy. *
* *
* This program 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, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "loggingcategories.h"
#include "awscredentialprovider.h"
#include <QJsonDocument>
#include <QNetworkReply>
#include <QJsonParseError>
#include <QNetworkRequest>
namespace remoteproxy {
AwsCredentialProvider::AwsCredentialProvider(QNetworkAccessManager *networkManager, const QUrl &awsCredentialsUrl, QObject *parent) :
QObject(parent),
m_networkManager(networkManager),
m_requestUrl(awsCredentialsUrl)
{
m_timer = new QTimer(this);
m_timer->setSingleShot(false);
m_timer->setInterval(90000);
connect(m_timer, &QTimer::timeout, this, &AwsCredentialProvider::onTimeout);
}
QString AwsCredentialProvider::accessKey() const
{
return m_accessKey;
}
QString AwsCredentialProvider::secretAccessKey() const
{
return m_secretAccessKey;
}
QString AwsCredentialProvider::sessionToken() const
{
return m_sessionToken;
}
bool AwsCredentialProvider::isValid() const
{
return !m_accessKey.isEmpty() && !m_secretAccessKey.isEmpty() && !m_sessionToken.isEmpty();
}
bool AwsCredentialProvider::enabled() const
{
return m_enabled;
}
void AwsCredentialProvider::refreshCredentials()
{
qCDebug(dcAwsCredentialsProvider()) << "Update dynamic credentials form" << m_requestUrl.toString();
m_requestTimer.start();
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(m_requestUrl));
connect(reply, &QNetworkReply::finished, this, &AwsCredentialProvider::onReplyFinished);
}
void AwsCredentialProvider::clear()
{
m_accessKey.clear();
m_secretAccessKey.clear();
m_sessionToken.clear();
}
void AwsCredentialProvider::onTimeout()
{
// TODO: check if we need to refresh
refreshCredentials();
}
void AwsCredentialProvider::onReplyFinished()
{
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
qCDebug(dcAwsCredentialsProvider()) << "Dynamic credentials request finished (" << m_requestTimer.elapsed() << "[ms] )";
if (reply->error()) {
qCWarning(dcAwsCredentialsProvider()) << "Dynamic credentials reply error: " << reply->errorString();
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if(error.error != QJsonParseError::NoError) {
qCWarning(dcAwsCredentialsProviderTraffic()) << "Failed to parse dynamic credentials reply data" << data << ":" << error.errorString();
return;
}
QVariantMap response = jsonDoc.toVariant().toMap();
m_accessKey = response.value("AccessKeyId").toString();
m_secretAccessKey = response.value("SecretAccessKey").toString();
m_sessionToken = response.value("Token").toString();
qCDebug(dcAwsCredentialsProviderTraffic()) << "Dynamic credentials updated:" << response;
QDateTime expirationTime = QDateTime::fromString(response.value("Expiration").toString(), "yyyy-MM-ddThh:mm:ssZ");
QDateTime lastUpdateTime = QDateTime::fromString(response.value("LastUpdated").toString(), "yyyy-MM-ddThh:mm:ssZ");
qCDebug(dcAwsCredentialsProviderTraffic()) << "Exipration time" << expirationTime.toString("dd.MM.yyyy hh:mm:ss");
qCDebug(dcAwsCredentialsProviderTraffic()) << "Last update time" << lastUpdateTime.toString("dd.MM.yyyy hh:mm:ss");
}
void AwsCredentialProvider::enable()
{
if (!m_enabled) {
clear();
m_enabled = true;
}
qCDebug(dcAwsCredentialsProvider()) << "Enable AWS dynamic credentials provider";
m_timer->start();
refreshCredentials();
}
void AwsCredentialProvider::disable()
{
qCDebug(dcAwsCredentialsProvider()) << "Disable AWS dynamic credentials provider";
m_enabled = false;
m_timer->stop();
clear();
}
}

View File

@ -0,0 +1,82 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
* *
* This file is part of nymea-remoteproxy. *
* *
* This program 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, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef AWSCREDENTIALPROVIDER_H
#define AWSCREDENTIALPROVIDER_H
#include <QUrl>
#include <QTimer>
#include <QObject>
#include <QDateTime>
#include <QElapsedTimer>
#include <QNetworkAccessManager>
namespace remoteproxy {
class AwsCredentialProvider : public QObject
{
Q_OBJECT
public:
explicit AwsCredentialProvider(QNetworkAccessManager *networkManager, const QUrl &awsCredentialsUrl, QObject *parent = nullptr);
QString accessKey() const;
QString secretAccessKey() const;
QString sessionToken() const;
bool isValid() const;
bool enabled() const;
private:
QNetworkAccessManager *m_networkManager = nullptr;
QTimer *m_timer = nullptr;
QElapsedTimer m_requestTimer;
bool m_enabled = false;
QUrl m_requestUrl;
QString m_accessKey;
QString m_secretAccessKey;
QString m_sessionToken;
QDateTime m_expirationTime;
QDateTime m_lastUpdateTime;
void refreshCredentials();
void clear();
private slots:
void onTimeout();
void onReplyFinished();
signals:
void credentialsChanged();
public slots:
void enable();
void disable();
};
}
#endif // AWSCREDENTIALPROVIDER_H

View File

@ -1,5 +1,26 @@
#include "sigv4utils.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@guh.io> *
* *
* This file is part of nymea-remoteproxy. *
* *
* This program 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, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "sigv4utils.h"
#include <QDateTime>
#include <QCryptographicHash>
@ -8,34 +29,37 @@
#include <QUrlQuery>
#include <QList>
namespace remoteproxy {
#include "loggingcategories.h"
namespace remoteproxy {
SigV4Utils::SigV4Utils()
{
}
void SigV4Utils::signRequest(QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QString &region, const QString &service, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray &payload)
void SigV4Utils::signRequest(QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QString &region, const QString &service, const QString &invocationType, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray &payload)
{
QByteArray dateTime;
if (request.rawHeaderList().contains("X-Amz-Date")) {
dateTime = request.rawHeader("X-AMZ-Date");
if (request.rawHeaderList().contains("x-amz-date")) {
dateTime = request.rawHeader("x-amz-date");
} else {
dateTime = SigV4Utils::getCurrentDateTime();
request.setRawHeader("X-Amz-Date", dateTime);
request.setRawHeader("x-amz-date", dateTime);
}
request.setRawHeader("x-amz-invocation-type", invocationType.toUtf8());
if (!sessionToken.isEmpty()) {
request.setRawHeader("x-amz-security-token", sessionToken);
}
QByteArray canonicalRequest = SigV4Utils::getCanonicalRequest(operation, request, payload);
// qDebug() << "canonical request:" << qUtf8Printable(canonicalRequest);
qCDebug(dcAuthenticationProcess()) << "canonical request:" << endl << qUtf8Printable(canonicalRequest);
QByteArray stringToSign = SigV4Utils::getStringToSign(canonicalRequest, dateTime, region.toUtf8(), service.toUtf8());
// qDebug() << "string to sign:" << stringToSign;
qCDebug(dcAuthenticationProcess()) << "string to sign:" << endl << qUtf8Printable(stringToSign);
QByteArray signature = SigV4Utils::getSignature(stringToSign, secretAccessKey, dateTime, region, service);
// qDebug() << "signature:" << signature;
qCDebug(dcAuthenticationProcess()) << "signature:" << signature;
QByteArray authorizeHeader = SigV4Utils::getAuthorizationHeader(accessKeyId, dateTime, region, service, request, signature);
request.setRawHeader("Authorization", authorizeHeader);
@ -80,7 +104,7 @@ QByteArray SigV4Utils::getSignatureKey(const QByteArray &key, const QByteArray &
return QMessageAuthenticationCode::hash("aws4_request",
QMessageAuthenticationCode::hash(service,
QMessageAuthenticationCode::hash(region,
QMessageAuthenticationCode::hash(date, "AWS4"+key,
QMessageAuthenticationCode::hash(date, "AWS4" + key,
hashAlgorithm), hashAlgorithm), hashAlgorithm), hashAlgorithm);
}
@ -100,6 +124,7 @@ QByteArray SigV4Utils::getCanonicalRequest(QNetworkAccessManager::Operation oper
Q_ASSERT_X(false, "Network operation not implemented", "SigV4Utils");
}
QByteArray uri = request.url().path(QUrl::FullyEncoded).toUtf8();
QUrlQuery query(request.url());
QList<QPair<QString, QString> > queryItems = query.queryItems();
QStringList queryItemStrings;
@ -118,7 +143,7 @@ QByteArray SigV4Utils::getCanonicalRequest(QNetworkAccessManager::Operation oper
QByteArray payloadHash = QCryptographicHash::hash(payload, QCryptographicHash::Sha256).toHex();
canonicalRequest = method + '\n' + uri + '\n' + canonicalQueryString + '\n' + canonicalHeaders + '\n' + request.rawHeaderList().join(';').toLower() + '\n' + payloadHash;
canonicalRequest = method + '\n' + uri + '\n' + canonicalQueryString + '\n' + canonicalHeaders + '\n' + request.rawHeaderList().join(';').toLower() + '\n' + payloadHash.toLower();
return canonicalRequest;
}
@ -133,7 +158,6 @@ QByteArray SigV4Utils::getStringToSign(const QByteArray &canonicalRequest, const
{
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;
}

View File

@ -0,0 +1,55 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@guh.io> *
* *
* This file is part of nymea-remoteproxy. *
* *
* This program 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, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SIGV4UTILS_H
#define SIGV4UTILS_H
#include <QString>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
namespace remoteproxy {
class SigV4Utils
{
public:
SigV4Utils();
// Signes a request by adding the "X-AMZ-Date" (if not present) and "X-AMZ-Signature" headers
static void signRequest(QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QString &region, const QString &service, const QString &invocationType, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken = QByteArray(), const QByteArray &payload = QByteArray());
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

@ -25,7 +25,7 @@
#include <QObject>
#include "proxyclient.h"
#include "authenticator.h"
#include "authentication/authenticator.h"
namespace remoteproxy {

View File

@ -1,33 +0,0 @@
#ifndef SIGV4UTILS_H
#define SIGV4UTILS_H
#include <QString>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
namespace remoteproxy {
class SigV4Utils
{
public:
SigV4Utils();
// Signes a request by adding the "X-AMZ-Date" (if not present) and "X-AMZ-Signature" headers
static void signRequest(QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QString &region, const QString &service, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken = QByteArray(), const QByteArray &payload = QByteArray());
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

@ -78,8 +78,9 @@ JsonReply *AuthenticationHandler::Authenticate(const QVariantMap &params, ProxyC
void AuthenticationHandler::onAuthenticationFinished()
{
AuthenticationReply *authenticationReply = static_cast<AuthenticationReply *>(sender());
authenticationReply->deleteLater();
qCDebug(dcJsonRpc()) << "Authentication respons ready for" << authenticationReply->proxyClient() << authenticationReply->error();
qCDebug(dcJsonRpc()) << "Authentication response ready for" << authenticationReply->proxyClient() << authenticationReply->error();
JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply);
if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) {
@ -92,7 +93,6 @@ void AuthenticationHandler::onAuthenticationFinished()
// Set client authenticated
authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError);
authenticationReply->deleteLater();
jsonReply->setData(errorToReply(authenticationReply->error()));
jsonReply->finished();

View File

@ -136,9 +136,9 @@ QVariantMap JsonHandler::errorToReply(Authenticator::AuthenticationError error)
return returns;
}
JsonReply *JsonHandler::createReply(const QVariantMap &data) const
JsonReply *JsonHandler::createReply(const QString &method, const QVariantMap &data) const
{
return JsonReply::createReply(const_cast<JsonHandler*>(this), data);
return JsonReply::createReply(const_cast<JsonHandler*>(this), method, data);
}
JsonReply *JsonHandler::createAsyncReply(const QString &method) const

View File

@ -63,7 +63,7 @@ protected:
QVariantMap errorToReply(Authenticator::AuthenticationError error) const;
JsonReply *createReply(const QVariantMap &data) const;
JsonReply *createReply(const QString &method, const QVariantMap &data) const;
JsonReply *createAsyncReply(const QString &method) const;
};

View File

@ -24,9 +24,9 @@
namespace remoteproxy {
JsonReply *JsonReply::createReply(JsonHandler *handler, const QVariantMap &data)
JsonReply *JsonReply::createReply(JsonHandler *handler, const QString &method, const QVariantMap &data)
{
return new JsonReply(TypeSync, handler, QString(), data);
return new JsonReply(TypeSync, handler, method, data);
}
JsonReply *JsonReply::createAsyncReply(JsonHandler *handler, const QString &method)

View File

@ -41,7 +41,7 @@ public:
friend class JsonRpcServer;
static JsonReply *createReply(JsonHandler *handler, const QVariantMap &data);
static JsonReply *createReply(JsonHandler *handler, const QString &method, const QVariantMap &data);
static JsonReply *createAsyncReply(JsonHandler *handler, const QString &method);
Type type() const;
@ -50,7 +50,6 @@ public:
void setData(const QVariantMap &data);
JsonHandler *handler() const;
QString method() const;
QUuid clientId() const;

View File

@ -91,7 +91,7 @@ JsonReply *JsonRpcServer::Hello(const QVariantMap &params, ProxyClient *proxyCli
data.insert("version", SERVER_VERSION_STRING);
data.insert("apiVersion", API_VERSION_STRING);
return createReply(data);
return createReply("Hello", data);
}
JsonReply *JsonRpcServer::Introspect(const QVariantMap &params, ProxyClient *proxyClient) const
@ -118,7 +118,7 @@ JsonReply *JsonRpcServer::Introspect(const QVariantMap &params, ProxyClient *pro
data.insert("notifications", signalsMap);
return createReply(data);
return createReply("Introspect", data);
}
void JsonRpcServer::sendResponse(ProxyClient *client, int commandId, const QVariantMap &params)
@ -142,7 +142,7 @@ void JsonRpcServer::sendErrorResponse(ProxyClient *client, int commandId, const
QByteArray data = QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
client->interface()->sendData(client->clientId(), data);
client->sendData(data);
}
QString JsonRpcServer::formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const
@ -190,11 +190,19 @@ void JsonRpcServer::asyncReplyFinished()
Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first
,"validating return value", formatAssertion(reply->handler()->name(),
reply->method(), reply->handler(), reply->data()).toLatin1().data());
QPair<bool, QString> returnValidation = reply->handler()->validateReturns(reply->method(), reply->data());
if (!returnValidation.first) {
qCWarning(dcJsonRpc()) << "Return value validation failed. This should never happen. Please check the source code.";
}
sendResponse(proxyClient, reply->commandId(), reply->data());
if (!reply->success()) {
// Disconnect this client since the request was not successfully
proxyClient->interface()->killClientConnection(proxyClient->clientId(), "API call was not successfully.");
}
} else {
sendErrorResponse(proxyClient, reply->commandId(), "Command timed out");
// Disconnect this client since he requested something that created a timeout
@ -289,11 +297,18 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data
m_asyncReplies.insert(reply, proxyClient);
reply->setClientId(proxyClient->clientId());
reply->setCommandId(commandId);
connect(reply, &JsonReply::finished, this, &JsonRpcServer::asyncReplyFinished);
reply->startWait();
} else {
Q_ASSERT_X((targetNamespace == "RemoteProxy" && method == "Introspect") || handler->validateReturns(method, reply->data()).first
,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data());
// QPair<bool, QString> returnValidation = reply->handler()->validateReturns(reply->method(), reply->data());
// if (!returnValidation.first) {
// qCWarning(dcJsonRpc()) << "Return value validation failed of sync reply. This should never happen. Please check the source code.";
// }
reply->setClientId(proxyClient->clientId());
reply->setCommandId(commandId);
sendResponse(proxyClient, commandId, reply->data());
@ -310,7 +325,7 @@ void JsonRpcServer::sendNotification(const QString &nameSpace, const QString &me
QByteArray data = QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending notification:" << data;
proxyClient->interface()->sendData(proxyClient->clientId(), data);
proxyClient->sendData(data);
}
}

View File

@ -19,12 +19,13 @@ HEADERS += \
jsonrpc/jsontypes.h \
jsonrpc/authenticationhandler.h \
authentication/authenticator.h \
authentication/awsauthenticator.h \
authentication/authenticationreply.h \
authentication/userinformation.h \
authentication/authenticationprocess.h \
authentication/dummyauthenticator.h \
authentication/sigv4utils.h
authentication/dummy/dummyauthenticator.h \
authentication/aws/awsauthenticator.h \
authentication/aws/userinformation.h \
authentication/aws/authenticationprocess.h \
authentication/aws/sigv4utils.h \
authentication/aws/awscredentialprovider.h
SOURCES += \
engine.cpp \
@ -42,12 +43,13 @@ SOURCES += \
jsonrpc/jsontypes.cpp \
jsonrpc/authenticationhandler.cpp \
authentication/authenticator.cpp \
authentication/awsauthenticator.cpp \
authentication/authenticationreply.cpp \
authentication/userinformation.cpp \
authentication/authenticationprocess.cpp \
authentication/dummyauthenticator.cpp \
authentication/sigv4utils.cpp
authentication/dummy/dummyauthenticator.cpp \
authentication/aws/awsauthenticator.cpp \
authentication/aws/userinformation.cpp \
authentication/aws/authenticationprocess.cpp \
authentication/aws/sigv4utils.cpp \
authentication/aws/awscredentialprovider.cpp
# install header file with relative subdirectory

View File

@ -32,4 +32,5 @@ Q_LOGGING_CATEGORY(dcAuthenticationProcess, "AuthenticationProcess")
Q_LOGGING_CATEGORY(dcProxyServer, "ProxyServer")
Q_LOGGING_CATEGORY(dcProxyServerTraffic, "ProxyServerTraffic")
Q_LOGGING_CATEGORY(dcMonitorServer, "MonitorServer")
Q_LOGGING_CATEGORY(dcAwsCredentialsProvider, "AwsCredentialsProvider")
Q_LOGGING_CATEGORY(dcAwsCredentialsProviderTraffic, "AwsCredentialsProviderTraffic")

View File

@ -36,5 +36,7 @@ Q_DECLARE_LOGGING_CATEGORY(dcAuthenticationProcess)
Q_DECLARE_LOGGING_CATEGORY(dcProxyServer)
Q_DECLARE_LOGGING_CATEGORY(dcProxyServerTraffic)
Q_DECLARE_LOGGING_CATEGORY(dcMonitorServer)
Q_DECLARE_LOGGING_CATEGORY(dcAwsCredentialsProvider)
Q_DECLARE_LOGGING_CATEGORY(dcAwsCredentialsProviderTraffic)
#endif // LOGGINGCATEGORIES_H

View File

@ -56,7 +56,7 @@ bool ProxyConfiguration::loadConfiguration(const QString &fileName)
setAuthenticationTimeout(settings.value("authenticationTimeout", 8000).toInt());
setInactiveTimeout(settings.value("inactiveTimeout", 8000).toInt());
setAloneTimeout(settings.value("aloneTimeout", 8000).toInt());
setAwsCredentialsUrl(QUrl(settings.value("awsCredentialsUrl", "http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-Remote-Connection-Proxy-Role").toString()));
settings.endGroup();
settings.beginGroup("SSL");
@ -205,6 +205,16 @@ void ProxyConfiguration::setAloneTimeout(int timeout)
m_aloneTimeout = timeout;
}
QUrl ProxyConfiguration::awsCredentialsUrl() const
{
return m_awsCredentialsUrl;
}
void ProxyConfiguration::setAwsCredentialsUrl(const QUrl &url)
{
m_awsCredentialsUrl = url;
}
QString ProxyConfiguration::sslCertificateFileName() const
{
return m_sslCertificateFileName;
@ -292,6 +302,7 @@ QDebug operator<<(QDebug debug, ProxyConfiguration *configuration)
debug.nospace() << " - Authentication timeout:" << configuration->authenticationTimeout() << " [ms]" << endl;
debug.nospace() << " - Inactive timeout:" << configuration->inactiveTimeout() << " [ms]" << endl;
debug.nospace() << " - Alone timeout:" << configuration->aloneTimeout() << " [ms]" << endl;
debug.nospace() << " - AWS credentials URL:" << configuration->awsCredentialsUrl().toString() << endl;
debug.nospace() << "SSL configuration" << endl;
debug.nospace() << " - Certificate:" << configuration->sslCertificateFileName() << endl;
debug.nospace() << " - Certificate key:" << configuration->sslCertificateKeyFileName() << endl;

View File

@ -22,6 +22,7 @@
#ifndef PROXYCONFIGURATION_H
#define PROXYCONFIGURATION_H
#include <QUrl>
#include <QObject>
#include <QSettings>
#include <QHostAddress>
@ -64,6 +65,9 @@ public:
int aloneTimeout() const;
void setAloneTimeout(int timeout);
QUrl awsCredentialsUrl() const;
void setAwsCredentialsUrl(const QUrl &url);
// Ssl
QString sslCertificateFileName() const;
void setSslCertificateFileName(const QString &fileName);
@ -99,9 +103,11 @@ private:
QString m_monitorSocketFileName;
int m_jsonRpcTimeout = 10000;
int m_authenticationTimeout = 5000;
int m_inactiveTimeout = 5000;
int m_aloneTimeout = 5000;
int m_authenticationTimeout = 8000;
int m_inactiveTimeout = 8000;
int m_aloneTimeout = 8000;
QUrl m_awsCredentialsUrl;
// Ssl
QString m_sslCertificateFileName = "/etc/ssl/certs/ssl-cert-snakeoil.pem";

View File

@ -61,14 +61,14 @@ JsonReply *JsonRpcClient::callAuthenticate(const QUuid &clientUuid, const QStrin
void JsonRpcClient::sendRequest(const QVariantMap &request)
{
QByteArray data = QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact) + '\n';
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Sending" << qUtf8Printable(data);
QByteArray data = QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact);
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Sending" << data;
m_connection->sendData(data);
}
void JsonRpcClient::processData(const QByteArray &data)
{
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << qUtf8Printable(data);
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << data;
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);

View File

@ -57,9 +57,13 @@ TerminalWindow::TerminalWindow(QObject *parent) :
m_headerWindow = newwin(m_headerHeight, m_terminalSizeX, 0, 0);
m_contentWindow = newwin(m_terminalSizeY - m_headerHeight, m_terminalSizeX, m_headerHeight, 0);
// Draw borders
nodelay(m_headerWindow, TRUE);
nodelay(m_contentWindow, TRUE);
werase(m_headerWindow);
werase(m_contentWindow);
// Draw borders
drawWindowBorder(m_headerWindow);
drawWindowBorder(m_contentWindow);
@ -106,6 +110,8 @@ void TerminalWindow::resizeWindow()
int terminalSizeX;
int terminalSizeY;
getmaxyx(stdscr, terminalSizeY, terminalSizeX);
// Resize the window if size has changed
if (m_terminalSizeX != terminalSizeX || m_terminalSizeY != terminalSizeY) {
m_terminalSizeX = terminalSizeX;
m_terminalSizeY = terminalSizeY;
@ -155,12 +161,10 @@ void TerminalWindow::paintHeader()
headerString.append(" ");
wclear(m_headerWindow);
wattron(m_headerWindow, A_BOLD | COLOR_PAIR(1));
mvwprintw(m_headerWindow, 1, 2, convertString(headerString));
wattroff(m_headerWindow, A_BOLD | COLOR_PAIR(1));
drawWindowBorder(m_headerWindow);
wrefresh(m_headerWindow);
}
void TerminalWindow::paintContentClients()
@ -169,7 +173,6 @@ void TerminalWindow::paintContentClients()
return;
int i = 1;
wclear(m_contentWindow);
foreach (const QVariant &clientVariant, m_clientHash.values()) {
QVariantMap clientMap = clientVariant.toMap();
@ -191,14 +194,12 @@ void TerminalWindow::paintContentClients()
// Draw borders
drawWindowBorder(m_contentWindow);
wrefresh(m_contentWindow);
}
void TerminalWindow::paintContentTunnels()
{
int i = 1;
wclear(m_contentWindow);
foreach (const QVariant &tunnelVaiant, m_dataMap.value("proxyStatistic").toMap().value("tunnels").toList()) {
QVariantMap tunnelMap = tunnelVaiant.toMap();
QVariantMap clientOne = m_clientHash.value(tunnelMap.value("clientOne").toString());
@ -223,12 +224,15 @@ void TerminalWindow::paintContentTunnels()
// Draw borders
drawWindowBorder(m_contentWindow);
wrefresh(m_contentWindow);
}
void TerminalWindow::eventLoop()
{
resizeWindow();
werase(m_headerWindow);
werase(m_contentWindow);
resizeWindow();
int keyInteger = getch();
switch (keyInteger) {
@ -264,11 +268,13 @@ void TerminalWindow::eventLoop()
break;
}
refresh();
wrefresh(m_headerWindow);
wrefresh(m_contentWindow);
//refresh();
// Reinvoke the method for the next event loop run
//QMetaObject::invokeMethod(this, "eventLoop", Qt::QueuedConnection);
QTimer::singleShot(50, this, &TerminalWindow::eventLoop);
QTimer::singleShot(100, this, &TerminalWindow::eventLoop);
}
void TerminalWindow::refreshWindow(const QVariantMap &dataMap)

View File

@ -7,6 +7,7 @@ jsonRpcTimeout=10000
authenticationTimeout=8000
inactiveTimeout=8000
aloneTimeout=8000
awsCredentialsUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-Remote-Connection-Proxy-Role
[SSL]
certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem

View File

@ -5,7 +5,7 @@ QT -= gui
SERVER_NAME=nymea-remoteproxy
API_VERSION_MAJOR=0
API_VERSION_MINOR=2
SERVER_VERSION=0.1.2
SERVER_VERSION=0.1.3
DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \
SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \

View File

@ -42,8 +42,8 @@
#include "loggingcategories.h"
#include "proxyconfiguration.h"
#include "remoteproxyserverapplication.h"
#include "authentication/awsauthenticator.h"
#include "authentication/dummyauthenticator.h"
#include "authentication/aws/awsauthenticator.h"
#include "authentication/dummy/dummyauthenticator.h"
using namespace remoteproxy;
@ -120,12 +120,14 @@ int main(int argc, char *argv[])
s_loggingFilters.insert("Authentication", true);
s_loggingFilters.insert("ProxyServer", true);
s_loggingFilters.insert("MonitorServer", true);
s_loggingFilters.insert("AwsCredentialsProvider", true);
// Only with verbose enabled
s_loggingFilters.insert("JsonRpcTraffic", false);
s_loggingFilters.insert("ProxyServerTraffic", false);
s_loggingFilters.insert("AuthenticationProcess", false);
s_loggingFilters.insert("WebSocketServerTraffic", false);
s_loggingFilters.insert("AwsCredentialsProviderTraffic", false);
QString configFile = "/etc/nymea/nymea-remoteproxy.conf";
@ -183,6 +185,7 @@ int main(int argc, char *argv[])
s_loggingFilters["ProxyServerTraffic"] = true;
s_loggingFilters["AuthenticationProcess"] = true;
s_loggingFilters["WebSocketServerTraffic"] = true;
s_loggingFilters["AwsCredentialsProviderTraffic"] = true;
}
QLoggingCategory::installFilter(loggingCategoryFilter);
@ -232,14 +235,14 @@ int main(int argc, char *argv[])
if (s_loggingEnabled)
qCDebug(dcApplication()) << "Logging enabled. Writing logs to" << s_logFile.fileName();
qCDebug(dcApplication()) << "Using ssl version:" << QSslSocket::sslLibraryVersionString();
qCDebug(dcApplication()) << "Using SSL version:" << QSslSocket::sslLibraryVersionString();
Authenticator *authenticator = nullptr;
if (parser.isSet(mockAuthenticatorOption)) {
authenticator = qobject_cast<Authenticator *>(new DummyAuthenticator(nullptr));
} else {
// Create default authenticator
authenticator = qobject_cast<Authenticator *>(new AwsAuthenticator(nullptr));
authenticator = qobject_cast<Authenticator *>(new AwsAuthenticator(configuration->awsCredentialsUrl(), nullptr));
}
// Configure and start the engines

View File

@ -34,27 +34,6 @@
BaseTest::BaseTest(QObject *parent) :
QObject(parent)
{
m_configuration = new ProxyConfiguration(this);
m_configuration->loadConfiguration(":/test-configuration.conf");
m_mockAuthenticator = new MockAuthenticator(this);
m_dummyAuthenticator = new DummyAuthenticator(this);
m_awsAuthenticator = new AwsAuthenticator(this);
m_authenticator = qobject_cast<Authenticator *>(m_mockAuthenticator);
m_testToken = "eyJraWQiOiJXdnFFT3prVVh5VDlINzFyRUpoNWdxRnkxNFhnR2l3SFAzVEIzUFQ1V3ZrPSIsImFsZyI6IlJT"
"MjU2In0.eyJzdWIiOiJmZTJmZDNlNC1hMGJhLTQ1OTUtOWRiZS00ZDkxYjRiMjFlMzUiLCJhdWQiOiI4cmpoZ"
"mRsZjlqZjFzdW9rMmpjcmx0ZDZ2IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV2ZW50X2lkIjoiN2Y5NTRiNm"
"ItOTYyZi0xMWU4LWI0ZjItMzU5NWRiZmRiODVmIiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE1MzM"
"xOTkxMzgsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC5ldS13ZXN0LTEuYW1hem9uYXdzLmNvbVwvZXUt"
"d2VzdC0xXzZlWDZZam1YciIsImNvZ25pdG86dXNlcm5hbWUiOiIyYzE3YzUwZC0xZDFlLTRhM2UtYmFjOS0zZ"
"DI0YjQ1MTFiYWEiLCJleHAiOjE1MzMyMDI3MzgsImlhdCI6MTUzMzE5OTEzOCwiZW1haWwiOiJqZW5raW5zQG"
"d1aC5pbyJ9.hMMSvZMx7pMvV70PaUmTZOZgdez5WGX5yagRFPZojBm8jNWZND1lUmi0RFkybeD4HonDiKHxTF"
"_psyJoBVndgHbxYBBl3Np4gn0MxECWjvLxYzGxVBBkN24SqNUyAGkr0uFcZKkBecdtJlqNQnZN8Uk49twmODf"
"raRaRmGmKmRBAK1qDITpUgP6AWqH9xoJWaoDzt0kwJ3EtPxS7vL1PHqOaN8ggXA8Eq4iTCSfXU1HAXhIWJH9Y"
"pQbj58v1vktaAEATdmKmlzmcix-HJK9wWHRSuv3TsNa8DGxvcPOoeTu8Vql7krZ-y7Zu-s2WsgeP4VxyT80VE"
"T_xh6pMkOhE6g";
}
@ -142,6 +121,12 @@ QVariant BaseTest::invokeApiCall(const QString &method, const QVariantMap params
socket->deleteLater();
for (int i = 0; i < dataSpy.count(); i++) {
// Make sure the response ends with '}\n'
if (!dataSpy.at(i).last().toByteArray().endsWith("}\n")) {
qWarning() << "JSON data does not end with \"}\n\"";
return QVariant();
}
// Make sure the response it a valid JSON string
QJsonParseError error;
jsonDoc = QJsonDocument::fromJson(dataSpy.at(i).last().toByteArray(), &error);
@ -184,6 +169,12 @@ QVariant BaseTest::injectSocketData(const QByteArray &data)
socket->deleteLater();
for (int i = 0; i < spy.count(); i++) {
// Make sure the response ends with '}\n'
if (!spy.at(i).last().toByteArray().endsWith("}\n")) {
qWarning() << "JSON data does not end with \"}\n\"";
return QVariant();
}
// Make sure the response it a valid JSON string
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.at(i).last().toByteArray(), &error);
@ -202,6 +193,28 @@ void BaseTest::initTestCase()
qRegisterMetaType<RemoteProxyConnection::State>();
qRegisterMetaType<RemoteProxyConnection::ConnectionType>();
m_configuration = new ProxyConfiguration(this);
m_configuration->loadConfiguration(":/test-configuration.conf");
m_mockAuthenticator = new MockAuthenticator(this);
m_dummyAuthenticator = new DummyAuthenticator(this);
m_awsAuthenticator = new AwsAuthenticator(m_configuration->awsCredentialsUrl(), this);
m_authenticator = qobject_cast<Authenticator *>(m_mockAuthenticator);
m_testToken = "eyJraWQiOiJXdnFFT3prVVh5VDlINzFyRUpoNWdxRnkxNFhnR2l3SFAzVEIzUFQ1V3ZrPSIsImFsZyI6IlJT"
"MjU2In0.eyJzdWIiOiJmZTJmZDNlNC1hMGJhLTQ1OTUtOWRiZS00ZDkxYjRiMjFlMzUiLCJhdWQiOiI4cmpoZ"
"mRsZjlqZjFzdW9rMmpjcmx0ZDZ2IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV2ZW50X2lkIjoiN2Y5NTRiNm"
"ItOTYyZi0xMWU4LWI0ZjItMzU5NWRiZmRiODVmIiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE1MzM"
"xOTkxMzgsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC5ldS13ZXN0LTEuYW1hem9uYXdzLmNvbVwvZXUt"
"d2VzdC0xXzZlWDZZam1YciIsImNvZ25pdG86dXNlcm5hbWUiOiIyYzE3YzUwZC0xZDFlLTRhM2UtYmFjOS0zZ"
"DI0YjQ1MTFiYWEiLCJleHAiOjE1MzMyMDI3MzgsImlhdCI6MTUzMzE5OTEzOCwiZW1haWwiOiJqZW5raW5zQG"
"d1aC5pbyJ9.hMMSvZMx7pMvV70PaUmTZOZgdez5WGX5yagRFPZojBm8jNWZND1lUmi0RFkybeD4HonDiKHxTF"
"_psyJoBVndgHbxYBBl3Np4gn0MxECWjvLxYzGxVBBkN24SqNUyAGkr0uFcZKkBecdtJlqNQnZN8Uk49twmODf"
"raRaRmGmKmRBAK1qDITpUgP6AWqH9xoJWaoDzt0kwJ3EtPxS7vL1PHqOaN8ggXA8Eq4iTCSfXU1HAXhIWJH9Y"
"pQbj58v1vktaAEATdmKmlzmcix-HJK9wWHRSuv3TsNa8DGxvcPOoeTu8Vql7krZ-y7Zu-s2WsgeP4VxyT80VE"
"T_xh6pMkOhE6g";
qCDebug(dcApplication()) << "Init test case.";
restartEngine();
}
@ -209,6 +222,13 @@ void BaseTest::initTestCase()
void BaseTest::cleanupTestCase()
{
qCDebug(dcApplication()) << "Clean up test case.";
delete m_configuration;
delete m_mockAuthenticator;
delete m_dummyAuthenticator;
delete m_awsAuthenticator;
m_authenticator = nullptr;
cleanUpEngine();
}

View File

@ -35,8 +35,8 @@
#include "mockauthenticator.h"
#include "proxyconfiguration.h"
#include "remoteproxyconnection.h"
#include "authentication/awsauthenticator.h"
#include "authentication/dummyauthenticator.h"
#include "authentication/aws/awsauthenticator.h"
#include "authentication/dummy/dummyauthenticator.h"
using namespace remoteproxy;
using namespace remoteproxyclient;