commit a6435c47
Do not run as root by default. Also do not return http 500 error codes if the request was valid. Use 422 instead. This makes it easier to spot routing issues which tend to use 500
Changed files
| M | Dockerfile before |
| M | README.md before |
| M | src/application.cpp before |
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_<uid>`).
- `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 <http://localhost:11000/>.
## 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<void *, size_t> 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<char> 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<GitRepo> GitApp::open(const std::string &name) {
try {
return std::make_unique<GitRepo>(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");