From e4747ede14339e2e8cb7603e0f12c2f8f8d7be02 Mon Sep 17 00:00:00 2001 From: Ned Wright Date: Fri, 8 Aug 2025 11:10:26 +0000 Subject: [PATCH] sync code --- .../nginx_conf_collector/fog_connection.cc | 200 ++++++++++++++++++ .../nginx_conf_collector/fog_connection.h | 33 +++ .../nginx_conf_collector/req_res_objects.h | 132 ++++++++++++ core/curl_http_client/CMakeLists.txt | 10 + core/curl_http_client/curl_http_client.cc | 183 ++++++++++++++++ core/include/internal/curl_http_client.h | 76 +++++++ .../services_sdk/interfaces/i_http_client.h | 50 +++++ 7 files changed, 684 insertions(+) create mode 100644 components/utils/utilities/nginx_conf_collector/fog_connection.cc create mode 100644 components/utils/utilities/nginx_conf_collector/fog_connection.h create mode 100644 components/utils/utilities/nginx_conf_collector/req_res_objects.h create mode 100755 core/curl_http_client/CMakeLists.txt create mode 100644 core/curl_http_client/curl_http_client.cc create mode 100644 core/include/internal/curl_http_client.h create mode 100644 core/include/services_sdk/interfaces/i_http_client.h diff --git a/components/utils/utilities/nginx_conf_collector/fog_connection.cc b/components/utils/utilities/nginx_conf_collector/fog_connection.cc new file mode 100644 index 0000000..9818e9e --- /dev/null +++ b/components/utils/utilities/nginx_conf_collector/fog_connection.cc @@ -0,0 +1,200 @@ +#include "fog_connection.h" + +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include "internal/curl_http_client.h" + +using namespace std; + +USE_DEBUG_FLAG(D_NGINX_MANAGER); + +// Helper function to check if HTTPResponse indicates success +bool +isSuccessfulResponse(const HTTPResponse& response) +{ + HTTPStatusCode code = response.getHTTPStatusCode(); + return (code == HTTPStatusCode::HTTP_OK || + code == HTTPStatusCode::HTTP_NO_CONTENT || + code == HTTPStatusCode::HTTP_MULTI_STATUS); +} + +FogConnection::FogConnection(const string& token, const string& fog_address) + : var_token(token), var_fog(fog_address), curl_client(std::make_unique()) {} + +void +FogConnection::setProxy(const string& hosts) +{ + curl_client->setProxy(hosts); +} + +Maybe +FogConnection::getCredentials() +{ + AgentRegistrationRequest request; + AgentRegistrationRequest::AuthData auth; + + auth.authenticationMethod = "token"; + auth.data = var_token; + + request.authenticationData.push_back(auth); + request.metaData.agentName = "ConfCollector"; + request.metaData.agentType = "Embedded"; + request.metaData.platform = "linux"; + request.metaData.architecture = "x86"; + request.metaData.additionalMetaData["agentVendor"] = "nginx-conf-collector"; + + stringstream ss_req; + { + cereal::JSONOutputArchive ar(ss_req); + request.serialize(ar); + } + + dbgTrace(D_NGINX_MANAGER) << "Registration JSON: " << ss_req.str(); + + string url = var_fog + "/agents"; + map headers = {{"Content-Type", "application/json"}, + {"User-Agent", "Infinity Next (a7030abf93a4c13)"}}; + + auto response = curl_client->post(url, ss_req.str(), headers); + + dbgTrace(D_NGINX_MANAGER) + << "Register agent response code: " + << static_cast(response.getHTTPStatusCode()) + << ", body: " + << response.getBody(); + + if (!isSuccessfulResponse(response)) { + return genError("Failed to register agent: HTTP " + + to_string(static_cast(response.getHTTPStatusCode())) + + " - " + + response.getBody()); + } + + if (response.getBody().find("referenceId") != string::npos) { + return genError("Registration failed: " + response.getBody()); + } + + try { + AgentRegistrationResponse reg_response; + stringstream ss_res(response.getBody()); + cereal::JSONInputArchive ar(ss_res); + reg_response.serialize(ar); + + agent_id = reg_response.agentId; + clientId = reg_response.clientId; + clientSecret = reg_response.clientSecret; + tenant_id = reg_response.tenantId; + profile_id = reg_response.profileId; + } catch (const exception& e) { + dbgTrace(D_NGINX_MANAGER) << "Failed to parse registration response: " << response.getBody(); + return genError("Failed to parse registration response: " + string(e.what())); + } + + return {}; +} + +Maybe +FogConnection::getJWT() +{ + TokenRequest request; + request.login = clientId; + request.password = clientSecret; + + stringstream ss_req; + { + cereal::JSONOutputArchive ar(ss_req); + ar(request); + } + + string url = var_fog + "/oauth/token?grant_type=client_credentials"; + map headers = {{"Content-Type", "application/json"}, + {"User-Agent", "Infinity Next (a7030abf93a4c13)"}}; + + dbgTrace(D_NGINX_MANAGER) << "get JWT JSON: " << ss_req.str(); + + curl_client->setBasicAuth(clientId, clientSecret); + curl_client->authEnabled(true); + auto response = curl_client->post(url, ss_req.str(), headers); + + dbgTrace(D_NGINX_MANAGER) + << "get JWT response code: " + << static_cast(response.getHTTPStatusCode()) + << ", body: " + << response.getBody(); + + if (!isSuccessfulResponse(response)) { + return genError("Failed to get JWT: HTTP " + + to_string(static_cast(response.getHTTPStatusCode())) + + " - " + + response.getBody()); + } + + if (response.getBody().find("referenceId") != string::npos) { + return genError("JWT request failed: " + response.getBody()); + } + + try { + TokenResponse token_response; + stringstream ss_res(response.getBody()); + cereal::JSONInputArchive ar(ss_res); + token_response.serialize(ar); + + ra_token = token_response.access_token; + } catch (const exception& e) { + dbgTrace(D_NGINX_MANAGER) << "Failed to parse JWT response: " << response.getBody(); + return genError("Failed to parse JWT response: " + string(e.what())); + } + + return {}; +} + +Maybe +FogConnection::uploadNginxConfig(const string& config_file_path) +{ + if (tenant_id.empty() || profile_id.empty() || ra_token.empty()) { + return genError("Missing required data for upload: tenant_id, profile_id, or ra_token"); + } + + ifstream file(config_file_path, ios::binary); + if (!file.is_open()) { + return genError("Cannot open file: " + config_file_path); + } + + string file_content((istreambuf_iterator(file)), istreambuf_iterator()); + file.close(); + + if (file_content.empty()) { + dbgTrace(D_NGINX_MANAGER) << "Warning: Uploading empty file content from " << config_file_path; + } + + + string upload_url = var_fog + "/agents-core/storage/" + tenant_id + "/nginx/" + profile_id + "/1/nginx.conf"; + map headers = { + {"Authorization", "Bearer " + ra_token}, + {"Content-Type", "text/plain"}, + {"User-Agent", "Infinity Next (a7030abf93a4c13)"} + }; + + auto response = curl_client->put(upload_url, file_content, headers); + + dbgTrace(D_NGINX_MANAGER) + << "Upload status code: " + << static_cast(response.getHTTPStatusCode()) + << ", body: " + << response.getBody(); + + if (!isSuccessfulResponse(response)) { + return genError("Upload failed: HTTP " + + to_string(static_cast(response.getHTTPStatusCode())) + + " - " + + response.getBody()); + } + + return {}; +} diff --git a/components/utils/utilities/nginx_conf_collector/fog_connection.h b/components/utils/utilities/nginx_conf_collector/fog_connection.h new file mode 100644 index 0000000..f140626 --- /dev/null +++ b/components/utils/utilities/nginx_conf_collector/fog_connection.h @@ -0,0 +1,33 @@ +#ifndef __FOG_CONNECTION_H__ +#define __FOG_CONNECTION_H__ + +#include +#include +#include +#include "services_sdk/interfaces/i_http_client.h" +#include "req_res_objects.h" +#include "maybe_res.h" + +class FogConnection +{ +public: + FogConnection(const std::string& token, const std::string& fog_address); + + void setProxy(const std::string& hosts); + Maybe getCredentials(); + Maybe getJWT(); + Maybe uploadNginxConfig(const std::string& config_file_path); + +private: + std::string var_token; + std::string var_fog; + std::string agent_id; + std::string tenant_id; + std::string profile_id; + std::string ra_token; + std::string clientId; + std::string clientSecret; + std::unique_ptr curl_client; +}; + +#endif // __FOG_CONNECTION_H__ diff --git a/components/utils/utilities/nginx_conf_collector/req_res_objects.h b/components/utils/utilities/nginx_conf_collector/req_res_objects.h new file mode 100644 index 0000000..5d615cf --- /dev/null +++ b/components/utils/utilities/nginx_conf_collector/req_res_objects.h @@ -0,0 +1,132 @@ +#ifndef __REQ_RES_OBJECTS_H__ +#define __REQ_RES_OBJECTS_H__ + +#include "cereal/archives/json.hpp" +#include "cereal/types/string.hpp" +#include "cereal/types/vector.hpp" +#include "cereal/types/map.hpp" + +#include "debug.h" + +USE_DEBUG_FLAG(D_NGINX_MANAGER); + +struct AgentRegistrationRequest +{ + struct AuthData + { + template + void serialize(Archive& ar) + { + try { + ar(cereal::make_nvp("authenticationMethod", authenticationMethod)); + ar(cereal::make_nvp("data", data)); + } catch (const cereal::Exception &e) { + dbgWarning(D_NGINX_MANAGER) << "Serialization error in AuthData: " << e.what(); + ar.setNextName(nullptr); + } + } + + std::string authenticationMethod; + std::string data; + }; + + struct MetaData + { + template + void serialize(Archive& ar) + { + try { + ar(cereal::make_nvp("agentName", agentName)); + ar(cereal::make_nvp("agentType", agentType)); + ar(cereal::make_nvp("platform", platform)); + ar(cereal::make_nvp("architecture", architecture)); + for (const auto& pair : additionalMetaData) { + ar(cereal::make_nvp(pair.first.c_str(), pair.second)); + } + } catch (const cereal::Exception &e) { + dbgWarning(D_NGINX_MANAGER) << "Serialization error in MetaData: " << e.what(); + ar.setNextName(nullptr); + } + } + + std::string agentName; + std::string agentType; + std::string platform; + std::string architecture; + std::map additionalMetaData; + }; + + template + void serialize(Archive& ar) + { + try { + ar(cereal::make_nvp("authenticationData", authenticationData)); + ar(cereal::make_nvp("metaData", metaData)); + } catch (const cereal::Exception &e) { + dbgWarning(D_NGINX_MANAGER) << "Serialization error in AgentRegistrationRequest: " << e.what(); + ar.setNextName(nullptr); + } + } + + std::vector authenticationData; + MetaData metaData; +}; + +struct TokenRequest +{ + template + void serialize(Archive& ar) + { + try { + ar(cereal::make_nvp("login", login)); + ar(cereal::make_nvp("password", password)); + } catch (const cereal::Exception &e) { + dbgWarning(D_NGINX_MANAGER) << "Serialization error in TokenRequest: " << e.what(); + ar.setNextName(nullptr); + } + } + + std::string login; + std::string password; +}; + +struct AgentRegistrationResponse +{ + template + void serialize(Archive& ar) + { + try { + ar(cereal::make_nvp("agentId", agentId)); + ar(cereal::make_nvp("clientId", clientId)); + ar(cereal::make_nvp("clientSecret", clientSecret)); + ar(cereal::make_nvp("tenantId", tenantId)); + ar(cereal::make_nvp("profileId", profileId)); + } catch (const cereal::Exception &e) { + dbgWarning(D_NGINX_MANAGER) << "Serialization error in AgentRegistrationResponse: " << e.what(); + ar.setNextName(nullptr); + } + } + + std::string agentId; + std::string clientId; + std::string clientSecret; + std::string tenantId; + std::string profileId; +}; + +struct TokenResponse +{ + template + void serialize(Archive& ar) + { + try { + ar(cereal::make_nvp("access_token", access_token)); + } catch (const cereal::Exception &e) { + dbgWarning(D_NGINX_MANAGER) << "Serialization error in TokenResponse: " << e.what(); + ar.setNextName(nullptr); + } + } + + std::string access_token; +}; +#endif diff --git a/core/curl_http_client/CMakeLists.txt b/core/curl_http_client/CMakeLists.txt new file mode 100755 index 0000000..f88fd72 --- /dev/null +++ b/core/curl_http_client/CMakeLists.txt @@ -0,0 +1,10 @@ +find_package(CURL REQUIRED) + +include_directories(${CURL_INCLUDE_DIRS}) +link_directories(${CURL_LIBRARY_DIRS}) + +add_library(curl_http_client STATIC + curl_http_client.cc +) + +target_link_libraries(curl_http_client ${CURL_LIBRARIES}) diff --git a/core/curl_http_client/curl_http_client.cc b/core/curl_http_client/curl_http_client.cc new file mode 100644 index 0000000..db00b8b --- /dev/null +++ b/core/curl_http_client/curl_http_client.cc @@ -0,0 +1,183 @@ +#include "curl_http_client.h" + +#include + +#include "debug.h" +#include "messaging/messaging_enums.h" + +using namespace std; + +USE_DEBUG_FLAG(D_NGINX_MANAGER); + +// Helper function to convert HTTP status code to HTTPStatusCode enum +HTTPStatusCode convertStatusCode(long code) +{ + switch (code) { + case 200: return HTTPStatusCode::HTTP_OK; + case 204: return HTTPStatusCode::HTTP_NO_CONTENT; + case 207: return HTTPStatusCode::HTTP_MULTI_STATUS; + case 400: return HTTPStatusCode::HTTP_BAD_REQUEST; + case 401: return HTTPStatusCode::HTTP_UNAUTHORIZED; + case 403: return HTTPStatusCode::HTTP_FORBIDDEN; + case 404: return HTTPStatusCode::HTTP_NOT_FOUND; + case 405: return HTTPStatusCode::HTTP_METHOD_NOT_ALLOWED; + case 407: return HTTPStatusCode::HTTP_PROXY_AUTHENTICATION_REQUIRED; + case 408: return HTTPStatusCode::HTTP_REQUEST_TIME_OUT; + case 413: return HTTPStatusCode::HTTP_PAYLOAD_TOO_LARGE; + case 429: return HTTPStatusCode::HTTP_TOO_MANY_REQUESTS; + case 500: return HTTPStatusCode::HTTP_INTERNAL_SERVER_ERROR; + case 501: return HTTPStatusCode::HTTP_NOT_IMPLEMENTED; + case 502: return HTTPStatusCode::HTTP_BAD_GATEWAY; + case 503: return HTTPStatusCode::HTTP_SERVICE_UNABAILABLE; + case 504: return HTTPStatusCode::HTTP_GATEWAY_TIMEOUT; + case 505: return HTTPStatusCode::HTTP_VERSION_NOT_SUPPORTED; + case 506: return HTTPStatusCode::HTTP_VARIANT_ALSO_NEGOTIATES; + case 507: return HTTPStatusCode::HTTP_INSUFFICIENT_STORAGE; + case 508: return HTTPStatusCode::HTTP_LOOP_DETECTED; + case 510: return HTTPStatusCode::HTTP_NOT_EXTENDED; + case 511: return HTTPStatusCode::HTTP_NETWORK_AUTHENTICATION_REQUIRED; + default: return HTTPStatusCode::NO_HTTP_RESPONSE; + } +} + +CurlHttpClient::CurlHttpClient(): auth_enabled(false) +{ + curl_global_init(CURL_GLOBAL_DEFAULT); +} + +CurlHttpClient::~CurlHttpClient() +{ + curl_global_cleanup(); +} + +void +CurlHttpClient::setProxy(const string& hosts) +{ + no_proxy_hosts = hosts; +} + +void +CurlHttpClient::setBasicAuth(const string& username, const string& password) +{ + this->username = username; + this->password = password; +} + +void +CurlHttpClient::authEnabled(bool enabled) +{ + auth_enabled = enabled; +} + +HTTPResponse +CurlHttpClient::get(const string& url, const map& headers) +{ + return perform_request("GET", url, "", headers); +} + +HTTPResponse +CurlHttpClient::post(const string& url, const string& data, const map& headers) +{ + return perform_request("POST", url, data, headers); +} + +HTTPResponse +CurlHttpClient::put(const string& url, const string& body, const map& headers) +{ + return perform_request("PUT", url, body, headers); +} + +HTTPResponse +CurlHttpClient::patch(const string& url, const string& body, const map& headers) +{ + return perform_request("PATCH", url, body, headers); +} + +HTTPResponse +CurlHttpClient::del(const string& url, const map& headers) +{ + return perform_request("DELETE", url, "", headers); +} + +size_t +CurlHttpClient::WriteCallback(void *contents, size_t size, size_t nmemb, string *userp) +{ + size_t totalSize = size * nmemb; + userp->append(static_cast(contents), totalSize); + return totalSize; +} + +HTTPResponse +CurlHttpClient::perform_request( + const string& method, + const string& url, + const string& body, + const map& headers +) +{ + string response_body; + long status_code = 0; + CURL *curl = curl_easy_init(); + if (!curl) { + return HTTPResponse(HTTPStatusCode::NO_HTTP_RESPONSE, "Failed to initialize curl"); + } + + struct curl_slist *header_list = nullptr; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_body); + + if (!no_proxy_hosts.empty()) { + dbgTrace(D_NGINX_MANAGER) << "Using proxy url: " << no_proxy_hosts; + curl_easy_setopt(curl, CURLOPT_PROXY, no_proxy_hosts.c_str()); + } + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + if (!username.empty() && !password.empty() && auth_enabled) { + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str()); + curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str()); + } + + if (method == "POST") { + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.length()); + } else if (method == "PUT") { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.length()); + } else if (method == "PATCH") { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.length()); + } else if (method == "DELETE") { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + } + + for (const auto &header_pair : headers) { + string header_str = header_pair.first + ": " + header_pair.second; + header_list = curl_slist_append(header_list, header_str.c_str()); + } + if (header_list) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); + } + + CURLcode res = curl_easy_perform(curl); + + if (res == CURLE_OK) { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code); + } else { + response_body = "curl_easy_perform() failed: " + string(curl_easy_strerror(res)); + status_code = 0; + } + + if (header_list) { + curl_slist_free_all(header_list); + } + curl_easy_cleanup(curl); + + return HTTPResponse(convertStatusCode(status_code), response_body); +} diff --git a/core/include/internal/curl_http_client.h b/core/include/internal/curl_http_client.h new file mode 100644 index 0000000..ed4efd0 --- /dev/null +++ b/core/include/internal/curl_http_client.h @@ -0,0 +1,76 @@ +#ifndef __CURL_HTTP_CLIENT_H__ +#define __CURL_HTTP_CLIENT_H__ + +#include +#include +#include +#include +#include "messaging/http_response.h" +#include "i_http_client.h" + +class CurlHttpClient : public I_HttpClient +{ +public: + CurlHttpClient(); + ~CurlHttpClient(); + + void setProxy(const std::string& hosts) override; + void setBasicAuth(const std::string& username, const std::string& password) override; + void authEnabled(bool enabled) override; + + HTTPResponse + get( + const std::string& url, + const std::map& headers = {} + ) override; + + HTTPResponse + post( + const std::string& url, + const std::string& data, + const std::map& headers = {} + ) override; + + HTTPResponse + put( + const std::string& url, + const std::string& body, + const std::map& headers = {} + ) override; HTTPResponse + patch( + const std::string& url, + const std::string& body, + const std::map& headers = {} + ) override; + + HTTPResponse + del( + const std::string& url, + const std::map& headers = {} + ) override; + +private: + static size_t + WriteCallback( + void *contents, + size_t size, + size_t nmemb, + std::string *userp + ); + + HTTPResponse + perform_request( + const std::string& method, + const std::string& url, + const std::string& body, + const std::map& headers + ); + + std::string no_proxy_hosts; + bool auth_enabled; + std::string username; + std::string password; +}; + +#endif // __CURL_HTTP_CLIENT_H__ + diff --git a/core/include/services_sdk/interfaces/i_http_client.h b/core/include/services_sdk/interfaces/i_http_client.h new file mode 100644 index 0000000..2e259f6 --- /dev/null +++ b/core/include/services_sdk/interfaces/i_http_client.h @@ -0,0 +1,50 @@ +#ifndef __I_HTTP_CLIENT_H__ +#define __I_HTTP_CLIENT_H__ + +#include +#include +#include "messaging/http_response.h" + +class I_HttpClient +{ +public: + virtual ~I_HttpClient() = default; + virtual void setProxy(const std::string& hosts) = 0; + virtual void setBasicAuth(const std::string& username, const std::string& password) = 0; + virtual void authEnabled(bool enabled) = 0; + + virtual HTTPResponse + get( + const std::string& url, + const std::map& headers = {} + ) = 0; + + virtual HTTPResponse + post( + const std::string& url, + const std::string& data, + const std::map& headers = {} + ) = 0; + + virtual HTTPResponse + put( + const std::string& url, + const std::string& body, + const std::map& headers = {} + ) = 0; + + virtual HTTPResponse + patch( + const std::string& url, + const std::string& body, + const std::map& headers = {} + ) = 0; + + virtual HTTPResponse + del( + const std::string& url, + const std::map& headers = {} + ) = 0; +}; + +#endif // __I_HTTP_CLIENT_H__