Problem/Motivation

It would be nice to have an option to whitelist certain IP (e.g. intranet IP) and in order to manipulate the shield settings not by enabling or disable the module per environment but use config override from settings.php (https://www.drupal.org/docs/8/api/configuration-api/configuration-overri...) add a global option disable/enable shield.

Proposed resolution

Add additional form elements to settings form: Enable shield, Whitelist.
shield

Remaining tasks

None.

User interface changes

Added two form elements to settings form: Enable shield, Whitelist.

API changes

None.

Data model changes

None.

Comments

SurfinSpirit created an issue. See original summary.

apugacescu’s picture

StatusFileSize
new4.13 KB
apugacescu’s picture

Status: Needs work » Needs review
kevinquillen’s picture

Wow, totally read my mind. Was just coming here to ask about this for this exact reason. I want to toggle functionality from settings.php for different environments.

apugacescu’s picture

Just found one possible issue with varnish cache when using IP whitelist. Varnish does not cache authorization requests as it should, but if an anonymous user requests a page and his/her IP is in whitelist a page without authorization is served, this page is saved in varnish and server for all anonymous users later.

spurlos’s picture

StatusFileSize
new4.13 KB

Updated patch due to changes in install schema (commit f48a53f0d7a658abbdf4eb68f3877a6ee4d9227b)

phily’s picture

Status: Needs review » Reviewed & tested by the community

Thanks Spurlos, the patch #6 works

douggreen’s picture

Status: Reviewed & tested by the community » Needs work

The whitelist functionality is fine. I don't like the idea of a configuration item to disable it. Shield is to protect a site. If shield is enabled, I expect the site to be protected. I might even add it to a core module info file to prevent it from being disabled. But if we introduce a configuration kill-switch, then this could easily be missed, if/when someone changes the configuration accidentally or maliciously.

apugacescu’s picture

Status: Needs work » Needs review

douggreen
The module can be disabled easily so the fact that "enable/disable" shield is a configuration in this feature request does not lower it's security feature. If you want to protect a site by specifying shield as hard dependency the same can be done using config override to force enable this config option.

In my case with multiple environments for the same site I need to have it enabled/disabled per environment, without this option the only way to have this functionality is write additional code in deployment process that will enable/disable the module after each deployment, using configuration it can be done easily using config override in settings.php

Enable shield option is also present in D7 version of the module (see https://cgit.drupalcode.org/shield/tree/shield.admin.inc#n17) so not having it here looks like a missed feature.

douggreen’s picture

If I write a custom module and add shield as a dependency, it can't be disabled easily. With the enable/disable option though, it will be easy to disable and harder to prevent. I still don't like the feature. I think you can control it for dev/stage/prod simply in your settings.php (use a different one per environment) and set or don't set shield.user and shield.pass.

apugacescu’s picture

douggreen First of all, there is a permission that limits users that have access to the admin page of the shield module so if you feel unsecure just keep this permission only for admins. As you noted shield will stop working if username/password is empty what is the difference for a "bad guy" between unchecking a checkbox and removing username to disable shield is it much easier? If you have a custom module that has shield dependency it does not make it harder to disable, it will just disable your module as well or half of the site if there are multiple dependencies.
Drupal is not about "like" or "don't like" and community should decide if this is a feature that is worth to be used or not.

manningpete’s picture

IP whitelisting for the shield module is a much needed feature for us, and was included in the D7 version of this module. We use config split to enable it on our dev and test environments (or under-development prod enviros) and we often to need to send folks to test features or enter content.

No matter how many times we explain the process, having to "login" to shield then "login" to Drupal is very confusing to virtually every single one of users (PhDs, MDs, and HS grads alike), so having the ability to whitelist our internal IPs makes my life easier. Plus 1 for this patch.

geek-merlin’s picture

Status: Needs review » Reviewed & tested by the community

As a maintainer i give a fullhearted placet to bringing back the enable setting.
Reviewed the code, looks totally straightforward.
Tested in #7, so RTBC.
(Will commit in my next console session.)

❤ Thanks everyone!! ❤

geek-merlin’s picture

Status: Reviewed & tested by the community » Needs work

I think we need an update hook so that a protected site does not lose protection.

apugacescu’s picture

StatusFileSize
new6.68 KB

@axel.rutz Good catch!

Updated the patch based on latest dev version, fixed some minor coding standards issues. Added hook update that sets shield_enable value to TRUE if username value is not empty because according to bypass cases empty username means shield is not active. Added warning info about reverse proxy caching not working as expected if whitelist option is used (see https://www.drupal.org/project/shield/issues/2855364#comment-12012601).

apugacescu’s picture

Status: Needs work » Needs review
amanire’s picture

Does this whitelist only work for IP addresses? I'm looking at the use case of removing basic authentication for CDN requests so we can test it on the staging site.

apugacescu’s picture

amanire

Currently whitelist logic is implemented for end user's IP.

geek-merlin’s picture

Status: Needs review » Needs work
+++ b/shield.install
@@ -28,3 +28,35 @@ function shield_update_8002() {
+function shield_update_8003() {
+  $config = \Drupal::configFactory()->getEditable('shield.settings');
+

Do we really want getEditable (non-overridden config) here? I think it's a nontrivial question. What if an empty username is overridden with a nonempty one? What if a nonempty username is overridden with an empty one? In both cases i tend to take the overridden, effective value into account. Thoughts?

The rest is straightforrard and well-done!

I think if we settle this question, and someone does reasonable manual tests of the new functionalities and the update path, this can well go in!

RumyanaRuseva’s picture

It was not a good idea to include two separate feature changes into the same issue. Now the IP white list cannot be committed before the "enable" switch is decided and vice versa.

  1. Whitelist: In a duplicate issue Whitelisting ip addresses is static - Drupal 8 port the suggested patch implements IP or Subnet Mask listing, instead of just IP. It might be worth considering that implementation.
  2. Enable: I believe the "enable" checkbox is slightly unnecessary, as the functionality can be disabled by leaving the credentials empty. Yet, I agree that having a checkbox is more intuitive, and it also allows temporarily disabling the shield without reseting the credentials.
    Considering that nothing breaks if we check "enable" with an empty username, it's safer to enable than leave a site unprotected.

    Please note that adding the "enable" option should also be described in the documentation, and it will conflict with enable/disable in settings.php

RumyanaRuseva’s picture

Status: Needs work » Needs review
StatusFileSize
new8.38 KB
new9.83 KB

Updated the patch as follows:
1. Changed the update hook to get the effective value instead of the stored value.
2. Added support for Network ranges. Now using Symfony\Component\HttpFoundation\IpUtils to check the IP.
3. Added trim() to allow whitespaces in the list. There is no need for validation of the input, because IpUtils::checkIp() handles that.
4. Changed field descriptions and added instructions in Readme. Added red color to the reverse proxy caching issue warning, because it's very important.

Interdiff file attached.

tcfunk’s picture

StatusFileSize
new9.86 KB

+1 for the trim, was going to mention that as an issue.

I also wonder, how do you feel about a multi-line text field for the IP address, rather than one comma-separated line? I feel like this would be more consistent with how multi-value fields are presented throughout Drupal. It also is much easier to read than one long line of text.

This patch is working off #21, but changes the input to a textarea, and the delimiter to "\n". I've also moved the example values from the description into the placeholder, since it was getting a little messy having a multi-line example in the description.

RumyanaRuseva’s picture

Status: Needs review » Reviewed & tested by the community

Thank you, I agree that a text area is better.
I assume that tcfunk has reviewed the patch from #21, and axel.rutz and I reviewed the previous versions, so I'm setting the issue RTBC.

apugacescu’s picture

22tcfunk
I am concerned about multiple line text instead of commas because of new line separator issue on different OSes. As you might know on windows instead of LF, CR-LF is used, on mac there are cases when only CR is the line separator.

RumyanaRuseva’s picture

Different line endings should not be a problem when exploding by "\n".
When the input uses "\r\n", the lines will be correctly separated, and trim() will remove the "\r".
Line ending with "\r" alone is not standard, and it hasn't been used in Macs since 2001. There is no need to consider it.

There are plenty of examples in core where new lines are exploded by "\n", and nobody has ever complained :)

tcfunk’s picture

I did some digging through core source to see how Drupal handles the multiple-value fields. Looks like it uses the same explode by "\n" method. The method I'm looking at is here:

core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php, line 173

protected static function extractAllowedValues($string, $has_data) {
    $values = [];

    $list = explode("\n", $string);
    $list = array_map('trim', $list);
    $list = array_filter($list, 'strlen');
    ...

If anything it could be worth adding the same $list = array_filter($list, 'strlen'); to our code to filter out empty items.

RumyanaRuseva’s picture

It's already there, just in a single line:
$whitelist = array_filter(array_map('trim', explode("\n", $whitelist)));

anybody’s picture

Confirming RTBC. There are several RTBC'd issues for the D8 branch. Is there an active maintainer able to have a look and create a new release?

geek-merlin’s picture

geek-merlin’s picture

  • axel.rutz committed 1438487 on 8.x-1.x authored by tcfunk
    Issue #2855364 by SurfinSpirit, RumyanaRuseva, tcfunk, Spurlos, axel....
geek-merlin’s picture

Status: Reviewed & tested by the community » Fixed
Related issues: +#3052723: Add whitelist default in shield.settings.yml, +#3052725: Let empty user no more disable shield

> It was not a good idea to include two separate feature changes into the same issue. Now the IP white list cannot be committed before the "enable" switch is decided and vice versa.

Yes.

Finally committed this. Please look into the followups. And a big ❤️ to everybody!

anybody’s picture

Thank you very much Axel! That's really useful, love to see it back in D8.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

kkohlbrenner’s picture

Category: Feature request » Bug report

Tested the patch in #22 https://www.drupal.org/project/shield/issues/2855364#comment-12576428 and observed the following:

     else {
      // Check if user IP is in whitelist.
      $in_whitelist = FALSE;
      if ($whitelist = $config->get('whitelist')) {
        $whitelist = array_filter(array_map('trim', explode("\n", $whitelist)));
        $in_whitelist = IpUtils::checkIp($request->getClientIp(), $whitelist);
      }

This code doesn't check if whitelist is configured. If no Ip's are whitelisted/added to the configuration, $in_whitelist will always be false. Thus, the authentication will always be prompted. If $whitelist is empty, $in_whitelist should be true OR logic shouldn't be processed against it.

$in_whitelist = IpUtils::checkIP($request->getClientIp(), $whitelist)

I think IpUtils::checkIP($request->getClientIp(), $whitelist) introduces unwanted complexity to checking if a ip is included in whitelisted ip configuration. Restrict IP implements similar functionality to whitelist IP's, see here. I've configured ip's successfully using Restrict IP and observed the expected whitelisting. I think there is room to adopt how that module checks ips, here

Next Steps

- Review similar fnctionality used in other contrib modules.
- Create patch

anybody’s picture