diff --git a/Dockerfile b/Dockerfile index be66f8b..e3d2f72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,6 +48,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ && rm -rf /var/lib/apt/lists/* +# Add a non-root user to run the application. +RUN groupadd -r sago && useradd -r -g sago -d /app sago + COPY --from=build /usr/local/lib/libcppcms.so* /usr/local/lib/ COPY --from=build /usr/local/lib/libbooster.so* /usr/local/lib/ RUN ldconfig @@ -55,6 +58,7 @@ RUN ldconfig COPY --from=build /src/build/sago_web_git /app/sago_web_git COPY --from=build /src/static /app/static COPY --from=build /src/config.json /app/config.json +RUN chown -R sago:sago /app WORKDIR /app @@ -64,8 +68,11 @@ VOLUME ["/srv/git"] # Generated tag archives are cached here; mount a volume to persist them. ENV GIT_CACHE_ROOT=/var/cache/sago_web_git +RUN mkdir -p /var/cache/sago_web_git && chown sago:sago /var/cache/sago_web_git VOLUME ["/var/cache/sago_web_git"] +USER sago + EXPOSE 11000 CMD ["/app/sago_web_git", "-c", "/app/config.json"] diff --git a/README.md b/README.md index 5c48125..2cfd080 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ default branch HEAD. - `git.cache_root` — directory where generated tag archives are cached, created on demand. Overridden by the `GIT_CACHE_ROOT` environment variable; when neither is set it defaults to `$XDG_CACHE_HOME/sago_web_git` (or - `$HOME/.cache/sago_web_git`, falling back to `/tmp/sago_web_git`). + `$HOME/.cache/sago_web_git`, falling back to `/tmp/sago_web_git_`). - `git.github_enterprise_hosts` — comma- or space-separated hostnames to treat as GitHub (in addition to `github.com`) when showing clone instructions on a repository's page, e.g. `github.mycompany.com git.example.org`. Overridden by @@ -70,7 +70,8 @@ Then open . ## Docker The image builds CppCMS from source (it is not packaged in Ubuntu) and bundles -everything. Mount a directory of repositories at `/srv/git`: +everything. The container runs as a non-root user `sago` for security. +Mount a directory of repositories at `/srv/git`: ```sh docker build -t sago007/sago_web_git . diff --git a/src/application.cpp b/src/application.cpp index 57364d2..258c9ad 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -52,7 +52,7 @@ const std::string SKIN = "git"; // Cache directory resolution order: GIT_CACHE_ROOT env (e.g. set by the // Dockerfile), config key "git.cache_root", then XDG_CACHE_HOME/sago_web_git, -// then $HOME/.cache/sago_web_git, falling back to a temp directory. +// then $HOME/.cache/sago_web_git. Throws if none of these are set. std::string resolve_cache_root(const std::string &configured) { if (const char *env = std::getenv("GIT_CACHE_ROOT"); env && *env) return env; @@ -62,7 +62,7 @@ std::string resolve_cache_root(const std::string &configured) { return std::string(xdg) + "/sago_web_git"; if (const char *home = std::getenv("HOME"); home && *home) return std::string(home) + "/.cache/sago_web_git"; - return "/tmp/sago_web_git"; + throw std::runtime_error("GIT_CACHE_ROOT, XDG_CACHE_HOME or $HOME not set"); } // Splits a comma- or whitespace-separated host list into trimmed, non-empty @@ -218,13 +218,18 @@ void GitApp::git_http(std::string name, std::string endpoint) { int input_fd = -1; if (request().request_method() == "POST") { const std::pair data = request().raw_post_data(); - fs::create_directories(cache_root_); + std::error_code ec; + fs::create_directories(cache_root_, ec); + if (ec) { + show_error(422, "cannot create cache directory: " + ec.message()); + return; + } std::string tmpl = cache_root_ + "/upload-pack-XXXXXX"; std::vector path_buf(tmpl.begin(), tmpl.end()); path_buf.push_back('\0'); input_fd = mkstemp(path_buf.data()); if (input_fd < 0) { - show_error(500, "cannot create temp file for request body"); + show_error(422, "cannot create temp file for request body"); return; } unlink(path_buf.data()); // unlink now; the open fd keeps the file alive @@ -272,7 +277,7 @@ void GitApp::git_http(std::string name, std::string endpoint) { } catch (const std::exception &e) { std::cerr << "git_http: " << e.what() << std::endl; if (!started) - show_error(500, "git http-backend failed"); + show_error(422, "git http-backend failed"); } if (input_fd >= 0) @@ -288,7 +293,7 @@ std::unique_ptr GitApp::open(const std::string &name) { try { return std::make_unique(path); } catch (const GitError &e) { - show_error(500, e.what()); + show_error(422, e.what()); return nullptr; } } @@ -574,7 +579,7 @@ void GitApp::tag_archive(std::string name, std::string tag_name) { std::error_code ec; std::filesystem::create_directories(cache_root_, ec); if (ec) { - show_error(500, "cannot create cache directory: " + ec.message()); + show_error(422, "cannot create cache directory: " + ec.message()); return; } @@ -590,7 +595,7 @@ void GitApp::tag_archive(std::string name, std::string tag_name) { repo->write_archive(oid, base, tmp); } catch (const std::exception &e) { std::filesystem::remove(tmp, ec); - show_error(500, std::string("failed to build archive: ") + e.what()); + show_error(422, std::string("failed to build archive: ") + e.what()); return; } std::filesystem::rename(tmp, cache_file, ec); @@ -598,7 +603,7 @@ void GitApp::tag_archive(std::string name, std::string tag_name) { // A concurrent request may have produced it first; drop our copy. std::filesystem::remove(tmp, ec); if (!std::filesystem::exists(cache_file)) { - show_error(500, "failed to store archive"); + show_error(422, "failed to store archive"); return; } } @@ -606,7 +611,7 @@ void GitApp::tag_archive(std::string name, std::string tag_name) { std::ifstream file(cache_file, std::ios::binary); if (!file) { - show_error(500, "archive unavailable"); + show_error(422, "archive unavailable"); return; } response().content_type("application/gzip");