Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Git hook post-receive run restorecon

I use cgit for myself, which is available at /cgit. I wrote about it previously about a year ago:

And I have now improved my process with a post-receive hook. This task runs every time the server handles an upload. My hook does a few things:

* generates metadata to store the most-recently-modified timestamp for this repo, for sorting in the cgit web view.
* runs `restorecon` to get my proper context, so new branches are visible in cgit web view.

Configure SELinux

Of course I use SELinux, so I need a custom policy for my git-hook to work. I used the standard mechanisms to troubleshoot SELinux.

sudo setenforce 0
semodule --disable_dontaudit --build
echo "" | sudo tee /var/log/audit/audit.log 1>/dev/null
# perform a large number of git and cgit operations
sudo tail -n15000 /var/log/audit/audit.log | audit2allow -M foo
# manually merge any new entries into cgitstackrpms.te
semodule --build
_func() { sudo checkmodule -M -m -o cgitstackrpms.mod cgitstackrpms.te && sudo semodule_package -o cgitstackrpms.pp -m cgitstackrpms.mod && sudo semodule -i cgitstackrpms.pp ; } ; time _func

Final asset is cgitstackrpms.te which can be loaded and installed with the last line of the above command block. The most recent version includes the rules for the restorecon invocation from the post-receive git hook.

module cgitstackrpms 1.2;

require {
    type default_context_t;
    type git_script_t;
    type httpd_cache_t;
    type httpd_sys_content_t;
    type httpd_sys_rw_content_t;
    type httpd_t;
    type initrc_var_run_t;
    type selinux_config_t;
    type shadow_t;
    type sssd_conf_t;
    type systemd_logind_sessions_t;
    type systemd_logind_t;
    type var_t;
    class capability { audit_write fowner net_admin sys_resource };
    class dbus send_msg;
    class dir { relabelfrom relabelto getattr open read search };
    class fifo_file write;
    class file { getattr map lock open read relabelfrom relabelto };
    class netlink_audit_socket { create nlmsg_relay read write };
    class process { setrlimit noatsecure rlimitinh siginh };
}

#============= git_script_t ==============
allow git_script_t var_t:dir read;
allow git_script_t var_t:file { read getattr open };
allow git_script_t httpd_cache_t:dir { getattr open read search };
allow git_script_t httpd_cache_t:file { map getattr open read };
allow git_script_t httpd_sys_content_t:dir { getattr open read search };

#============= httpd_t ==============
allow httpd_t git_script_t:process { noatsecure rlimitinh siginh };
allow httpd_t default_context_t:file { getattr open read };
allow httpd_t httpd_sys_content_t:dir relabelto;
allow httpd_t httpd_sys_content_t:file relabelto;
allow httpd_t httpd_sys_rw_content_t:dir relabelfrom;
allow httpd_t httpd_sys_rw_content_t:file relabelfrom;
allow httpd_t initrc_var_run_t:file { lock open read };
allow httpd_t self:capability { net_admin audit_write fowner sys_resource };
allow httpd_t self:netlink_audit_socket { create nlmsg_relay read write };
allow httpd_t self:process setrlimit;
allow httpd_t selinux_config_t:file { getattr open read };
allow httpd_t shadow_t:file { getattr open read };
allow httpd_t sssd_conf_t:file { getattr open read };
allow httpd_t systemd_logind_sessions_t:fifo_file write;
allow httpd_t systemd_logind_t:dbus send_msg;

#============= systemd_logind_t ==============
allow systemd_logind_t httpd_t:dbus send_msg;

I already had a file context for my entire www directory.

semanage fcontext -a -t httpd_sys_content_t '/var/server1/shares/public/www(/.*)?'
Running restorecon after each push

The git hook for post-receive runs after every push to this cgit instance. Sometimes new files are buitl with incorrect selinux file contexts, but a restorecon will fix the contexts. Special permissions are needed, in the selinux policy above, and also for sudo.

My environment uses FreeIPA, so I can make a rule in the domain for this. Even though of course apache is a local user, I want to make a domain user so I can make a sudoers rule for it. Sudo reads usernames from sudoers entries, and not uids, so this will work for a local user apache on the target host.

# gid 960600013 is my preexisting service-accounts group
ipa user-add --homedir=/usr/share/httpd --shell /sbin/nologin apache --first=apache --last="domain user" --gidnumber=960600013
ipa group-add-member service-accounts --users apache 
ipa sudocmd-add '/sbin/restorecon -R /var/www/git'
ipa sudocmd-add '/sbin/restorecon -Rv /var/www/git'
ipa sudocmd-add '/sbin/restorecon -Rvn /var/www/git'
ipa sudorule-add apache-restorecon-cgit
ipa sudorule-add-host apache-restorecon-cgit --hosts server1
ipa sudorule-add-allow-command apache-restorecon-cgit --sudocmds /sbin/restorecon
ipa sudorule-add-allow-command apache-restorecon-cgit --sudocmds '/sbin/restorecon -R /var/www/git'
ipa sudorule-add-allow-command apache-restorecon-cgit --sudocmds '/sbin/restorecon -Rv /var/www/git'
ipa sudorule-add-allow-command apache-restorecon-cgit --sudocmds '/sbin/restorecon -Rvn /var/www/git'
ipa sudorule-add-option apache-restorecon-cgit --sudooption '!requiretty'
ipa sudorule-add-option apache-restorecon-cgit --sudooption '!authenticate'
ipa sudorule-add-user apache-restorecon-cgit --users apache
ipa sudorule-mod apache-restorecon-cgit --desc="Apache can run restorecon /var/www/git on server1"

If you are using plain sudo, use these contents.

# File /etc/sudoers.d/60_git-hook_post-receive_sudo
Defaults>apache   !requiretty
apache  server1=(root)       NOPASSWD: /usr/sbin/restorecon -R /var/www/git, /usr/sbin/restorecon -Rv /var/www/git, /usr/sbin/restorecon -Rvn /var/www/git

With all of these steps, the user apache can now run restorecon on that directory as part of the git-hook for post-receive.

All of that, to explain one line in the following git hook file, /usr/local/bin/git-hooks/post-receive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
# File: post-receive
# Project: cgit-Internal
# Startdate: 2022-01-27
# History:
#    2022-05-11 added restorecon
# Ripped directly from https://landchad.net/cgit
# Dependencies:
#    sudoers rule in freeipa
#exec 1>>/tmp/asdf2
#exec 2>&1
#exec 3>&1
set -x

agefile="$(git rev-parse --git-dir)"/info/web/last-modified
date "+%FT%T used post-receive" >> /tmp/asdf2

mkdir -p "$(dirname "$agefile")" &&
git for-each-ref \
    --sort=-authordate --count=1 \
    --format='%(authordate:iso8601)' \
    >"$agefile"

sudo --non-interactive /usr/sbin/restorecon -R /var/www/git 1>/dev/null 2>&1 &

This git hook can be established for all future git repositories established as part of my documented solution, by modifying the skeleton git hooks directory to store it:

sudo ln -sf /usr/local/bin/git-hooks/post-receive /usr/share/git-core/templates/hooks/post-receive

Operations

An additional operation is available for the cgit-Internal solution.

Refreshing last-modified date for all repos

If for some reason the the repositories should get refreshed info/web/last-modified contents, you can run the hook manually. This command will switch to user apache so the permissions are preserved on the info/web/last-modified files.

On server1:

sudo chown -R apache.admins /var/www/git/{*,*/*}/info/web
su apache -s /bin/bash -c 'cd /var/www/git ; for word in {*,*/*}/hooks/post-receive ; do ( cd "$( dirname "${word}" )" ; sh "./$( basename "${word}" )" ; ) ; done'

Comments