2025-08-03 21:53:15 -04:00
|
|
|
#include <drogon/drogon.h>
|
|
|
|
|
#include <drogon/orm/DbClient.h>
|
|
|
|
|
#include <iostream>
|
2026-01-05 22:54:27 -05:00
|
|
|
#include <fstream>
|
2025-08-03 21:53:15 -04:00
|
|
|
#include <cstdlib>
|
2026-01-05 22:54:27 -05:00
|
|
|
#include <ctime>
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
#include <sstream>
|
2025-08-03 21:53:15 -04:00
|
|
|
|
|
|
|
|
using namespace drogon;
|
|
|
|
|
using namespace drogon::orm;
|
|
|
|
|
|
2026-01-05 22:54:27 -05:00
|
|
|
// SECURITY FIX #32: Add audit logging for admin CLI operations
|
|
|
|
|
namespace {
|
|
|
|
|
void writeAuditLog(const std::string& action, const std::string& target,
|
|
|
|
|
const std::string& status, const std::string& details = "") {
|
|
|
|
|
// Get current timestamp
|
|
|
|
|
auto now = std::time(nullptr);
|
|
|
|
|
auto tm = *std::localtime(&now);
|
|
|
|
|
std::ostringstream timestamp;
|
|
|
|
|
timestamp << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
|
|
|
|
|
|
|
|
|
|
// Get hostname for audit trail
|
|
|
|
|
char hostname[256] = "unknown";
|
|
|
|
|
gethostname(hostname, sizeof(hostname));
|
|
|
|
|
|
|
|
|
|
// Build log entry
|
|
|
|
|
std::ostringstream logEntry;
|
|
|
|
|
logEntry << "[" << timestamp.str() << "] "
|
|
|
|
|
<< "HOST=" << hostname << " "
|
|
|
|
|
<< "ACTION=" << action << " "
|
|
|
|
|
<< "TARGET=" << target << " "
|
|
|
|
|
<< "STATUS=" << status;
|
|
|
|
|
if (!details.empty()) {
|
|
|
|
|
logEntry << " DETAILS=" << details;
|
|
|
|
|
}
|
|
|
|
|
logEntry << std::endl;
|
|
|
|
|
|
|
|
|
|
// Write to audit log file
|
|
|
|
|
std::string logPath = "/var/log/admin_tool_audit.log";
|
|
|
|
|
std::ofstream logFile(logPath, std::ios::app);
|
|
|
|
|
if (logFile.is_open()) {
|
|
|
|
|
logFile << logEntry.str();
|
|
|
|
|
logFile.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also output to stderr for immediate visibility
|
|
|
|
|
std::cerr << "[AUDIT] " << logEntry.str();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-03 21:53:15 -04:00
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
|
if (argc < 2) {
|
|
|
|
|
std::cerr << "Usage: " << argv[0] << " -promote-admin <username>" << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string command = argv[1];
|
|
|
|
|
|
|
|
|
|
if (command != "-promote-admin" || argc != 3) {
|
|
|
|
|
std::cerr << "Usage: " << argv[0] << " -promote-admin <username>" << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string username = argv[2];
|
|
|
|
|
|
|
|
|
|
// Get database config from environment or use defaults
|
|
|
|
|
std::string dbHost = std::getenv("DB_HOST") ? std::getenv("DB_HOST") : "postgres";
|
|
|
|
|
std::string dbName = std::getenv("DB_NAME") ? std::getenv("DB_NAME") : "streaming";
|
|
|
|
|
std::string dbUser = std::getenv("DB_USER") ? std::getenv("DB_USER") : "streamuser";
|
|
|
|
|
std::string dbPass = std::getenv("DB_PASS") ? std::getenv("DB_PASS") : "streampass";
|
|
|
|
|
|
|
|
|
|
// Create database client directly
|
|
|
|
|
auto dbClient = DbClient::newPgClient(
|
|
|
|
|
"host=" + dbHost + " port=5432 dbname=" + dbName +
|
|
|
|
|
" user=" + dbUser + " password=" + dbPass,
|
|
|
|
|
1 // connection number
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-05 22:54:27 -05:00
|
|
|
writeAuditLog("PROMOTE_ADMIN_ATTEMPT", username, "STARTED");
|
|
|
|
|
|
2025-08-03 21:53:15 -04:00
|
|
|
try {
|
|
|
|
|
// Check if user exists
|
|
|
|
|
auto result = dbClient->execSqlSync(
|
|
|
|
|
"SELECT id, username, is_admin FROM users WHERE username = $1",
|
|
|
|
|
username
|
|
|
|
|
);
|
2026-01-05 22:54:27 -05:00
|
|
|
|
2025-08-03 21:53:15 -04:00
|
|
|
if (result.empty()) {
|
2026-01-05 22:54:27 -05:00
|
|
|
writeAuditLog("PROMOTE_ADMIN", username, "FAILED", "user_not_found");
|
2025-08-03 21:53:15 -04:00
|
|
|
std::cerr << "Error: User '" << username << "' not found." << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2026-01-05 22:54:27 -05:00
|
|
|
|
2025-08-03 21:53:15 -04:00
|
|
|
bool isAdmin = result[0]["is_admin"].as<bool>();
|
|
|
|
|
if (isAdmin) {
|
2026-01-05 22:54:27 -05:00
|
|
|
writeAuditLog("PROMOTE_ADMIN", username, "SKIPPED", "already_admin");
|
2025-08-03 21:53:15 -04:00
|
|
|
std::cout << "User '" << username << "' is already an admin." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2026-01-05 22:54:27 -05:00
|
|
|
|
2025-08-03 21:53:15 -04:00
|
|
|
// Promote to admin
|
|
|
|
|
dbClient->execSqlSync(
|
|
|
|
|
"UPDATE users SET is_admin = true WHERE username = $1",
|
|
|
|
|
username
|
|
|
|
|
);
|
2026-01-05 22:54:27 -05:00
|
|
|
|
|
|
|
|
writeAuditLog("PROMOTE_ADMIN", username, "SUCCESS");
|
2025-08-03 21:53:15 -04:00
|
|
|
std::cout << "Successfully promoted '" << username << "' to admin." << std::endl;
|
|
|
|
|
return 0;
|
2026-01-05 22:54:27 -05:00
|
|
|
|
2025-08-03 21:53:15 -04:00
|
|
|
} catch (const DrogonDbException& e) {
|
2026-01-05 22:54:27 -05:00
|
|
|
writeAuditLog("PROMOTE_ADMIN", username, "ERROR", e.base().what());
|
2025-08-03 21:53:15 -04:00
|
|
|
std::cerr << "Database error: " << e.base().what() << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
} catch (const std::exception& e) {
|
2026-01-05 22:54:27 -05:00
|
|
|
writeAuditLog("PROMOTE_ADMIN", username, "ERROR", e.what());
|
2025-08-03 21:53:15 -04:00
|
|
|
std::cerr << "Error: " << e.what() << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|