Requests for comment/MediaWiki HTTPS policy

This is a follow-up / extension of the login security request for comments.

Request for comment (RFC)
MediaWiki HTTPS policy
Component Security
Creation date
Author(s) CSteipp (WMF)
Document status in draft

Although this RFC is specific to https connections and cookie handling, the security policy framework can eventually apply to password, two-factor authentication, and captcha policies.

Background

edit

The Wikimedia Foundation transitioned to use $wgSecureLogin for nearly all sites in August 2013. CentralAuth had several updates to ensure that the site and user preferences around HTTP versus HTTPS were respected throughout the central login as well.

Some sites need to handle a few special use-cases in their HTTPS policy:

  • Most users should enter their password into an HTTPS page, and submit the password via HTTPS
  • Some users want to opt out of using HTTPS after they have logged in, by setting a user preference
  • User from China and Iran should not be required to use HTTPS for login
  • Wikimedia may potentially want to prevent some users of Wikipedia Zero from encrypting their traffic, as many ISPs will not zero-rate encrypted traffic

Additionally, in the future:

  • Some projects only want to use HTTPS, see m:HTTPS/Beta program
  • We would like the option to force users with privileged accounts to use HTTPS for their logged in session

Problem

edit

The http vs https handling code was added into MediaWiki at a time when using http to login was common, and using https was considered the exception. The code is fairly brittle (bugzilla:71716, etc.), and not always consistent between core and CentralAuth. It's also difficult to know what the expected behavior is in the interactions between:

  • The site enforcing $wgSecureLogin, and for CentralAuth, if the central login wiki is configured different from the local wiki
  • The user option opting into using a secure connection while logged in
  • The IP-based blacklist to prevent redirecting users to HTTPS
  • The hook to override redirecting to a secure connection when the 'forceHTTPS' cookie is set, used by Zero
  • Whether the user clicked the login link from an HTTP or HTTPS page

Additionally, MediaWiki has no way to specify that a privileged user (local administrator, CheckUser, etc.) must use a secure connection, and potentially login using an insecure session.

Proposal

edit

Make the use of HTTP or HTTPS governed by a set of site, user, and ip-based policies, and allow the user to set their own preferences when allowed by the policy.

The policies should try to cover nearly every case that site administrators want to enforce, however the policy mechanism should be extensible by extensions that want to make different decisions or decisions based on other factors (e.g., the use of two-factor authentication for login).

The decisions to use HTTP versus HTTPS for login and authenticated sessions, setting cookies with the 'secure' flag, setting the 'forceHTTPS' cookie, etc would be governed by the following policies. The default policies and ordering will be:

  1. Site policy
  2. IP-based policy
  3. Group-based policy
  4. User preference, if allowed by the other policies.

However, the ordering can be configured by adjusting the integer order of the policies, and policies with a particular order can be added by extensions.

These policies would specify whether https (or 'secure' flag for cookies) is required for the following situations:

  • User login page
  • Logged in sessions
  • Session cookies (session, user, and optionally the token cookies)
  • Whether to send the forceHTTPS cookie, and whether to redirect when it's received via http
  • Whether to send HSTS headers

For example, when an anonymous user is rendering the login link on a page (determining HTTP versus HTTPS), MediaWiki will first check the site policy to see if using a secure login page is enforced. It will next check if there any IP-based policies based on the incoming IP address. Since the user is anonymous, no user-based policies will apply. If an anonymous user preference is implemented to adjust HTTP versus HTTPS login pages, then that preference would be consulted last. During this process, each policy is able to indicate that it's decision is final, and further policies will not be consulted about the decision.

Defining the security policy will be done with arrays, to keep it simple and similar to other authn/z configurations. Something like

// Configure a default security policy for this wiki
$wgSiteSecurityPolicyConfig = array(
  'secure-redirect' => array(
    'login-page' => true,
    'logged-in-user' => true,
    'anon-with-forcehttps' => true,
  ),
  'cookies' => array(
    'set-secure' => true,
    'set-forcehttps' => true,
  ),
  'hsts' => array(
    'hsts-timeout' => 1209600,
  ),
  'enforce' => array(
     'secure-redirect' => function ( $policy ) {
        $output = $policy->getContext()->getOutput();
        $output->addVaryHeader( 'X-Forwarded-Proto' );
        $output->redirect(
          preg_replace(
            '#^http://#',
            'https://',
            $policy->getContext()->getRequest()->getFullRequestURL()
          )
        );
      }
    )
  ),
);

$wgSecurityPolicyOrder = array(
  'site' => 0,
  'ip' => 25,
  'group' => 50,
  'user' => 75,
);

Code to make the decision when to use HTTPS, or set secure cookies, would do something like:

$securityPolicy = $context->getSecurityPolicy();

// Check if the policy says we should use https
$useHttps = $securityPolicy->connection->useHttps( $title );

// or just allow the callbacks to enforce the policy
$secuirtyPolicy->connection->enforce();


$setSecureCookie = $securityPolicy->cookies->setSecure();
$setForceHTTPS = $securityPolicy->cookies->setForceHTTPS()

MediaWiki would lazily setup the policy when requested from the RequestContext with something like:

function getSecurityPolicy() {
  if ( !$this->securityPolicy ) {
    $pf = new SecurityPolicyFactory( $this );
    $policy = $pf->getSecurityPolicy();
    $policy->addSitePolicy(  $wgSiteSecurityPolicyConfig );
    $policy->addIPPolicy( SecurityPolicy::getIPPolicy( $ip ) );
    $policy->addUserPolicy( SecurityPolicy::getUserPolicy( $user ) );
    wfRunHooks( 'SecurityPolicySetup',             $policy, $user, $ip, $title );
    $this->securityPolicy = $policy;
  }
  return $this->securityPolicy;
}

Extensions will be able to add policies, enforced at any ordering through the SecurityPolicySetup hook.

Extensions that add new individual or categories of policies can also add callback functions to:

  • Test if the current RequestContext is in compliance with the policy
  • Enforce compliance, called when the check for compliance determines the current request is out of policy

Defaults

edit

Questions for the RFC

edit
  • Is the policy config expressive enough, or should we use something else?
  • In the policy config, should we define a callback that enforces the policy? So then code can just call something like $policy->connection->enforce(), and the connection policies have a callback that handles the redirect, if the user doesn't comply with the policy.