woensdag 21 oktober 2009

Git daemon, SELinux and Fedora 12 Beta.

Recently i decided to redo my Git daemon domain and reinstall a Git daemon server.

This article will explain the issues i had to consider.

SELinux can be used to confined the Git daemon and Git Shell. I have not used SELinux in a optimal way here but i decided to implement a mix of MAC, DAC and Git ACL.

As for SELinux i have installed the following module:

gitd.te:

[code]

policy_module(gitd, 1.0.0)

########################################
#
# Git daemon global private declarations.
#

attribute gitd_type;
attribute gitd_content_type;

type gitd_exec_t;

# FIXME
type gitd_port_t;
corenet_port(gitd_port_t)

########################################
#
# Git daemon system private declarations.
#

##
##


## Allow Git-shell to modify and execute public files
## used for public file transfer services. Directories/Files must
## be labeled public_content_rw_t.
##


##
# gen_tunable(gitd_allow_anon_write, false)

##
##


## Allow Git daemon system to search home directories.
##


##
gen_tunable(gitd_system_enable_homedirs, false)

##
##


## Allow Git daemon system to access cifs file systems.
##


##
gen_tunable(gitd_system_use_cifs, false)

##
##


## Allow Git daemon system to access nfs file systems.
##


##
gen_tunable(gitd_system_use_nfs, false)

type gitd_system_t, gitd_type;
inetd_service_domain(gitd_system_t, gitd_exec_t)
role system_r types gitd_system_t;

type gitd_shared_t, gitd_content_type;
files_type(gitd_shared_t)

# permissive gitd_system_t;

########################################
#
# Git shell private declarations.
#

gen_require(`
attribute unpriv_userdomain, userdomain;
class context contains;
')

attribute gits_file_type;
attribute gits_usertype;

type gits_t, userdomain, gits_usertype, unpriv_userdomain;
domain_type(gits_t)

role gits_r types gits_t;
allow system_r gits_r;

corecmd_shell_entry_type(gits_t)
corecmd_bin_entry_type(gits_t)

domain_interactive_fd(gits_t)
domain_user_exemption_target(gits_t)

# permissive gits_t;

########################################
#
# Git daemon session session private declarations.
#

##
##


## Allow Git daemon session to bind
## tcp sockets to all unreserved ports.
##


##
gen_tunable(gitd_session_bind_all_unreserved_ports, false)

type gitd_session_t, gitd_type;
application_domain(gitd_session_t, gitd_exec_t)
ubac_constrained(gitd_session_t)

type gitd_personal_t, gitd_content_type;
userdom_user_home_content(gitd_personal_t)

# permissive gitd_session_t;

########################################
#
# Git daemon global private policy.
#

allow gitd_type self:fifo_file rw_fifo_file_perms;
allow gitd_type self:netlink_route_socket { create_socket_perms nlmsg_read };
allow gitd_type self:tcp_socket create_socket_perms;
allow gitd_type self:udp_socket create_socket_perms;
allow gitd_type self:unix_dgram_socket create_socket_perms;

# FIXME
allow gitd_type gitd_port_t:tcp_socket name_bind;

corenet_all_recvfrom_netlabel(gitd_type)
corenet_all_recvfrom_unlabeled(gitd_type)

corenet_tcp_sendrecv_all_if(gitd_type)
corenet_tcp_sendrecv_all_nodes(gitd_type)
corenet_tcp_sendrecv_all_ports(gitd_type)

corenet_tcp_bind_all_nodes(gitd_type)

corecmd_exec_bin(gitd_type)

files_read_etc_files(gitd_type)
files_read_usr_files(gitd_type)

fs_search_auto_mountpoints(gitd_type)

kernel_read_system_state(gitd_type)

logging_send_syslog_msg(gitd_type)

miscfiles_read_localization(gitd_type)

sysnet_read_config(gitd_type)

optional_policy(`
nis_use_ypbind(gitd_type)
')

optional_policy(`
nscd_read_pid(gitd_type)
')

########################################
#
# Git daemon system repository private policy.
#

list_dirs_pattern(gitd_system_t, gitd_content_type, gitd_content_type)
read_files_pattern(gitd_system_t, gitd_content_type, gitd_content_type)
files_search_var(gitd_system_t)

# This will not work since git-shell needs to execute gitd content thus public content files.
# There is currently no clean way to execute public content files.
# miscfiles_read_public_files(gitd_system_t)

tunable_policy(`gitd_system_enable_homedirs', `
userdom_search_user_home_dirs(gitd_system_t)
')

tunable_policy(`gitd_system_enable_homedirs && use_nfs_home_dirs', `
fs_list_nfs(gitd_system_t)
fs_read_nfs_files(gitd_system_t)
')

tunable_policy(`gitd_system_enable_homedirs && use_samba_home_dirs', `
fs_list_cifs(gitd_system_t)
fs_read_cifs_files(gitd_system_t)
')

tunable_policy(`gitd_system_use_cifs', `
fs_list_cifs(gitd_system_t)
fs_read_cifs_files(gitd_system_t)
')

tunable_policy(`gitd_system_use_nfs', `
fs_list_nfs(gitd_system_t)
fs_read_nfs_files(gitd_system_t)
')

########################################
#
# Git shell private policy.
#

allow gits_t self:context contains;
allow gits_t self:fifo_file rw_fifo_file_perms;

corecmd_exec_bin(gits_t)

kernel_read_system_state(gits_t)

files_read_etc_files(gits_t)

files_search_home(gits_t)

gitd_execute_shared_files(gits_t)
gitd_manage_shared_content(gits_t)

miscfiles_read_localization(gits_t)
# miscfiles_read_public_files(gits_t)

ssh_rw_stream_sockets(gits_t)

# FIXME
# This will not work since git-shell needs to execute gitd content thus public content files.
# There is currently no clean way to execute public content files.
# tunable_policy(`gitd_allow_anon_write', `
# miscfiles_exec_public_files(gits_t)
# miscfiles_manage_public_files(gits_t)
# ')

tunable_policy(`gitd_system_enable_homedirs && use_nfs_home_dirs', `
fs_exec_nfs_files(gits_t)
fs_manage_nfs_dirs(gits_t)
fs_manage_nfs_files(gits_t)
')

tunable_policy(`gitd_system_enable_homedirs && use_samba_home_dirs', `
fs_exec_cifs_files(gits_t)
fs_manage_cifs_dirs(gits_t)
fs_manage_cifs_files(gits_t)
')

tunable_policy(`gitd_system_use_cifs', `
fs_exec_cifs_files(gits_t)
fs_manage_cifs_dirs(gits_t)
fs_manage_cifs_files(gits_t)
')

tunable_policy(`gitd_system_use_nfs', `
fs_exec_nfs_files(gits_t)
fs_manage_nfs_dirs(gits_t)
fs_manage_nfs_files(gits_t)
')

optional_policy(`
nscd_read_pid(gits_t)
')

########################################
#
# Git daemon session repository private policy.
#

list_dirs_pattern(gitd_session_t, gitd_personal_t, gitd_personal_t)
read_files_pattern(gitd_session_t, gitd_personal_t, gitd_personal_t)
userdom_search_user_home_dirs(gitd_session_t)

userdom_use_user_terminals(gitd_session_t)

tunable_policy(`gitd_session_bind_all_unreserved_ports', `
corenet_tcp_bind_all_unreserved_ports(gitd_session_t)
')

tunable_policy(`use_nfs_home_dirs', `
fs_list_nfs(gitd_session_t)
fs_read_nfs_files(gitd_session_t)
')

tunable_policy(`use_samba_home_dirs', `
fs_list_cifs(gitd_session_t)
fs_read_cifs_files(gitd_session_t)
')
[/code]

gitd.if
[code]
## Git daemon is a really simple server for Git repositories.
##
##


## A really simple TCP git daemon that normally listens on
## port DEFAULT_GIT_PORT aka 9418. It waits for a
## connection asking for a service, and will serve that
## service if it is enabled.
##


##


## It verifies that the directory has the magic file
## git-daemon-export-ok, and it will refuse to export any
## git directory that has not explicitly been marked for
## export this way (unless the --export-all parameter is
## specified). If you pass some directory paths as
## git-daemon arguments, you can further restrict the
## offers to a whitelist comprising of those.
##


##


## By default, only upload-pack service is enabled, which
## serves git-fetch-pack and git-ls-remote clients, which
## are invoked from git-fetch, git-pull, and git-clone.
##


##


## This is ideally suited for read-only updates, i.e.,
## pulling from git repositories.
##


##


## An upload-archive also exists to serve git-archive.
##


##

#######################################
##
## Role access for Git daemon session.
##

##
##
## Role allowed access.
##

##
##
##
## User domain for the role.
##

##
#
interface(`gitd_session_role', `
gen_require(`
type gitd_session_t, gitd_exec_t, gitd_personal_t;
')

########################################
#
# Git daemon session shared declarations.
#

##
##


## Allow transitions to the Git daemon
## session domain.
##


##
gen_tunable(gitd_session_transition, false)

role $1 types gitd_session_t;

########################################
#
# Git daemon session shared policy.
#

tunable_policy(`gitd_session_transition', `
domtrans_pattern($2, gitd_exec_t, gitd_session_t)
', `
can_exec($2, gitd_exec_t)
')

allow $2 gitd_session_t:process { ptrace signal_perms };
ps_process_pattern($2, gitd_session_t)

exec_files_pattern($2, gitd_personal_t, gitd_personal_t)
manage_dirs_pattern($2, gitd_personal_t, gitd_personal_t)
manage_files_pattern($2, gitd_personal_t, gitd_personal_t)

relabel_dirs_pattern($2, gitd_personal_t, gitd_personal_t)
relabel_files_pattern($2, gitd_personal_t, gitd_personal_t)
')

########################################
##
## Allow the specified domain to execute
## Git daemon shared files.
##

##
##
## Domain allowed access.
##

##
##
#
interface(`gitd_execute_shared_files', `
gen_require(`
type gitd_shared_t;
')

exec_files_pattern($1, gitd_shared_t, gitd_shared_t)
files_search_var($1)
')

########################################
##
## Allow the specified domain to manage
## Git daemon shared content.
##

##
##
## Domain allowed access.
##

##
##
#
interface(`gitd_manage_shared_content', `
gen_require(`
type gitd_shared_t;
')

manage_dirs_pattern($1, gitd_shared_t, gitd_shared_t)
manage_files_pattern($1, gitd_shared_t, gitd_shared_t)
files_search_var($1)
')

########################################
##
## Allow the specified domain to manage
## Git daemon personal content.
##

##
##
## Domain allowed access.
##

##
##
#
interface(`gitd_manage_personal_content', `
gen_require(`
type gitd_personal_t;
')

manage_dirs_pattern($1, gitd_personal_t, gitd_personal_t)
manage_files_pattern($1, gitd_personal_t, gitd_personal_t)
files_search_home($1)
')

########################################
##
## Allow the specified domain to read
## Git daemon personal content.
##

##
##
## Domain allowed access.
##

##
##
#
interface(`gitd_read_personal_content', `
gen_require(`
type gitd_personal_t;
')

list_dirs_pattern($1, gitd_personal_t, gitd_personal_t)
read_files_pattern($1, gitd_personal_t, gitd_personal_t)
files_search_home($1)
')

########################################
##
## Allow the specified domain to read
## Git daemon shared content.
##

##
##
## Domain allowed access.
##

##
##
#
interface(`gitd_read_shared_content', `
gen_require(`
type gitd_shared_t;
')

list_dirs_pattern($1, gitd_shared_t, gitd_shared_t)
read_files_pattern($1, gitd_shared_t, gitd_shared_t)
files_search_var($1)
')

########################################
##
## Allow the specified domain to relabel
## Git daemon shared content.
##

##
##
## Domain allowed access.
##

##
##
#
interface(`gitd_relabel_shared_content', `
gen_require(`
type gitd_shared_t;
')

relabel_dirs_pattern($1, gitd_shared_t, gitd_shared_t)
relabel_files_pattern($1, gitd_shared_t, gitd_shared_t)
files_search_var($1)
')

########################################
##
## Allow the specified domain to relabel
## Git daemon personal content.
##

##
##
## Domain allowed access.
##

##
##
#
interface(`gitd_relabel_personal_content', `
gen_require(`
type gitd_personal_t;
')

relabel_dirs_pattern($1, gitd_personal_t, gitd_personal_t)
relabel_files_pattern($1, gitd_personal_t, gitd_personal_t)
files_search_home($1)
')

########################################
##
## All of the rules required to administrate an
## Git daemon system environment
##

##
##
## Prefix of the domain. Example, user would be
## the prefix for the user_t domain.
##

##
##
##
## Domain allowed access.
##

##
##
##
## The role to be allowed to manage the Git daemon domain.
##

##
##
#
interface(`gitd_system_admin', `
gen_require(`
type gitd_system_t, gitd_exec_t;
')

allow $1 gitd_system_t:process { getattr ptrace signal_perms };

kernel_search_proc($1)
allow $1 git_system_t:dir list_dir_perms;
read_files_pattern($1, gitd_system_t, gitd_system_t)
read_lnk_files_pattern($1, gitd_system_t, gitd_system_t)

manage_files_pattern($1, gitd_exec_t, gitd_exec_t)

# This will not work since git-shell needs to execute gitd content thus public content files.
# There is currently no clean way to execute public content files.
# miscfiles_manage_public_files($1)

gitd_manage_shared_content($1)
gitd_relabel_shared_content($1)

seutil_domtrans_setfiles($1)
')
[/code]

gitd.fc
[code]HOME_DIR/public_git(/.*)? gen_context(system_u:object_r:gitd_personal_t, s0)
HOME_DIR/\.gitconfig -- gen_context(system_u:object_r:gitd_personal_t, s0)

/srv/git(/.*)? gen_context(system_u:object_r:gitd_shared_t, s0)

/usr/libexec/git-core/git-daemon -- gen_context(system_u:object_r:gitd_exec_t, s0)

# Conflict with Fedora cgit fc spec.
# /var/lib/git(/.*)? gen_context(system_u:object_r:gitd_shared_t, s0)
[/code]

I manually labelled tcp:9418 gitd_port_t
[code]
semanage port -a -t gitd_port_t -p tcp 9418
[/code]

I also manually added a SELinux user mapping:

semanage user -a -L s0 -r s0 -R "gits_r" -P gits_u

echo "system_r:sshd_t:s0 gits_r:gits_t:s0" > /etc/selinux/targeted/contexts/users/gits_u

I edited /etc/xinetd.d/git:
[code]
# default: off
# description: The git dæmon allows git repositories to be exported using \
# the git:// protocol.

service git
{
disable = no
socket_type = stream
type = UNLISTED
port = 9418
wait = no
user = nobody
# server = /usr/bin/git
# server_args = daemon --base-path=/srv/git --export-all
--user-path=public_git --syslog --inetd --verbose
server = /usr/libexec/git-core/git-daemon
server_args = --base-path=/srv/git --export-all
--user-path=public_git --syslog --inetd --verbose
log_on_failure += USERID
# xinetd doesn't do this by default. bug #195265
flags = IPv6
}
[/code]

Basically the selinux policy has 3 parts. one part is for the gitd system inetd server. the second part is for running gitd as a unprivileged user, and the third part is policy for the system wide git-shell environment.
So we secure the system wide gitd process, gitd process that users run and the git-shell process that is used to push pull to shared repositories.

When you compile git-daemon, configured it like above and install the selinux module, restore the contexts of the paths in gitd.fc. then start xinetd: youll notice that inetd_t is not allowed to bind to gitd_port_t.
You can simply use audit2allow with the -M option to allow inetd_t to bind to gitd_port on behalf of gitd_system_t

I use DAC to give usergroups access to the particular repositories and i use git ACL to restrict access to branches, tags etc.

I used this great web site do implement this:

http://www.kernel.org/pub/software/scm/git/docs/everyday.html

I wrote two simple scripts:

1. to add new users
2. to add new repostiries

create_repository:

crepo.sh:
[code]
#!/bin/bash

# crepo.sh

groupadd $1 || exit 1;
usermod -a -G $1 badabing || exit 1;

mkdir /srv/git/$1.git || exit 1;
chmod -R +t /srv/git/$1.git || exit 1;
cd /srv/git/$1.git && git --bare init || exit 1;

cp /home/dgrift/create_repository/update /srv/git/$1.git/hooks/ || exit 1;
cp /home/dgrift/create_repository/allowed-users /srv/git/$1.git/info/ || exit 1;

chown -R nobody:$1 /srv/git/$1.git || exit 1;

chmod -R g+s /srv/git/$1.git/branches || exit 1;
chmod -R g+s /srv/git/$1.git/hooks || exit 1;
chmod -R g+s /srv/git/$1.git/info || exit 1;
chmod -R g+s /srv/git/$1.git/objects || exit 1;
chmod -R g+s /srv/git/$1.git/refs || exit 1;
chmod -R g+w /srv/git/$1.git || exit 1;

exit 0;

#EOF
[/code]

This script installs the git ACL files update and allowed-users:

update:
[code]
http://www.kernel.org/pub/software/scm/git/docs/howto/update-hook-example.txt
[/code]

allowed-users:
[code]
refs/heads/master badabing
+refs/heads/pu badabing
refs/heads/bw/.* badabing
refs/heads/tmp/.* .*
refs/tags/v[0-9].* badabing
[/code]

I also created a script to do part of what is required to add new users (i havent tested this script yet:

[code]
#!/bin/bash

# agits.sh

useradd -Z gits_u -s /usr/bin/git-shell $1 || exit 1;
usermod -a -G sshusers,$2 $1 || exit 1;

echo "Do not forget to set a password!"
echo "Manually add entries for $1 in /etc/security/namespace.conf!"
echo "You may need to edit /srv/git/$2.git/info/allowed-users!"

# TODO: usrquota

exit 0;
[/code]

By the way xinetd and selinux dont play nice so you may need to edit the xinetd init script:

[code]
https://bugzilla.redhat.com/show_bug.cgi?id=529681
[/code]

So that is basically it.
The SELinux policy for the git system service should work by default. Additionally there are some booleans to to toggle for example to allow the git system service to also host personal repositories in ~/public_git, allow git to use any unreserved port for mass git repostory hosting. enable disable transition to git_session_t which is the user daemon.
For this to work you must also enable git policy for users by creating a module:

for example if you want unconfined users to transition to git domain if they run git daemon <...>

myunconfined.te:
[code]
policy_module(myunconfined, 0.0.1)
optional_policy(`
gen_require(`
type unconfined_t;
')

git_session_role(unconfined_r, unconfined_t)
[/code]

build and install that:

make -f /usr/share/selinux/devel/Makefile
sudo semodule -i myunconfined.pp

That should work and make unconfined_t users transition to git_session_t when they run git daemon <...>

For me this setup works, but i have not thoroughly tested the git_session_t domain yet.

Would be nice to get some feedback so that i can improve this.


The git selinux policy is malformed above use the command below to pull the current gitd policy from my git repository

git clone git://
82.197.205.60/selinux-modules.git

2 opmerkingen: