diff --git a/projects/packages/waf/changelog/add-protect-standalone-brute-force-access b/projects/packages/waf/changelog/add-protect-standalone-brute-force-access new file mode 100644 index 0000000000000..987d00afe53f1 --- /dev/null +++ b/projects/packages/waf/changelog/add-protect-standalone-brute-force-access @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add brute force protection access for particular environments that do not support the WAF diff --git a/projects/packages/waf/composer.json b/projects/packages/waf/composer.json index 83383bffb0ba9..093d95799528d 100644 --- a/projects/packages/waf/composer.json +++ b/projects/packages/waf/composer.json @@ -60,7 +60,7 @@ "link-template": "https://github.com/Automattic/jetpack-waf/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.11.x-dev" + "dev-trunk": "0.12.x-dev" } }, "config": { diff --git a/projects/packages/waf/src/class-brute-force-protection.php b/projects/packages/waf/src/class-brute-force-protection.php index 1437821a1624d..4c426d3437b9d 100644 --- a/projects/packages/waf/src/class-brute-force-protection.php +++ b/projects/packages/waf/src/class-brute-force-protection.php @@ -212,6 +212,10 @@ public static function is_enabled() { * @return bool */ public static function enable() { + // Return true if already enabled. + if ( self::is_enabled() ) { + return true; + } return ( new Modules() )->activate( 'protect', false, false ); } @@ -221,6 +225,10 @@ public static function enable() { * @return bool */ public static function disable() { + // Return true if already disabled. + if ( ! self::is_enabled() ) { + return true; + } return ( new Modules() )->deactivate( 'protect' ); } diff --git a/projects/packages/waf/src/class-compatibility.php b/projects/packages/waf/src/class-compatibility.php index f20c619e6fc96..4404a556fdbeb 100644 --- a/projects/packages/waf/src/class-compatibility.php +++ b/projects/packages/waf/src/class-compatibility.php @@ -33,6 +33,11 @@ public static function add_compatibility_hooks() { /** * Run compatibility migrations. * + * Note that this method should be compatible with sites where + * the request firewall is not active or not supported. + * + * @see Waf_Runner::is_supported_environment(). + * * @since 0.11.0 * * @return void diff --git a/projects/packages/waf/src/class-rest-controller.php b/projects/packages/waf/src/class-rest-controller.php index 599184ff3fa43..d2ad497eb0d7a 100644 --- a/projects/packages/waf/src/class-rest-controller.php +++ b/projects/packages/waf/src/class-rest-controller.php @@ -145,10 +145,13 @@ public static function update_waf( $request ) { } } - try { - Waf_Runner::update_waf(); - } catch ( Waf_Exception $e ) { - return $e->get_wp_error(); + // Only attempt to update the WAF if the module is supported + if ( Waf_Runner::is_supported_environment() ) { + try { + Waf_Runner::update_waf(); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); + } } return self::waf(); diff --git a/projects/packages/waf/src/class-waf-initializer.php b/projects/packages/waf/src/class-waf-initializer.php index 823446b59823a..11891fe34910b 100644 --- a/projects/packages/waf/src/class-waf-initializer.php +++ b/projects/packages/waf/src/class-waf-initializer.php @@ -35,22 +35,18 @@ public static function init() { // Ensure backwards compatibility Waf_Compatibility::add_compatibility_hooks(); - // Register REST routes. + // Register REST routes add_action( 'rest_api_init', array( new REST_Controller(), 'register_rest_routes' ) ); - // Run the WAF on supported environments - if ( Waf_Runner::is_supported_environment() ) { - // Update the WAF after installing or upgrading a relevant Jetpack plugin - add_action( 'upgrader_process_complete', __CLASS__ . '::update_waf_after_plugin_upgrade', 10, 2 ); - add_action( 'admin_init', __CLASS__ . '::check_for_waf_update' ); + // Update the WAF after installing or upgrading a relevant Jetpack plugin + add_action( 'upgrader_process_complete', __CLASS__ . '::update_waf_after_plugin_upgrade', 10, 2 ); - // WAF activation/deactivation hooks - add_action( 'jetpack_activate_module_waf', __CLASS__ . '::on_waf_activation' ); - add_action( 'jetpack_deactivate_module_waf', __CLASS__ . '::on_waf_deactivation' ); + // Check for compatibility updates + add_action( 'admin_init', __CLASS__ . '::check_for_updates' ); - // Run the WAF - Waf_Runner::initialize(); - } + // WAF activation/deactivation hooks + add_action( 'jetpack_activate_module_waf', __CLASS__ . '::on_waf_activation' ); + add_action( 'jetpack_deactivate_module_waf', __CLASS__ . '::on_waf_deactivation' ); // Brute force protection activation/deactivation hooks add_action( 'jetpack_activate_module_protect', __CLASS__ . '::on_brute_force_protection_activation' ); @@ -58,6 +54,11 @@ public static function init() { // Run brute force protection Brute_Force_Protection::initialize(); + + // Run the WAF + if ( Waf_Runner::is_supported_environment() ) { + Waf_Runner::initialize(); + } } /** @@ -164,31 +165,37 @@ public static function update_waf_after_plugin_upgrade( $upgrader, $hook_extra ) * * @return bool|WP_Error True if the WAF is up-to-date or was sucessfully updated, WP_Error if the update failed. */ - public static function check_for_waf_update() { + public static function check_for_updates() { if ( get_option( self::NEEDS_UPDATE_OPTION_NAME ) ) { - // Compatiblity patch for cases where an outdated WAF_Constants class has been - // autoloaded by the standalone bootstrap execution at the beginning of the current request. - if ( ! method_exists( Waf_Constants::class, 'define_mode' ) ) { + if ( Waf_Runner::is_supported_environment() ) { + // Compatiblity patch for cases where an outdated WAF_Constants class has been + // autoloaded by the standalone bootstrap execution at the beginning of the current request. + if ( ! method_exists( Waf_Constants::class, 'define_mode' ) ) { + try { + ( new Waf_Standalone_Bootstrap() )->generate(); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); + } + } + + Waf_Compatibility::run_compatibility_migrations(); + + Waf_Constants::define_mode(); + if ( ! Waf_Runner::is_allowed_mode( JETPACK_WAF_MODE ) ) { + return new WP_Error( 'waf_mode_invalid', 'Invalid firewall mode.' ); + } + try { + Waf_Rules_Manager::generate_ip_rules(); + Waf_Rules_Manager::generate_rules(); ( new Waf_Standalone_Bootstrap() )->generate(); } catch ( Waf_Exception $e ) { return $e->get_wp_error(); } - } - - Waf_Compatibility::run_compatibility_migrations(); - - Waf_Constants::define_mode(); - if ( ! Waf_Runner::is_allowed_mode( JETPACK_WAF_MODE ) ) { - return new WP_Error( 'waf_mode_invalid', 'Invalid firewall mode.' ); - } - - try { - Waf_Rules_Manager::generate_ip_rules(); - Waf_Rules_Manager::generate_rules(); - ( new Waf_Standalone_Bootstrap() )->generate(); - } catch ( Waf_Exception $e ) { - return $e->get_wp_error(); + } else { + // If the site doesn't support the request firewall, + // just migrate the IP allow list used by brute force protection. + Waf_Compatibility::migrate_brute_force_protection_ip_allow_list(); } } diff --git a/projects/plugins/debug-helper/changelog/add-protect-standalone-brute-force-access b/projects/plugins/debug-helper/changelog/add-protect-standalone-brute-force-access new file mode 100644 index 0000000000000..987d00afe53f1 --- /dev/null +++ b/projects/plugins/debug-helper/changelog/add-protect-standalone-brute-force-access @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add brute force protection access for particular environments that do not support the WAF diff --git a/projects/plugins/debug-helper/modules/class-waf-helper.php b/projects/plugins/debug-helper/modules/class-waf-helper.php index ab8f67155311f..3540ddff7685c 100644 --- a/projects/plugins/debug-helper/modules/class-waf-helper.php +++ b/projects/plugins/debug-helper/modules/class-waf-helper.php @@ -182,7 +182,7 @@ public function render_ui() {

Status

-

Environment is supported:

+

WAF is supported:

Firewall status:


diff --git a/projects/plugins/debug-helper/plugin.php b/projects/plugins/debug-helper/plugin.php index 54d83de771ce6..fceae1d0fb534 100644 --- a/projects/plugins/debug-helper/plugin.php +++ b/projects/plugins/debug-helper/plugin.php @@ -3,7 +3,7 @@ * Plugin Name: Jetpack Debug Tools * Description: Give me a Jetpack connection, and I'll break it every way possible. * Author: Automattic - Jetpack Crew - * Version: 1.6.1-alpha + * Version: 1.7.0-alpha * Text Domain: jetpack * * @package automattic/jetpack-debug-helper. @@ -33,7 +33,7 @@ * The plugin version. * Increase that if you do any edits to ensure refreshing the cached assets. */ -define( 'JETPACK_DEBUG_HELPER_VERSION', '1.6.1-alpha' ); +define( 'JETPACK_DEBUG_HELPER_VERSION', '1.7.0-alpha' ); /** * Include file names from the modules directory here. diff --git a/projects/plugins/jetpack/changelog/add-protect-standalone-brute-force-access b/projects/plugins/jetpack/changelog/add-protect-standalone-brute-force-access new file mode 100644 index 0000000000000..a1c1831fa1ef7 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-protect-standalone-brute-force-access @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: Updated composer.lock. + + diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index abd490f4c0dba..f10f75b420997 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -2658,7 +2658,7 @@ "dist": { "type": "path", "url": "../../packages/waf", - "reference": "278b09f381778a035f26eeb7a0905e3e87533114" + "reference": "233fe943efc72d8b0fcdd036a2467bb9c9710676" }, "require": { "automattic/jetpack-connection": "@dev", @@ -2684,7 +2684,7 @@ "link-template": "https://github.com/Automattic/jetpack-waf/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.11.x-dev" + "dev-trunk": "0.12.x-dev" } }, "autoload": { diff --git a/projects/plugins/protect/changelog/add-protect-standalone-brute-force-access b/projects/plugins/protect/changelog/add-protect-standalone-brute-force-access new file mode 100644 index 0000000000000..987d00afe53f1 --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-standalone-brute-force-access @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add brute force protection access for particular environments that do not support the WAF diff --git a/projects/plugins/protect/changelog/add-protect-standalone-brute-force-access#2 b/projects/plugins/protect/changelog/add-protect-standalone-brute-force-access#2 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-standalone-brute-force-access#2 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index f6a96ee3f7850..b290643053c90 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -1415,7 +1415,7 @@ "dist": { "type": "path", "url": "../../packages/waf", - "reference": "278b09f381778a035f26eeb7a0905e3e87533114" + "reference": "233fe943efc72d8b0fcdd036a2467bb9c9710676" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1441,7 +1441,7 @@ "link-template": "https://github.com/Automattic/jetpack-waf/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.11.x-dev" + "dev-trunk": "0.12.x-dev" } }, "autoload": { diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 12e6fb87e62b2..89a1aa4826eda 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -13,6 +13,7 @@ use Automattic\Jetpack\Assets; use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\IP\Utils as IP_Utils; use Automattic\Jetpack\JITMS\JITM as JITM; use Automattic\Jetpack\Modules; use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer; @@ -218,7 +219,8 @@ public function initial_state() { 'jetpackScan' => My_Jetpack_Products::get_product( 'scan' ), 'hasRequiredPlan' => Plan::has_required_plan(), 'waf' => array( - 'isSupported' => Waf_Runner::is_supported_environment(), + 'wafSupported' => Waf_Runner::is_supported_environment(), + 'currentIp' => IP_Utils::get_ip(), 'isSeen' => self::get_waf_seen_status(), 'upgradeIsSeen' => self::get_waf_upgrade_seen_status(), 'displayUpgradeBadge' => self::get_waf_upgrade_badge_display_status(), diff --git a/projects/plugins/protect/src/class-rest-controller.php b/projects/plugins/protect/src/class-rest-controller.php index 465f679d8a651..a1f3632233911 100644 --- a/projects/plugins/protect/src/class-rest-controller.php +++ b/projects/plugins/protect/src/class-rest-controller.php @@ -12,6 +12,7 @@ use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication; use Automattic\Jetpack\Waf\Waf_Runner; use Jetpack_Protect; +use WP_Error; use WP_REST_Response; /** diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index b551c122f716d..31ec5ff5325fd 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -17,7 +17,7 @@ import useRegistrationWatcher from './use-registration-watcher'; const AdminPage = ( { children } ) => { useRegistrationWatcher(); - const { isSupported: wafSupported, isSeen: wafSeen } = useWafData(); + const { isSeen: wafSeen } = useWafData(); const { refreshPlan, startScanOptimistically, refreshStatus } = useDispatch( STORE_ID ); const { adminUrl } = window.jetpackProtectInitialState || {}; const { run, isRegistered, hasCheckoutStarted } = useProductCheckoutWorkflow( { @@ -54,19 +54,17 @@ const AdminPage = ( { children } ) => { - { wafSupported && ( - - { __( 'Firewall', 'jetpack-protect' ) } - { wafSeen === false && ( - { __( 'New', 'jetpack-protect' ) } - ) } - - } - /> - ) } + + { __( 'Firewall', 'jetpack-protect' ) } + { wafSeen === false && ( + { __( 'New', 'jetpack-protect' ) } + ) } + + } + /> { children } diff --git a/projects/plugins/protect/src/js/components/firewall-header/index.jsx b/projects/plugins/protect/src/js/components/firewall-header/index.jsx index 317a41f9349ec..74883ee205389 100644 --- a/projects/plugins/protect/src/js/components/firewall-header/index.jsx +++ b/projects/plugins/protect/src/js/components/firewall-header/index.jsx @@ -95,16 +95,17 @@ const FirewallSubheading = ( { jetpackWafIpList, jetpackWafAutomaticRules, bruteForceProtectionIsEnabled, + wafSupported, } ) => { - const allRules = jetpackWafAutomaticRules && jetpackWafIpList; - const automaticRules = jetpackWafAutomaticRules && ! jetpackWafIpList; - const manualRules = ! jetpackWafAutomaticRules && jetpackWafIpList; - const noRules = ! jetpackWafAutomaticRules && ! jetpackWafIpList; + const allRules = wafSupported && jetpackWafAutomaticRules && jetpackWafIpList; + const automaticRules = wafSupported && jetpackWafAutomaticRules && ! jetpackWafIpList; + const manualRules = wafSupported && ! jetpackWafAutomaticRules && jetpackWafIpList; + const noRules = wafSupported && ! jetpackWafAutomaticRules && ! jetpackWafIpList; return ( <>
- { bruteForceProtectionIsEnabled && ( + { wafSupported && bruteForceProtectionIsEnabled && ( { return ( @@ -166,13 +168,15 @@ const FirewallHeader = ( { { __( 'Active', 'jetpack-protect' ) }

- { automaticRulesEnabled - ? __( 'Automatic firewall is on', 'jetpack-protect' ) - : __( - 'Firewall is on', - 'jetpack-protect', - /* dummy arg to avoid bad minification */ 0 - ) } + { ! wafSupported && __( 'Brute force protection is active', 'jetpack-protect' ) } + { wafSupported && + ( automaticRulesEnabled + ? __( 'Automatic firewall is on', 'jetpack-protect' ) + : __( + 'Firewall is on', + 'jetpack-protect', + /* dummy arg to avoid bad minification */ 0 + ) ) }

) } @@ -188,14 +193,16 @@ const FirewallHeader = ( { { __( 'Inactive', 'jetpack-protect' ) } -

- { automaticRulesAvailable - ? __( 'Automatic firewall is off', 'jetpack-protect' ) - : __( - 'Firewall is off', - 'jetpack-protect', - /* dummy arg to avoid bad minification */ 0 - ) } +

+ { ! wafSupported && __( 'Brute force protection is disabled', 'jetpack-protect' ) } + { wafSupported && + ( automaticRulesAvailable + ? __( 'Automatic firewall is off', 'jetpack-protect' ) + : __( + 'Firewall is off', + 'jetpack-protect', + /* dummy arg to avoid bad minification */ 0 + ) ) }

) } @@ -235,10 +243,13 @@ const ConnectedFirewallHeader = () => { bruteForceProtection, }, isToggling, + wafSupported, } = useWafData(); const { hasRequiredPlan } = useProtectData(); const currentStatus = - jetpackWafAutomaticRules || jetpackWafIpList || bruteForceProtection ? 'on' : 'off'; + ( wafSupported && ( jetpackWafAutomaticRules || jetpackWafIpList ) ) || bruteForceProtection + ? 'on' + : 'off'; return ( { jetpackWafIpList={ jetpackWafIpList } jetpackWafAutomaticRules={ jetpackWafAutomaticRules } bruteForceProtectionIsEnabled={ bruteForceProtection } + wafSupported={ wafSupported } /> ); }; diff --git a/projects/plugins/protect/src/js/components/firewall-page/index.jsx b/projects/plugins/protect/src/js/components/firewall-page/index.jsx index c320c7457e812..870adf37657af 100644 --- a/projects/plugins/protect/src/js/components/firewall-page/index.jsx +++ b/projects/plugins/protect/src/js/components/firewall-page/index.jsx @@ -14,8 +14,7 @@ import { createInterpolateElement } from '@wordpress/element'; import { __, sprintf, _n } from '@wordpress/i18n'; import { Icon, arrowLeft, closeSmall } from '@wordpress/icons'; import moment from 'moment'; -import { useCallback, useEffect, useState } from 'react'; -import { Navigate } from 'react-router-dom'; +import { useCallback, useEffect, useState, useMemo } from 'react'; import API from '../../api'; import { JETPACK_SCAN_SLUG, PLUGIN_SUPPORT_URL } from '../../constants'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; @@ -27,6 +26,7 @@ import FirewallFooter from '../firewall-footer'; import ConnectedFirewallHeader from '../firewall-header'; import FormToggle from '../form-toggle'; import Notice from '../notice'; +import ScanFooter from '../scan-footer'; import Textarea from '../textarea'; import styles from './styles.module.scss'; @@ -46,20 +46,20 @@ const FirewallPage = () => { automaticRulesAvailable, bruteForceProtection, }, + currentIp, isEnabled, isSeen, upgradeIsSeen, displayUpgradeBadge, - isSupported, + wafSupported, isUpdating, - stats, + stats: { ipAllowListCount, ipBlockListCount, rulesVersion, automaticRulesLastUpdated }, toggleAutomaticRules, toggleManualRules, toggleBruteForceProtection, toggleWaf, updateConfig, } = useWafData(); - const { ipAllowListCount, ipBlockListCount, rulesVersion, automaticRulesLastUpdated } = stats; const { hasRequiredPlan } = useProtectData(); const { run: runCheckoutWorkflow } = useProductCheckoutWorkflow( { productSlug: JETPACK_SCAN_SLUG, @@ -160,7 +160,7 @@ const FirewallPage = () => { ); /** - * Save Changes + * Save WAF Changes * * Updates the WAF settings with the current form state values. * @@ -252,9 +252,9 @@ const FirewallPage = () => { ] ); /** - * Handle Automatic Rules Change + * Handle Brute Force Protection Change * - * Toggles the WAF's automatic rules option. + * Toggles the brute force protection module. * * @returns void */ @@ -345,6 +345,32 @@ const FirewallPage = () => { API.wafUpgradeSeen(); }, [ setWafUpgradeIsSeen ] ); + /** + * Checks if the current IP address is allow listed. + * + * @returns {boolean} - Indicates whether the current IP address is allow listed. + */ + const isCurrentIpAllowed = useMemo( () => { + return formState.jetpack_waf_ip_allow_list.includes( currentIp ); + }, [ formState.jetpack_waf_ip_allow_list, currentIp ] ); + + /** + * Adds the current IP address to the IP allow list. + * + * @returns {void} + */ + const addCurrentIpToAllowList = useCallback( () => { + const updatedList = + formState.jetpack_waf_ip_allow_list.length > 0 + ? `${ formState.jetpack_waf_ip_allow_list }\n${ currentIp }` + : currentIp; + + setFormState( prevState => ( { + ...prevState, + jetpack_waf_ip_allow_list: updatedList, + } ) ); + }, [ formState.jetpack_waf_ip_allow_list, currentIp ] ); + /** * Sync formState with application state WAF config */ @@ -413,10 +439,10 @@ const FirewallPage = () => { ); /** - * Main Settings + * Automatic Firewall Rules Settings */ - const mainSettings = ( -
+ const automaticRulesSettings = ( + <>
{ />
) } + + ); + + const bruteForceAllowListSettings = ( + <> +
+