Git-daemon public git protocol access #000 Index #001 Introduction #002 System user #003 Repository structure #004 Inetd #005 Access control #006 Hook integration #007 Testing #008 Security #001 Introduction Git-daemon provides unauthenticated, read-only access to git repositories via the native git protocol on port 9418. It serves public repositories from /var/git, completely isolated from gitolite's private repository management. Security implementation enforces strict privilege separation through dedicated system users, filesystem isolation, and controlled repository synchronization via gitolite hooks. Git-daemon operates under unprivileged _gitdaemon user with exclusive /var/git access, while gitolite maintains write permissions to populate public repositories through post-receive triggers. #002 System user Create dedicated _gitdaemon system user following OpenBSD conventions for unprivileged daemon accounts: ___________________________________________________________ # useradd -r -c "git Daemon" -d /nonexistent -s /sbin/nologin _gitdaemon ___________________________________________________________ The -r flag creates a system user with UID below 1000. The -d /nonexistent prevents home directory creation, following security best practices for system daemons. Create git-daemon repository directory: ___________________________________________________________ # mkdir -p /var/git # chown _gitdaemon:_gitdaemon /var/git # chmod 750 /var/git ___________________________________________________________ Grant gitolite write access to git-daemon directory: ___________________________________________________________ # usermod -G _gitdaemon gitolite ___________________________________________________________ This allows gitolite to create and update repositories in /var/git with group access while maintaining _gitdaemon ownership. Git-daemon must not have access to /var/gitolite. #003 Repository structure Repository separation maintains security boundaries: /var/gitolite/ - gitolite exclusive (private) /var/git/ - gitdaemon exclusive (public) /var/www/repositories/ - www user (cgit web interface) Public repositories are populated by gitolite through repo-specific hooks when pushes occur to main or dev branches of repositories marked for public access. Each service operates within its designated directory with controlled cross-access permissions only for gitolite to populate git-daemon repositories. #004 Inetd Configure git-daemon through inetd for proper process management and resource control. Add entry to /etc/inetd.conf: ___________________________________________________________ git stream tcp nowait _gitdaemon /usr/local/bin/git-daemon git-daemon --base-path=/var/git --syslog --verbose --export-all ___________________________________________________________ Enable inetd service: ___________________________________________________________ # rcctl enable inetd # rcctl start inetd ___________________________________________________________ The --base-path=/var/git restricts git-daemon access exclusively to the public repository directory. The --export-all flag allows serving all repositories in /var/git without individual export files. #005 Access control Git-daemon serves all repositories in /var/git. Repository availability is controlled by gitolite configuration and repo-specific hooks that populate /var/git. SECURITY: /var/git is READ-ONLY to git-daemon. Updates come ONLY from gitolite hooks. Repositories MUST be: - Owned by _gitdaemon:_gitdaemon - Permissions 750 (dirs) / 640 (files) Example gitolite configuration for public repositories: ___________________________________________________________ @public = daemon @project = doc www repo @project R = @public RW+ = @admins repo @public option hook.post-receive = sync-daemon ___________________________________________________________ Repositories with daemon read access trigger public repository creation in /var/git through repo-specific hooks. #006 Hook integration Configure gitolite repo-specific hook to automatically populate git-daemon repositories when main or dev branches receive pushes to public repositories. Repo-specific hooks require enablement in .gitolite.rc per gitolite-hooks.txt #002 before assignment. Create sync-daemon hook in gitolite-admin repository: ___________________________________________________________ $ git clone gitserver:gitolite-admin $ cd gitolite-admin $ mkdir -p local/hooks/repo-specific ___________________________________________________________ ~/gitolite-admin/local/hooks/repo-specific/sync-daemon ___________________________________________________________ #!/bin/sh NULL_SHA="0000000000000000000000000000000000000000" PUBLIC_BASE="/var/git" while read oldrev newrev refname; do case "$refname" in refs/heads/main|refs/heads/dev) [ "$newrev" = "$NULL_SHA" ] && continue branch="${refname#refs/heads/}" if gitolite access -q daemon "$GL_REPO" R 2>/dev/null; then PUBLIC_REPO="${PUBLIC_BASE}/${GL_REPO}.git" PRIVATE_REPO="$GL_REPO_BASE/$GL_REPO.git" if [ ! -d "$PUBLIC_REPO" ]; then TMP_REPO="${PUBLIC_REPO}.tmp.$$" git clone --bare "$PRIVATE_REPO" "$TMP_REPO" chown -R _gitdaemon:_gitdaemon "$TMP_REPO" chmod -R u+rwX,g+rX,o-rwx "$TMP_REPO" mv "$TMP_REPO" "$PUBLIC_REPO" else cd "$PUBLIC_REPO" || exit 1 git fetch "$PRIVATE_REPO" "${branch}:${branch}" || exit 1 cd - >/dev/null chown -R _gitdaemon:_gitdaemon "$PUBLIC_REPO" chmod -R u+rwX,g+rX,o-rwx "$PUBLIC_REPO" fi fi ;; esac done ___________________________________________________________ Make hook executable and commit: ___________________________________________________________ $ chmod +x local/hooks/repo-specific/sync-daemon $ git add local/hooks/repo-specific/sync-daemon $ git commit -m "Add sync-daemon hook" $ git push ___________________________________________________________ The hook uses gitolite environment variables GL_REPO and GL_REPO_BASE to identify repository location and name. Hook executes after successful pushes to main or dev branches, checking repository permissions and creating or updating corresponding public repository in /var/git. Initial clone uses a temporary directory and atomic move to prevent a partially-created repository from being served. Fetch failures abort the hook before permissions are applied. #007 Testing Test git-daemon functionality by cloning a public repository: ___________________________________________________________ $ git clone git://git.sophia.host/doc ___________________________________________________________ Verify repository structure: ___________________________________________________________ # ls -la /var/git/ # ls -la /var/git/doc.git/ ___________________________________________________________ Check git-daemon process and connections: ___________________________________________________________ # netstat -an | grep :9418 # ps aux | grep git-daemon ___________________________________________________________ Monitor syslog for connection activity: ___________________________________________________________ # tail -f /var/log/daemon ___________________________________________________________ Test hook functionality by pushing to main or dev branch of a public repository and verifying /var/git update: ___________________________________________________________ $ cd /path/to/local/repo $ git push origin main # ls -la /var/git/doc.git/ ___________________________________________________________ #008 Security Critical rules for git-daemon operation: 1. /var/git is OWNED by _gitdaemon:_gitdaemon (750) 2. gitolite WRITES via group (_gitdaemon), but CANNOT read /var/gitolite 3. git-daemon process runs as _gitdaemon, NO access to gitolite repos 4. Cgit accesses /var/git through read-only null mount at /var/www/repositories 5. Hooks must chown/chmod after cloning to /var/git: ___________________________________________________________ chown -R _gitdaemon:_gitdaemon /var/git/repo.git find /var/git/repo.git -type d -exec chmod 750 {} \; find /var/git/repo.git -type f -exec chmod 640 {} \; ___________________________________________________________ * * * [1] git-daemon(1) manual page [2] inetd.conf(5) manual page [3] gitolite-hooks.txt repo-specific hooks [4] cgit.txt web interface configuration ____________________________________________________________ rev-1.0.3 gopher://sophia.host/0/services/gitdaemon.txt