Skip to content

Commit 8c728ab

Browse files
author
Till Brehm
committed
Merge branch 'php-fpm-chroot' into 'master'
jailkit for php-fpm chroot See merge request ispconfig/ispconfig3!722
2 parents b91f0cb + e75525a commit 8c728ab

File tree

4 files changed

+328
-2
lines changed

4 files changed

+328
-2
lines changed

server/conf/nginx_vhost.conf.master

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ server {
184184
fastcgi_index index.php;
185185
<tmpl_if name='php_fpm_chroot'>
186186
fastcgi_param SCRIPT_FILENAME /web$fastcgi_script_name;
187-
</tmpl_else>
187+
</tmpl_if>
188+
<tmpl_if name='php_fpm_nochroot'>
188189
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
189190
</tmpl_if>
190191
#fastcgi_param PATH_INFO $fastcgi_script_name;

server/plugins-available/nginx_plugin.inc.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1026,8 +1026,10 @@ function update($event_name, $data) {
10261026

10271027
if($data['new']['php_fpm_chroot'] == 'y'){
10281028
$php_fpm_chroot = 1;
1029+
$php_fpm_nochroot = 0;
10291030
} else {
10301031
$php_fpm_chroot = 0;
1032+
$php_fpm_nochroot = 1;
10311033
}
10321034
if($data['new']['php_fpm_use_socket'] == 'y'){
10331035
$use_tcp = 0;
@@ -1039,6 +1041,7 @@ function update($event_name, $data) {
10391041
$tpl->setVar('use_tcp', $use_tcp);
10401042
$tpl->setVar('use_socket', $use_socket);
10411043
$tpl->setVar('php_fpm_chroot', $php_fpm_chroot);
1044+
$tpl->setVar('php_fpm_nochroot', $php_fpm_nochroot);
10421045
$fpm_socket = $socket_dir.$pool_name.'.sock';
10431046
$tpl->setVar('fpm_socket', $fpm_socket);
10441047
$tpl->setVar('rnd_php_dummy_file', '/'.md5(uniqid(microtime(), 1)).'.htm');
@@ -2035,7 +2038,14 @@ function delete($event_name, $data) {
20352038
//exec('fuser -km '.escapeshellarg($data['old']['document_root'].'/'.$log_folder).' 2>/dev/null');
20362039
exec('umount '.escapeshellarg($data['old']['document_root'].'/'.$log_folder).' 2>/dev/null');
20372040
}
2038-
2041+
2042+
//try umount mysql
2043+
if(file_exists($data['old']['document_root'].'/var/run/mysqld')) {
2044+
$fstab_line = '/var/run/mysqld ' . $data['old']['document_root'] . '/var/run/mysqld none bind,nobootwait 0 0';
2045+
$app->system->removeLine('/etc/fstab', $fstab_line);
2046+
$command = 'umount ' . escapeshellarg($data['old']['document_root']) . '/var/run/mysqld/';
2047+
exec($command);
2048+
}
20392049
// remove letsencrypt if it exists (renew will always fail otherwise)
20402050

20412051
$old_domain = $data['old']['domain'];
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
<?php
2+
3+
/*
4+
Copyright (c) 2018, Till Brehm, projektfarm Gmbh
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without modification,
8+
are permitted provided that the following conditions are met:
9+
10+
* Redistributions of source code must retain the above copyright notice,
11+
this list of conditions and the following disclaimer.
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
* Neither the name of ISPConfig nor the names of its contributors
16+
may be used to endorse or promote products derived from this software without
17+
specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22+
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
class php_fpm_jailkit_plugin {
32+
33+
//* $plugin_name and $class_name have to be the same then the name of this class
34+
var $plugin_name = 'php_fpm_jailkit_plugin';
35+
var $class_name = 'php_fpm_jailkit_plugin';
36+
37+
private $parent_domain;
38+
private $jailkit_config;
39+
40+
//* This function is called during ispconfig installation to determine
41+
// if a symlink shall be created for this plugin.
42+
function onInstall() {
43+
global $conf;
44+
45+
if($conf['services']['web'] == true) {
46+
return true;
47+
} else {
48+
return false;
49+
}
50+
51+
}
52+
53+
54+
/*
55+
This function is called when the plugin is loaded
56+
*/
57+
58+
function onLoad() {
59+
global $app;
60+
61+
62+
/*
63+
Register for the events
64+
*/
65+
66+
$app->plugins->registerEvent('web_domain_insert', $this->plugin_name, 'insert');
67+
$app->plugins->registerEvent('web_domain_update', $this->plugin_name, 'update');
68+
$app->plugins->registerEvent('web_domain_delete', $this->plugin_name, 'delete');
69+
70+
}
71+
72+
//* This function is called, when a website is inserted in the database
73+
function insert($event_name, $data) {
74+
global $app, $conf;
75+
76+
$this->action = 'insert';
77+
// just run the update function
78+
$this->update($event_name, $data);
79+
80+
}
81+
82+
//* This function is called, when a website is updated in the database
83+
function update($event_name, $data)
84+
{
85+
global $app, $conf;
86+
87+
if ($data["new"]["php_fpm_chroot"] != 'y') {
88+
if ($this->action == 'update' && $data['old']['document_root'] != '' && $data['new']['document_root'] != $data['old']['document_root']) {
89+
$this->remove_old_mount_mysql($data);
90+
}
91+
return 0;
92+
}
93+
if( file_exists("/var/run/mysqld")) {
94+
if ($this->action == 'update' && $data['old']['document_root'] != '' && $data['new']['document_root'] != $data['old']['document_root']) {
95+
$this->remove_old_mount_mysql($data);
96+
}
97+
98+
$fstab_line = '/var/run/mysqld ' . $data['new']['document_root'] . '/var/run/mysqld none bind,nobootwait 0 0';
99+
$app->system->replaceLine('/etc/fstab', $fstab_line, $fstab_line, 0, 1);
100+
101+
$command = 'mount --bind /var/run/mysqld ' . escapeshellarg($data['new']['document_root']) . '/var/run/mysqld/';
102+
exec($command);
103+
}
104+
105+
$this->parent_domain = $data["new"];
106+
$parent_domain = $data["new"];
107+
if(!$app->system->is_allowed_user($parent_domain['system_user'], true, true)
108+
|| !$app->system->is_allowed_group($parent_domain['system_group'], true, true)) {
109+
$app->log("Websites cannot be owned by the root user or group.", LOGLEVEL_WARN);
110+
return false;
111+
}
112+
113+
$app->uses('system');
114+
115+
116+
if($app->system->is_user($parent_domain['system_user'])) {
117+
118+
/**
119+
* Setup Jailkit Chroot System If Enabled
120+
*/
121+
122+
$app->log("Jailkit Plugin (PHP-FPM Chroot) -> setting up jail", LOGLEVEL_DEBUG);
123+
// load the server configuration options
124+
125+
$app->uses("getconf");
126+
$this->data = $data;
127+
$this->app = $app;
128+
$this->jailkit_config = $app->getconf->get_server_config($conf["server_id"], 'jailkit');
129+
130+
$this->_update_website_security_level();
131+
132+
$app->system->web_folder_protection($parent_domain['document_root'], false);
133+
134+
$this->_setup_jailkit_chroot();
135+
$this->_add_jailkit_user();
136+
137+
$this->_update_website_security_level();
138+
$app->system->web_folder_protection($parent_domain['document_root'], true);
139+
140+
141+
$app->log("Jailkit Plugin (PHP-FPM Chroot) -> update username:".$parent_domain['system_user'], LOGLEVEL_DEBUG);
142+
143+
} else {
144+
$app->log("Jailkit Plugin (PHP-FPM Chroot) -> update username:".$parent_domain['system_user']." skipped, the user does not exist.", LOGLEVEL_WARN);
145+
}
146+
}
147+
148+
function remove_old_mount_mysql($data){
149+
global $app, $conf;
150+
151+
if(!file_exists("/var/run/mysqld")){
152+
return;
153+
}
154+
$fstab_line = '/var/run/mysqld ' . $data['old']['document_root'] . '/var/run/mysqld none bind,nobootwait 0 0';
155+
156+
$app->system->removeLine('/etc/fstab', $fstab_line);
157+
158+
$command = 'umount '.escapeshellarg($data['old']['document_root']).'/var/run/mysqld/';
159+
exec($command);
160+
}
161+
162+
//* This function is called, when a website is deleted in the database
163+
function delete($event_name, $data) {
164+
global $app, $conf;
165+
166+
$this->remove_old_mount_mysql($data);
167+
}
168+
169+
function _setup_jailkit_chroot()
170+
{
171+
global $app;
172+
173+
//check if the chroot environment is created yet if not create it with a list of program sections from the config
174+
if (!is_dir($this->parent_domain['document_root'].'/etc/jailkit'))
175+
{
176+
$command = '/usr/local/ispconfig/server/scripts/create_jailkit_chroot.sh';
177+
$command .= ' '.escapeshellcmd($this->parent_domain['document_root']);
178+
$command .= ' \''.$this->jailkit_config['jailkit_chroot_app_sections'].'\'';
179+
exec($command.' 2>/dev/null');
180+
181+
$this->app->log("Added jailkit chroot with command: ".$command, LOGLEVEL_DEBUG);
182+
183+
//$this->_add_jailkit_programs(); // done later on
184+
185+
$this->app->load('tpl');
186+
187+
$tpl = new tpl();
188+
$tpl->newTemplate("bash.bashrc.master");
189+
190+
$tpl->setVar('jailkit_chroot', true);
191+
$tpl->setVar('domain', $this->parent_domain['domain']);
192+
$tpl->setVar('home_dir', $this->_get_home_dir(""));
193+
194+
$bashrc = escapeshellcmd($this->parent_domain['document_root']).'/etc/bash.bashrc';
195+
if(@is_file($bashrc) || @is_link($bashrc)) unlink($bashrc);
196+
197+
$app->system->file_put_contents($bashrc, $tpl->grab());
198+
unset($tpl);
199+
200+
$this->app->log('Added bashrc script: '.$bashrc, LOGLEVEL_DEBUG);
201+
202+
$tpl = new tpl();
203+
$tpl->newTemplate('motd.master');
204+
205+
$tpl->setVar('domain', $this->parent_domain['domain']);
206+
207+
$motd = escapeshellcmd($this->parent_domain['document_root']).'/var/run/motd';
208+
if(@is_file($motd) || @is_link($motd)) unlink($motd);
209+
210+
$app->system->file_put_contents($motd, $tpl->grab());
211+
212+
}
213+
$this->_add_jailkit_programs();
214+
}
215+
216+
function _add_jailkit_programs()
217+
{
218+
global $app;
219+
220+
//copy over further programs and its libraries
221+
$jailkit_chroot_app_programs = preg_split("/[\s,]+/", $this->jailkit_config['jailkit_chroot_app_programs']);
222+
if(is_array($jailkit_chroot_app_programs) && !empty($jailkit_chroot_app_programs)){
223+
foreach($jailkit_chroot_app_programs as $jailkit_chroot_app_program){
224+
$jailkit_chroot_app_program = trim($jailkit_chroot_app_program);
225+
if(is_file($jailkit_chroot_app_program) || is_dir($jailkit_chroot_app_program)){
226+
//copy over further programs and its libraries
227+
$command = '/usr/local/ispconfig/server/scripts/create_jailkit_programs.sh';
228+
$command .= ' '.escapeshellcmd($this->data['new']['dir']);
229+
$command .= ' '.$jailkit_chroot_app_program;
230+
exec($command.' 2>/dev/null');
231+
232+
$this->app->log("Added programs to jailkit chroot with command: ".$command, LOGLEVEL_DEBUG);
233+
}
234+
}
235+
}
236+
237+
// $command = '/usr/local/ispconfig/server/scripts/create_jailkit_programs.sh';
238+
// $command .= ' '.escapeshellcmd($this->parent_domain['document_root']);
239+
// $command .= ' \''.$this->jailkit_config['jailkit_chroot_cron_programs'].'\'';
240+
// exec($command.' 2>/dev/null');
241+
//
242+
// $this->app->log("Added cron programs to jailkit chroot with command: ".$command, LOGLEVEL_DEBUG);
243+
}
244+
245+
function _add_jailkit_user()
246+
{
247+
global $app;
248+
249+
//add the user to the chroot
250+
$jailkit_chroot_userhome = $this->_get_home_dir($this->parent_domain['system_user']);
251+
252+
if(!is_dir($this->parent_domain['document_root'].'/etc')) mkdir($this->parent_domain['document_root'].'/etc');
253+
if(!is_file($this->parent_domain['document_root'].'/etc/passwd')) exec('touch '.$this->parent_domain['document_root'].'/etc/passwd');
254+
255+
// IMPORTANT!
256+
// ALWAYS create the user. Even if the user was created before
257+
// if we check if the user exists, then a update (no shell -> jailkit) will not work
258+
// and the user has FULL ACCESS to the root of the server!
259+
$command = '/usr/local/ispconfig/server/scripts/create_jailkit_user.sh';
260+
$command .= ' '.escapeshellcmd($this->parent_domain['system_user']);
261+
$command .= ' '.escapeshellcmd($this->parent_domain['document_root']);
262+
$command .= ' '.$jailkit_chroot_userhome;
263+
$command .= ' '.escapeshellcmd("/bin/bash");
264+
exec($command.' 2>/dev/null');
265+
266+
$this->app->log("Added jailkit user to chroot with command: ".$command, LOGLEVEL_DEBUG);
267+
268+
$app->system->mkdir(escapeshellcmd($this->parent_domain['document_root'].$jailkit_chroot_userhome), 0755, true);
269+
$app->system->chown(escapeshellcmd($this->parent_domain['document_root'].$jailkit_chroot_userhome), escapeshellcmd($this->parent_domain['system_user']));
270+
$app->system->chgrp(escapeshellcmd($this->parent_domain['document_root'].$jailkit_chroot_userhome), escapeshellcmd($this->parent_domain['system_group']));
271+
272+
}
273+
274+
function _get_home_dir($username)
275+
{
276+
return str_replace("[username]", escapeshellcmd($username), $this->jailkit_config["jailkit_chroot_home"]);
277+
}
278+
279+
//* Update the website root directory permissions depending on the security level
280+
function _update_website_security_level() {
281+
global $app, $conf;
282+
283+
// load the server configuration options
284+
$app->uses("getconf");
285+
$web_config = $app->getconf->get_server_config($conf["server_id"], 'web');
286+
287+
$web = $this->parent_domain['new'];
288+
289+
//* If the security level is set to high
290+
if($web_config['security_level'] == 20 && is_array($web)) {
291+
$app->system->web_folder_protection($web["document_root"], false);
292+
$app->system->chmod($web["document_root"], 0755);
293+
$app->system->chown($web["document_root"], 'root');
294+
$app->system->chgrp($web["document_root"], 'root');
295+
$app->system->web_folder_protection($web["document_root"], true);
296+
}
297+
}
298+
299+
//* Wrapper for exec function for easier debugging
300+
private function _exec($command) {
301+
global $app;
302+
$app->log('exec: '.$command, LOGLEVEL_DEBUG);
303+
exec($command);
304+
}
305+
306+
307+
308+
} // end class
309+
310+
?>

server/scripts/create_jailkit_chroot.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,8 @@ if [ -e "/var/run/mysqld/mysqld.sock" ]
4444
then
4545
ln /var/run/mysqld/mysqld.sock $CHROOT_HOMEDIR/var/run/mysqld/mysqld.sock
4646
fi
47+
# copy zone info
48+
if [ ! -d "$CHROOT_HOMEDIR/usr/share/zoneinfo" ]
49+
then
50+
jk_cp -v -j $CHROOT_HOMEDIR/ /usr/share/zoneinfo
51+
fi

0 commit comments

Comments
 (0)