Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WAF: Move rule files directory to the WAF wp-content directory #28049

Merged
merged 18 commits into from
Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/packages/waf/changelog/move-waf-rules-to-wp-content
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Change directory location that stores firewall rules.
50 changes: 50 additions & 0 deletions projects/packages/waf/src/class-compatibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
/**
* Class used to manage backwards-compatibility of the package.
*
* @since 0.8.0
*
* @package automattic/jetpack-waf
*/

namespace Automattic\Jetpack\Waf;

/**
* Defines methods for ensuring backwards compatibility.
*/
class Waf_Compatibility {

/**
* Add compatibilty hooks
*
* @since 0.8.0
*
* @return void
*/
public static function add_compatibility_hooks() {
add_filter( 'default_option_' . Waf_Initializer::NEEDS_UPDATE_OPTION_NAME, __CLASS__ . '::default_option_waf_needs_update', 10, 3 );
}

/**
* Provides a default value for sites that installed the WAF
* before the NEEDS_UPDATE_OPTION_NAME option was added.
*
* @since 0.8.0
*
* @param mixed $default The default value to return if the option does not exist in the database.
* @param string $option Option name.
* @param bool $passed_default Was get_option() passed a default value.
*
* @return mixed The default value to return if the option does not exist in the database.
*/
public static function default_option_waf_needs_update( $default, $option, $passed_default ) {
// Allow get_option() to override this default value
if ( $passed_default ) {
return $default;
}

// If the option hasn't been added yet, the WAF needs to be updated.
return true;
}

}
2 changes: 1 addition & 1 deletion projects/packages/waf/src/class-waf-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public function generate_rules() {
sprintf(
/* translators: %1$s is the name of the mode that was just switched to. */
__( 'Jetpack WAF rules successfully created to: "%1$s".', 'jetpack-waf' ),
Waf_Runner::RULES_FILE
Waf_Runner::get_waf_file_path( Waf_Runner::RULES_FILE )
)
);
}
Expand Down
47 changes: 38 additions & 9 deletions projects/packages/waf/src/class-waf-initializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
* Initializes the module
*/
class Waf_Initializer {

/**
* Option for storing whether or not the WAF files are potentially out of date.
*
* @var string NEEDS_UPDATE_OPTION_NAME
*/
const NEEDS_UPDATE_OPTION_NAME = 'jetpack_waf_needs_update';

/**
* Initializes the configurations needed for the waf module.
*
Expand All @@ -31,6 +39,9 @@ public static function init() {
add_action( 'jetpack_activate_module_waf', __CLASS__ . '::on_activation' );
add_action( 'jetpack_deactivate_module_waf', __CLASS__ . '::on_deactivation' );

// Ensure backwards compatibility
Waf_Compatibility::add_compatibility_hooks();

// Run the WAF
Waf_Runner::initialize();
}
Expand Down Expand Up @@ -60,37 +71,55 @@ public static function on_deactivation() {
* @return void
*/
public static function update_waf_after_plugin_upgrade( $upgrader, $hook_extra ) {
$jetpack_plugins_with_waf = array( 'jetpack/jetpack.php', 'jetpack-protect/jetpack-protect.php' );
$jetpack_text_domains_with_waf = array( 'jetpack', 'jetpack-protect' );
$jetpack_plugins_with_waf = array( 'jetpack/jetpack.php', 'jetpack-protect/jetpack-protect.php' );

// Only run on upgrades affecting plugins
if ( empty( $hook_extra['plugins'] ) ) {
if ( 'plugin' !== $hook_extra['type'] ) {
return;
}

// Only run on updates and installations
if ( ! in_array( $hook_extra['action'], array( 'update', 'install' ), true ) ) {
if ( 'update' !== $hook_extra['action'] && 'install' !== $hook_extra['action'] ) {
return;
}

// Only run when Jetpack plugins were affected
if ( empty( array_intersect( $jetpack_plugins_with_waf, $hook_extra['plugins'] ) ) ) {
if ( 'update' === $hook_extra['action'] &&
! empty( $hook_extra['plugins'] ) &&
empty( array_intersect( $jetpack_plugins_with_waf, $hook_extra['plugins'] ) )
) {
return;
}
if ( 'install' === $hook_extra['action'] &&
! empty( $upgrader->new_plugin_data['TextDomain'] ) &&
empty( in_array( $upgrader->new_plugin_data['TextDomain'], $jetpack_text_domains_with_waf, true ) )
) {
return;
}

set_transient( 'jetpack_waf_needs_update', 1 );
update_option( self::NEEDS_UPDATE_OPTION_NAME, 1 );
}

/**
* Check for WAF update
*
* Updates the WAF when the transient is present.
* Updates the WAF when the "needs update" option is enabled.
*
* @return void
*/
public static function check_for_waf_update() {
if ( get_transient( 'jetpack_waf_needs_update' ) ) {
delete_transient( 'jetpack_waf_needs_update' );
Waf_Runner::update_waf();
if ( get_option( self::NEEDS_UPDATE_OPTION_NAME ) ) {
Waf_Runner::define_mode();
if ( ! Waf_Runner::is_allowed_mode( JETPACK_WAF_MODE ) ) {
return;
}

Waf_Runner::generate_ip_rules();
Waf_Runner::generate_rules();
( new Waf_Standalone_Bootstrap() )->generate();

update_option( self::NEEDS_UPDATE_OPTION_NAME, 0 );
}
}

Expand Down
64 changes: 44 additions & 20 deletions projects/packages/waf/src/class-waf-runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class Waf_Runner {
const IP_LISTS_ENABLED_OPTION_NAME = 'jetpack_waf_ip_list';
const IP_ALLOW_LIST_OPTION_NAME = 'jetpack_waf_ip_allow_list';
const IP_BLOCK_LIST_OPTION_NAME = 'jetpack_waf_ip_block_list';
const RULES_FILE = __DIR__ . '/../rules/rules.php';
const ALLOW_IP_FILE = __DIR__ . '/../rules/allow-ip.php';
const BLOCK_IP_FILE = __DIR__ . '/../rules/block-ip.php';
const RULES_FILE = '/rules/rules.php';
const ALLOW_IP_FILE = '/rules/allow-ip.php';
const BLOCK_IP_FILE = '/rules/block-ip.php';
const VERSION_OPTION_NAME = 'jetpack_waf_rules_version';
const RULE_LAST_UPDATED_OPTION_NAME = 'jetpack_waf_last_updated_timestamp';
const SHARE_DATA_OPTION_NAME = 'jetpack_waf_share_data';
Expand Down Expand Up @@ -201,6 +201,23 @@ private static function get_bootstrap_file_path() {
return $bootstrap->get_bootstrap_file_path();
}

/**
* Get WAF File Path
*
* @param string $file The file path starting in the WAF directory.
* @return string The full file path to the provided file in the WAF directory.
*/
public static function get_waf_file_path( $file ) {
Waf_Constants::initialize_constants();

// Ensure the file path starts with a slash.
if ( '/' !== substr( $file, 0, 1 ) ) {
$file = "/$file";
}

return JETPACK_WAF_DIR . $file;
}

/**
* Runs the WAF and potentially stops the request if a problem is found.
*
Expand Down Expand Up @@ -238,11 +255,12 @@ public static function run() {
$waf = new Waf_Runtime( new Waf_Transforms(), new Waf_Operators() );

// execute waf rules.
if ( file_exists( self::RULES_FILE ) ) {
$rules_file_path = self::get_waf_file_path( self::RULES_FILE );
if ( file_exists( $rules_file_path ) ) {
// phpcs:ignore
include self::RULES_FILE;
include $rules_file_path;
}
} catch ( \Exception $err ) { // phpcs:ignore
} catch ( \Exception $err ) { // phpcs:ignore
// Intentionally doing nothing.
}

Expand Down Expand Up @@ -365,7 +383,7 @@ public static function deactivate() {

self::initialize_filesystem();

if ( ! $wp_filesystem->put_contents( self::RULES_FILE, "<?php\n" ) ) {
if ( ! $wp_filesystem->put_contents( self::get_waf_file_path( self::RULES_FILE ), "<?php\n" ) ) {
throw new \Exception( 'Failed to empty rules.php file.' );
}
}
Expand Down Expand Up @@ -468,6 +486,8 @@ public static function generate_rules() {

self::initialize_filesystem();

$rules_file_path = self::get_waf_file_path( self::RULES_FILE );

$api_exception = null;
$throw_api_exception = true;
try {
Expand All @@ -478,7 +498,7 @@ public static function generate_rules() {
$throw_api_exception = false;
}

if ( $wp_filesystem->exists( self::RULES_FILE ) && $throw_api_exception ) {
if ( $wp_filesystem->exists( $rules_file_path ) && $throw_api_exception ) {
throw $e;
}

Expand All @@ -487,12 +507,12 @@ public static function generate_rules() {
}

// Ensure that the folder exists.
if ( ! $wp_filesystem->is_dir( dirname( self::RULES_FILE ) ) ) {
$wp_filesystem->mkdir( dirname( self::RULES_FILE ) );
if ( ! $wp_filesystem->is_dir( dirname( $rules_file_path ) ) ) {
$wp_filesystem->mkdir( dirname( $rules_file_path ) );
}

$ip_allow_rules = self::ALLOW_IP_FILE;
$ip_block_rules = self::BLOCK_IP_FILE;
$ip_allow_rules = self::get_waf_file_path( self::ALLOW_IP_FILE );
$ip_block_rules = self::get_waf_file_path( self::BLOCK_IP_FILE );

$ip_list_code = "if ( file_exists( '$ip_allow_rules' ) ) { if ( require( '$ip_allow_rules' ) ) { return; } }\n" .
"if ( file_exists( '$ip_block_rules' ) ) { if ( require( '$ip_block_rules' ) ) { return \$waf->block('block', -1, 'ip block list'); } }\n";
Expand All @@ -502,8 +522,8 @@ public static function generate_rules() {

$rules = implode( "\n", $rules_divided_by_line );

if ( ! $wp_filesystem->put_contents( self::RULES_FILE, $rules ) ) {
throw new \Exception( 'Failed writing rules file to: ' . self::RULES_FILE );
if ( ! $wp_filesystem->put_contents( $rules_file_path, $rules ) ) {
throw new \Exception( 'Failed writing rules file to: ' . $rules_file_path );
}

if ( null !== $api_exception && $throw_api_exception ) {
Expand Down Expand Up @@ -550,9 +570,13 @@ public static function generate_ip_rules() {

self::initialize_filesystem();

$rules_file_path = self::get_waf_file_path( self::RULES_FILE );
$allow_ip_file_path = self::get_waf_file_path( self::ALLOW_IP_FILE );
$block_ip_file_path = self::get_waf_file_path( self::BLOCK_IP_FILE );

// Ensure that the folder exists.
if ( ! $wp_filesystem->is_dir( dirname( self::RULES_FILE ) ) ) {
$wp_filesystem->mkdir( dirname( self::RULES_FILE ) );
if ( ! $wp_filesystem->is_dir( dirname( $rules_file_path ) ) ) {
$wp_filesystem->mkdir( dirname( $rules_file_path ) );
}

$allow_list = self::ip_option_to_array( get_option( self::IP_ALLOW_LIST_OPTION_NAME ) );
Expand All @@ -571,8 +595,8 @@ public static function generate_ip_rules() {
// phpcs:enable
$allow_rules_content .= 'return $waf->is_ip_in_array( $waf_allow_list );' . "\n";

if ( ! $wp_filesystem->put_contents( self::ALLOW_IP_FILE, "<?php\n$allow_rules_content" ) ) {
throw new \Exception( 'Failed writing allow list file to: ' . self::ALLOW_IP_FILE );
if ( ! $wp_filesystem->put_contents( $allow_ip_file_path, "<?php\n$allow_rules_content" ) ) {
throw new \Exception( 'Failed writing allow list file to: ' . $allow_ip_file_path );
}

$block_rules_content = '';
Expand All @@ -581,8 +605,8 @@ public static function generate_ip_rules() {
// phpcs:enable
$block_rules_content .= 'return $waf->is_ip_in_array( $waf_block_list );' . "\n";

if ( ! $wp_filesystem->put_contents( self::BLOCK_IP_FILE, "<?php\n$block_rules_content" ) ) {
throw new \Exception( 'Failed writing block list file to: ' . self::BLOCK_IP_FILE );
if ( ! $wp_filesystem->put_contents( $block_ip_file_path, "<?php\n$block_rules_content" ) ) {
throw new \Exception( 'Failed writing block list file to: ' . $block_ip_file_path );
}
}
}