add api to deploy certificates

This commit is contained in:
Michael Zanetti 2018-08-16 20:59:06 +02:00
parent 1dc142bb45
commit a29a0483b2
7 changed files with 163 additions and 45 deletions

View File

@ -25,12 +25,17 @@
#include "cloudnotifications.h"
#include "nymeaconfiguration.h"
#include "cloudtransport.h"
#include "nymeaconfiguration.h"
#include "nymeasettings.h"
#include "nymea-remoteproxyclient/remoteproxyconnection.h"
#include <QDir>
using namespace remoteproxyclient;
CloudManager::CloudManager(NetworkManager *networkManager, QObject *parent) : QObject(parent),
CloudManager::CloudManager(NymeaConfiguration *configuration, NetworkManager *networkManager, QObject *parent):
QObject(parent),
m_configuration(configuration),
m_networkManager(networkManager)
{
m_awsConnector = new AWSConnector(this);
@ -49,35 +54,30 @@ CloudManager::CloudManager(NetworkManager *networkManager, QObject *parent) : QO
m_transport = new CloudTransport(ServerConfiguration());
connect(m_awsConnector, &AWSConnector::proxyConnectionRequestReceived, m_transport, &CloudTransport::connectToCloud);
m_deviceId = m_configuration->serverUuid();
m_deviceName = m_configuration->serverName();
m_serverUrl = m_configuration->cloudServerUrl();
m_caCertificate = m_configuration->cloudCertificateCA();
m_clientCertificate = m_configuration->cloudCertificate();
m_clientCertificateKey = m_configuration->cloudCertificateKey();
setEnabled(m_configuration->cloudEnabled());
connect(m_configuration, &NymeaConfiguration::cloudEnabledChanged, this, &CloudManager::setEnabled);
connect(m_configuration, &NymeaConfiguration::serverNameChanged, this, &CloudManager::setDeviceName);
}
CloudManager::~CloudManager()
{
}
void CloudManager::setServerUrl(const QString &serverUrl)
{
m_serverUrl = serverUrl;
}
void CloudManager::setDeviceId(const QUuid &deviceId)
{
m_deviceId = deviceId;
}
void CloudManager::setDeviceName(const QString &name)
{
m_deviceName = name;
m_awsConnector->setDeviceName(name);
}
void CloudManager::setClientCertificates(const QString &caCertificate, const QString &clientCertificate, const QString &clientCertificateKey)
{
m_caCertificate = caCertificate;
m_clientCertificate = clientCertificate;
m_clientCertificateKey = clientCertificateKey;
}
bool CloudManager::enabled() const
{
return m_enabled;
@ -86,6 +86,9 @@ bool CloudManager::enabled() const
void CloudManager::setEnabled(bool enabled)
{
if (enabled) {
m_enabled = true;
emit connectionStateChanged();
bool missingConfig = false;
if (m_deviceId.isNull()) {
qCWarning(dcCloud()) << "Don't have a unique device ID.";
@ -117,7 +120,6 @@ void CloudManager::setEnabled(bool enabled)
}
qCDebug(dcCloud()) << "Enabling cloud connection.";
m_enabled = true;
if (!m_awsConnector->isConnected() && m_networkManager->state() == NetworkManager::NetworkManagerStateConnectedGlobal) {
connect2aws();
}
@ -125,12 +127,77 @@ void CloudManager::setEnabled(bool enabled)
qCDebug(dcCloud()) << "Disabling cloud connection.";
m_enabled = false;
m_awsConnector->disconnectAWS();
emit connectionStateChanged();
}
}
bool CloudManager::connected() const
bool CloudManager::installClientCertificates(const QByteArray &rootCA, const QByteArray &certificatePEM, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint)
{
return m_awsConnector->isConnected();
QString baseDir = NymeaSettings::storagePath() + "/certs/cloud/";
QDir dir;
// We never delete old certs, cycle until we find an unused path
int i = 0;
do {
dir = QDir(baseDir + QString::number(i++) + '/');
} while (dir.exists());
if (!dir.mkpath(dir.absolutePath())) {
qCWarning(dcCloud) << "Cannot install cloud certificates. Unable to create path.";
return false;
}
QFile ca(dir.absoluteFilePath("aws-certification-authority.crt"));
if (!ca.open(QFile::WriteOnly) || ca.write(rootCA) != rootCA.length()) {
qCWarning(dcCloud()) << "Cannot install cloud certificates. Unable to write CA file" << dir.absoluteFilePath(ca.fileName());
ca.close();
return false;
}
ca.close();
QFile pem(dir.absoluteFilePath("guh-cloud.pem"));
if (!pem.open(QFile::WriteOnly) || pem.write(certificatePEM) != certificatePEM.length()) {
qCWarning(dcCloud()) << "Cannot install cloud certificates. Unable to write certificate file" << dir.absoluteFilePath(pem.fileName());
pem.close();
return false;
}
pem.close();
QFile pub(dir.absoluteFilePath("guh-cloud.pub"));
if (!pub.open(QFile::WriteOnly) || pub.write(publicKey) != publicKey.length()) {
qCWarning(dcCloud()) << "Cannot install cloud certificates. Unable to write public key file" << dir.absoluteFilePath(pub.fileName());
pub.close();
return false;
}
pub.close();
QFile key(dir.absoluteFilePath("guh-cloud.key"));
if (!key.open(QFile::WriteOnly) || key.write(privateKey) != privateKey.length()) {
qCWarning(dcCloud()) << "Cannot install cloud certificates. Unable to write private key file" << dir.absoluteFilePath(key.fileName());
key.close();
return false;
}
key.close();
qCDebug(dcCloud) << "Installed cloud certificates to" << dir.absolutePath();
m_caCertificate = dir.absoluteFilePath("aws-certification-authority.crt");
m_clientCertificate = dir.absoluteFilePath("guh-cloud.pem");
m_clientCertificateKey = dir.absoluteFilePath("guh-cloud.key");
m_serverUrl = endpoint;
if (m_enabled) {
m_awsConnector->disconnectAWS();
connect2aws();
}
return true;
}
CloudManager::CloudConnectionState CloudManager::connectionState() const
{
if (m_awsConnector->isConnected()) {
return CloudConnectionStateConnected;
}
if (!m_enabled) {
return CloudConnectionStateDisabled;
}
if (m_deviceId.isNull() || m_deviceName.isEmpty() || m_serverUrl.isEmpty() || m_clientCertificate.isEmpty() || m_clientCertificateKey.isEmpty() || m_caCertificate.isEmpty()) {
return CloudConnectionStateUnconfigured;
}
return CloudConnectionStateConnecting;
}
void CloudManager::pairDevice(const QString &idToken, const QString &userId)
@ -191,10 +258,10 @@ void CloudManager::onJanusWebRtcHandshakeMessageReceived(const QString &transact
void CloudManager::awsConnected()
{
emit connectedChanged(true);
emit connectionStateChanged();
}
void CloudManager::awsDisconnected()
{
emit connectedChanged(false);
emit connectionStateChanged();
}

View File

@ -37,22 +37,33 @@ class RemoteProxyConnection;
namespace nymeaserver {
class NymeaConfiguration;
class CloudTransport;
class CloudManager : public QObject
{
Q_OBJECT
public:
explicit CloudManager(NetworkManager *networkManager, QObject *parent = nullptr);
enum CloudConnectionState {
CloudConnectionStateDisabled,
CloudConnectionStateUnconfigured,
CloudConnectionStateConnecting,
CloudConnectionStateConnected
};
Q_ENUM(CloudConnectionState)
explicit CloudManager(NymeaConfiguration *configuration, NetworkManager *networkManager, QObject *parent = nullptr);
~CloudManager();
void setServerUrl(const QString &serverUrl);
void setDeviceId(const QUuid &deviceId);
void setDeviceName(const QString &name);
void setClientCertificates(const QString &caCertificate, const QString &clientCertificate, const QString &clientCertificateKey);
// void setServerUrl(const QString &serverUrl);
// void setDeviceId(const QUuid &deviceId);
// void setClientCertificates(const QString &caCertificate, const QString &clientCertificate, const QString &clientCertificateKey);
bool enabled() const;
void setEnabled(bool enabled);
bool connected() const;
bool installClientCertificates(const QByteArray &rootCA, const QByteArray &certificatePEM, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint);
CloudConnectionState connectionState() const;
void pairDevice(const QString &idToken, const QString &userId);
@ -62,7 +73,7 @@ public:
CloudTransport* createTransportInterface() const;
signals:
void connectedChanged(bool connected);
void connectionStateChanged();
void pairingReply(QString cognitoUserId, int status, const QString &message);
@ -76,12 +87,14 @@ private slots:
void onJanusWebRtcHandshakeMessageReceived(const QString &transactionId, const QVariantMap &data);
void awsConnected();
void awsDisconnected();
void setDeviceName(const QString &name);
private:
QTimer m_reconnectTimer;
bool m_enabled = false;
AWSConnector *m_awsConnector = nullptr;
JanusConnector *m_janusConnector = nullptr;
NymeaConfiguration *m_configuration = nullptr;
NetworkManager *m_networkManager = nullptr;
QString m_serverUrl;

View File

@ -165,6 +165,17 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
returns.insert("error", JsonTypes::userErrorRef());
setReturns("RemoveToken", returns);
params.clear(); returns.clear();
setDescription("SetupCloudConnection", "Sets up the cloud connection by deploying a certificate and its configuration.");
params.insert("rootCA", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("certificatePEM", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("publicKey", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("privateKey", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("endpoint", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("SetupCloudConnection", params);
returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("SetupCloudConnection", returns);
params.clear(); returns.clear();
setDescription("SetupRemoteAccess", "Setup the remote connection by providing AWS token information. This requires the cloud to be connected.");
params.insert("idToken", JsonTypes::basicTypeToString(JsonTypes::String));
@ -175,9 +186,10 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
setReturns("SetupRemoteAccess", returns);
params.clear(); returns.clear();
setDescription("IsCloudConnected", "Check whether the cloud is currently connected.");
setDescription("IsCloudConnected", "Check whether the cloud is currently connected. \"connected\" will be true whenever connectionState equals CloudConnectionStateConnected and is deprecated. Please use the connectionState value instead.");
setParams("IsCloudConnected", params);
returns.insert("connected", JsonTypes::basicTypeToString(JsonTypes::Bool));
returns.insert("connectionState", JsonTypes::cloudConnectionStateRef());
setReturns("IsCloudConnected", returns);
params.clear(); returns.clear();
@ -329,6 +341,25 @@ JsonReply *JsonRPCServer::RemoveToken(const QVariantMap &params)
return createReply(ret);
}
JsonReply *JsonRPCServer::SetupCloudConnection(const QVariantMap &params)
{
if (NymeaCore::instance()->cloudManager()->connectionState() != CloudManager::CloudConnectionStateUnconfigured) {
qCDebug(dcCloud) << "Cloud already configured. Not changing configuration as it won't work anyways. If you want to reconfigure this instance to a different cloud, change the system UUID and wipe the cloud settings from the config.";
QVariantMap data;
data.insert("success", false);
return createReply(data);
}
QByteArray rootCA = params.value("rootCA").toByteArray();
QByteArray certificatePEM = params.value("certificatePEM").toByteArray();
QByteArray publicKey = params.value("publicKey").toByteArray();
QByteArray privateKey = params.value("privateKey").toByteArray();
QString endpoint = params.value("endpoint").toString();
bool status = NymeaCore::instance()->cloudManager()->installClientCertificates(rootCA, certificatePEM, publicKey, privateKey, endpoint);
QVariantMap ret;
ret.insert("success", status);
return createReply(ret);
}
JsonReply *JsonRPCServer::SetupRemoteAccess(const QVariantMap &params)
{
QString idToken = params.value("idToken").toString();
@ -345,9 +376,10 @@ JsonReply *JsonRPCServer::SetupRemoteAccess(const QVariantMap &params)
JsonReply *JsonRPCServer::IsCloudConnected(const QVariantMap &params)
{
Q_UNUSED(params)
bool connected = NymeaCore::instance()->cloudManager()->connected();
bool connected = NymeaCore::instance()->cloudManager()->connectionState() == CloudManager::CloudConnectionStateConnected;
QVariantMap data;
data.insert("connected", connected);
data.insert("connectionState", JsonTypes::cloudConnectionStateToString(NymeaCore::instance()->cloudManager()->connectionState()));
return createReply(data);
}
@ -457,7 +489,7 @@ void JsonRPCServer::setup()
registerHandler(new TagsHandler(this));
connect(NymeaCore::instance()->cloudManager(), &CloudManager::pairingReply, this, &JsonRPCServer::pairingFinished);
connect(NymeaCore::instance()->cloudManager(), &CloudManager::connectedChanged, this, &JsonRPCServer::onCloudConnectedChanged);
connect(NymeaCore::instance()->cloudManager(), &CloudManager::connectionStateChanged, this, &JsonRPCServer::onCloudConnectionStateChanged);
}
void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &data)
@ -616,10 +648,11 @@ void JsonRPCServer::pairingFinished(QString cognitoUserId, int status, const QSt
reply->finished();
}
void JsonRPCServer::onCloudConnectedChanged(bool connected)
void JsonRPCServer::onCloudConnectionStateChanged()
{
QVariantMap params;
params.insert("connected", connected);
params.insert("connected", NymeaCore::instance()->cloudManager()->connectionState() == CloudManager::CloudConnectionStateConnected);
params.insert("connectionState", JsonTypes::cloudConnectionStateToString(NymeaCore::instance()->cloudManager()->connectionState()));
emit CloudConnectedChanged(params);
}

View File

@ -43,7 +43,7 @@ class JsonRPCServer: public JsonHandler
{
Q_OBJECT
public:
JsonRPCServer(const QSslConfiguration &sslConfiguration = QSslConfiguration(), QObject *parent = 0);
JsonRPCServer(const QSslConfiguration &sslConfiguration = QSslConfiguration(), QObject *parent = nullptr);
// JsonHandler API implementation
QString name() const;
@ -57,6 +57,7 @@ public:
Q_INVOKABLE JsonReply *RequestPushButtonAuth(const QVariantMap &params);
Q_INVOKABLE JsonReply *Tokens(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap &params);
Q_INVOKABLE JsonReply *SetupCloudConnection(const QVariantMap &params);
Q_INVOKABLE JsonReply *SetupRemoteAccess(const QVariantMap &params);
Q_INVOKABLE JsonReply *IsCloudConnected(const QVariantMap &params);
Q_INVOKABLE JsonReply *KeepAlive(const QVariantMap &params);
@ -91,7 +92,7 @@ private slots:
void asyncReplyFinished();
void pairingFinished(QString cognitoUserId, int status, const QString &message);
void onCloudConnectedChanged(bool connected);
void onCloudConnectionStateChanged();
void onPushButtonAuthFinished(int transactionId, bool success, const QByteArray &token);
private:

View File

@ -90,6 +90,7 @@ QVariantList JsonTypes::s_networkManagerState;
QVariantList JsonTypes::s_networkDeviceState;
QVariantList JsonTypes::s_userError;
QVariantList JsonTypes::s_tagError;
QVariantList JsonTypes::s_cloudConnectionState;
QVariantMap JsonTypes::s_paramType;
QVariantMap JsonTypes::s_param;
@ -151,6 +152,7 @@ void JsonTypes::init()
s_networkDeviceState = enumToStrings(NetworkDevice::staticMetaObject, "NetworkDeviceState");
s_userError = enumToStrings(UserManager::staticMetaObject, "UserError");
s_tagError = enumToStrings(TagsStorage::staticMetaObject, "TagError");
s_cloudConnectionState = enumToStrings(CloudManager::staticMetaObject, "CloudConnectionState");
// ParamType
s_paramType.insert("id", basicTypeToString(Uuid));
@ -2124,6 +2126,12 @@ QPair<bool, QString> JsonTypes::validateVariant(const QVariant &templateVariant,
qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(logEntryRef());
return result;
}
} else if (refName == cloudConnectionStateRef()) {
QPair<bool, QString> result = validateEnum(s_cloudConnectionState, variant);
if (!result.first) {
qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(cloudConnectionStateRef());
return result;
}
} else {
Q_ASSERT_X(false, "JsonTypes", QString("Unhandled ref: %1").arg(refName).toLatin1().data());
return report(false, QString("Unhandled ref %1. Server implementation incomplete.").arg(refName));

View File

@ -56,6 +56,8 @@
#include "networkmanager/wirelessnetworkdevice.h"
#include "networkmanager/wirelessaccesspoint.h"
#include "cloud/cloudmanager.h"
#include <QObject>
#include <QVariantMap>
@ -139,6 +141,7 @@ public:
DECLARE_TYPE(networkDeviceState, "NetworkDeviceState", NetworkDevice, NetworkDeviceState)
DECLARE_TYPE(userError, "UserError", UserManager, UserError)
DECLARE_TYPE(tagError, "TagError", TagsStorage, TagError)
DECLARE_TYPE(cloudConnectionState, "CloudConnectionState", CloudManager, CloudConnectionState)
DECLARE_OBJECT(paramType, "ParamType")
DECLARE_OBJECT(param, "Param")

View File

@ -549,12 +549,7 @@ void NymeaCore::init() {
m_debugServerHandler = new DebugServerHandler(this);
qCDebug(dcApplication) << "Creating Cloud Manager";
m_cloudManager = new CloudManager(m_networkManager, this);
m_cloudManager->setDeviceId(m_configuration->serverUuid());
m_cloudManager->setDeviceName(m_configuration->serverName());
m_cloudManager->setServerUrl(m_configuration->cloudServerUrl());
m_cloudManager->setClientCertificates(m_configuration->cloudCertificateCA(), m_configuration->cloudCertificate(), m_configuration->cloudCertificateKey());
m_cloudManager->setEnabled(m_configuration->cloudEnabled());
m_cloudManager = new CloudManager(m_configuration, m_networkManager, this);
CloudNotifications *cloudNotifications = m_cloudManager->createNotificationsPlugin();
m_deviceManager->registerStaticPlugin(cloudNotifications, cloudNotifications->metaData());
@ -563,8 +558,6 @@ void NymeaCore::init() {
m_serverManager->jsonServer()->registerTransportInterface(cloudTransport, false);
connect(m_configuration, &NymeaConfiguration::localeChanged, this, &NymeaCore::onLocaleChanged);
connect(m_configuration, &NymeaConfiguration::cloudEnabledChanged, m_cloudManager, &CloudManager::setEnabled);
connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_cloudManager, &CloudManager::setDeviceName);
connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_serverManager, &ServerManager::setServerName);
connect(m_deviceManager, &DeviceManager::pluginConfigChanged, this, &NymeaCore::pluginConfigChanged);