From 98cbb8514b0b3394ecb9fa9894fc93c2ceeda3af Mon Sep 17 00:00:00 2001
From: John Kubiatowicz
Date: Wed, 23 Nov 2011 16:44:25 -0800
Subject: [PATCH 1/2] Added hooks, policy and rakefile to install selinux
policy for this plugin
---
.gitignore | 2 +
README.mkd | 38 +++
app/models/git_hosting_settings_observer.rb | 23 +-
.../settings/_redmine_git_hosting.html.erb | 7 +-
config/locales/bg.yml | 1 +
config/locales/bs.yml | 1 +
config/locales/ca.yml | 1 +
config/locales/cs.yml | 1 +
config/locales/da.yml | 1 +
config/locales/de.yml | 1 +
config/locales/el.yml | 1 +
config/locales/en.yml | 1 +
config/locales/es.yml | 1 +
config/locales/fi.yml | 1 +
config/locales/fr.yml | 1 +
config/locales/gl.yml | 1 +
config/locales/he.yml | 1 +
config/locales/hu.yml | 1 +
config/locales/id.yml | 1 +
config/locales/it.yml | 1 +
config/locales/ja.yml | 1 +
config/locales/ko.yml | 1 +
config/locales/lt.yml | 1 +
config/locales/nl.yml | 1 +
config/locales/no.yml | 1 +
config/locales/pl.yml | 1 +
config/locales/pt-BR.yml | 1 +
config/locales/pt.yml | 1 +
config/locales/ro.yml | 1 +
config/locales/ru.yml | 1 +
config/locales/sk.yml | 1 +
config/locales/sl.yml | 1 +
config/locales/sr.yml | 1 +
config/locales/sv.yml | 1 +
config/locales/th.yml | 1 +
config/locales/tr.yml | 1 +
config/locales/uk.yml | 1 +
config/locales/vi.yml | 1 +
config/locales/zh-TW.yml | 1 +
config/locales/zh.yml | 1 +
lib/git_hosting.rb | 62 +++-
selinux/README | 67 +++++
selinux/redmine_git.fc | 9 +
selinux/redmine_git.if | 131 +++++++++
selinux/redmine_git.pp | Bin 0 -> 127822 bytes
selinux/redmine_git.sh | 44 +++
selinux/redmine_git.te | 69 +++++
tasks/selinux.rake | 273 ++++++++++++++++++
48 files changed, 739 insertions(+), 22 deletions(-)
create mode 100644 selinux/README
create mode 100644 selinux/redmine_git.fc
create mode 100644 selinux/redmine_git.if
create mode 100644 selinux/redmine_git.pp
create mode 100755 selinux/redmine_git.sh
create mode 100644 selinux/redmine_git.te
create mode 100644 tasks/selinux.rake
diff --git a/.gitignore b/.gitignore
index a01ee289f..5412c5a54 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
.*.swp
+bin/
+selinux/tmp/
diff --git a/README.mkd b/README.mkd
index 945b9cf82..dfae03514 100755
--- a/README.mkd
+++ b/README.mkd
@@ -246,6 +246,44 @@ This library allows you to quickly deploy ChiliProject, with this plugin to an u
chili\_test.sh script, modifying the variables in those scripts as desired. This library is still under development,
so these instructions may need to be updated in the near future.
+## Selinux Configuration for redmine
+
+This plugin can be configured to run with selinux. We have included a rakefile in tasks/selinux.rake to assist
+with installing with selinux. You should start by editing init.rb and migrating as described above. Then, you
+can execute one of the selinux rake tasks (from the redmine root). For instance, the simplest option installs
+a selinux configuration for both redmine and the redmine_git_hosting plugin:
+
+ rake selinux:install RAILS_ENV=production
+
+This will generate the redmine_git_hosting binaries in ./bin, install a selinux policy for these binaries (called
+redmine_git.pp), then install a complete context for redmine as follows:
+
+**(1)** Most of redmine will be marked with "public_content_rw_t".
+
+**(2)** The dispatch files in Rails.root/public/dispatch.* will be marked with "httpd_sys_script_exec_t"
+
+**(3)** The redmine_git_hosting binaries in Rails.root/vendor/plugins/redmine_git_hosting/bin will be labeled
+with "httpd_redmine_git_script_exec_t", which has been crafted to allow the sudo behavior required by these
+binaries.
+
+Note that this rake file has additional options. For instance, you can specify redmine roots with regular
+expressions (not globbed expessions!) as follows (notice the use of double quotes):
+
+ rake selinux:install RAILS_ENV=production ROOT_PATTERN="/source/.*/redmine"
+
+These additional options are documented in the selinux.rake file.
+
+Once this plugin is placed under selinux control, three of the redmine_git_hosting settings can
+no longer be modified from the settings page. They are: 'gitUser', 'gitoliteIdentityFile', and
+'gitoliteIdentityPublicKeyFile'. The plugin settings page will make this clear. The simplest way to
+modify these options is to temporarily place your system into permissive mode, refresh the setting page,
+change options, then place your system back into enforcing mode. Alternatively, you can alter the init.rb
+file and reinstall the plugin. Under normal operation, you will get one selinux complaint about /bin/touch
+in your log each time that you visit the plugin settings page.
+
+This rakefile and selinux configuration has been primarily tested on Redhat Enterprise Linux version 6.x
+with apache and fcgi. Other configurations may require slight tweaking.
+
## Tested Configurations
This plugin has been primarily tested on Ubuntu Server 10.10 and 11.04 (32 and 64 bit) with ChiliProject v1.x,
diff --git a/app/models/git_hosting_settings_observer.rb b/app/models/git_hosting_settings_observer.rb
index fee8d8a3f..fcd16e9ae 100644
--- a/app/models/git_hosting_settings_observer.rb
+++ b/app/models/git_hosting_settings_observer.rb
@@ -5,6 +5,8 @@ class GitHostingSettingsObserver < ActiveRecord::Observer
@@old_hook_asynch = Setting.plugin_redmine_git_hosting['gitHooksAreAsynchronous']
@@old_http_server = Setting.plugin_redmine_git_hosting['httpServer']
@@old_git_user = Setting.plugin_redmine_git_hosting['gitUser']
+ @@old_gitolite_identity = Setting.plugin_redmine_git_hosting['gitoliteIdentityFile']
+ @@old_gitolite_publickey = Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']
@@old_repo_base = Setting.plugin_redmine_git_hosting['gitRepositoryBasePath']
@@ -14,12 +16,25 @@ def reload_this_observer
end
end
-
-
+ def before_save(object)
+ if object.name == "plugin_redmine_git_hosting" && !GitHosting.bin_dir_writeable?
+ # If bin directory not alterable, don't alow changes to
+ # Git Username, or Gitolite public or private keys
+ valuehash = object.value
+ valuehash['gitUser'] = @@old_git_user
+ valuehash['gitoliteIdentityFile'] = @@old_gitolite_identity
+ valuehash['gitoliteIdentityPublicKeyFile'] = @@old_gitolite_publickey
+ object.value = valuehash
+ end
+ end
+
def after_save(object)
if object.name == "plugin_redmine_git_hosting"
- %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ]
+ if GitHosting.bin_dir_writeable?
+ %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ]
+ %x[ rm -rf '#{ GitHosting.get_bin_dir }' ]
+ end
if @@old_repo_base != object.value['gitRepositoryBasePath']
GitHostingObserver.set_update_active(false)
@@ -49,6 +64,8 @@ def after_save(object)
@@old_hook_asynch = object.value['gitHooksAreAsynchronous']
@@old_http_server = object.value['httpServer']
@@old_git_user = object.value['gitUser']
+ @@old_gitolite_identity = object.value['gitoliteIdentityFile']
+ @@old_gitolite_publickey = object.value['gitoliteIdentityPublicKeyFile']
@@old_repo_base = object.value['gitRepositoryBasePath']
end
diff --git a/app/views/settings/_redmine_git_hosting.html.erb b/app/views/settings/_redmine_git_hosting.html.erb
index c6b1e7760..9e6527c84 100644
--- a/app/views/settings/_redmine_git_hosting.html.erb
+++ b/app/views/settings/_redmine_git_hosting.html.erb
@@ -26,19 +26,19 @@
- <%= l(:label_git_user)%>
+ <%= l(:label_git_user) %><%= "[ #{l(:label_cannot_change_selinux)} ] " if !GitHosting.bin_dir_writeable? :reset %>
<%= text_field_tag("settings[gitUser]", @settings['gitUser'].split(/[\r\n\t ,;]+/).join("\n"), :size => 60) %>
- <%= l(:label_gitolite_identity_file)%>
+ <%= l(:label_gitolite_identity_file) %><%= "[ #{l(:label_cannot_change_selinux)} ] " if !GitHosting.bin_dir_writeable? %>
<%= text_field_tag("settings[gitoliteIdentityFile]", @settings['gitoliteIdentityFile'], :size => 60) %>
- <%= l(:label_gitolite_identity_public_key_file)%>
+ <%= l(:label_gitolite_identity_public_key_file) %><%= "[ #{l(:label_cannot_change_selinux)} ] " if !GitHosting.bin_dir_writeable? %>
<%= text_field_tag("settings[gitoliteIdentityPublicKeyFile]", @settings['gitoliteIdentityPublicKeyFile'], :size => 60) %>
@@ -48,7 +48,6 @@
-
<%= l(:label_git_cache_parameters)%>
<%= l(:label_git_cache_max_elements)%>
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/bs.yml b/config/locales/bs.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/bs.yml
+++ b/config/locales/bs.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/el.yml b/config/locales/el.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/he.yml b/config/locales/he.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/no.yml b/config/locales/no.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 90c31bf3a..e9989cd0e 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -7,6 +7,7 @@ pt-BR:
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada)
label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)'
+ label_cannot_change_selinux: 'não pode ser mudada (selinux)'
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index dc5f5f1d6..f6db9e33d 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -7,6 +7,7 @@ pt:
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada)
label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)'
+ label_cannot_change_selinux: 'não pode ser mudada (selinux)'
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/ro.yml b/config/locales/ro.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/ro.yml
+++ b/config/locales/ro.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
index 2ce8a4693..723401ce1 100644
--- a/config/locales/zh.yml
+++ b/config/locales/zh.yml
@@ -7,6 +7,7 @@
label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key)
label_gitolite_identity_file: Gitolite SSH Identity File (Private Key)
label_git_repository_base_path: Git Repository Base Path (Relative to git user home)
+ label_cannot_change_selinux: Cannot be changed under selinux
field_git_daemon: Git Daemon
field_git_http: Git Smart HTTP
diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb
index 8bcea3432..07aa7e0e3 100755
--- a/lib/git_hosting.rb
+++ b/lib/git_hosting.rb
@@ -23,6 +23,10 @@ def self.web_user
return @@web_user
end
+ def self.web_user=(setuser)
+ @@web_user = setuser
+ end
+
def self.git_user
Setting.plugin_redmine_git_hosting['gitUser']
end
@@ -92,7 +96,7 @@ def self.sudo_web_to_git_user
@@sudo_web_to_git_user_stamp = Time.new
return @@sudo_web_to_git_user_cached
end
- test = %x[sudo -nu #{git_user} echo "yes"]
+ test = %x[#{GitHosting.git_user_runner} echo "yes"]
if test.match(/yes/)
@@sudo_web_to_git_user_cached = true
@@sudo_web_to_git_user_stamp = Time.new
@@ -146,7 +150,7 @@ def self.add_route_for_project_with_map(p,m)
end
end
def self.get_tmp_dir
- @@git_hosting_tmp_dir ||= File.join(Dir.tmpdir, "redmine_git_hosting")
+ @@git_hosting_tmp_dir ||= File.join(Dir.tmpdir, "redmine_git_hosting", "#{git_user}")
if !File.directory?(@@git_hosting_tmp_dir)
%x[mkdir -p "#{@@git_hosting_tmp_dir}"]
%x[chmod 700 "#{@@git_hosting_tmp_dir}"]
@@ -154,17 +158,51 @@ def self.get_tmp_dir
end
return @@git_hosting_tmp_dir
end
+ def self.get_bin_dir
+ @@git_hosting_bin_dir ||=
+ Rails.root.join("vendor/plugins/redmine_git_hosting/bin")
+ if !File.directory?(@@git_hosting_bin_dir)
+ logger.error "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}"
+ %x[mkdir -p "#{@@git_hosting_bin_dir}"]
+ %x[chmod 750 "#{@@git_hosting_bin_dir}"]
+ %x[chown #{web_user} "#{@@git_hosting_bin_dir}"]
+ end
+ if !File.directory?(@@git_hosting_bin_dir)
+ logger.error "Cannot create bin directory: #{@@git_hosting_bin_dir}"
+ end
+ return @@git_hosting_bin_dir
+ end
-
+ @@git_bin_dir_writeable = nil
+ def self.bin_dir_writeable?(*option)
+ @@git_bin_dir_writeable = nil if option.length > 0 && option[0] == :reset
+ if @@git_bin_dir_writeable == nil
+ mybindir = get_bin_dir
+ mytestfile = "#{mybindir}/writecheck"
+ if (!File.directory?(mybindir))
+ @@git_bin_dir_writeable = false
+ else
+ %x[touch "#{mytestfile}"]
+ if (!File.exists?("#{mytestfile}"))
+ @@git_bin_dir_writeable = false
+ else
+ %x[rm "#{mytestfile}"]
+ @@git_bin_dir_writeable = true
+ end
+ end
+ end
+ @@git_bin_dir_writeable
+ end
def self.git_exec_path
- return File.join(get_tmp_dir(), "run_git_as_git_user")
+ return File.join(get_bin_dir(), "run_git_as_git_user")
end
+
def self.gitolite_ssh_path
- return File.join(get_tmp_dir(), "gitolite_admin_ssh")
+ return File.join(get_bin_dir(), "gitolite_admin_ssh")
end
def self.git_user_runner_path
- return File.join(get_tmp_dir(), "run_as_git_user")
+ return File.join(get_bin_dir(), "run_as_git_user")
end
@@ -189,7 +227,7 @@ def self.git_user_runner
def self.update_git_exec
- logger.info "Setting up #{get_tmp_dir()}"
+ logger.info "Setting up #{get_bin_dir()}"
gitolite_key=Setting.plugin_redmine_git_hosting['gitoliteIdentityFile']
File.open(gitolite_ssh_path(), "w") do |f|
@@ -211,8 +249,6 @@ def self.update_git_exec
sudo_version = 100*100*(split_version[0].to_i) + 100*(split_version[1].to_i) + split_version[2].to_i
sudo_version_switch = (100*100*1) + (100 * 7) + 3
-
-
File.open(git_exec_path(), "w") do |f|
f.puts '#!/bin/sh'
f.puts "if [ \"\$(whoami)\" = \"#{git_user}\" ] ; then"
@@ -251,14 +287,10 @@ def self.update_git_exec
f.puts '}'
end if !File.exists?(git_user_runner_path())
-
-
File.chmod(0550, git_exec_path())
File.chmod(0550, gitolite_ssh_path())
File.chmod(0550, git_user_runner_path())
-
-
-
+ %x[chown #{web_user} -R "#{@@git_hosting_bin_dir}"]
end
@@ -291,7 +323,7 @@ def self.clone_or_pull_gitolite_admin
# clone/pull from admin repo
local_dir = get_tmp_dir()
if File.exists? "#{local_dir}/gitolite-admin"
- logger.info "Fethcing changes for #{local_dir}/gitolite-admin"
+ logger.info "Fetching changes for #{local_dir}/gitolite-admin"
%x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' fetch]
%x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' merge FETCH_HEAD]
else
diff --git a/selinux/README b/selinux/README
new file mode 100644
index 000000000..a877fb20b
--- /dev/null
+++ b/selinux/README
@@ -0,0 +1,67 @@
+This directory contains a selinux policy crafted to cover the sudo and
+ssh scripts for the redmine_git_hosting plugin. In it, we define a new
+httpd-compatible type, "httpd_redmine_git_script_exec_t" which can be
+placed on the redmine_git_hosting/bin directory to allow sudo access
+from redmine code. The basic assumption is that scripts placed into
+this directory will be called from context "httpd_script_t" (i.e
+redmine).
+
+Once this plugin is placed under selinux control, three of the
+redmine_git_hosting settings can no longer be modified from the
+settings page. They are: 'gitUser', 'gitoliteIdentityFile', and
+'gitoliteIdentityPublicKeyFile'. The plugin settings page will make
+this clear. The simplest way to modify these options is to
+temporarily place your system into permissive mode, refresh the
+setting page, change options, then place your system back into
+enforcing mode. Alternatively, you can alter the init.rb file and
+reinstall the plugin. Under normal operation, you will get one
+selinux complaint about /bin/touch in your log each time that you
+visit the plugin settings page.
+
+******************* INSTALLATION AND SETUP *************************
+Note that the redmine_git_hosting/bin directory must be constructed
+statically so that it can be labeled. You can do this with a series
+of rake tasks at the top-level of the redmine directory (after fixing
+up the defaults in the redmine_git_hosting init.rb file):
+
+ # Build bin directory with customized scripts for redmine_git_hosting,
+ # install new selinux policy, and install complete selinux context for
+ # redmine+this plugin:
+
+ rake selinux:install RAILS_ENV=production
+
+Since redmine doesn't currently have a selinux install option, this
+installation command is only available for this plugin. What this
+will do is label the whole redmine site with "public_content_rw_t",
+with the exception of the "dispatch*" files in public (set to
+"httpd_script_exec_t") and the scripts in redmine_git_hosting/bin (set
+to "httpd_redmine_git_script_exec_t").
+
+If you happen to have multiple redmine installations, you can use a
+regular expression to describe the redmine root directories (this will
+translate into file context descriptions). For instance, if you have
+multiple redmine installations in directories whose paths start with
+"/source" and end with "redmine" you can use (notice the use of
+double-quotes!):
+
+ rake selinux:install RAILS_ENV=production ROOT_PATTERN="/source/.*/redmine"
+
+Somewhat less far-reaching options include:
+
+ # Build bin directory with customized scripts for redmine_git_hosting,
+ # install new selinux policy, and install selinux context for
+ # the redmine_git_hosting plugin
+
+ rake selinux:redmine_git_hosting:install
+
+Finally, for those who are hand-crafting their own file context:
+
+ # Build bin directory with customized scripts for redmine_git_hosting
+ # and install new selinux policy. No file contexts will be
+ # installed (so that you must do customization afterwards).
+
+ rake selinux:redmine_git_hosting:install_scripts_and_policy
+
+
+
+
diff --git a/selinux/redmine_git.fc b/selinux/redmine_git.fc
new file mode 100644
index 000000000..7924d2496
--- /dev/null
+++ b/selinux/redmine_git.fc
@@ -0,0 +1,9 @@
+# We do not install contexts as part of the policy, since it is likely
+# to be overridden by the local policy for parent directories. The rakefile
+# ( ../tasks/selinux.rake) will install file contexts "manually".
+#
+# If you really want to install a file context, uncomment the following
+# and rerun the install script or rake task
+#
+# /.*/redmine/vendor/plugins/redmine_git_hosting/bin(/.*)? httpd_redmine_git_script_exec_t
+
diff --git a/selinux/redmine_git.if b/selinux/redmine_git.if
new file mode 100644
index 000000000..f5e66e48a
--- /dev/null
+++ b/selinux/redmine_git.if
@@ -0,0 +1,131 @@
+
+## policy for httpd_redmine_git_script
+
+
+########################################
+##
+## Execute a domain transition to run httpd_redmine_git_script.
+##
+##
+##
+## Domain allowed access.
+##
+##
+#
+interface(`httpd_redmine_git_script_domtrans',`
+ gen_require(`
+ type httpd_redmine_git_script_t, httpd_redmine_git_script_exec_t;
+ ')
+
+ domtrans_pattern($1, httpd_redmine_git_script_exec_t, httpd_redmine_git_script_t)
+')
+
+
+########################################
+##
+## Search httpd_redmine_git_script rw directories.
+##
+##
+##
+## Domain allowed access.
+##
+##
+#
+interface(`httpd_redmine_git_script_search_rw_dir',`
+ gen_require(`
+ type httpd_redmine_git_script_rw_t;
+ ')
+
+ allow $1 httpd_redmine_git_script_rw_t:dir search_dir_perms;
+ files_search_rw($1)
+')
+
+########################################
+##
+## Read httpd_redmine_git_script rw files.
+##
+##
+##
+## Domain allowed access.
+##
+##
+#
+interface(`httpd_redmine_git_script_read_rw_files',`
+ gen_require(`
+ type httpd_redmine_git_script_rw_t;
+ ')
+
+ allow $1 httpd_redmine_git_script_rw_t:file r_file_perms;
+ allow $1 httpd_redmine_git_script_rw_t:dir list_dir_perms;
+ files_search_rw($1)
+')
+
+########################################
+##
+## Create, read, write, and delete
+## httpd_redmine_git_script rw files.
+##
+##
+##
+## Domain allowed access.
+##
+##
+#
+interface(`httpd_redmine_git_script_manage_rw_files',`
+ gen_require(`
+ type httpd_redmine_git_script_rw_t;
+ ')
+
+ manage_files_pattern($1, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t)
+')
+
+########################################
+##
+## Create, read, write, and delete
+## httpd_redmine_git_script rw dirs.
+##
+##
+##
+## Domain allowed access.
+##
+##
+#
+interface(`httpd_redmine_git_script_manage_rw_dirs',`
+ gen_require(`
+ type httpd_redmine_git_script_rw_t;
+ ')
+
+ manage_dirs_pattern($1, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t)
+')
+
+
+########################################
+##
+## All of the rules required to administrate
+## an httpd_redmine_git_script environment
+##
+##
+##
+## Domain allowed access.
+##
+##
+##
+##
+## Role allowed access.
+##
+##
+##
+#
+interface(`httpd_redmine_git_script_admin',`
+ gen_require(`
+ type httpd_redmine_git_script_t;
+ type httpd_redmine_git_script_rw_t;
+ ')
+
+ allow $1 httpd_redmine_git_script_t:process { ptrace signal_perms };
+ ps_process_pattern($1, httpd_redmine_git_script_t)
+
+ files_search_etc($1)
+ admin_pattern($1, httpd_redmine_git_script_rw_t)
+
+')
diff --git a/selinux/redmine_git.pp b/selinux/redmine_git.pp
new file mode 100644
index 0000000000000000000000000000000000000000..bfe21e6945a9e61a536b6baf50f119f2006732a9
GIT binary patch
literal 127822
zcmeHw2b^P9b^ds8z?f!CZ??b&*05ghs6PYo1B`Xgb0oFW>&05l2x`4f|Z2y
z5J(T{z4zYZB%~*h-bg1sKtehRVgKhl=bfXg(Rtb#&ztezX6|pU-urGl-*@}F(nxPR
z_ofeBu(GnUO8$}L-|>eRth|-N9!B@KzM$}uPH%Fia8tiM=_W{WF?l4vh&R?=`oQ1}r|5z~d2+(rdN{
z!_Fxb^H>XIgW&QA@*|==?!yLZVVJZ|HAoonqtaMl2!Y|vWZWE&hkz(xBoMbS+-$Xy
z0jq=@O4}cdp%?8$lG{jk33u8Yex}{$t3`R3ijV>lJkiH*O8mhC~T_Z8fKPP$c??)CF{aH^`-(VBBBcf=JFS
z*l$adq7Jz6lG7*xybq$`&~Ld%qHCqx52P_+1P;bgo7$Ar4evnZpgx)-8jT$qM5qmn
zhS`up2={QLMW34VI%jC0HhDCnE4Gu-c-TLSaxngaJ`K}ppT;d3N;85*(z|1lOcLCp
za4wilp|0R+phG8BtA`P*D()OnN}bX%iZP(YuGqR{vDez`O=SNE5UZ3P{^YXN_jN&8dN9
zCL1(|R6$zpI{#qOpl~V|g~%M&9JWp%9W1%C4I(gAZS^O;alePOsIorI_lUxrcE_YY
zZuWU}V$KOmEt#o*yuCx-V>^?(Vw>HG-H?xmG&!bNe`DVNqC`WY%L
z9deCczcC&*dn2cNqKbB0uVqS%v8mG-OpEWytxffiAMOle0>JMJSK?n&ek2@ls9v~UZX2fK8kCSiHD
zR%uNk6muFpT$eVw_uaV$HJ>?6YiB`IUY2^gtnk`B8lma2($NCcYVz96Gdv??Mc)~9
zHb=Cn1wHFIY853r$`G~Ycj-IrHdbhY>Z@Rp~Isy
zQP9cc>}-K5vMjW)_0Zk8ZuuAD)C|*djrrWHd_C
zV}9!On-oNJc-7?r)7d=P>2@Iu^+3XUlde5?5UpXiv(@1Z2zpYwNJA?+7%Y%j-g;r?
zqV$lDXpkUi(NIds2bE4OtWo2)oEJAT=!>bJ!Z99NG=ZvjJ9PA(GYznh~YbTrsOO
zh=715%fTiag={5Ts0_Gk$fMfe;i$Qr>z!_Ae6|RBsK2ddt3f-H;jq)T;g7H;9yv81
z*dj4J+(sLn0*EFJ$bowXc`$;98;}-xzC;MM)9L20-RMvFK?yx+quW0XtWAkWH{8Q2
zb`hvi6s}o2Jv0F<+cb%ik%f!u@NR_=_ik+Z;Nj#^7iFQ$y*$^VW>Fi#^baYh3jYBX
zMi1igW>#_g_*{zmheKyZ_lJku=xp@w!;>xQ7E;f<0YO4IG<7b6bzj6QB^oZ`eHD8PGGaJb7R+;#@>AW{gQ&u+Axx12YarGiDq~Jrcz8qNsnf#R?@)pIC=n>B3_Q8U>S^XgrZYqmBMhZn09xkI>~96
z2PJ*9=EBbmnmt}RAWzl+7-0Fc(=;T7-gMmpL}|2i;G7Gz!OZ4QtdVd}CLd0idQ#Y<
ziFI>-512lRrcX2s+%w6eW#HjxCxli3l!ir}ATb}tp#9M1+A|P|a|R?wwzO@v)9Ar~
zG&_z0bQLU6V(hHo;k1#d=eIk}d$gI+lle@Lk9j{D4s>ybs{fAK{ARaj%#rXgUOFt~cfJefuPhLvne(S~^K%
z8b*mUavOB2*W~pen6(jyG~Ir)-Js(YdTR)kEhE~W$dMc^O7p%Hi3dC^Tj>#NT8!$b
zr?E%@N4mPr-sYsaxo}7_QvwdifQO9ibKO9PLmEMyEjqy1qUXbfNPj-83UG6gv#@ZdwDuvY3B{*f`jxB4tY1|edA<^zKDIn
z=7;mN1yO$dseh(WlG?6vvaR#NKsgZ~eZ@`Hwm;&gX1u6$2IMhNC
zSyqqtb-UDFe2iG=j0`M8SNfM+MVC
zP-~1M0~8Ln>qA=DTFucIrDFJUAidkKGdwB)QzB^fXvPP|@fPs7BX4zK$Zt^fb@)^g
z<8tdHJrn9inqq>ZHbN6gh{MNjLhCmef)s3XeSruT3Hfvc??E9Bl_rCwv$Zwh_Yn|;
zWd(HP5Dx`iPG0g-`_LP*U9d(8s5cB#$mau6$~H_!{RurR>eA3UK{cyO)l(^BHZukyOHRz;YN(NYVA#W_PZzh
z>p@K#*f3yMp=VS3^30Bv9qtJEu&QZ+$8lNXWPkk)^j3dklANMv&%%vrWX3!t<8jPukjur^PCTO9%
zArxut$Xofre8?B?(a?r&obUr3h}i=j0Fv@&qUfp7A&Y33oV`H}1S084JjwtUE`v7T
zy7A+WU?HuH4};iKPkIa&Bw$w$_Z0HpQg~)#c#lpiAAQ?IG!a~hJnj|aM1wvru+_{S
z22N)}5K7qgp+R(sv^QzzqURdCuY=CTJvRN&AUZsf%$~*OhJf~;r%@7brfm0yhx4O6
zDerxLM9ahBgZbJ{z`aq+-dm(oq+|=Ho7%3_%qRNtE@Im1o5KOR1h*gT@<^p?xqDr($ShcsTl}0!%nai;o_lc@ae)
zJV-jH`12BkR@TUe?Kz(G4jVJ)#>6uY&MfrYf_2>wjQG_v9Xie}%M9
zi%}rI6$zv8aB`x9j)D+*)05tOqYoL#;}d!<-kTkUQZsChmUrl94#xRdv#QOjUoMOB)!$#;CJ6|p_r`1
zc#x;hkqnWe!E^4*)6_a{Vl9`h`1kbE(gn@A5
zH}b`h_Fpg8iSc+qA4{Nj?0JaM`v%PI*b+m7$vTZ4OFul_7-OUKk|kNTaU*jyS{h?K
z++jq5n9jK|-K!IF!M8)uVdJnfAdVOSSQU}elck^kAyM>6k_LY=1SF9*vp;Ir!hdOV
zm17V1nlzVZrJZavC*85mP7OY=!uz*bwAu+i-zVu_xjY?aXf6*&u36>Lr!JZpJgCH{
zE=h;+&j0fD%q597Xj4TWKf$Dn=jSY&8s3qAbg4_x|GHvMt}jW(Z|-xd<4aQE
zzxqTX-h9oXH5_b7x43;tqI7+u|6q`Iz(9ZDlJwj9E#B{G@M?42l1!~`2k*b7XGlD~
z>N)}Pa52BiOF9B4GBH5nbI2oQ*ij`dRw|rz(rjjv73KxZJ==4{X&7B>s_pgZ90dO^
zNJ}=qd(GV(Q|LA1v#M4Yj<*JlG3Im_%t|^skXNkB#bRo1;J@n|V-$PC
zlH@kA4JY&_7F7Uu3n<4sbKBKZ5W6AmeXOc5>pqWsR$?`gwxF&__-I;|V62NgRPo6w
z%x75%7w58J)8^1Ia%1*0yT4_F6aA;KB>lGJ*jQr!lT|!#Gnfy32$soQy(IOvMUDqO
z8Y!!ETGHt2eH!Tpx%nN`r!PrniFk^%oy;Y
zivzIO12i*D=*Wz28oD?D*_qQ#!iy+zRuyiWY<25{cEx*3S=lhUacV5195ecrX=-o&
z#DQ6-)x`f?r_&T|Jh9H-y2J#1FI9ik8Sy5#+o2@@YdkDvamuU*nHjMhpenPnr=5wH
z|MHS_n0(ICtc>OVDNEAP7fs1x{Hi61TJ-G=va%~JT;XDVl^0RQYc^_4lOi(4OX@m3
z`T{QIb%a8)QgK~kFHu{Ph~G;R`Y&CU3{NS{>)Iu$q?4e&p0Om=WJK!`65&&~S>#dq
zV|0R>mn7QOr|~atjWN~}48g3D7kiIRqx>o3qf4^N9UUVHUY({5t2$1@=)&iwxVD--+A`RI10G9|
zCCiNT25KXH_z@l0E@9{(Tb4z&nHC|>PLe%++{R`<%kXH@?l+5g~M@*4kP*!EevoR!i^g3b{D0?LA}(;`lQnx(^?_bLhrJ#asn}yR+|*k+U%%e
zJQC2ylIg21&JOU6hA`$5A(_kRrw-H%D@
zw^bCn4>K%uA7@zTUSU}1Vk-pK5_4B;bV=ye3=7@o8Wy@QHZ1vXGc0r)hJ|j&u+SYD
z7P_BgSm=I+VWE4Kuw7|M@t?2JWuf~OhK24o85X+VV_4|^m|-dJ=M4+p-!Lq6|G==&
z{Y%3__n!<4-ICIk;{HRUD?<02VWIoLbV#)-LiZ7dh3=CK3*Dz1mh#sO3*BcK7P>Dm
zEOcLLSm-`S=}PujYjjoUwhRm1EyF_hv|*w9$%cjQ=NJ~cUu;?8K{*qy#`&)*E?t2Ui-M=v`bpP3~(EX5Mp?g1_ifUs2A%=zSV+;#j
ztbTAUiGH$1*M;sg4GZ0hVWE4SVWE4YVWE4gVWImv!$Nn{u+Y83u<-dr!$KDi<8ZBr
zC$3M^=t$_k&9KmYhhd@n4Tgp8cNrGCKVn$u{;Xl4`)h`U?(Z2Ex_@psI*x`(8)v1p
zhK1xGHI^2VHLNrnkU^Kf2ZLZ;~zF$XMC68i1Du~E*@vPSg+w)Lg#k{0dn#`Gc56cZ&>L5
zt6`!0VZ%cA{yJ3^h3+MWh3?}G3*9RX3*BRiOG3Ax<%{{cMGM{M85X)PF)Zm{X;|pK
z-muVpgJGdNHY{}CY*^@ireUG`1&Yf;7mu58Eiw0%7Ai4|0szUdS#S7g}F)VaH*Rat2
z62nsd*BTbO-)>mw{-9x@`_qbRLidHjmew7v&0n@?q5Ip0h3=mi7P^0HSm^$XVWIm!
zhK25hIy2S8-a`!w-Nzahx=&GD7rHN3x{~~;MGM`kVWArv7P>DoEOcLCSm=I&VWE4%
zu+SYE7W;29EOb9zaU^sD8xq5JEGh3@Yg
z7P^07IO28UpA^%|Qp8g8AG3h9B9@v>6|g1=&dm}OIlV6BKaya@u=lKh`22#}M1
zn&A@Tw;3)oeuv=-<8LrrW&B-+Ym9%yaGmkb8jg5ir*vuQfSO;^*y3@yp-J1+c{OyK?Zqu;PJ!x3zP7Dj(I}Hon&oV4@zff^m=zf)9q5DjwE9Sq&qJ{4F
z85X)fVOY}tf?=WiZo@+Nj|>al_Zk+u?>8)T|4VU2=w6^xRYmAll&+Y2kVOmKM;R8n
zmm3zkR~eT4CBs7ZIfjMq#~Bv7FEcE3U#qw(bom!7h$5{sJn*hlx>9`Kk_+9lhK25@
z8Wy^5H7s<$)UcHIb%urRcNiABKV(?w{*2<9(ESy|Lia|cE5-eeMGM_OH7s=h&alw^
zpkbl=zlMeG$LKs&lky*CSm-{^u+Y6iab4(MZCL2us&plL&7y_wa}5jK7aJD3w;2|?
z4Z}iLPq12bp*ym8vG+-ah3;o4j)d;#8y32+Q@WD;D=b>*ev@IL`#pw*?vEK3x<7AN
z=>CRbq5B7hh3;P(j(AylpJG~PN?2$9a~8110eVVEk2J*+rs@-_VC^{Lq2V>gZ!%mg
zp)ef$!?h&((**%?^0yleLg=>l0e-s4B#r?Zsq5BcTLiZw_yNW{hQo};`iH3#l(+o#E521$PT5OHaJQ)f3
zYc+LA$X{<*$UlYiA~$Eh*`fu%%CL}MH!S4!w6|3f@~13b$WILm`Oh{ib^S$(%R=|7
z4GZ0GH7s$x(2bR@
zn0uo|3*EOE7P_BjSm=J4VafmXhK25T8Wy@gY*^^trMM<^f7P(i{awRC_eDxqivKf<
z7P^0LSm^$%VWInB!$SA|I+4|+yh{uV-Nzdix>qW$3*BReg>K!j(8b$Pa4jkBc@`~n
zUt(D3zS6MJeZ66!`v${8mwzLHiZ1F<(GZjZd_X`XQ-LEt(bU%T~5?#su%@!?m
zzt^zP{c*!W_nn4??z;>N-9I!ebpOh5#H+~r71KIX#yay~vw*cSCcTalTszKqWLV-q
z$#9Y5Kf`dTjI!`n7+gz|f4(3DrEOf6>J|(92YKxZIZ5bB2TZV=1X~RPIlMM^q&oLbFEQuZh*J5j5tg&Sw|22k%
z{I?kv@;_i$$X_jmQE6P-h3*}Oh3+RB
z7P`+>x?=v*EL!Nk&9KmYhha(o4Tgp8cNrGCKVn$u{;cAf(ET;TLihI!3*A3AEOcM2
zbj931ShUdnH^V~r{|pP=2k5+2ll%`iEOeh>Sm-`gab4(MV_4`$hK26)4GZ1dl&%zi
zlSK>N+YJldreUFb(y-8-7?$$xG%R#KOK~K0ztFJI{VKyk_gf4L-GOg@ewGvmL%8DNf2an7wc426uOTzEOak3EOd_;7P>{nC87Il!$S84!(#sy
z!$S8phK25x!j_(2m|okWg>KKV&^=>V=zfY}q5HXph3=OqE(_hSH7s<$-LTO8LBqo5
zrwt3;V@kKobbr~Rh3;<~7P^08Sm^$(VWImkh9jQT@d+Qe7MK1%8e0+a7wXJZ5%Lc;
zEaV?+Sja!cu#m4y;Z!;of7GG{R}BmK*szd)kzpbK3d2&@pP;xZbWa!-x}eqz0#H9
zf6}6b?k^e^y1!{y=>D-`q5Ic{BVI-RSurguRV*tXngy&?v8lXAKM8Pcbf0Tj=)Tyn(7nxY#B0z4v^XxUp|MpV-!UxYM}~#`Cm9yC*pq1#ltV(ynL
zTIl|kVWImT!$S9O3`e|*{EK2*R%%#Q{%01jR>QK=RfB8C8J{vN@l(S^j{j`KCB|Q5
zxXk#g4ObX{tKn)5wl6iz(ceFVjDON_o$)Ukju`)@;-b*~W5YuCuMG>`4;U7@|7}?4
zuIkKG6uJ*KEOeh}>Dm0FExN|IU|8rr!?4gTD=rD$k2NfGUuan9zTB|T{dmJdcf+vI
z9T*n6PgA;*{f!nabl+lF=zgAIq5EZu%R=|-4GZ1xG%R#~*s$2U%dpV>Rl`E}cMS{O
z<4RYO|CvP#-M=?1bpO?`(EYIDiqO5k&P5fWdx>G8`*_1b_e#TJ|CnK+TQ@9puT{Dd
z{XB~nx-T&-bYE#$=)PWYRp`FKu+SYF7P@aXEObB9u+aSi!@}n)4GZ1tm2Q>$>Ni`o
z(EVP+LifiFN4$Vuq}e(5J2kc@oVyX%Go6S0#
zzu2OM{38tu`O6GTT^~_g7rOkh2r8166)x}D7B6&fFf4R$F)VanV_4|64GY~@DP5t{
zvuL4v#<0YHieaJqxr!s9`z3~j?$;U?y5DYC=>DK#q5IQ@h3+pK7P{+7SIqymMGM_O
zF)Vcd)^NnD$OjeEvQo#g^1riywK|rSEj6%qobhSH6937DiyZ$shD(gU*l?Ng*BGua
z{x-u^#y?=VRtJ|y80P4onnA|DWH@5{TZ)T9_dSM%?%x;|y8mog=zhqs(7m6|Ohuvl
z5W_<89&po(5)yg3Ek@q3*8$H3*B1{3*FZl7P^~;h3*}Oh3+RB
z7P?PYx>DSyS+vl7n_;2*4#j1m`wfPL?spj$x<6uA%KurzLig7U3*Fx{EOh_eu+Uvo
zx|01LEL!OPn_;2*e~K$Y_W?RpRfO)t4GY~T7#6xuH7xe7F)VZ=!$SA@hK264l&&Pd
z$)bhs?S_SJQ*l-3o-{0UCx(UYorZ<(XBif{UuanDf0bdO`z?lr?hBNzM1P+}3*DbE
zEOdWCaZTvH+py66Bf~=Xy@rME`wa`-|1vCeFVLx|CVU=bSm?e~>DG8?J<6hm?&XFf
zUj84b$vO2^8e13g{Ba{nLCXrqKgZ&Q{Kpv<@-H(ix8$R~z{eBZE;f3>k;b7w7D
z$bYI~sq42Yj)d-)8Wy@=XISWdhhd@nLxzR!&lncEzhYSE{*Gax+fuqh=T9wK=>DDI
zh*yz+RZPoDgk|N!vw*b-%SulRTszMAjA4oY6vIW1|6Ic*#$RH%%=l{!R~Uc0;VR=F
zG+bl+(}wF2xINM^NB{B+GX8DFMWOpAhK26m8Wy_$Vp!<@k71#Eq0UT2q5Dw7Lie$T
zh3-=f3*F1CJf?Tlq9evt#U-H|8y31RGAwjoVOZ#Xf?=V1!m!XC8Wy^5GAwjI-LTL-
zqI9MBw_CK({c^=+q5F-7h3!bSjzi3!$SAh4GZ1hH!O7j!m!Z&N5ew5sC1>c
zf469%`w_(zp?i@|Tos{vsbQh}M8iV&X@;fzLhK_p=QP-7hjM_P*M%(EV1!LihU(3*8%(t|b3Six#@SsJJF{
zf77tg{bR#I_pc2L-47TRy8mrh=&tHiR1^CTHY{`>ZCL2uqI4y?V9`SN8H(#dmp@QM
zS!f~Qq4=>DFLYmMSm?gou+aT@!$Nn%u+SYC7Cvt@EOcL^bn9&XEfyW|ZslT)=Gf2E
z*ht8KnPDOS^@fG~cN!M*KWtdY-(^_H|Eggj|GS2T{GS;X@@-?s^#0zWqX^44#$7u0
z^eNNAu*$%NX=1|gn&%R_hl}}j_5uvA>GbOj5EeQuTgy?59`+panm?v{c*U9?VWEo&
zz-zh{6htoO*Vzl=ag=?@$}N^QCJe86cWi_5Kwr$)*$c`+9phL}%Z%wBUU42a4noKM
ziSw;gTgn?6;1%CnbfB(o&8y8<_rG7EUQy4XL!IZ@ud;09*{`O=IVK;vymd^e@HjL;
z_posgI`SMJQ{o(x58XlO$a8#5NkANmp!>XatgL3+*za+k=NMe3BeyjwqptGoyZ5Ay
zJo_%bTf*zRdA9jM%)41S@@(^i(6P)$ZsVM0C$G)155^lln7iQO)GG!zxR_sOFTn7c
zj&AuLVWC4j9wKtuU&Sb!UvQH;*vNAoT$yIu$gMq=*vM^8%&?K?IA2y*`>i9-alYR=
zbezO1^A6#whDe4+2a#i`f4S^T!A)AR@r9S+eSN*;C|mR?aN
zFg}%vE7IWlsPu|{0gQf!D^9j?y*#}Z((9Ac>r>L}73uZL^!n8F`n2?lBWILzReHr+
zhrl8CIKX?=j&fJY@AtTW2-(0Jhx@5R=a#TeVzR3?^@_02#RT9repz2cUFlwhZaH5}
zSIw(egoQ390I%^OM+!EFc-X+G(zG!lc*S}}Smwl^f4w^BwAcm3$s^
zLxD?NtY6s3Yi?L(<9u^NR^FKS!SAFmqOO+B4F!sai}`i-0t~M<&NptC%?$-A2QKE<
z*$XiIPHn6PX^{_J^A_kHF6P(S3oyK<+soXLRR_DZA-$JKkpnOFk5BIr9;aS;>lNXl
ziwVGMd=^KJCC_2Fszdxi>Bwcix`%b3u5Mp>xz%e-
z7+&jHuLuiWOaNZv^%h~FL;QouKYDf5(#C}0HLqR~7P^=KyvFM-!a|4m9NV1D#i0SZ
zMt!_-4}_6NcA1&?~}17ZZTjc)dkf=t3O`e=m8>6VpAs
z;;en(_H-+&l!;u-ud^4mmB%@?maSK{lBbQ8l>!&**+Booo?Gh%Y2X#_xUSA)1u{r3=GWPa*4480aGEBMXW94;
z7~XWyISyRRud^4map{sapQg#Mm%&)T-VECQd!1H^^
zBg{Lca^PZqoxK3VtBqyP)~l2b=Mb6}`QR0Ywkg
zvw^z0wQ#BRBcy@fb-!A&pFo@E^ck8}DvJ5xs!rQu+4J=-+I*>vJf0hfccu+)52{P8
z|CkcPE50}BSa$w&Z_#nS^S~YJzyd)o=GWPa_T5~@$9Zhz(s!5G$gS@#v60*QvBXAh
zZJrL>tTw-WCHwqo$NlO&HkO^+=s4fG=DkG+_UdO>6miKF~81UU?abMyKF{18@a8u#CMf^mW^EYKIgHKPv4!A6<>$$8TLNT
zvu&WwR|>hzyGz?6pSsF!bFC|#pJI{)uXq>p)jg^s$LHEi9f#w5zHd-B$*ik`n6L8d
zy9G#b=fwL;o995j`7uwln5T
z&qkhe)G`}+tr=N1^6X1lHu9W5vTUHuACb#>t2}hvm)o4jM?Pnv{D78KSNEpATcG}v
zr6aF>^fDWH)m4^_Jlj0WMxNt5%SN8_RlJhj=6SV8mW@1hOqb}$tM4wekyl+!X@h|J
z3LW+g2US;j>A)r_4n@#CbmX~4t*rVs@*IOx;vAC?9dzWm_nA_8G5OG`jl9}pN`=Rv
z0lJ5cdt1Mn66ctF=TlREMogLpGw-`jkN
zjojKi!$z)co?#=;^&_i2a&7bD2hiqAY~@ZH@F(<=a
zBA12n8&f#ErqfTkA}n+|x1&AwYC~sxb8gSFk>?tfW#b@p=o+PM98>+lE8b0;
z7t*|RF{ka3SDP28{M4W#fF~gSra4IKAox7#?jZPG-+oVS3fM
zWZC~k?$k!s{0ti?13qh?n3s)Z{};Ja8~QX96W`FOjy(P^GK&jrSb;b9IJ|^Uc+lq-__cSG@qk>)4~)m6bwvo9FbLy3|Hamo?6(f|tDOG7;vUt3
zex=_VgFX_cUi-_kenm;y#pzWq$Sa=(TPff^?{e>=BJ(cynJMSI%e|7yrSI;|oiD!6
z_p%FPuG>88SJvDDmrEvg)gNL?4=!-wx-22`ig=kXbNPQwHWP;i=zeD77UP37@XBvl
zzn-S)kY1I@JqPehzBXjE+*3C8vPMp+Hc%JB{hrmamo@VLCmlKNy*?_&$K74)ciQH8
zt@S$h$XHo1hj6@~aytEe07S&8*F4MSE2PzQbbEYds^gJR>JXpfKDB^iW4r_oHuf@?
z#FRhKamhWYV=rTHn(Ba_m6Za{4>afzJHqs{$7s^Xu#dHt_$Nk6iNohRgcZl=7mC
znEbMH3%uWm9Us!d#i>`EhmG(%X_OP}?8F8oV;84ay&x~R@7wQkG3
z9M3U|jlMrN
z-2289hB&yLkAs-g%PZgVi`vldvI9mM_?_6GWbESfsu$#4Wst`ho^nYszs_D@1N}9h
zwR*}0$NV~bQ5$#nd|^sOp^TXP3Kk!7F~81U)W&}6*vp<{N_2Osln9baLl)}mptsDjI>k1#4shkJPCdybv_eq&b^ASu1ao!rNC)$$*KKyq>FHC(R<$26(qOZc2Shj6{02KRRb_b{Sq;^2N7+}{=4
zLxiOd?x(^1UBNwmSl-eH_tW4WKXb2X@VjDh>d7p-bNH|xxj6L}e(v0LUO0a1%nOGN
zH=OLd;pe2Jm?Gh4cJCy#$VcPjG*0okb8@#Bo>jgOT1>y&@au@lB2TIY>vOO%XlRqYSSFp6PI02v3LOj>GR{tBZEC!n5jA`){0TP^{PO;aTmT6&~tJDYM4#
z9yZk8Y8rtscn!bg%1hV;!qmljL)WbPAj#dn1qbDYLV%T5O!x4Lbsa%i=wbr!nvNH>
zG>k6h5FhRXH6qpXjp2f$vd$@*5
z+i~Tm?U={Cma&&B`k3woP|hlO?RPQV!za**9+Q9!x+`1whU>7dtS9!rbymHw6di;UI
z^_V*U;d*>I^&;s@FoMvjbN)>uP-pN~Z&~4Kf`#-M
z>qpkQk{zDej>OJu-^}zG`>=DnD(CR|Sn8ZBEv^4N?zN1)T+tQr|BC9cHp@G#?lxu@;(M)fURXx7)zL!29F-sYBoA534Z_0Pf*!zTpLO7w7JW?_HleJa@;w>T>Eu
zi{t&=x!vv`veNH%eYrD(Pe%CRb3WJC;ki3k!{Kv&I+mq2cE9(~e0#ZKO~U%6Zk2BK
zzE(;Ju@;(MVJpA&INQeIdVKEOlq%bv!-M-3YG4hIgZpW4e^+pihbc=R+#jyThwCxM
zGhBGC852H#_~B>Cq~6ZqvJ$6$2laONoKJNnyVu2f%L)(sCuL_Br&nC^x(|xH+qdWg
z={DKM5p*G(re(eY>)CuU4Wr9KKU#JUFirEt>6I>X^#BZ;x|V9+j42FWaUM2wPoQP)
zmyNsoe=E~eFL;F<37SLrUg|2Q{8}~&MZLzj2^`}0QdcqM4{Z>fA}n->e+Kzg@|rfL
zdw9jm+B{50S>Q5lpgb)zrh9nByR;D_Z|G1?&N?z}W>IHdu{i7Ex2Wj9H@3Jplh`gagIa)ushmTXg6C0G2U7TL^g1iU8UB+6z
zZ3```J=L>{E5gFMLRg%7O~1?bSVjKOg*NgW6EPW4j;TGnm@+@2
zXl_<-vGI;67XI$(@NJ}X`>K|?HybPN`XTjwk@4O?8@YU67S*t?L+a|WIqC4*<5Dty
zA@$^Z+9TV>MY+wv**5M+8|!>@#P0aO|9WCmvfnl?%B?-Jb=;304)f7bSg0#JcS_$~
zP3Qa7m4{Opo)-4YCdxfV9@l(qT(Y1G<>FZZ`u5Yt(+|MLLDW^gZSJ>+%=LNoE8m9p
z<$T)wBDY`Vc_x6TvzYo&=)gU{@BKi@z73o^==lk3#;Mo3TecUXr0nAKsu$!%zWw-4
zD75XJAGyHhjhHa}Uis?Yor+p;aS(!gc(o_*q3rfEAM$+y7wq8s3gkPx7TZPbg9{!!
zw4Lld9SYX_X>fm6a1RlhCJyeW!Tnvq{mN>PKDeI-_jd*Nn0T%H;C>q1-xb^+eqRCV
zizQplSGU4
zpA}B?2$vVrE4;RgUUwRv)$Up0A%8N5qwu-vg%(@ip=fBCln}bPun1$2JO-iCoODvlo<8C7+88UhsVz
zmAq`EjS*9Mp@WWV$q&eD-NbYcuXx9Gl_o^q(4m}&d@gmh#6~$U8>_-TmW#bYM~OUi
zz~MJV!n%nG@3zj62QKE<*$c`6jT^{onK9kND-LZ_u(_D-rsR<>CSM>A7xU}v1@W)}
zuVuz`53hKqHn@;9FI~)`9Msuc=q4;Hrm)@GNQa5512$e1l!@};cWXmQB5&wW4*J(S
zY16M|#)RP&hx#bkTugUU@<qPI
zz^Ow#`Yyf$J1i?EyjvS#e#&l6ZM=j+w9J_9;T31)MVe`v4)>aY?}1Z?a?jb_c#KHYExW6m7hX_j_+)so1yMp`Op0%LvxxRIq5@doZ7H<%y{MwpJkEf{OJ!r&r8*W
zafpl4j=OF5-IXKnW9-JR&*x5NzgSLIc$Qx6FL7EIiuJlZJnKv)D?GD*X7&Hn@OHbI
z^H@gu%zDU3e~$FS#i>{Q>9nyBo>_h{eo-D=%&+p2*L_f=dkg+}ul#xBai7&by6%mV
z!RF|WZll%hB)xHCe0GpfuB8se;_WPT^mA@(3gL0;aSx|vVn(x<5Qq0jfxLLDMBj2k
zSe*Kulsl!g%0=4%`9p{JXOma%h>OWj$)7JT(!rmP3*yn+^({HN-4`3)+m}Sa_PQBt~Hjq}+(Jfa6!a|35^sBr3mGV={i!x&JYEAArfM0U8F_m&-x!CJL*~slD
zpHcyy4$N1m(LMLwDHo*OO8~v1t}xE=k+IOl0l+;x(!$9j#|8Y+<(ROBuOgCMOnypU
zarlU}a@TCTOG((p^a`)#V3~o7Q?KEAMfiQ?5es7)df-)G=GG+L`)p5tIpu|mQ*Y&@
zd$fh{U`v6xS$ZjHA$`Uphvr*MFENYZS>+3%#q_%kzitI?v7A|-L!9!?{F?m?y;6!(
zerA1U@!4(o;Ug9`+u;6V
z-NVKFDlhwS9~8f*Tg>Tr3$mAtyPe+2Msw2cj2okV>tr&9CiNH7{kS!7V(9#EWoxu~
zho;08yN&jGV=(NulJ;bnXdHG1XvubGs6n`*h(VIfg(2yB_he(E)3xkKi}2Qop&L#n
z$IbOwdZdL%_+&624jP?7E2M#>U8;laCb7?CDWg%C9Pi+^nuF$gr`s8yErwC66t&R~
zL$GfdG`H6(LKo{A&lww?js5}=)YEoy2G#)0+EvIPI7qMzLsg0T=BMfF~RJYmNoHRG*
zqVRBfG8y)gZex@TPjy-r3vQvLVSh4Cvh9wNJ0?kQmK}DXc64GZOYJD3F)<-Y?r@m3
zeV9LF)EQJaPCL0-Zp$4_*h6b&Z*+#q>1LONFW5&HLFhsk!AG9C8QPmew-ufg7&zHq
ze?!t5Hzvs`oO>*qNjuVdx4*6$QEiwE&<{svN8@C!_OKmR`e!zVUN-=B>IS3MiDau8
zmKx@5_l6!H?(CzMjVzP}GSp)o_V^k)BGcJWE4bpjRb?PLIZ)PCvPFY^O!X3GLr4pARyZckVe@py%Zpyq98sQ5bKL3oLMmyUw))-4
zmPMeYFnY8-7OipjpKm~(krtX2MU9fyWJnWTXczMw;?jOJ1lD-Y!zDH5!e38m&q$3h&rG1lBhR2u9S{eBS{Nc8MPAPebsC#X1kKP!3sk>9Zirzm0ax^KEkQ=WLVirQ
zliq+N(u61HlDw$RIKm1FIx!v(+6@|njaI)m=9A0qwg!{+Zl`7O!_%`;kQB4cHd|C2
zwdO1tj6L3r4U_g(rV@X>`-|e@W=on!+m>JOjI8gbch2b}-GDq|2dEwm_
zhNNAp{{DPYsKk0a3q?Zdwf)qhQUQ(OU~85Xc2I>>b)!C!_OzlCZfi5l&$B{MNqDCv
zJWJKL`GsL6SUICnX7t)V{n`eMm0!c
zK3neu3pQ+G>T4xfO;r!BNG?p~f|0n>qs7HSu}49<3tCVD@9t)45ybN^b`MqphBD8;
zyre4$tz0
zVoJ=qe
zB26ZH6-lyf)SzuglXky>V*+GT&k<)wRIdR$MB*85TMf|DCHVpVP%lUNegRjc2JD=N(@kR5qbdbpQfpX
zCKh|{L!9gb=$RyGY+=78y#yUb$>#zZOw&Zs%fpw1z|%*L33fK3xs)r
z^SRTM#krJH;)GS5u$mKAcfz8%Fs>PgUU9w>SVqy2R`l{Z(u$6>
zq9d*7NGm$hijK6RBh8^wa-@|UX(dNm$t%y1R&u139BCy-TFH@Ca-=zC%Z{|NBdzR6
zD?8H4o()G@*^yRuq?H|MWk;H0qT)!aIMOPPw2C9G;z+A_b{%OIM_R>^R&k^`#Z?_?
zRYzLYkydr2RUK(nM_Se6;z+AH(yER$Cu_}-R&%7)9BDO2TFsGGbEMTAX*G|ZBdz90
zbCT5^X>~_h-H}#zq}3g1bw^s=kydx4)jiIRwD};O=p#p3a->C$w8)Vb
zInp9WS~RbN_W*0*&OjIUxj;9;#{C?E9Qcv!1=rqHlJ0(N>1Lg6IOA;
zs!mwV39CC{(PCI}z7k|#;3RXT6&-0sM_SR5R&=Bl9ce{JTG5eKbflFWX%4-TBdz2}
zD|vYxX(dNm$&praq?H_LB}ZD>k>*e-JJQOIw6Y_u?3L$8D?8H4j~`MleO+ht2@%_ja->D`oOQ*Y`v8Zo?sI|ZHefC+O<0@@
zOqch$Fxdsn1*I##6IOD<%1&6t39C9`H7Bg@ghg{PU+mX^|r>
za->C$w8)VbInttee!2%JIY$9&USM%9P!{szPS|{ANjINY_61JXk`q>T!YWQ!)d{OP
zVRa`gS_~^X(u$6>q9biSNOl2^w4x)e=twI%(u$6>q9d(1FO9c1i(*TT*pef*G_Q=?
z(Ggbi!W^+BM{LOvTXMvf9I>T&v0U+GM_SpDR(7O0l*^8^vX{=0R(7P79cg7pTG^3S
zaimooX%$DBL$BgUt2ojsUS3C9#gSHVq*WYgRYzLYkydr2IaI2Sw5lVm>PV}4#FpInrv5G{(ZWNQ)e4ks~c~q(zRj$dMM!bJ0CO+1mrm1*Xfr6Sn4r
z6`iosT$pSD=7PjT*$JySVO1xr=7iOquxK%?c-#qdq!k@$MMs*K%#l`fq!k@$MMqlE
zkydo1l^kg$M_S3@Tymt%2XU*F9BCy-TFH@Ca-@|UX{C8-ypvgMv$7+$?1(KpV$1W&
zxQjZ%%3hcww(N*4J7UX@*z&wsuH=d%t>Q?lIMOPPG>3Ai#&5>4fq}3d0HLpBJ
zTFsGGccj%FX>~_h-I3;)tvk}{jd#)t#_tF|2sp
z33H?s9ce{JTG5f_Wp$(#9ce{JTG5eKbflFWX(dNm$>CRWq?H_LUNT2o$&praq?H_L
zB}ZD>kydu3l^tnihjZDHHXpPT}aR~>0pFP$T;>PV|P(rS*h
znj@{|NUJ&0YK}CAUd@qKbEMV0ypFV*BdzX8t2@%_jHjPFUFq
ztIUPT7GN$&s8pS>niE!c!lK2n;&CU;kydo16&-0sM_SR5<`w5iD>~AOjj26
zInqiFmy#o`SR5IMU{Wc&t?%X%$CW#gSH-m&QAp#gSHZ#8w@#RYz>q5nFY{
zR_B%R#Nr66dSQ;(sw1{KFP1B>=18kK(rS*hnj@{|NUJ&09LhCETFp!6NUJ&0>W;L!
zBdzX8t2@%_j;7CF))M_S}a^Qbt|B1c-}NQ>sn
z)8)SAE%$SQ>2mLctvO*uC#>Xzm7TDP6IPuIlP$npkkG3+VRa`gS_~^5cfuTLMMqlE
zkydo16&-0sN1A8Ckydo16&-0MM_S2|R&wl?9BCy-TFH@Ca-@01Inqjww2~vO>_{s+
z(#npsvcsk9NGm(i%8oQIt0S%KNGm(iDvq>@Bdy{{t2ojs4!?>ct>Q@Yk~z{UjPV|PoU4ws`5>M~s*bd(Bdt0wjdwDOlSs`GTXV$L9I-V=Y|Rl{
zbHvu>mGPYE2&;Kvj@a6~SgxqLBdzX8t2@%_j;7CF))N18{^krp}9qWQeK+}FM3el9Rw?wznTC#>j%m7K7$6IOA;s!mvK
zE=;xnb3sD6?u12)Va4N4m?N#|NGm$hijK6RBdzF2D>~9VvyQZ)Bdz2}D>>3ij*XHd
zt>j26Inqjww2~vuGvP=pInv6Gw6Y_u>_{s+cFT^mvLmhRNGm(iyy6^bWk*`Ykyde}
zRUBy*M_R?4fq}3d0HAh;_kydl0)f~<>N7{T4&yY1oT5Vn$?_?I|$GRi7?ue~BV(X6Bx+Avk
zh^;$f>+{NZIdFv4y|8()Tq%(wEpntqj;7CF))M_S}aiyUblWk*^xpH7$i$Xo8`
z0@LN*30rf*icVO`2`f8c6(_9fgw>p|`dpZ70p@~4Mzk1KJnn=!(u$6>q9d*7NGm$h
zijK6RBdzF2^Qbt|N{+OWBdz3=S8}A49BCy-TFH@Ca-@|UX`Wd}TG^3ScBGXZX=TSo
z*^yRuq?H|MWk*`sk>;6jq*WYg6-Qddkyde}RUEq&M_R>^R&k_N9BE#0jTsz#(yETMsw2(I>PV|O(rS*hnj@{|NUJ&0YL2v;!>{H@t2xrVWRA4D
zBdzX8t2@%_jWBH#|B1)
zBlHs$S6z(ATblGm`U!=Ot|hc~tRUZzSiqNqQ$X=p0pIRP-iH*VY_(`;WiZp_O(PsXQ{q*oaAC&LysGkrbwX$~8Q@!GL3Fu!wt54Z
zYqs04H9nD88RI7X6bOB-Kg-==G4%DZg-(zDs0(Z~=}U=^7RLQTe}LMiuYEmBG4utq
z<3h7Aq#tjgS+qdkbl*BDCJOZR@dN5Q8}uE*$Z)hU>d?=-6t>ER(=;I$k~0Hn-r`E4
z3g~Q5IW&Kf$r0VP`dgq|IC8~R*X~I7jXl=8F&WcW&IX(dqZ9o}w@nofEmp-+wgNb%tfND!^O1Cm9n{kv%v!xJ*xixfm!|EYz2yW|C)MBPPFwhTESmVW2vq%P
z>Sj_;XmoU(^oJg(WyTZ!HsQjlW_OYlnxn#KkhD4*9h%(N&vIJp#nktxFl6u3mlzK_
zG?7WJStC&S?VVD5!5JFft#M(!PrtT6@cuQflr3jl>f)s)y9MPSB4ij0!C(4T+Co8rVULzap)Uc0!ZL
zkiYJIr+PH2)K_5_EjHuDJ@$n%wsi0iZ&Efo(b*XHj`Sv5N4uTT_}H;yGBI;FHwuCa
z6kouvx}fh%#?D5MTKfp9^eBC~c+%iud~~DP9i{hD5DndSpK7ev9~1Xe=qH^KRexGc
zqmghAOd1X5)1GW?os|KORG>u{G?TOpXc{Rp
zmlivV3=EJiw|wft!y%2Aw0Nowty>bf)jUZft#_6xm>6s{JKeh>a5L#OsD_6}@WNAD
zfyT^6=giT<5WikBN~p58y7UF%@|!MQ`kAS&gwUe8nH(*=cB@4{UPCkEW`Ed8Mz7n+
zthlWbK!U$s|IGMEdOZr8qlB03Q}l~PM_n_sa+cq5Rnr*epIp%89^cSD;h*KXo0Ku*
z$h53Kc*FA;mxi%+t4)FWxZ&T1euj?LvUIdOcQU50av#&C{qC0VQkuYq
z)THzyek0n&Vlw9mQ3m#Trm|V3as_fTHOHePH_IT9$&F`6ZhhPx$0%+cx%uifsnqk$
zmW~Ou`htmBS0F1RwnB->ip18BzuIhi$3|p4cfn
zeI0!=!Zadfoak)QJTLnmuKO9SrFT{>JS?cNqc=&_2rg_4`&%j7fb49rr1f>=rn^~P
z&m)cWK!-6|z~D_clKhKZ(8xPRh$EuxmZ}LNsT$FNWmB2%zhsk|jA@14D9Epc6?ltG
zX0c0~?_BA6%g?Ck!rt%Ec97SEF!#2+1)6cx;MJJp&tiDc^2@ntYD#ETrY&Mvgb=uf
zCdz;VE!7x=&+T@Dhj;K~N-1%!7fsgd_pY8j=ZDE=>(|Xl!@piNSI#WK^~)kbCVgf|
z2}nj}dOiK9B7X31F-0Mr4sPY2Wn7F;qi^G19HXb6P2SVshbpasXyI;kC$!b#U#2@s
z`yl?U(4(|=BHdNHT{_S`!e^JXTLkrws^wmE%WTr&F0`49=?5if#lrI$oz)CG>-3|K
zx<^34+jGcFt$CKEq*2dLH7HwpCH0^VJ?!knjAlkB*}X~RqF?u+?G1?1EXW*Y9b>yF
z+TNU`lt<}-GQH)IK)t(3bD2~Rq-x`D&yN|7>roGIc2Y0ey=kFlYj?AD(q~7Ra!6`W
zY|x=Y#*}EQPrnq%Rwq5V=N>@6{zA`IO5SPx3D4u{LCj7n1TC@COli7sVC|^V0~>qE
zOHaZya`qyMr;f0uag<16?}*Ey*tI
zZ1vYzg8dvkMgI#{Bx)bTzH3()m;z^upCY9YNM(cGc(5ey&QWz8o>
z+WGU|BRWegWn=PWxG{+a^VQZsU45bXMv%GLrk&~9$wDpU?c7c
zBV`Lu7Pn^$wld3ZxqRNHH@jI4a~nNi9>LPUQ`c$w`P78o7~oS8>UPo>xW1(ZW&tlw
z&dwg6vGPk4QN5yYJDr{M`5Bp>cvvYcILpxPI80(C2
z^40B}OuE(X2}680j$zJ?`pR3;$lGp7~%xkSFB1dAdLcBG?X`q^G?u_RU8M$Btby
zZjMflu1SCX^B7{W!zO;pljrR8A#GH6%2u;CA^K;Z!sGvn!i^h+v;7H;GX&D(G0pfy
zQZqNxO%s0V^bBpk@zY^)ga@e`ef&%m4NfA1iEEh9?`!d6zUG%7p`yJ!^DclaH{mtM
zo^eh3Q;yfr&mgz^!)pfJ$tGpfVE(n!6a5iZ%xmapsE$yUtF9{)7JpkU{cS=1{jTxF
G7ym!(tO+Rq
literal 0
HcmV?d00001
diff --git a/selinux/redmine_git.sh b/selinux/redmine_git.sh
new file mode 100755
index 000000000..0a6beaee5
--- /dev/null
+++ b/selinux/redmine_git.sh
@@ -0,0 +1,44 @@
+#!/bin/sh -e
+
+DIRNAME=`dirname $0`
+cd $DIRNAME
+USAGE="$0 [ --update ]"
+if [ `id -u` != 0 ]; then
+echo 'You must be root to run this script'
+exit 1
+fi
+
+if [ $# -eq 1 ]; then
+ if [ "$1" = "--update" ] ; then
+ time=`ls -l --time-style="+%x %X" redmine_git.te | awk '{ printf "%s %s", $6, $7 }'`
+ rules=`ausearch --start $time -m avc --raw -se redmine_git`
+ if [ x"$rules" != "x" ] ; then
+ echo "Found avc's to update policy with"
+ echo -e "$rules" | audit2allow -R
+ echo "Do you want these changes added to policy [y/n]?"
+ read ANS
+ if [ "$ANS" = "y" -o "$ANS" = "Y" ] ; then
+ echo "Updating policy"
+ echo -e "$rules" | audit2allow -R >> redmine_git.te
+ # Fall though and rebuild policy
+ else
+ exit 0
+ fi
+ else
+ echo "No new avcs found"
+ exit 0
+ fi
+ else
+ echo -e $USAGE
+ exit 1
+ fi
+elif [ $# -ge 2 ] ; then
+ echo -e $USAGE
+ exit 1
+fi
+
+echo "Building and Loading Policy"
+set -x
+make -f /usr/share/selinux/devel/Makefile
+/usr/sbin/semodule -i redmine_git.pp
+
diff --git a/selinux/redmine_git.te b/selinux/redmine_git.te
new file mode 100644
index 000000000..54b0a7aa1
--- /dev/null
+++ b/selinux/redmine_git.te
@@ -0,0 +1,69 @@
+policy_module(redmine_git,1.0.0)
+
+########################################
+#
+# Declarations
+#
+require {
+ type httpd_t, httpd_sys_script_t, httpd_sys_script_exec_t;
+ type sudo_db_t;
+ type httpd_redmine_git_script_t;
+ class process { setrlimit setfscreate };
+ class netlink_route_socket { write getattr read bind create nlmsg_read };
+ class capability { setuid sys_resource setgid };
+ class dir { getattr search write};
+}
+
+apache_content_template(redmine_git)
+
+permissive httpd_redmine_git_script_t;
+
+########################################
+#
+# httpd_redmine_git_script local policy
+#
+########################################
+
+manage_dirs_pattern(httpd_redmine_git_script_t, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t)
+manage_files_pattern(httpd_redmine_git_script_t, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t)
+
+domain_use_interactive_fds(httpd_redmine_git_script_t)
+
+files_read_etc_files(httpd_redmine_git_script_t)
+
+miscfiles_read_localization(httpd_redmine_git_script_t)
+
+# Allow our scripts to be called by redmine/apache
+httpd_redmine_git_script_domtrans(httpd_sys_script_t)
+
+# Allow us to access to rest of redmine site
+miscfiles_read_public_files(httpd_redmine_git_script_t)
+miscfiles_manage_public_files(httpd_redmine_git_script_t)
+
+#============= httpd_redmine_git_script_t ==============
+#Specific capabilities identified by audit2allow
+
+allow httpd_redmine_git_script_t self:capability audit_write;
+allow httpd_redmine_git_script_t self:capability { setuid sys_resource setgid };
+allow httpd_redmine_git_script_t self:key write;
+allow httpd_redmine_git_script_t self:netlink_audit_socket { write nlmsg_relay create read };
+allow httpd_redmine_git_script_t self:netlink_route_socket { write getattr read bind create nlmsg_read };
+allow httpd_redmine_git_script_t self:process setrlimit;
+allow httpd_redmine_git_script_t sudo_db_t:dir { getattr search };
+
+gitosis_read_lib_files(httpd_redmine_git_script_t)
+gitosis_manage_lib_files(httpd_redmine_git_script_t)
+
+httpd_rw_stream_sockets(httpd_redmine_git_script_t)
+kernel_read_kernel_sysctls(httpd_redmine_git_script_t)
+logging_send_syslog_msg(httpd_redmine_git_script_t)
+
+# These seem to be needed for ssh.... Not sure why ssh needs
+# to read and/or validate contexts...
+allow httpd_redmine_git_script_t self:process setfscreate;
+miscfiles_manage_cert_dirs(httpd_redmine_git_script_t)
+miscfiles_manage_cert_files(httpd_redmine_git_script_t)
+selinux_load_policy(httpd_redmine_git_script_t)
+selinux_validate_context(httpd_redmine_git_script_t)
+seutil_read_file_contexts(httpd_redmine_git_script_t)
+seutil_search_default_contexts(httpd_redmine_git_script_t)
diff --git a/tasks/selinux.rake b/tasks/selinux.rake
new file mode 100644
index 000000000..92e1e4c7c
--- /dev/null
+++ b/tasks/selinux.rake
@@ -0,0 +1,273 @@
+################################################################################
+# Rakefile for selinux installation for Redmine+Redmine_Git_Hosting Plugin #
+# #
+# This rakefile provides a variety of options for configuring the selinux #
+# context for Redmine + Redmine_Git_Hosting Plugin. In addition to the usual #
+# environment variables (such as RAIL_ENV), this rakefile has one additional #
+# variable, ROOT_PATTERN. ROOT_PATTERN holds an optional regular expression #
+# (not globbed filename) which describes the possible root locations for #
+# redmine installations; note that such patterns must be quoted to avoid #
+# attempts by the shell to expand them. If undefined, the rakefile will use #
+# the Rails.root for the local installation. #
+# #
+# TOP-LEVEL TARGETS: #
+# #
+# These commands should be executed after altering the init.rb file as #
+# described in the README.mkb file. Each target type comes in both "install" #
+# and "remove" versions. In the following, the environment variables are #
+# optional (of course). Default for ROOT_PATTERN is Rails.root #
+# #
+# 1) Build bin directory with customized scripts for redmine_git_hosting, #
+# install new selinux policy, and install complete selinux context for #
+# redmine+redmine_git_hosting plugin #
+# #
+# rake selinux:install RAILS_ENV=xxx ROOT_PATTERN="yyy" #
+# rake selinux:remove RAILS_ENV=xxx ROOT_PATTERN="yyy" #
+# #
+# 2) Build bin directory with customized scripts for redmine_git_hosting, #
+# install new selinux policy, and install selinux context for #
+# redmine_git_hosting plugin (not for complete redmine installation). This #
+# option assumes that the redmine installation (and plugin) code are #
+# already labeled as "public_content_rw_t" except for dispatch.* files #
+# which should be labeled as "httpd_sys_script_exec_t". #
+# #
+# rake selinux:redmine_git_hosting:install RAILS_ENV=xxx ROOT_PATTERN="yyy" #
+# rake selinux:redmine_git_hosting:remove RAILS_ENV=xxx ROOT_PATTERN="yyy" #
+# #
+# 3) Build bin directory with customized scripts for redmine_git_hosting and #
+# install new selinux policy. Do not install file contexts of any sort. #
+# Proper labeling (done in some other way) should have all of redmine #
+# (including plugins) labeled as "public_content_rw_t", with the exception #
+# of public/dispatch.* (which should be labeled "httpd_sys_script_exec_t") #
+# and vendor/plugins/redmine_git_hosting/bin(/.*) which should be labeled #
+# with the new label "httpd_redmine_git_script_exec_t". #
+# #
+# rake selinux:redmine_git_hosting:install_scripts_and_policy RAILS_ENV=xxx ROOT_PATTERN="yyy"
+# rake selinux:redmine_git_hosting:remove_scripts_and_policy RAILS_ENV=xxx ROOT_PATTERN="yyy"
+# #
+################################################################################
+
+namespace :selinux do
+ desc "Configure selinux for Redmine and Redmine_Git_Hosting plugin"
+ task :install => [:environment,:install_contexts,"selinux:redmine_git_hosting:install"] do
+ end
+
+ desc "Unconfigure selinux for Redmine and Redmine_Git_Hosting plugin"
+ task :remove => [:environment,"selinux:redmine_git_hosting:remove",:remove_contexts] do
+ end
+
+ desc "Install selinux file contexts for redmine (without plugins)"
+ task :install_contexts => [:environment] do
+ roots = redmine_roots
+ root_pattern = redmine_root_pattern
+ puts "[Installing file contexts for redmine:"
+
+ sh "semanage fcontext -a -t public_content_rw_t \"#{root_pattern}(/.*)?\""
+ sh "semanage fcontext -a -t httpd_sys_script_exec_t \"#{root_pattern}/public/dispatch.*\""
+
+ roots.each do |path|
+ puts "Setting new context for redmine root instance at #{path}."
+ sh "restorecon -R -p #{path}"
+ end
+ puts "DONE.]"
+ end
+
+ desc "Remove selinux file contexts for redmine (without plugins)"
+ task :remove_contexts => [:environment] do
+ roots = redmine_roots
+ root_pattern = redmine_root_pattern
+ puts "[Removing file contexts for redmine (ignoring errors):"
+
+ sh "semanage fcontext -d \"#{root_pattern}(/.*)?\""
+ sh "semanage fcontext -d \"#{root_pattern}/public/dispatch.*\""
+
+ roots.each do |path|
+ puts "Setting new context for redmine root instance at #{path}."
+ sh "restorecon -R -p #{path}"
+ end
+ puts "DONE.]"
+ end
+
+ namespace :redmine_git_hosting do
+ desc "Install scripts, policy, and file context for redmine_git_hosting plugin."
+ task :install => [:environment,:install_scripts,:install_policy,:install_contexts] do
+ end
+
+ desc "Remove scripts, policy, and file context for redmine_git_hosting plugin."
+ task :remove => [:environment,:remove_contexts,:remove_policy,:remove_scripts] do
+ end
+
+ desc "Install scripts and policy for redmine_git_hosting plugin."
+ task :install_scripts_and_policy => [:environment,:install_scripts,:install_policy] do
+ end
+
+ desc "Remove scripts and policy for redmine_git_hosting plugin."
+ task :remove_scripts_and_policy => [:environment,:remove_policy,:remove_scripts] do
+ end
+
+ desc "Generate and install redmine_git_hosting shell scripts."
+ task :install_scripts => [:environment] do
+ puts "[Generating and installing redmine_git_hosting shell scripts:"
+
+ plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting")
+ plugin_roots.each do |path|
+ if path != "#{Rails.root}/vendor/plugins/redmine_git_hosting"
+ # Have to call another rails environment. Keep default root in that environment
+ chdir File.expand_path("#{path}/../../..") do
+ print %x[rake selinux:redmine_git_hosting:install_scripts_helper]
+ end
+ else
+ Rake::Task["selinux:redmine_git_hosting:install_scripts_helper"].invoke
+ end
+ end
+ puts "DONE.]"
+ end
+
+ desc "Helper function for generating and installing redmine_git_hosting shell scripts."
+ task :install_scripts_helper => [:environment] do
+ web_program = ENV['HTTPD'] || 'httpd'
+ web_user = ENV['WEB_USER'] || %x[ps aux | grep #{web_program} | sed "s/ .*$//" | sort -u | grep -v `whoami`].split("\n")[0]
+ GitHosting.web_user = web_user
+
+ # Helper only executed in local environment
+ path = "#{Rails.root}/vendor/plugins/redmine_git_hosting"
+ print "Clearing out #{path}/bin directory..."
+ %x[rm -rf "#{path}/bin"]
+ puts "Success!"
+ print "Writing customized scripts to #{path}/bin directory..."
+ GitHosting.update_git_exec
+ puts "Success!"
+ end
+
+ desc "Remove redmine_git_hosting shell scripts."
+ task :remove_scripts => [:environment] do
+ puts "[Deleting redmine_git_hosting shell scripts:"
+ plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting")
+ plugin_roots.each do |path|
+ sh "rm -rf #{path}/bin"
+ puts "Success!"
+ end
+ puts "DONE.]"
+ end
+
+ desc "Install selinux tags and policy for redmine_git_hosting."
+ task :install_policy => [:environment] do
+ puts "[Installing selinux tags and policy for redmine_git_hosting:"
+ plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting")
+ sh "#{plugin_roots[0]}/selinux/redmine_git.sh"
+ puts "DONE.]"
+ end
+
+ desc "Remove selinux tags and policy for redmine_git_hosting."
+ task :remove_policy => [:environment] do
+ puts "[Deleting selinux tags and policy for redmine_git_hosting."
+ sh "semodule -r redmine_git | true"
+ puts "DONE.]"
+ end
+
+ desc "Install file contexts for redmine_git_hosting plugin."
+ task :install_contexts => [:environment] do
+ plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting")
+ plugin_root_pattern = redmine_root_pattern("vendor/plugins/redmine_git_hosting")
+ puts "[Installing file context for redmine_git_hosting plugin:"
+ sh "semanage fcontext -a -t httpd_redmine_git_script_exec_t \"#{plugin_root_pattern}/bin(/.*)?\" | true"
+
+ plugin_roots.each do |path|
+ puts "Setting new context for plugin instance at #{path}."
+ sh "restorecon -R -p #{path}"
+ end
+ puts "DONE.]"
+ end
+
+ desc "Remove file contexts for redmine_git_hosting plugin."
+ task :remove_contexts => [:environment] do
+ plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting")
+ plugin_root_pattern = redmine_root_pattern("vendor/plugins/redmine_git_hosting")
+ puts "[Deleting file context for redmine_git_hosting plugin (ignoring errors)."
+ sh "semanage fcontext -d \"#{plugin_root_pattern}/bin(/.*)?\" | true"
+ plugin_roots.each do |path|
+ puts "Setting new context for plugin instance at #{path}."
+ sh "restorecon -R -p #{path}"
+ end
+ puts "DONE.]"
+ end
+ end
+end
+
+#############################################################################
+# #
+# Path support logic #
+# #
+#############################################################################
+@@redmine_roots = {}
+@@redmine_root_pattern = ENV['ROOT_PATTERN'] || Rails.root
+@@find_maxdepth = 6
+
+# Turn a regex file descriptor (file context) into a
+# conservative (enclosing) globbed expression
+#
+# Grabbed this from /usr/sbin/fixfiles...
+def regex_to_glob(in_regex)
+ # clobber anything after space char
+ my_regex = in_regex.gsub(/\s.*/,"")
+ my_regex = my_regex.gsub(%r|\(([/\w]+)\)\?|,"{\1,}")
+ my_regex = my_regex.gsub(%r|([/\w])\?|,"{\1,}")
+ my_regex = my_regex.gsub(%r|\?.*|,"*")
+ my_regex = my_regex.gsub(%r|\(.*|,"*")
+ my_regex = my_regex.gsub(%r|\[.*|,"*")
+ my_regex = my_regex.gsub(%r|\.\*.*|,"*")
+ my_regex = my_regex.gsub(%r|\.\+.*|,"*")
+
+ return my_regex
+end
+
+# Return a pattern for the root a redmine installation (as defined
+# either by Rails.root or ROOT_PATTERN.
+#
+# Optional arguments are joined together with "/" as a path and
+# appended to the end of the root pattern as described above
+def redmine_root_pattern(*optionpath)
+ if optionpath.length > 0
+ pathend = optionpath.join("/")
+ "#{@@redmine_root_pattern}/#{pathend}"
+ else
+ "#{@@redmine_root_pattern}"
+ end
+end
+
+# Return an array of pointers to redmine directories as defined by
+# the redmine_root_pattern (see above).
+#
+# When optional arguments are included, they are joined together with
+# "/" and appended to the end of each redmine root path.
+#
+# Note that we use the @@redmine_roots hash to cache our results and
+# thus avoid repeating work.
+def redmine_roots(*optionpath)
+ if @@redmine_roots["/"].nil?
+ glob_pattern = regex_to_glob(redmine_root_pattern)
+ search_command = "find #{glob_pattern} -maxdepth #{@@find_maxdepth} -type d -regextype posix-extended -regex #{@@redmine_root_pattern} -prune"
+ if glob_pattern =~ /.*[\(\[\*\+\?].*/
+ puts "Searching for directories matching \"#{@@redmine_root_pattern}\" (may take a bit):"
+ puts "#{search_command}"
+ end
+ new_roots=%x[#{search_command}].split("\n")
+ if new_roots.length == 0
+ fail "Error: ROOT_PATTERN does not match any directories!"
+ end
+ @@redmine_roots["/"] = new_roots
+ end
+ if optionpath.length > 0
+ pathend = optionpath.join("/")
+ if @@redmine_roots[pathend].nil?
+ subpaths = @@redmine_roots["/"].map{|myroot|"#{myroot}/#{pathend}"}.select{|dir|File.directory?(dir)}
+ if subpaths.length == 0
+ fail "Error: ROOT_PATTERN/#{pathend} does not match any directories!"
+ end
+ @@redmine_roots[pathend] = subpaths
+ end
+ @@redmine_roots[pathend]
+ else
+ @@redmine_roots["/"]
+ end
+end
From 0f336104ea654ad3d97a940e65ec4dbf7dd2ae0d Mon Sep 17 00:00:00 2001
From: John Kubiatowicz
Date: Tue, 3 Jan 2012 21:05:53 -0800
Subject: [PATCH 2/2] Update of selinux policy for latest version of selinux
package. Also change rakefile to install binary policy (redmine_git.pp)
instead of compiling source policy (redmine_git.te) before every
installation. New rake task added to recompile policy if necessary:
rake selinux:redmine_git_hosting:build_policy
---
selinux/README | 14 +++++++++++---
selinux/redmine_git.pp | Bin 127822 -> 128153 bytes
selinux/redmine_git.te | 10 ++++++++--
tasks/selinux.rake | 10 ++++++++--
4 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/selinux/README b/selinux/README
index a877fb20b..b57a919ec 100644
--- a/selinux/README
+++ b/selinux/README
@@ -52,15 +52,23 @@ Somewhat less far-reaching options include:
# install new selinux policy, and install selinux context for
# the redmine_git_hosting plugin
- rake selinux:redmine_git_hosting:install
+ rake selinux:redmine_git_hosting:install RAILS_ENV=production
-Finally, for those who are hand-crafting their own file context:
+For those who are hand-crafting their own file context:
# Build bin directory with customized scripts for redmine_git_hosting
# and install new selinux policy. No file contexts will be
# installed (so that you must do customization afterwards).
- rake selinux:redmine_git_hosting:install_scripts_and_policy
+ rake selinux:redmine_git_hosting:install_scripts_and_policy RAILS_ENV=production
+
+Finally, to rebuild the policy file (redmine_git.pp) from source (redmine_git.te),
+you can type:
+
+ # Rebuild redmine_git_hosting selinux policy from source
+
+ rake selinux:redmine_git_hosting:build_policy RAILS_ENV=production
+
diff --git a/selinux/redmine_git.pp b/selinux/redmine_git.pp
index bfe21e6945a9e61a536b6baf50f119f2006732a9..80895f79f0f1bb1e8a076256f6c605b863778ce1 100644
GIT binary patch
delta 4706
zcmb7He^k^}7M}|;4loSE4~GF}U`*UF1KlDmrD28{hWW*SyPH4QWccZ(61Xt6*0xBe
z&34<G)`np=Lk895pE?b?Gu+J0?R^q1N_of8M!e^{HoMBc&k-(Ok
z;}J?`Dt%See6A!iRf_diI%4pUISytC?xa0_U7-R>Xri0&)^ArGr8H^R@jnY_+Ni=2!NCDZYkB?@j4zD6txmRJDEXfP|3^|jvSMkxw+
z2S`O`iuQv4zBU`ds#)f62Paf0pf+Gh%
zDpup};<<|7d3|aEZ=fyd&}mPCslv$vb~B!ktI=Rl;z@flsx3N{JCYzC2c{;)Q^xT
z#}xw^9FA^&pt;6Z@AWCL)s%_9cPU|-P~YNOh@Bo??DB@?J}EmhDBU{DcPrp_p;76M
zg)&aXigPZ`_k1Ct!#G*8naMXD-P|N`dM=IO1>u`Q>o4W&XR{s!c~rXCAxVRgR#Nm7
zQhIWP%O^?_z(#wU#@0Uy2~oTjVugU`x}GOWN02%MQF@d(+oJ-zK*~Lt;N-KZ#bJ{U
zQVbbP5#nQ{m`Gwhe(!08Xnv%y<242Q$6C-}SLs4|C?pYZK1il7>&N(}EEj(Mq08-F?XXIjqhu*
z_lOH$Kaz^75f5rNk{)@~Y}AH$zUH7Hq|jc1DH~HdJuhk_D3&zM>u&UKGvN8nR>5a%
z%EFG+RklT)RI9*m!fmv6(s~U6u9EwI*jR>Xc5z;#cf+Y!^ih#
zVc%90jXJ#PuXZsuyD~vVX7WOkiLr1)VQ~2eh}B=t8WzJ
z(%q>j?{v}l2YmIAWV3X)9!K94GUkt_uFaiZ1@)MAP#40Y7s(bc*)gL5i7HK+F`?ZLtm#0RLEeClzc;ux~;!}Bg^3nJ%
z?Og~`DTm_?QnBH^iTs?yE_P=Gq~hpd1#UiU!P6V{7;&Unlxf9!H{O4=8h0E?L*w6T
zMfv{WO+5}Cx8o-tXenZDG4e8&co#l#NROT&8ca7e_AZ-O?+Z;NTzGE&IzkF@Z$!JGRvH;G1c
z9OK0a6&hrv3f(HIf_G0|lg5j@CheWZi|+wkf5X-I_*pFu{HFx}ayA9~&bjcR0TN5>)gsm$5Mw%*2i>h3vyPN_%b`)QSS}oT}yrj{`0i9S;wx8ZUe|
zHWnZJy|^5BCLYd3O`83*OwRWEAdijCq*qh!SMp~yWUz8Ny*!p_pi0&F)wNa!qR>!~
z%sO;%nC*(8w{iDH1;2$^M-1rLH+pXL^-mZXv&d*9kBlyzcM5{7oz4xu`w0V@Wi_+i
zOb;*~j@*q3mXSk`?;kK!U$|$;gt=R?*ia5E`H|gTUH59{&m)h)x)gTCz*P2W4%~fx
zJL=*|Hm^`pnZjCZAZITWf`dibU@DVa;CWCm
z^9o3oMEfl8QV1YVa<6_VRX)h$WH
z7BVVAk~t(Pl_X*#=PSrMt(?5gBT0!Q;Q@fTEVBY?S6F==U|9jpk6&d{r=We05!3bD2>Ono1AG$^|L(6A;JJQ{Z6Wt!hY?gC#(}O_PCj
z-%GK(u$)dhQ7QaMIZXcos{Um5{tAej2)6$S=;{3dn9CkpNwF86KVM1F6_leMLT^Or
zmN|1i4F@kf*|+tSg>QJ$ha!W@IJ#ZIiWKQ$#FDi6P7_)^Qz+8(lPQ)IAv#2d)p=w)
zJ9e`d_dpIs#ZS~w7PRVI2h0zw5S#-y<$!qnhFKBOqim<$eedPhrqr|fNBZvfz3<-p
z?t8y({X8xHNm|bDhVS?uR4SF4ev|08^`Cok?p=&Wt1Y;9aW>RqcC|S-yso7+bZgVv
z(ArzVp@@dWuEt)M87JB@K!;0g)_m2JiWDNNR^Hg&7K+@qrY)TilVqtXI7ijCzO6;D
zG6|W7`yEC4_NL~x_O{kggi9|FCOdtp7`LV4Zr2q2w=Ekc2kkw4SM)MSn==Ql5<3n%^C63el**Hc
zPdbZXwotb@H5rlBYeEqnS-tqHLq?jZM6{|SQZGnO$EDP7WCxV?*GQ(|=P)kR6CF$T7-Z}Wb+d#5kd1n!n
zuKh(QVjxqcv?~kxJh^C~M!C&n$D0EdO8g`!7G^cxT)0#~^SpLE>z%J{3Pp-YF_SFK
z(^Bp@!p$xMAFmTG_32=d(CGB#z!b5WsILI75ws6|c^C^?kWxay>)6D5APr~vGoXmG
zv)0|Vt|ihQifGB`T%mBgUk8^6Xs7>L$mUSyjo}+3vOLpx5phwC7OoI7jWwAF0ZXRn
zbJ5=m1)Z8PO=^_!4Nq9NmN0zZHCYqMBUeEY`OH8DWD4;_bd)HqtjSD4zAumsPTJ69
zJP^o(bVW3yofpPJcJvB~?+q)0dTg95W1}ujKK`An{zmJQCESno{z8@NUWl
zwGXS*ftC$zf_!{&$+h4Y!7Z%44C;jZuWPeFBht#l?5j0ErJ#5Ew0NdA7p4kcVVxeo
zq0&0)a`7H#3HEH!#_p{9#>TW(D8#OA6E*GZZU=t(Co}eJYsLQK0X(zKNN_Kn-ewZC
z;*SHk?Sv8IScB?gCX$gW#~(l5N^Xhp>i_rdK2PuzfmJG=>4
z@{QY#N%n(i+MX|La=(gyZl`015zDvNV9ucy^uAey(XC$m@-w6G!Phtj1kQbp;%4EG
z@L2F6lbR>kg2nL?tbAk|_UtOb(~mgVU^z^QJ-_n?$&M|2+yGp#1mou{7=6Oj?Mr3x
zOqhnzogR*3*^?j9YQeInIBvoI^I~PesWhohK4u!DwD9qg1djcW%*6g(icK7Ru>v#5
zT(XOSb0utO7EQ`eo_-at+h@jTuUD+`7+$+Gx$*+f{?bFPVtw!Hq9j6s85g{n|r_iFFz2(mSNr(q)s1
zl~3)(jF$xiSH1jEiYnEBbB+{HLkY7A9*h5ZKS;;u#pBPn($9?F{?(7A`8h
zp4eW1WrzF;+(U=3ncb64yR7_M4Q|+O!mdMfn(5?oIF3HajP`1AFkU4Q-;q&CK2I*z
z`XNi)YX5Pc>;&sE_lOcV(D0TC8;(ATuA@`1^6xe=sCWp;gCluxsgGGuf6DU%az*&%
z4)-`c{U0niwAmBu|Hm_c@pl`=l;V?4nQ%0n)SZC6&&;PGJ5vn7@TpeJI5j#vFA^U&
zJTI2rxaA;j+|o(|Fo44}Kxc1ljaff95A-~#eYZJ<-eGvx=4Q26^0<@)n~DEcype60
zN+pS-mPO0KMl;TY;m35bji25O82ZkLn$LHOGpUq(e9O4lT{Co@1gg)NS$QTsXB=k=DY=Wr
zQ&}`svPb3kIWOJ+v+<_rQm$uUmW)dfYs|jLR80#N$lP%dS25};Ty5B
zGCgGQ?#aUEVhdP*I+QW(L}~<{4GyhLRMsb2Op@ms;CB7;fBk28I89~a<6Je=2PSxR
z0*ZZ}*WpcdSt(NtDeU1}VLfYDPH&qsBR%~uf6*+iO7@@;mg$d=v#?XcVja}TbiJ}Y
z9bjRXSfNS3{EIQ3?zaNTtvAveab%Ep&*y|f_Q$DQesGL@>7`trzk%4vS&6)iLdv13
zk|~Vt)Uuq}6x{gj)oIwkJh*zyQLB>J|JJI%3f8dx#q|D2be2wFJeM9xFOG|Tk#v$$
z&R&q6wNcn%JNVgsHYi{jb~wcJc5t(gt00f9vcnPyRoW=W?{LQVZpz#CDl)yoLm~~;
z#OSFahPN7uB=j36m|1Hzc)4&MJ5Wtl2r{#o4tSWYaFN_;2khXGFrZ8I{HnM{e
z)J)@o>$r6@J4i-G>6LCtPbk&6DTq7#kk59xAt)ieNBYWRYe%4W5|EkMyoyGTSJ6=W
z6lf)fPWd5+z2>7j2&l}jKwTW_3=sF6ADTJT$_z`$dAx?y1aw(IfmV&sE{xC`f(n;%
zreOzz^!?YlIUjqzl27HooSocvVF|Q>?2X}J-uj>#V$}MjeO+~MzlNP@qNFTbN}1ig
zjG!J4J-wVD^)iA!T}seO4*5BEK!IQ`+p&~(Zsq*$dZ=Ztu4jxFw(#6J1{vUwL>F1N;RnmCZ^@^Kw3CPOoSAat*rE{gZ^b4>Z^-#@v
zR*=7dVn9TEcLPPFzaA_T=8>GA9a#n;<_jf=FrQf)poQf$5@DbLENKf#sz$LO^6(z<
zlH6TE0$%Bkn@tmVVvD%;DGSpykCDHUl*yu-pp-*KddAZWd>ReINo++sWQ`$qJ^KU1Xzw`UOtz;ToYFFX
z?z4i`R2t#vmeusdrrZSVbeMe8+4izv#~5fr2hq3!ZtU0M+fU3DH;0ru+`$J|(m>od
U%tvY#^m%Uv>xRBfXCNp2zdwhul>h($
diff --git a/selinux/redmine_git.te b/selinux/redmine_git.te
index 54b0a7aa1..36c73d2d8 100644
--- a/selinux/redmine_git.te
+++ b/selinux/redmine_git.te
@@ -8,10 +8,13 @@ require {
type httpd_t, httpd_sys_script_t, httpd_sys_script_exec_t;
type sudo_db_t;
type httpd_redmine_git_script_t;
+ type httpd_redmine_git_script_exec_t;
+ type gitosis_var_lib_t;
class process { setrlimit setfscreate };
class netlink_route_socket { write getattr read bind create nlmsg_read };
class capability { setuid sys_resource setgid };
- class dir { getattr search write};
+ class dir { getattr search write write rename create reparent rmdir };
+ class lnk_file unlink;
}
apache_content_template(redmine_git)
@@ -51,10 +54,13 @@ allow httpd_redmine_git_script_t self:netlink_route_socket { write getattr read
allow httpd_redmine_git_script_t self:process setrlimit;
allow httpd_redmine_git_script_t sudo_db_t:dir { getattr search };
+# Capabilities required to manage gitolite repositories
+allow httpd_redmine_git_script_t gitosis_var_lib_t:dir { rename create reparent rmdir };
+allow httpd_redmine_git_script_t gitosis_var_lib_t:lnk_file unlink;
gitosis_read_lib_files(httpd_redmine_git_script_t)
gitosis_manage_lib_files(httpd_redmine_git_script_t)
-httpd_rw_stream_sockets(httpd_redmine_git_script_t)
+apache_rw_stream_sockets(httpd_redmine_git_script_t)
kernel_read_kernel_sysctls(httpd_redmine_git_script_t)
logging_send_syslog_msg(httpd_redmine_git_script_t)
diff --git a/tasks/selinux.rake b/tasks/selinux.rake
index 92e1e4c7c..3eae4cfd5 100644
--- a/tasks/selinux.rake
+++ b/tasks/selinux.rake
@@ -153,8 +153,14 @@ namespace :selinux do
desc "Install selinux tags and policy for redmine_git_hosting."
task :install_policy => [:environment] do
puts "[Installing selinux tags and policy for redmine_git_hosting:"
- plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting")
- sh "#{plugin_roots[0]}/selinux/redmine_git.sh"
+ sh "semodule -i #{Rails.root}/vendor/plugins/redmine_git_hosting/selinux/redmine_git.pp"
+ puts "DONE.]"
+ end
+
+ desc "Build and install selinux tags and policy for redmine_git_hosting."
+ task :build_policy => [:environment] do
+ puts "[Building and installing selinux policy for redmine_git_hosting:"
+ sh "#{Rails.root}/vendor/plugins/redmine_git_hosting/selinux/redmine_git.sh"
puts "DONE.]"
end