Problem/Motivation

Security issues occur. Security update will be made available at specific times. Not every Drupal user has a 24/7 SLA in place. Sometime people run sites for fun and go on holiday to remote Scottish islands and don't know if they will have internet. Drupal could provide an easy way to put a site into read only mode.

Prior art: https://www.drupal.org/project/readonlymode

Proposed resolution

There are a couple of options:

Option 1: Read only mode

Add a read only module or mode to core or just make it part of system. When enabled the module will redirect all non-safe requests to a read only page whose message can be configured by an administrator and translated via Configuration translation. The module will also strip all user content apart from the url path from the incoming request to prevent attacks where something is possible with query string parameters or cookies.

As noted by @fago this approach currently has the downside or breaking pagers and basic search. We could allow the the page or keys query parameter through after type checking them. However allowing any input is bound to make this mode less secure.

This works via a new state variable that contains a timestamp when in operation. It is a timestamp so that users can schedule a future time when the site becomes read only.

How this differs from maintenance mode

  1. Anonymous users can still still any content they are supposed to. This is one of the purposes of the read only mode - to still provide a working site if a user is not submitting data to it.
  2. No one can login until the mode has been disabled. The user that enables the mode is provided a special link with a token that can disable the mode.

Option 2: Add a site lock option to maintenance mode

Allow the user to further lock the site in maintenance mode and redirect to the maintenance page for all non-cached pages apart from a single page that unlocks the site if you have the right token. In a way this is what option 1 + maintenance mode would be like.

How this differs from maintenance mode

  1. No one can login until the lock has been disabled. The user that enables the lock is provided a special link with a token that can disable the lock.

Remaining tasks

User interface changes

New admin form
Maybe new module

API changes

New service to generate tokens for disabling the read only mode

Data model changes

None

Comments

alexpott created an issue. See original summary.

vijaycs85’s picture

Great idea and a missing solution in core for a long long time. +1!

alexpott’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new18.65 KB

Here's a prototype approach that adds a read only module to core.

webchick’s picture

StatusFileSize
new166.93 KB

I really like this idea; I was in a similar boat yesterday. Obviously, we would like to ultimately do #2367319: Implement automatic background updates for highly critical security issues, but this seems like a nice interim solution.

I guess my only question would be whether or not we can hinge this off the already existing, much-better known/documented "Maintenance mode" setting vs. adding a new module. For example (just a quick mock, doesn't include scheduling options [which would also be nice for maintenance mode actually], and the help text needs work):

A new checkbox that appears after the 'Put site in maintenance mode' checkbox that directs someone to also put site in read-only mode.

One other question I'd have is how does someone go about recovering from "read-only" mode, should they trigger it in the UI (regardless of where it lives)?

alexpott’s picture

I'd be hesitant to mix the concepts since maintenance mode prevents users from seeing content too whereas the purpose of read-only mode is allow access to content but not accept any user input.

One other question I'd have is how does someone go about recovering from "read-only" mode, should they trigger it in the UI (regardless of where it lives)?

At the moment after you submit the form you have a minute after which you can switch back by submitting the form. Also on submit it provides a link that can disable the read only mode and you can just visit that link to disable the mode. The plan is also to email the user that enable the read only mode the link too.

alexpott’s picture

Issue summary: View changes

I've added a section to the issue summary about how this differs from maintenance mode.

alexpott’s picture

Issue summary: View changes
naveenvalecha’s picture

+1 for having this feature in core. It makes lot of sense for bloggers who don't update their sites often even on security releases.

David_Rothstein’s picture

I think this idea may have merit in its own right (as a different kind of maintenance mode that some sites might prefer) but I don't see how this is something the security team could recommend in the general case.

It isn't necessarily more secure than regular maintenance mode, just different. I can think of security issues that it would protect against that maintenance mode wouldn't, but I can also think of security issues (theoretical examples as well as real examples from the past) that it wouldn't protect against, including some that maintenance mode would protect against but that this wouldn't.

One reason for that (although not the only one) is that I don't think it really strips all user input, and probably can't if it wants to actually display the site. Consider the URL of this page: www.drupal.org/project/ideas/issues/2957061. The "2957061" is user input. Any vulnerability that involves a dynamic menu path not being properly validated could still work with a site in read-only mode, right?

In short: Keep the issue, but change the title :)

alexpott’s picture

Title: A read only mode so that the security team could recommend a course of action for sites that cannot be updated at the very moment a security fix is released » A read only mode so that the security team could potentially recommend a course of action for sites that cannot be updated at the very moment a security fix is released

@David_Rothstein good point about path as user input.

I think have both maintenance mode and read only modes are possible mechanisms to mitigate and for the security team recommend is a good idea. Of course we'd prefer if sites were patched immediately but this can not always be done. This particular approach would have mitigated both SA05 and SA02. Also I'm not saying we should recommend anything in the general case but that we should disclose if maintenance mode or read only mode are mitigations in each particular case.

I've added the word "potentially" to the issue title so it's not claiming to be a panacea.

Another idea is that we could add a mode toggle that would only serve pages from page cache. In this mode you'd have to edit settings.php to get out of the read only mode. Which would add another level of security.

alexpott’s picture

Issue summary: View changes
fago’s picture

I really like the idea as well.

Initially, I was thinking this is more about pre-generating html pages, putting into some folder and redirecting requests to it. This would be perfectly fine for lots of small sites but not really feasible for larger sites with lots of content.

So just generating pages as is while stripping down things seems like the better option. However I'm not sure this is much useful when the query is stripped, as lots of stuff like paging or basic searches will be broken. Maybe they could be locked down insteads, so very basic parameters e.g. only having alphanumeric characters and values or even only whitelisted parameters are allowed?

chandeepkhosa’s picture

This is a great idea, would be amazing to see it get implemented!

focus13’s picture

This is amazing idea + 1 to add the module to core.

alexpott’s picture

Issue summary: View changes

I've added another option to the issue summary. All the +1 comments so far have been in favour of option 1.

alexpott’s picture

Issue summary: View changes
alexpott’s picture

Issue summary: View changes
alexpott’s picture

Issue summary: View changes
chandeepkhosa’s picture

Would either option 1 or 2 allow anonymous users to submit contact forms using the Contact or Webform modules?

alexpott’s picture

@ChandeepKhosa no. That's user input.

mpdonadio’s picture

+1 on Option 1. I can see potential uses beyond the security needs.

Some initial thoughts...

  1. +++ b/core/modules/read_only/read_only.info.yml
    @@ -0,0 +1,7 @@
    +description: 'Puts the site into a read only mode.'
    +package: Core
    +version: VERSION
    

    Super dumb question, but why does this need to be standalone and not part of system? And to follow policy, would this have to go in first as an experimental module?

  2. +++ b/core/modules/read_only/read_only.links.menu.yml
    @@ -0,0 +1,6 @@
    +  parent: system.admin_config_development
    

    I know you can't uninstall system.module now, but this introduces a dependency that isn't declared in the .yml

  3. +++ b/core/modules/read_only/src/Controller/ReadOnlyPage.php
    @@ -0,0 +1,114 @@
    +    if ($read_only_enabled === FALSE || $read_only_enabled > $this->time->getCurrentTime()) {
    +      throw new AccessDeniedHttpException();
    +    }
    

    Question, why this and not the AccessInterface API?

  4. +++ b/core/modules/read_only/src/Form/ReadOnlyAdmin.php
    @@ -0,0 +1,149 @@
    +      // @todo @dawehner suggests sending an email to user as well with the link
    +      //   so they have a copy. Good idea.
    

    Yeah, probably all admins to prevent someone from going rogue?

  5. +++ b/core/modules/read_only/src/ReadOnlyMiddleware.php
    @@ -0,0 +1,77 @@
    +        // Redirect to a page to inform the user is in read only mode. Using a
    +        // 303 to indicate this response cannot be cached. See
    +        // https://tools.ietf.org/html/rfc2616#section-10.3.4.
    +        return new RedirectResponse(Url::fromRoute('read_only.page')->toString(), 303);
    

    Nice comment.

dawehner’s picture

I certainly like this general idea! Being able to put it into read only mode would be amazing for brochure/one-off sites like events.drupal.org/paris2009.

Unlike the automated security updates idea, this module would be accepted by more conservative server admins.

  1. +++ b/core/modules/read_only/src/Controller/ReadOnlyPage.php
    @@ -0,0 +1,114 @@
    +    $user = $this->userStorage->load($uid);
    +    if (!$user || $token !== $this->readOnlyToken->get($user)) {
    

    In the spirit of @David_Rothstein's comment, it would be nice to cast it to an int/strip non a-z0-9 character out.

  2. +++ b/core/modules/read_only/src/Controller/ReadOnlyPage.php
    @@ -0,0 +1,114 @@
    +    return $this->redirect('<front>');
    +  }
    

    +1 for not making this a cacheable redirect :)

  3. +++ b/core/modules/read_only/src/ReadOnlyMiddleware.php
    @@ -0,0 +1,77 @@
    +      if (!$request->isMethodSafe(TRUE)) {
    

    Nitpick: swap around the if and else in order to avoid negation

  4. +++ b/core/modules/read_only/src/ReadOnlyMiddleware.php
    @@ -0,0 +1,77 @@
    +      else {
    +        // Do not allow any user input in. Strip $_GET and $_COOKIE.
    +        $request->query->replace([]);
    +        $request->cookies->replace([]);
    +        // This will cleanup $_REQUEST.
    +        $request->overrideGlobals();
    +      }
    

    while reading this I was wondering what happens if you send a GET request with a BODY. Does PHP parse that? It seems like it totally ignores it.

  5. +++ b/core/modules/read_only/src/ReadOnlyToken.php
    @@ -0,0 +1,53 @@
    +    return Crypt::hmacBase64($data, $this->settings->getHashSalt());
    

    Why do we not generate one hash using \Drupal\Component\Utility\Crypt::randomBytes and store this in state too

  6. +++ b/core/modules/read_only/src/ReadOnlyTokenInterface.php
    @@ -0,0 +1,24 @@
    +/**
    + * Interface for the read only token generator.
    + */
    +interface ReadOnlyTokenInterface {
    

    I'm curious, why would someone swap this out?

iyyappan.govind’s picture

+1 Great Idea!!

phenaproxima’s picture

+1 for this. Awesome idea.

wim leers’s picture

+1 for exploring this.

Reminds me of “Boost in core”, which is another idea issue. If the Boost technique were used, pagers would continue to work just fine. And no input would be processed at all!

swentel’s picture

Sometime people run sites for fun and go on holiday to remote Scottish islands and don't know if they will have internet

I'm not running sites for fun :)

alexverb’s picture

I would suggest avoiding to put such functionality in UI if its purely considered a security or deployment measure. If the site is comprimised an active administrator could trigger hidden xss that performs counter measures.

I think the functionality has added value for some use cases, but when taking security measures the last thing you want to do is to make an admin role available to hackers by visiting the site through the browser. And readme mode or maintenance mode in UI can also be used against a live site.

If you can break a site through UI as admin it doesnt belong in UI on production.

aburrows’s picture

Really for supporting this and getting into core would be a great idea.

jcnventura’s picture

I like the general concept, but the details are what trouble me. Would it be possible to be pre-emptive? I.e. authorize the site to go read-only if the update module reports a pending core security release?

adam.weingarten’s picture

@wim-leers,
This approach could replace traditional page-caching in Drupal.... This could have some very intriguing implications.

xem8vfdh’s picture

this is a great idea, I'd love to see it merged when ready!

solideogloria’s picture

borisson_’s picture

Would it be possible to be pre-emptive? I.e. authorize the site to go read-only if the update module reports a pending core security release?

I think this is a good idea but I'm not sure this should be in an initial implementation. It would make it easy to put a few sites lower on the update-list when a security release happens and not have to worry about them as much.

The implementation as-is should probably move to a real issue (in the core queue) where we can give a more in-depth review on the actual code, because that probably doesn't belong here. I just reread this issue and there hasn't really been any negative feedback on this idea, so I think that is the next step to getting this in?

effulgentsia’s picture

The implementation as-is should probably move to a real issue (in the core queue) where we can give a more in-depth review on the actual code, because that probably doesn't belong here... I think that is the next step to getting this in?

As far as I know, there's no requirement for feature requests to go through the entire Ideas process: they could start as implementation issues from the beginning or move there whenever the people doing the work feel like it, and there are plenty of feature requests that have succeeded that way. However, the risk is that without the Idea being fully approved by the product management team, the implementation might go in a direction that will never get accepted into core, and that's demoralizing. https://www.drupal.org/project/ideas has more details on the process and motivation.

I just reread this issue and there hasn't really been any negative feedback on this idea

I agree that it looks like there is pretty good consensus here to do what's in the issue title. I was tempted to RTBC this on those grounds, so that the product management team would review it next time they meet. However, I don't think the issue summary is at a point yet to do that. For example, it mentions two options, but does not state the pros and cons of each option. Also, it says that there's prior art here in https://www.drupal.org/project/readonlymode, however neither of the two options includes some pretty key capabilities that are in that module. For example that project page says that that module includes "Easy to configure whitelist of forms that should remain available", which I'm guessing can include the login form, and that project also includes "a permission that overrides access restrictions and can be given to site administrators". I think the issue summary could more clearly spell out that this proposal intentionally doesn't include that, the security reasons for that, and what that means in terms of use-cases that this would be a good fit for vs. use-cases that would still be required to use that more feature-rich project. For example, comments like #22 and #30 seem to imply that this could be a useful setting to leave on long-term for certain kinds of sites, but I think what's being proposed in the issue summary might be too restrictive for that. Or if it's not, maybe we could add some explanation as to how (and for whom) that could work. For example: the site owner of what is essentially a static site with 0 functionality or private/member-only content could leave this setting on whenever they're not actively editing content; then when they need to edit content, they turn the setting off, make their edits, and then turn it on again? Do we have any data that supports this being an even remotely common use case? I would imagine that those are the kinds of questions that the product management team would be interested in.

effulgentsia’s picture

Here's a start at some thoughts that could maybe make their way into the issue summary...

If I'm reading the issue comments correctly, it looks to me like there are 4 scenarios that this issue could be useful for:

  1. You're building a static site (except for the occasional times that you add or edit content) for yourself, for a friend, or for some small local organization like a book club. You could build it in anything (WordPress, SquareSpace, Jekyll, whatever), but you like Drupal so that's what you want to use. But you're concerned about using Drupal, because you know you won't have the time to (or remember to) keep it security updated every month (or every week if you're using contrib modules). This issue's proposal would allow you to deploy the site in read-only mode, and then whenever you need to add/edit content, you get the codebase up to date, then take the site out of read-only mode, make your edits, put it back into read-only mode, and confidently ignore the site until the next time you need to edit the content.
  2. You have a site that is not a static site initially (like https://events.drupal.org/seattle2019). But at some point, the event passes, but you want to leave a read-only (archived) version of the site still up. This issue's proposal would allow you to just turn on the read-only mode and forget about it (not worry about code updates ever, or at least for a very long time).
  3. You have a site that is not a static site. It has some important functionality that requires logging in for, or submitting forms for. But, you're about to take a vacation and want to be off the grid. So, to mitigate against the possibility of a disclosed security vulnerability while you're away, you put the site into read-only mode, perhaps with a message that lets site visitors know that you're away and when you'll be back. During this time, your site is not fully functional (people aren't buying your products, or whatever your site is there to do), but at least people can read the public content in the meantime.
  4. You have a site where putting it into read-only mode (and restricting all logins) for any noticeable length of time is generally unacceptable. For example, you're a library site where a key piece of functionality is letting members reserve books. However, a Drupal security release is made, but for whatever reason you're not able to apply it right away. Maybe the person who usually does that is sick that day and there's no one else who can do it. Or maybe you hacked core, and your local patches conflict with the security update, and it'll take you a bit of time to resolve that. This issue would let you put the site into read-only mode for a short time while you work on applying the security update. Site visitors (and the organization's management) will be unhappy in the meantime, but it's better than the site getting hacked.

Does that sound about right? Are any of those scenarios incorrect and/or are there others we should add?

solideogloria’s picture

The scenario that I was looking into it for, was that a 3rd party contractor wants to do some digging in a website to see how much work they would need to do to perform some amount of work, such as migrating from Drupal 7 to Drupal 8. It would be far more secure to let them poke around on a read-only version of the site.

solideogloria’s picture

Another scenario: A database upgrade is required, and it requires uninstalling the old version and installing the new. During that period of time, you can switch the site to point to a backup of the database on another machine from right before the upgrade. You don't want any changes to be made, because the database/data may be changed slightly as part of the upgrade process, and the database will simply be switched over and unlocked following the upgrade. Putting the site into read-only mode allows the content to be available but unchanged, provided writing to every table is disabled. This would mean read-only access to the Drupal database.

quietone’s picture

Project: Drupal core ideas » Drupal core
Version: » 11.x-dev
Component: Idea » base system
Related issues: +#3039687: Add an API read-only mode to Drupal core

The Ideas project is being deprecated. This issue is moved to the Drupal project. Check that the selected component is correct. Also, add the relevant tags, especially any 'needs manager review' tags.

needs-review-queue-bot’s picture

Status: Needs review » Needs work

The Needs Review Queue Bot tested this issue.

While you are making the above changes, we recommend that you convert this patch to a merge request. Merge requests are preferred over patches. Be sure to hide the old patch files as well. (Converting an issue to a merge request without other contributions to the issue will not receive credit.)

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.