Add native lambda invoce structure for first tests

This commit is contained in:
Simon Stürz 2018-08-24 13:30:51 +02:00
parent d308be58b3
commit c7dd9e72aa
5 changed files with 290 additions and 14 deletions

View File

@ -27,6 +27,9 @@
#include <QNetworkReply>
#include <QJsonDocument>
#include "sigv4utils.h"
namespace remoteproxy {
AuthenticationProcess::AuthenticationProcess(QNetworkAccessManager *manager, QObject *parent) :
@ -50,7 +53,53 @@ void AuthenticationProcess::requestDynamicCredentials()
connect(reply, &QNetworkReply::finished, this, &AuthenticationProcess::onDynamicCredentialsReady);
}
void AuthenticationProcess::startVerificationProcess()
void AuthenticationProcess::invokeLambdaFunction(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken)
{
// Known configurations
QString region = "eu-west-1";
QString lambdaFunctionName = "system-services-authorizer-dev-checkToken";
QString invocationType = "RequestResponse";
QString service = "lambda";
// {'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());
SigV4Utils::signRequest(QNetworkAccessManager::PostOperation, request, region, service, accessKey.toUtf8(), secretAccessKey.toUtf8(), sessionToken.toUtf8(), payload);
qCDebug(dcAuthenticationProcess()) << "Invoke lambda function" << lambdaFunctionName;
qCDebug(dcAuthenticationProcess()) << "--------------------------------------------";
qCDebug(dcAuthenticationProcess()) << request.url().toString();
foreach (const QByteArray &rawHeader, request.rawHeaderList()) {
qDebug(dcAuthenticationProcess()) << request.rawHeader(rawHeader);
}
qCDebug(dcAuthenticationProcess()) << payload;
qCDebug(dcAuthenticationProcess()) << "--------------------------------------------";
m_lambdaTimer.start();
QNetworkReply *reply = m_manager->get(request);
connect(reply, &QNetworkReply::finished, this, &AuthenticationProcess::onLambdaInvokeFunctionFinished);
}
void AuthenticationProcess::startVerificationProcess(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken)
{
if (m_process->state() != QProcess::NotRunning) {
qCWarning(dcAuthenticationProcess()) << "Authentication process already running. Killing the running process and restart.";
@ -65,10 +114,10 @@ void AuthenticationProcess::startVerificationProcess()
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("AWS_DEFAULT_REGION", "eu-west-1");
if (m_dynamicCredentials) {
qCDebug(dcAuthenticationProcess()) << "Using dynamic credentials" << m_awsAccessKeyId << m_awsSecretAccessKey << m_awsSessionToken;
env.insert("AWS_ACCESS_KEY_ID", m_awsAccessKeyId);
env.insert("AWS_SECRET_ACCESS_KEY", m_awsSecretAccessKey);
env.insert("AWS_SESSION_TOKEN", m_awsSessionToken);
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);
}
m_process->setProcessEnvironment(env);
@ -111,11 +160,49 @@ void AuthenticationProcess::onDynamicCredentialsReady()
QVariantMap response = jsonDoc.toVariant().toMap();
qCDebug(dcAuthentication()) << "-->" << response;
m_awsAccessKeyId = response.value("AccessKeyId").toString();
m_awsSecretAccessKey = response.value("SecretAccessKey").toString();
m_awsSessionToken = response.value("Token").toString();
startVerificationProcess(response.value("AccessKeyId").toString(),
response.value("SecretAccessKey").toString(),
response.value("Token").toString());
}
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;
}
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);
}
qCDebug(dcAuthenticationProcess()) << data;
qCDebug(dcAuthenticationProcess()) << "--------------------------------------------";
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();
}
void AuthenticationProcess::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
@ -199,8 +286,9 @@ void AuthenticationProcess::authenticate(const QString &token)
// Request the access information
requestDynamicCredentials();
} else {
// FIXME:
// Direct call aws cli and assume the credentials will be provided static
startVerificationProcess();
// startVerificationProcess();
}
}

View File

@ -45,17 +45,16 @@ private:
QString m_resultFileName;
bool m_dynamicCredentials = true;
QString m_awsAccessKeyId;
QString m_awsSecretAccessKey;
QString m_awsSessionToken;
QNetworkAccessManager *m_manager = nullptr;
QProcess *m_process = nullptr;
QElapsedTimer m_requestTimer;
QElapsedTimer m_lambdaTimer;
QElapsedTimer m_processTimer;
void requestDynamicCredentials();
void startVerificationProcess();
void invokeLambdaFunction(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken);
void startVerificationProcess(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken);
void cleanUp();
signals:
@ -63,6 +62,7 @@ signals:
private slots:
void onDynamicCredentialsReady();
void onLambdaInvokeFunctionFinished();
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
public slots:

View File

@ -0,0 +1,153 @@
#include "sigv4utils.h"
#include <QDateTime>
#include <QCryptographicHash>
#include <QMessageAuthenticationCode>
#include <QtDebug>
#include <QUrlQuery>
#include <QList>
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)
{
QByteArray dateTime;
if (request.rawHeaderList().contains("X-Amz-Date")) {
dateTime = request.rawHeader("X-AMZ-Date");
} else {
dateTime = SigV4Utils::getCurrentDateTime();
request.setRawHeader("X-Amz-Date", dateTime);
}
if (!sessionToken.isEmpty()) {
request.setRawHeader("x-amz-security-token", sessionToken);
}
QByteArray canonicalRequest = SigV4Utils::getCanonicalRequest(operation, request, payload);
// qDebug() << "canonical request:" << qUtf8Printable(canonicalRequest);
QByteArray stringToSign = SigV4Utils::getStringToSign(canonicalRequest, dateTime, region.toUtf8(), service.toUtf8());
// qDebug() << "string to sign:" << stringToSign;
QByteArray signature = SigV4Utils::getSignature(stringToSign, secretAccessKey, dateTime, region, service);
// qDebug() << "signature:" << signature;
QByteArray authorizeHeader = SigV4Utils::getAuthorizationHeader(accessKeyId, dateTime, region, service, request, signature);
request.setRawHeader("Authorization", authorizeHeader);
}
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(QUrl::FullyEncoded).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

@ -0,0 +1,33 @@
#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

@ -24,6 +24,7 @@ HEADERS += \
authentication/userinformation.h \
authentication/authenticationprocess.h \
authentication/dummyauthenticator.h \
authentication/sigv4utils.h
SOURCES += \
engine.cpp \
@ -46,6 +47,7 @@ SOURCES += \
authentication/userinformation.cpp \
authentication/authenticationprocess.cpp \
authentication/dummyauthenticator.cpp \
authentication/sigv4utils.cpp
# install header file with relative subdirectory