diff --git a/Dockerfile b/Dockerfile index 1caa351c6..25a391c00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,6 +52,16 @@ RUN rm zz-docker.conf && \ ## Set our pool configuration COPY deploy/config/php-pool.conf pool.conf +# Apend our relay config to the existing config file. +RUN { \ + echo 'relay.maxmemory = 16M'; \ + echo 'relay.loglevel = error'; \ + echo 'relay.logfile = /dev/stderr'; \ + } >> /usr/local/etc/php/conf.d/docker-php-ext-relay.ini + +# Don't log every request. +RUN perl -pi -e 's#^(?=access\.log\b)#;#' /usr/local/etc/php-fpm.d/docker.conf + WORKDIR /var/www/html @@ -120,9 +130,6 @@ WORKDIR /var/www/html COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -# Don't leg every request. -RUN perl -pi -e 's#^(?=access\.log\b)#;#' /usr/local/etc/php-fpm.d/docker.conf - VOLUME ["/sock"] # nginx USER 101 diff --git a/deploy/config/php-pool.conf b/deploy/config/php-pool.conf index 47e3cc3fd..06605bbb6 100644 --- a/deploy/config/php-pool.conf +++ b/deploy/config/php-pool.conf @@ -23,6 +23,7 @@ request_slowlog_timeout = 10s; slowlog = /proc/self/fd/2; [global] +log_buffering = false; daemonize = no emergency_restart_threshold = 10; emergency_restart_interval = 1m; diff --git a/docker-compose.yml b/docker-compose.yml index b0a5bd37a..e705db3de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,8 @@ services: volumes: - .:/var/www/html - php-socket:/sock + ### Deploy scripts + - ./deploy/config/php-pool.conf:/usr/local/etc/php-fpm.d/pool.conf env_file: - .env depends_on: diff --git a/public/app/themes/clarity/functions.php b/public/app/themes/clarity/functions.php index 09d607081..580965ef3 100644 --- a/public/app/themes/clarity/functions.php +++ b/public/app/themes/clarity/functions.php @@ -113,6 +113,7 @@ require_once 'inc/shortcodes.php'; require_once 'inc/security.php'; require_once 'inc/table-modification.php'; +require_once 'inc/updates.php'; require_once 'inc/uploads.php'; require_once 'inc/whitelisted-emails.php'; diff --git a/public/app/themes/clarity/inc/security.php b/public/app/themes/clarity/inc/security.php index 065b2db2a..95a0b21dd 100644 --- a/public/app/themes/clarity/inc/security.php +++ b/public/app/themes/clarity/inc/security.php @@ -2,6 +2,7 @@ namespace MOJ\Justice; +use function Env\env; use Roots\WPConfig\Config; // --------------------------------------------- @@ -13,12 +14,45 @@ */ class Security { + + /** + * A list of known hosts. + */ + private array $known_hosts = [ + 'api.deliciousbrains.com', + 'connect.advancedcustomfields.com' + ]; + + /** + * The application host e.g. intranet.docker or intranet.justice.gov.uk + */ + private string $home_host; + /** * Loads up actions that are called when WordPress initialises */ public function __construct() { + $this->home_host = parse_url(get_home_url(), PHP_URL_HOST); + $this->actions(); + + // Push the application host to known_hosts. + array_push($this->known_hosts, $this->home_host); + + // Push the OpenSearch host to known_hosts. + if ($ep_url = Config::get('EP_HOST')) { + array_push($this->known_hosts, parse_url($ep_url, PHP_URL_HOST)); + } + + // Push the S3 bucket host to known_hosts. + if ($s3_bucket = env('AWS_S3_BUCKET')) { + array_push($this->known_hosts, $s3_bucket . ".s3.eu-west-2.amazonaws.com"); + } + + if ($custom_s3_host = env('AWS_S3_CUSTOM_HOST')) { + array_push($this->known_hosts, $custom_s3_host); + } } /** @@ -34,6 +68,7 @@ public function actions(): void add_filter('wp_headers', [$this, 'headerMods']); add_filter('auth_cookie_expiration', [$this, 'setLoginPeriod'], 10, 0); add_filter('pre_http_request', [$this, 'handleLoopbackRequests'], 10, 3); + add_filter('pre_http_request', [$this, 'logUnknownHostRequests'], 10, 3); } /** @@ -90,13 +125,10 @@ public function setLoginPeriod(): float|int * @param string $url * @return false|array|\WP_Error */ - public function handleLoopbackRequests(false|array|\WP_Error $response, array $parsed_args, string $url): false|array|\WP_Error { // Is the request url to the application host? - if (parse_url($url, PHP_URL_HOST) !== parse_url(get_home_url(), PHP_URL_HOST)) { - // Request is not to the application host - log the url. - error_log('pre_http_request url: ' . $url); + if (parse_url($url, PHP_URL_HOST) !== $this->home_host) { return $response; } @@ -116,6 +148,26 @@ public function handleLoopbackRequests(false|array|\WP_Error $response, array $p // Return the result. return $http->request($new_url, $parsed_args); } + + /** + * Log the urls of requests to unknown hosts. + * + * This could be useful in identifying requests to malicious URLs. + * + * @param false|array|\WP_Error $response + * @param array $parsed_args + * @param string $url + * @return false|array|\WP_Error + */ + public function logUnknownHostRequests(false|array|\WP_Error $response, array $parsed_args, string $url): false|array|\WP_Error + { + if (!in_array(parse_url($url, PHP_URL_HOST), $this->known_hosts)) { + // Log the request url. + error_log('pre_http_request url: ' . $url); + } + + return $response; + } } new Security(); diff --git a/public/app/themes/clarity/inc/updates.php b/public/app/themes/clarity/inc/updates.php new file mode 100644 index 000000000..4f5970b12 --- /dev/null +++ b/public/app/themes/clarity/inc/updates.php @@ -0,0 +1,90 @@ +hooks(); + } + + /** + * @return void + */ + public function hooks(): void + { + /** + * Prevent http requests to api.wordpress.org to check for WP core versions. + * + * Even though `AUTOMATIC_UPDATER_DISABLED` is set to true, we need to block + * requests api.wordpress.org to where WP checks for available versions. + * + * This filter's function returns a dummy payload that will prevent an api request, + * and not cause an error. + * + * @see https://wp-kama.com/2020/disable-wp-updates-check + * @see https://developer.wordpress.org/reference/functions/wp_version_check/ + */ + add_filter( + 'pre_site_transient_update_core', + fn() => (object) [ + 'updates' => [], + 'version_checked' => $GLOBALS['wp_version'], + 'last_checked' => time() + ] + ); + + /** + * Prevent http requests to api.wordpress.org to check for theme version. + * + * This filter's function returns a dummy payload that will prevent an api request, + * and not cause an error. + * + * @see https://wp-kama.com/2020/disable-wp-updates-check + * @see https://developer.wordpress.org/reference/functions/wp_update_themes/ + */ + add_filter('pre_site_transient_update_themes', static function ($value) { + static $theme; + + $theme || $theme = wp_get_theme('clarity'); + + return (object) [ + 'last_checked' => time(), + 'checked' => [ + 'clarity' => $theme->get('Version') + ] + ]; + }); + + /** + * Prevent http requests to api.wordpress.org to check for plugin versions. + * + * This filter's function returns a dummy payload that will prevent an api request, + * and not cause an error. + * + * @see https://wp-kama.com/2020/disable-wp-updates-check + * @see https://developer.wordpress.org/reference/functions/wp_update_plugins/ + */ + add_filter('pre_site_transient_update_plugins', static function ($value) { + static $plugins; + $plugins || $plugins = get_plugins(); + + $return_value = (object) [ + 'last_checked' => time(), + 'checked' => [] + ]; + + foreach ($plugins as $file => $p) { + $return_value->checked[$file] = $p['Version']; + } + + return $return_value; + }); + } +} + +new Updates();