This commit is contained in:
parent
b4542c24be
commit
f20c8ff330
@ -67,6 +67,10 @@ FROM scratch AS project
|
||||
|
||||
ARG PROJECT
|
||||
|
||||
# Copy CA certificates for SSL validation
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/
|
||||
|
||||
# Copy the actual binary from the regular directory
|
||||
COPY --from=builder /output/${PROJECT} /${PROJECT}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include "GetbinClient.hpp"
|
||||
#include <drogon/HttpClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
@ -14,89 +13,108 @@
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
static constexpr const char* SERVER_HOST = "getpkg.xyz";
|
||||
|
||||
// Global flag to track if event loop is running
|
||||
static std::atomic<bool> g_eventLoopRunning{false};
|
||||
static std::thread g_eventLoopThread;
|
||||
static std::mutex g_eventLoopMutex;
|
||||
static std::condition_variable g_eventLoopCv;
|
||||
static std::string find_ca_certificates() {
|
||||
// Common CA certificate locations across different Linux distributions
|
||||
const std::vector<std::string> ca_paths = {
|
||||
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Raspbian
|
||||
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL/CentOS
|
||||
"/etc/ssl/ca-bundle.pem", // OpenSUSE
|
||||
"/etc/pki/tls/cert.pem", // Fedora/RHEL alternative
|
||||
"/etc/ssl/certs/ca-bundle.crt", // Some distros
|
||||
"/etc/ssl/cert.pem", // Alpine Linux
|
||||
"/usr/local/share/certs/ca-root-nss.crt", // FreeBSD
|
||||
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7+
|
||||
"/etc/ca-certificates/extracted/tls-ca-bundle.pem" // Arch Linux
|
||||
};
|
||||
|
||||
static void ensureEventLoop() {
|
||||
static std::once_flag initFlag;
|
||||
std::call_once(initFlag, []() {
|
||||
g_eventLoopThread = std::thread([]() {
|
||||
// Start the event loop in a separate thread
|
||||
drogon::app().getLoop()->queueInLoop([]() {
|
||||
std::unique_lock<std::mutex> lock(g_eventLoopMutex);
|
||||
g_eventLoopRunning = true;
|
||||
g_eventLoopCv.notify_all();
|
||||
});
|
||||
drogon::app().run();
|
||||
});
|
||||
for (const auto& path : ca_paths) {
|
||||
std::ifstream file(path);
|
||||
if (file.good()) {
|
||||
file.close();
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for event loop to start
|
||||
std::unique_lock<std::mutex> lock(g_eventLoopMutex);
|
||||
g_eventLoopCv.wait(lock, []() { return g_eventLoopRunning.load(); });
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
GetbinClient::GetbinClient() {
|
||||
ensureEventLoop();
|
||||
}
|
||||
GetbinClient::GetbinClient() {}
|
||||
|
||||
bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) {
|
||||
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
|
||||
bool success = false;
|
||||
bool done = false;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
|
||||
std::thread worker([&]() {
|
||||
trantor::EventLoop loop;
|
||||
|
||||
auto client = drogon::HttpClient::newHttpClient(
|
||||
"https://" + std::string(SERVER_HOST),
|
||||
&loop
|
||||
);
|
||||
|
||||
// Configure SSL certificates for HTTPS
|
||||
std::string ca_path = find_ca_certificates();
|
||||
if (!ca_path.empty()) {
|
||||
std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl;
|
||||
// Use addSSLConfigs with proper parameter names for OpenSSL
|
||||
std::vector<std::pair<std::string, std::string>> sslConfigs;
|
||||
sslConfigs.push_back({"VerifyCAFile", ca_path});
|
||||
client->addSSLConfigs(sslConfigs);
|
||||
} else {
|
||||
// If no CA certificates found, print warning but continue
|
||||
std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
|
||||
}
|
||||
|
||||
client->enableCookies();
|
||||
client->setUserAgent("getpkg/1.0");
|
||||
|
||||
std::string object_path = "/object/" + toolName + ":" + arch;
|
||||
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath(object_path);
|
||||
|
||||
std::atomic<bool> success(false);
|
||||
std::atomic<bool> completed(false);
|
||||
|
||||
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
||||
if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) {
|
||||
std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl;
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
|
||||
std::ofstream ofs(outPath, std::ios::binary);
|
||||
if (!ofs) {
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ofs) {
|
||||
const auto& body = response->getBody();
|
||||
ofs.write(body.data(), body.size());
|
||||
success = ofs.good();
|
||||
completed = true;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl;
|
||||
}
|
||||
done = true;
|
||||
cv.notify_one();
|
||||
loop.quit();
|
||||
}, 30.0); // 30 second timeout
|
||||
|
||||
loop.loop();
|
||||
});
|
||||
|
||||
// Wait for the async request to complete with timeout
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
const auto timeout = std::chrono::seconds(30); // 30 second timeout
|
||||
|
||||
while (!completed) {
|
||||
if (std::chrono::steady_clock::now() - start > timeout) {
|
||||
std::cerr << "[GetbinClient::download] Request timed out after 30 seconds." << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// Wait for completion
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
cv.wait(lock, [&] { return done; });
|
||||
}
|
||||
|
||||
worker.join();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token) {
|
||||
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
|
||||
|
||||
// Read file
|
||||
// Read file first
|
||||
std::ifstream ifs(archivePath, std::ios::binary);
|
||||
if (!ifs) {
|
||||
std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl;
|
||||
@ -104,50 +122,73 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
|
||||
}
|
||||
std::string file_content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
|
||||
// Compose metadata (minimal, can be extended)
|
||||
// Compose metadata
|
||||
json metadata = { {"labeltags", json::array()} };
|
||||
// Try to extract tool:arch from filename
|
||||
std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1);
|
||||
size_t dot = filename.find('.');
|
||||
std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename;
|
||||
metadata["labeltags"].push_back(labeltag);
|
||||
|
||||
// Create request with raw body upload (simplified approach)
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
bool success = false;
|
||||
bool done = false;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
|
||||
std::thread worker([&]() {
|
||||
trantor::EventLoop loop;
|
||||
|
||||
auto client = drogon::HttpClient::newHttpClient(
|
||||
"https://" + std::string(SERVER_HOST),
|
||||
&loop
|
||||
);
|
||||
|
||||
// Configure SSL certificates
|
||||
std::string ca_path = find_ca_certificates();
|
||||
if (!ca_path.empty()) {
|
||||
std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl;
|
||||
std::vector<std::pair<std::string, std::string>> sslConfigs;
|
||||
sslConfigs.push_back({"VerifyCAFile", ca_path});
|
||||
client->addSSLConfigs(sslConfigs);
|
||||
} else {
|
||||
std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
|
||||
}
|
||||
|
||||
client->enableCookies();
|
||||
client->setUserAgent("getpkg/1.0");
|
||||
|
||||
// Create upload file from memory content
|
||||
// First save content to a temporary file since UploadFile expects a file path
|
||||
std::string temp_file = "/tmp/getpkg_upload_" + std::to_string(std::time(nullptr)) + ".tgz";
|
||||
std::ofstream temp_ofs(temp_file, std::ios::binary);
|
||||
if (!temp_ofs) {
|
||||
std::cerr << "[GetbinClient::upload] Failed to create temporary file: " << temp_file << std::endl;
|
||||
success = false;
|
||||
done = true;
|
||||
cv.notify_one();
|
||||
loop.quit();
|
||||
return;
|
||||
}
|
||||
temp_ofs.write(file_content.data(), file_content.size());
|
||||
temp_ofs.close();
|
||||
|
||||
// Create upload request with file
|
||||
drogon::UploadFile upload_file(temp_file);
|
||||
|
||||
auto req = drogon::HttpRequest::newFileUploadRequest({upload_file});
|
||||
req->setMethod(drogon::Put);
|
||||
req->setPath("/upload");
|
||||
req->addHeader("Authorization", "Bearer " + token);
|
||||
req->addHeader("Content-Type", "application/gzip");
|
||||
req->addHeader("X-Metadata", metadata.dump());
|
||||
req->setBody(file_content);
|
||||
|
||||
std::atomic<bool> success(false);
|
||||
std::atomic<bool> completed(false);
|
||||
std::string response_body;
|
||||
int status_code = 0;
|
||||
// Add metadata as form parameter
|
||||
req->setParameter("metadata", metadata.dump());
|
||||
|
||||
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
||||
if (result != drogon::ReqResult::Ok || !response) {
|
||||
std::cerr << "[GetbinClient::upload] HTTP /upload request failed (did not get OK response)." << std::endl;
|
||||
if (response) {
|
||||
std::cerr << response->getStatusCode() << std::endl;
|
||||
std::cerr << response->getBody() << std::endl;
|
||||
}
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (result == drogon::ReqResult::Ok && response) {
|
||||
int status_code = static_cast<int>(response->getStatusCode());
|
||||
std::string response_body(response->getBody());
|
||||
|
||||
status_code = static_cast<int>(response->getStatusCode());
|
||||
response_body = response->getBody();
|
||||
|
||||
if (status_code != 200 && status_code != 201) {
|
||||
std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl;
|
||||
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response for URL/hash
|
||||
if (status_code == 200 || status_code == 201) {
|
||||
try {
|
||||
auto resp_json = json::parse(response_body);
|
||||
if (resp_json.contains("url")) outUrl = resp_json["url"].get<std::string>();
|
||||
@ -156,79 +197,106 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl;
|
||||
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
|
||||
} catch (...) {
|
||||
std::cerr << "[GetbinClient::upload] Unknown error while parsing JSON response." << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl;
|
||||
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
|
||||
}
|
||||
completed = true;
|
||||
} else {
|
||||
std::cerr << "[GetbinClient::upload] HTTP /upload request failed." << std::endl;
|
||||
}
|
||||
done = true;
|
||||
cv.notify_one();
|
||||
loop.quit();
|
||||
}, 60.0); // 60 second timeout
|
||||
|
||||
loop.loop();
|
||||
|
||||
// Clean up temporary file
|
||||
std::remove(temp_file.c_str());
|
||||
});
|
||||
|
||||
// Wait for the async request to complete with timeout
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
const auto timeout = std::chrono::seconds(60); // 60 second timeout for upload
|
||||
|
||||
while (!completed) {
|
||||
if (std::chrono::steady_clock::now() - start > timeout) {
|
||||
std::cerr << "[GetbinClient::upload] Request timed out after 60 seconds." << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// Wait for completion
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
cv.wait(lock, [&] { return done; });
|
||||
}
|
||||
|
||||
worker.join();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) {
|
||||
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
|
||||
std::string exists_path = "/exists/" + toolName + ":" + arch;
|
||||
bool success = false;
|
||||
bool done = false;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
|
||||
std::thread worker([&]() {
|
||||
trantor::EventLoop loop;
|
||||
|
||||
auto client = drogon::HttpClient::newHttpClient(
|
||||
"https://" + std::string(SERVER_HOST),
|
||||
&loop
|
||||
);
|
||||
|
||||
// Configure SSL certificates
|
||||
std::string ca_path = find_ca_certificates();
|
||||
if (!ca_path.empty()) {
|
||||
std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl;
|
||||
std::vector<std::pair<std::string, std::string>> sslConfigs;
|
||||
sslConfigs.push_back({"VerifyCAFile", ca_path});
|
||||
client->addSSLConfigs(sslConfigs);
|
||||
} else {
|
||||
std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
|
||||
}
|
||||
|
||||
client->enableCookies();
|
||||
client->setUserAgent("getpkg/1.0");
|
||||
|
||||
std::string hash_path = "/hash/" + toolName + ":" + arch;
|
||||
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath(exists_path);
|
||||
|
||||
std::atomic<bool> success(false);
|
||||
std::atomic<bool> completed(false);
|
||||
std::string response_body;
|
||||
req->setPath(hash_path);
|
||||
|
||||
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
||||
if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) {
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
|
||||
std::string response_body(response->getBody());
|
||||
|
||||
response_body = response->getBody();
|
||||
|
||||
// Try to parse hash from response body (assume plain text or JSON)
|
||||
// Try to parse hash from response body
|
||||
try {
|
||||
// Try JSON
|
||||
// Try JSON first
|
||||
auto resp_json = json::parse(response_body);
|
||||
if (resp_json.contains("hash")) {
|
||||
outHash = resp_json["hash"].get<std::string>();
|
||||
success = true;
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
} catch (...) {
|
||||
// Not JSON, treat as plain text
|
||||
outHash = response_body;
|
||||
success = !outHash.empty();
|
||||
completed = true;
|
||||
return;
|
||||
// Remove trailing newline if present
|
||||
if (!outHash.empty() && outHash.back() == '\n') {
|
||||
outHash.pop_back();
|
||||
}
|
||||
completed = true;
|
||||
success = !outHash.empty();
|
||||
}
|
||||
}
|
||||
done = true;
|
||||
cv.notify_one();
|
||||
loop.quit();
|
||||
}, 10.0); // 10 second timeout
|
||||
|
||||
loop.loop();
|
||||
});
|
||||
|
||||
// Wait for the async request to complete with timeout
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
const auto timeout = std::chrono::seconds(10); // 10 second timeout for hash check
|
||||
|
||||
while (!completed) {
|
||||
if (std::chrono::steady_clock::now() - start > timeout) {
|
||||
std::cerr << "[GetbinClient::getHash] Request timed out after 10 seconds." << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// Wait for completion
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
cv.wait(lock, [&] { return done; });
|
||||
}
|
||||
|
||||
worker.join();
|
||||
return success;
|
||||
}
|
@ -292,7 +292,10 @@ fi
|
||||
|
||||
# Test 13: Invalid tool name validation
|
||||
echo -e "\nTest 13: Invalid tool name validation"
|
||||
INVALID_OUTPUT=$(timeout 3 "$GETPKG" install "../evil-tool" 2>&1) || INVALID_OUTPUT=""
|
||||
INVALID_OUTPUT=$(timeout 3 "$GETPKG" install "../evil-tool" 2>&1)
|
||||
INVALID_EXIT_CODE=$?
|
||||
echo "Invalid tool output: $INVALID_OUTPUT"
|
||||
echo "Invalid tool exit code: $INVALID_EXIT_CODE"
|
||||
if [[ "$INVALID_OUTPUT" =~ "Invalid tool name" ]]; then
|
||||
print_test_result "Invalid tool name rejection" 0
|
||||
else
|
||||
|
2
getpkg/test_debug/test-tool-35020/setup_script.sh
Executable file
2
getpkg/test_debug/test-tool-35020/setup_script.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
echo 'Setup complete.'
|
2
getpkg/test_debug/test-tool-35957/setup_script.sh
Executable file
2
getpkg/test_debug/test-tool-35957/setup_script.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
echo 'Setup complete.'
|
2
getpkg/test_debug/test-tool-36793/setup_script.sh
Executable file
2
getpkg/test_debug/test-tool-36793/setup_script.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
echo 'Setup complete.'
|
Loading…
x
Reference in New Issue
Block a user