Fix vulnerabilities
Signed-off-by: Tiago Garcia <tiago.rgarcia@ua.pt>
This commit is contained in:
parent
5c8216edd1
commit
d5a2353ded
54
README.md
54
README.md
|
@ -108,7 +108,27 @@ Note: Tokens are only required for private repositories.
|
|||
|
||||
## Endpoints
|
||||
|
||||
Currently, the only endpoint for the application is `/update-files`, which is used to update the local files on every push as well as run post-update scripts.
|
||||
### `/run-actions`
|
||||
|
||||
#### Webhook event: `push`
|
||||
|
||||
This endpoint allows the application to run specific actions when a push to a specific branch is made. This way, there's no need to manually run the actions on the server.
|
||||
|
||||
The configuration file must contain the `run-actions` field, which is an object with the following format:
|
||||
|
||||
```json
|
||||
"run-actions": {
|
||||
"owner/repo-name": {
|
||||
"branch": "main",
|
||||
"actions": [
|
||||
{
|
||||
"name": "action-name",
|
||||
"command": "command-to-run"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `/update-files`
|
||||
|
||||
|
@ -129,43 +149,15 @@ The configuration file must contain the `update-files` field, which is an object
|
|||
"...": "..."
|
||||
},
|
||||
"post-update": [
|
||||
"post-update-command",
|
||||
"post-update-script",
|
||||
"..."
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `/run-actions`
|
||||
|
||||
#### Webhook event: `push`
|
||||
|
||||
This endpoint allows the application to run specific actions when a push to a specific branch is made. This way, there's no need to manually run the actions on the server.
|
||||
|
||||
The configuration file must contain the `run-actions` field, which is an object with the following format:
|
||||
|
||||
```json
|
||||
"run-actions": {
|
||||
"owner/repo-name": {
|
||||
"branch": "main",
|
||||
"actions": [
|
||||
{
|
||||
"name": "action-name",
|
||||
"command": "command-to-run",
|
||||
"args": [
|
||||
"arg1",
|
||||
"arg2",
|
||||
"..."
|
||||
]
|
||||
"name": "post-update-action-name",
|
||||
"command": "command-to-run"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: if you don't want to use the `args` field, just leave an empty array such as `"args": []`.
|
||||
|
||||
## Nginx Configuration
|
||||
|
||||
If you want to use Nginx as a reverse proxy for the application, you can use the following example configuration:
|
||||
|
|
|
@ -12,6 +12,7 @@ class Logger {
|
|||
static void fatal(std::string message);
|
||||
static void success(std::string message);
|
||||
static void code(std::string message);
|
||||
static void debug(std::string message);
|
||||
private:
|
||||
static void log(std::string message, std::string level);
|
||||
static std::ofstream log_file;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "logger.hpp"
|
||||
|
||||
crow::response run_actions(const nlohmann::json &run_actions, const crow::request &req) {
|
||||
crow::response run_actions(const nlohmann::json &config_run_actions, const crow::request &req) {
|
||||
nlohmann::json payload;
|
||||
try {
|
||||
payload = nlohmann::json::parse(req.body);
|
||||
|
@ -15,61 +15,62 @@ crow::response run_actions(const nlohmann::json &run_actions, const crow::reques
|
|||
return crow::response(400, response.dump());
|
||||
}
|
||||
|
||||
std::string ref;
|
||||
std::string repo;
|
||||
|
||||
try {
|
||||
ref = payload["ref"];
|
||||
if (size_t last_slash = ref.find_last_of('/'); last_slash != std::string::npos && last_slash + 1 < ref.length())
|
||||
ref = ref.substr(last_slash + 1);
|
||||
repo = payload["repository"]["full_name"];
|
||||
} catch (nlohmann::json::out_of_range& e) {
|
||||
Logger::error("[/run-actions] Invalid JSON payload: " + std::string(e.what()));
|
||||
if (payload.find("ref") == payload.end() || payload.find("repository") == payload.end() || payload["repository"].find("full_name") == payload["repository"].end()) {
|
||||
Logger::error("[/run-actions] Invalid JSON payload");
|
||||
nlohmann::json response = {
|
||||
{"status", 400},
|
||||
{"error", "Invalid JSON payload"}
|
||||
};
|
||||
return crow::response(400, response.dump());
|
||||
}
|
||||
std::string ref = payload["ref"];
|
||||
if (size_t last_slash = ref.find_last_of('/'); last_slash != std::string::npos && last_slash + 1 < ref.length())
|
||||
ref = ref.substr(last_slash + 1);
|
||||
|
||||
std::string repo = payload["repository"]["full_name"];
|
||||
|
||||
Logger::info("[/run-actions] Received push to " + repo + ":" + ref);
|
||||
|
||||
if (run_actions.find(repo) == run_actions.end()) {
|
||||
Logger::warn("[/run-actions] No run-actions webhook configuration for repo " + repo);
|
||||
if (config_run_actions.find(repo) == config_run_actions.end()) {
|
||||
Logger::warn("[/run-actions] No run-actions configuration for repo " + repo);
|
||||
nlohmann::json response = {
|
||||
{"status", 404},
|
||||
{"error", "No run-actions webhook configuration for repo"}
|
||||
{"error", "No run-actions configuration for repo"}
|
||||
};
|
||||
return crow::response(404, response.dump());
|
||||
}
|
||||
|
||||
Logger::info("[/run-actions] Found run-actions webhook configuration for repo " + repo);
|
||||
Logger::info("[/run-actions] Found run-actions configuration for repo " + repo);
|
||||
|
||||
nlohmann::json config;
|
||||
std::string config_branch;
|
||||
nlohmann::json config_actions;
|
||||
try {
|
||||
config = run_actions[repo];
|
||||
config_branch = config["branch"];
|
||||
config_actions = config["actions"];
|
||||
} catch (nlohmann::json::out_of_range& e) {
|
||||
Logger::error("[/run-actions] Invalid run-actions configuration for repo " + repo + ": " + std::string(e.what()));
|
||||
bool found = false;
|
||||
for (auto c_repo = config_run_actions.begin(); c_repo != config_run_actions.end(); ++c_repo) {
|
||||
if (c_repo.key() != repo) continue;
|
||||
if (c_repo.value().find("branch") == c_repo.value().end() || c_repo.value().find("actions") == c_repo.value().end()) {
|
||||
Logger::error("[/run-actions] Invalid run-actions configuration found for repo " + c_repo.key());
|
||||
nlohmann::json response = {
|
||||
{"status", 500},
|
||||
{"error", "Invalid run-actions configuration"}
|
||||
{"error", "Invalid update-files configuration"}
|
||||
};
|
||||
return crow::response(500, response.dump());
|
||||
}
|
||||
|
||||
if (ref != config_branch) {
|
||||
Logger::info("[/run-actions] Ignoring push to " + repo + " on branch " + ref);
|
||||
if (c_repo.value()["branch"].get<std::string>() != ref) continue;
|
||||
Logger::info("[/run-actions] Found run-actions configuration for branch " + ref);
|
||||
config = c_repo.value();
|
||||
found = true;
|
||||
}
|
||||
if (!found) {
|
||||
Logger::info("[/run-actions] Ignoring push to non-configured branch " + ref + " on repo " + repo);
|
||||
nlohmann::json response = {
|
||||
{"status", 200},
|
||||
{"message", "Ignoring push to " + repo + " on branch " + ref}
|
||||
{"message", "Ignoring push to non-configured branch" + ref}
|
||||
};
|
||||
return crow::response(200, response.dump());
|
||||
}
|
||||
|
||||
std::string config_branch = config["branch"];
|
||||
nlohmann::json config_actions = config["actions"];
|
||||
|
||||
if (config_actions.empty()) {
|
||||
Logger::info("[/run-actions] No actions configured for " + repo + " on branch " + ref);
|
||||
nlohmann::json response = {
|
||||
|
@ -88,30 +89,22 @@ crow::response run_actions(const nlohmann::json &run_actions, const crow::reques
|
|||
{"failed-actions", nlohmann::json::array()}
|
||||
};
|
||||
for (const auto &action : config_actions) {
|
||||
std::string action_name;
|
||||
std::string action_command;
|
||||
nlohmann::json action_args;
|
||||
try {
|
||||
action_name = action["name"];
|
||||
action_command = action["command"];
|
||||
action_args = action["args"];
|
||||
} catch (nlohmann::json::out_of_range& e) {
|
||||
Logger::error("[/run-actions] Invalid action configuration for repo " + repo + ": " + std::string(e.what()));
|
||||
nlohmann::json response = {
|
||||
if (action.find("name") == action.end() || action.find("command") == action.end()) {
|
||||
Logger::error("[/run-actions] Invalid action configuration for repo " + repo);
|
||||
nlohmann::json e_response = {
|
||||
{"status", 500},
|
||||
{"error", "Invalid action configuration"}
|
||||
{"error", "Invalid action configuration"},
|
||||
{"successful-actions", response["successful-actions"]},
|
||||
{"failed-actions", response["failed-actions"]}
|
||||
};
|
||||
return crow::response(500, response.dump());
|
||||
return crow::response(500, e_response.dump());
|
||||
}
|
||||
std::string action_name = action["name"];
|
||||
std::string action_command = action["command"];
|
||||
|
||||
Logger::info("[/run-actions] Running action '" + action_name + "'");
|
||||
|
||||
std::string action_command_with_args = action_command;
|
||||
for (const auto &arg : action_args) {
|
||||
action_command_with_args += " " + arg.get<std::string>();
|
||||
}
|
||||
|
||||
int ret = std::system(action_command_with_args.c_str());
|
||||
int ret = std::system(action_command.c_str());
|
||||
if (ret == 0) {
|
||||
Logger::info("[/run-actions] Action " + action_name + " completed successfully");
|
||||
response["successful-actions"].push_back(action_name);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "endpoints/update-files.hpp"
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "logger.hpp"
|
||||
|
@ -16,17 +17,23 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo
|
|||
return crow::response(400, response.dump());
|
||||
}
|
||||
|
||||
std::string ref;
|
||||
std::string repo;
|
||||
bool is_private;
|
||||
std::string token;
|
||||
if (payload.find("ref") == payload.end() || payload.find("repository") == payload.end() ||
|
||||
payload["repository"].find("full_name") == payload["repository"].end() || payload["repository"].find("private") == payload["repository"].end() ||
|
||||
payload.find("commits") == payload.end() || !payload["commits"].is_array() || payload["commits"].empty()){
|
||||
Logger::error("[/update-files] Invalid JSON payload");
|
||||
nlohmann::json response = {
|
||||
{"status", 400},
|
||||
{"error", "Invalid JSON payload"}
|
||||
};
|
||||
return crow::response(400, response.dump());
|
||||
}
|
||||
|
||||
try {
|
||||
ref = payload["ref"];
|
||||
std::string ref = payload["ref"];
|
||||
if (size_t last_slash = ref.find_last_of('/'); last_slash != std::string::npos && last_slash + 1 < ref.length())
|
||||
ref = ref.substr(last_slash + 1);
|
||||
repo = payload["repository"]["full_name"];
|
||||
is_private = payload["repository"]["private"];
|
||||
std::string repo = payload["repository"]["full_name"];
|
||||
bool is_private = payload["repository"]["private"];
|
||||
std::string token;
|
||||
if (is_private) {
|
||||
if (config_tokens.find(repo) == config_tokens.end()) {
|
||||
Logger::warn("[/update-files] No token configured for private repo " + repo);
|
||||
|
@ -38,14 +45,6 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo
|
|||
}
|
||||
token = config_tokens[repo];
|
||||
}
|
||||
} catch (nlohmann::json::out_of_range& e) {
|
||||
Logger::error("[/update-files] Invalid JSON payload: " + std::string(e.what()));
|
||||
nlohmann::json response = {
|
||||
{"status", 400},
|
||||
{"error", "Invalid JSON payload"}
|
||||
};
|
||||
return crow::response(400, response.dump());
|
||||
}
|
||||
|
||||
Logger::info("[/update-files] Received push to " + repo + ":" + ref + " (private: " + (is_private ? "true" : "false") + ")");
|
||||
|
||||
|
@ -63,16 +62,24 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo
|
|||
nlohmann::json config;
|
||||
bool found = false;
|
||||
for (auto c_repo = config_update_files.begin(); c_repo != config_update_files.end(); ++c_repo) {
|
||||
if (const std::string &c_repo_name = c_repo.key(); c_repo_name == repo) continue;
|
||||
if (c_repo.key() != repo) continue;
|
||||
if (c_repo.value().find("branch") == c_repo.value().end() || c_repo.value().find("files") == c_repo.value().end()) {
|
||||
Logger::error("[/update-files] Invalid update-files configuration found for repo " + c_repo.key());
|
||||
nlohmann::json response = {
|
||||
{"status", 500},
|
||||
{"error", "Invalid update-files configuration"}
|
||||
};
|
||||
return crow::response(500, response.dump());
|
||||
}
|
||||
if (c_repo.value()["branch"] != ref) continue;
|
||||
config = c_repo.value();
|
||||
found = true;
|
||||
}
|
||||
if (!found) {
|
||||
Logger::warn("[/update-files] No update-files webhook configuration for branch " + ref);
|
||||
Logger::warn("[/update-files] Ignoring push to non-configured branch" + ref + " on repo " + repo);
|
||||
nlohmann::json response = {
|
||||
{"status", 404},
|
||||
{"error", "No update-files webhook configuration for branch" + ref}
|
||||
{"status", 200},
|
||||
{"message", "Ignoring push to non-configured branch" + ref}
|
||||
};
|
||||
return crow::response(404, response.dump());
|
||||
}
|
||||
|
@ -82,23 +89,31 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo
|
|||
if (config["files"].empty()) {
|
||||
Logger::warn("[/update-files] No files configured for repo " + repo + ":" + ref);
|
||||
nlohmann::json response = {
|
||||
{"status", 404},
|
||||
{"error", "No files configured for repo" + repo + ":" + ref}
|
||||
{"status", 200},
|
||||
{"message", "No files configured for repo " + repo + ":" + ref}
|
||||
};
|
||||
return crow::response(404, response.dump());
|
||||
return crow::response(200, response.dump());
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> modified_files;
|
||||
for (auto &commit : payload["commits"]) {
|
||||
if (commit.find("added") == commit.end() || commit.find("modified") == commit.end()) {
|
||||
Logger::error("[/update-files] Invalid JSON payload");
|
||||
nlohmann::json response = {
|
||||
{"status", 400},
|
||||
{"error", "Invalid JSON payload"}
|
||||
};
|
||||
return crow::response(400, response.dump());
|
||||
}
|
||||
for (auto &file : commit["added"]) {
|
||||
std::string file_path = file;
|
||||
if (config_update_files[repo]["files"].find(file_path) == config_update_files[repo]["files"].end()) continue;
|
||||
if (config["files"].find(file_path) == config["files"].end()) continue;
|
||||
std::vector<std::string> file_info = {file_path, "added"};
|
||||
modified_files.push_back(file_info);
|
||||
}
|
||||
for (auto &file : commit["modified"]) {
|
||||
std::string file_path = file;
|
||||
if (config_update_files[repo]["files"].find(file_path) == config_update_files[repo]["files"].end()) continue;
|
||||
if (config["files"].find(file_path) == config["files"].end()) continue;
|
||||
std::vector<std::string> file_info = {file_path, "modified"};
|
||||
modified_files.push_back(file_info);
|
||||
}
|
||||
|
@ -120,8 +135,11 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo
|
|||
nlohmann::json response = {
|
||||
{"status", 200},
|
||||
{"message", "OK"},
|
||||
{"file_count", 0},
|
||||
{"updated-files", nlohmann::json::array()},
|
||||
{"updated-file-count", 0},
|
||||
{"updated-files", {
|
||||
{"successful", nlohmann::json::array()},
|
||||
{"failed", nlohmann::json::array()}
|
||||
}},
|
||||
{"post-update", {
|
||||
{"successful", nlohmann::json::array()},
|
||||
{"failed", nlohmann::json::array()}
|
||||
|
@ -130,36 +148,63 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo
|
|||
for (auto &file : modified_files) {
|
||||
std::string remote_path = file[0];
|
||||
|
||||
std::string local_path = config_update_files[repo]["files"][remote_path];
|
||||
std::string local_path = config["files"][remote_path];
|
||||
if (local_path.find_last_of('/') != std::string::npos)
|
||||
try {
|
||||
std::filesystem::create_directories(local_path.substr(0, local_path.find_last_of('/')));
|
||||
} catch (const std::exception &e) {
|
||||
Logger::error("[/update-files] Failed to create directories for " + local_path + ": " + std::string(e.what()));
|
||||
response["updated-files"]["failed"].push_back(local_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string command = "curl -s https://raw.githubusercontent.com/" + repo + "/" + ref + "/" + remote_path + " -o " + local_path;
|
||||
if (is_private) command += " -H 'Authorization: token " + token + "'";
|
||||
std::system(command.c_str());
|
||||
int ret = std::system(command.c_str());
|
||||
if (ret != 0) {
|
||||
Logger::error("[/update-files] Failed to update " + local_path);
|
||||
response["updated-files"]["failed"].push_back(local_path);
|
||||
continue;
|
||||
}
|
||||
Logger::info("[/update-files] " + std::string(file[1] == "added" ? "Created" : "Updated") + " " + local_path);
|
||||
response["file_count"] = response["file_count"].get<int>() + 1;
|
||||
response["updated-files"].push_back(remote_path);
|
||||
response["updated-file-count"] = response["updated-file-count"].get<int>() + 1;
|
||||
response["updated-files"]["successful"].push_back(local_path);
|
||||
}
|
||||
|
||||
Logger::success("[/update-files] Finished updating " + std::to_string(modified_files.size()) + " files for repo " + repo + ":" + ref);
|
||||
|
||||
if (config.find("post-update") == config.end()) {
|
||||
Logger::info("[/update-files] No post-update actions configured for repo " + repo + ":" + ref);
|
||||
return crow::response(200, response.dump());
|
||||
}
|
||||
|
||||
nlohmann::json post_update_actions = config["post-update"];
|
||||
|
||||
Logger::info("[/update-files] Running post-update actions for repo " + repo + ":" + ref);
|
||||
|
||||
for (auto &c_action : config_update_files[repo]["post-update"]) {
|
||||
std::string action = c_action.get<std::string>();
|
||||
Logger::info("[/update-files] Running post-update action: " + action);
|
||||
int return_code = std::system(action.c_str());
|
||||
if (return_code == 0) {
|
||||
Logger::success("[/update-files] Post-update action " + action + " ran successfully");
|
||||
response["post-update"]["successful"].push_back(action);
|
||||
for (const auto &action : post_update_actions) {
|
||||
if (action.find("name") == action.end() || action.find("command") == action.end()) {
|
||||
Logger::error("[/update-files] Invalid post-update configuration for repo " + repo);
|
||||
nlohmann::json e_response = {
|
||||
{"status", 500},
|
||||
{"error", "Invalid action configuration"},
|
||||
{"updated-files", response["updated-files"]},
|
||||
{"post-update", response["post-update"]}
|
||||
};
|
||||
return crow::response(500, e_response.dump());
|
||||
}
|
||||
std::string action_name = action["name"];
|
||||
std::string action_command = action["command"];
|
||||
|
||||
Logger::info("[/update-files] Running post-update action '" + action_name + "'");
|
||||
|
||||
int ret = std::system(action_command.c_str());
|
||||
if (ret == 0) {
|
||||
Logger::info("[/update-files] Action " + action_name + " completed successfully");
|
||||
response["post-update"]["successful"].push_back(action_name);
|
||||
} else {
|
||||
Logger::error("[/update-files] Post-update action " + action + " failed with return code " + std::to_string(return_code));
|
||||
response["post-update"]["failed"].push_back(action);
|
||||
Logger::error("[/update-files] Action " + action_name + " failed with exit code " + std::to_string(ret));
|
||||
response["post-update"]["failed"].push_back(action_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,10 @@ void Logger::code(std::string message) {
|
|||
Logger::log(message, "CODE");
|
||||
}
|
||||
|
||||
void Logger::debug(std::string message) {
|
||||
Logger::log(message, "DEBUG ");
|
||||
}
|
||||
|
||||
void Logger::log(std::string message, std::string level) {
|
||||
std::string formatted_message = "";
|
||||
if (isatty(fileno(stdout))) {
|
||||
|
@ -107,6 +111,11 @@ void Logger::log(std::string message, std::string level) {
|
|||
} else if (level == "CODE") {
|
||||
formatted_message += COLORS_FG_WHITE;
|
||||
formatted_message += COLORS_DIM;
|
||||
} else if (level == "DEBUG ") {
|
||||
formatted_message += COLORS_FG_BLUE;
|
||||
formatted_message += COLORS_UNDERLINED;
|
||||
formatted_message += COLORS_DIM;
|
||||
formatted_message += COLORS_BOLD;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,31 +23,6 @@ Routes::Routes(nlohmann::json config) {
|
|||
return crow::response(200, response.dump());
|
||||
});
|
||||
|
||||
Logger::info("[Routes] Registering route \"/update-files\"");
|
||||
CROW_ROUTE(this->app, "/update-files")
|
||||
.methods("POST"_method)
|
||||
.name("Update Files")
|
||||
([&config_update_files, &config_tokens](const crow::request &req) {
|
||||
if (config_update_files.is_null()) {
|
||||
Logger::warn("[Routes] No update-files configuration found");
|
||||
nlohmann::json response = {
|
||||
{"status", 404},
|
||||
{"error", "No update-files configuration found"}
|
||||
};
|
||||
return crow::response(404, response.dump());
|
||||
}
|
||||
try {
|
||||
return update_files(config_update_files, config_tokens, req);
|
||||
} catch (const std::exception &e) {
|
||||
Logger::error("[Routes] Unknown error in update_files: " + std::string(e.what()));
|
||||
nlohmann::json response = {
|
||||
{"status", 500},
|
||||
{"error", "Internal server error"}
|
||||
};
|
||||
return crow::response(500, response.dump());
|
||||
}
|
||||
});
|
||||
|
||||
Logger::info("[Routes] Registering route \"/run-actions\"");
|
||||
CROW_ROUTE(this->app, "/run-actions")
|
||||
.methods("POST"_method)
|
||||
|
@ -73,6 +48,31 @@ Routes::Routes(nlohmann::json config) {
|
|||
}
|
||||
});
|
||||
|
||||
Logger::info("[Routes] Registering route \"/update-files\"");
|
||||
CROW_ROUTE(this->app, "/update-files")
|
||||
.methods("POST"_method)
|
||||
.name("Update Files")
|
||||
([&config_update_files, &config_tokens](const crow::request &req) {
|
||||
if (config_update_files.is_null()) {
|
||||
Logger::warn("[Routes] No update-files configuration found");
|
||||
nlohmann::json response = {
|
||||
{"status", 404},
|
||||
{"error", "No update-files configuration found"}
|
||||
};
|
||||
return crow::response(404, response.dump());
|
||||
}
|
||||
try {
|
||||
return update_files(config_update_files, config_tokens, req);
|
||||
} catch (const std::exception &e) {
|
||||
Logger::error("[Routes] Unknown error in update_files: " + std::string(e.what()));
|
||||
nlohmann::json response = {
|
||||
{"status", 500},
|
||||
{"error", "Internal server error"}
|
||||
};
|
||||
return crow::response(500, response.dump());
|
||||
}
|
||||
});
|
||||
|
||||
Logger::info("[Routes] Registering catch-all route (404)");
|
||||
CROW_CATCHALL_ROUTE(this->app)
|
||||
([](const crow::request &req) {
|
||||
|
|
Loading…
Reference in New Issue