WordPress, SSL und Load-Balancing

WordPress1 ist ein weit verbreitetes Weblog- oder kurz Blog-System2. Einige würden sogar sagen, DAS Blog-System. WordPress als OpenSource-Projekt3 besticht durch seine Kontinuität und die Möglichkeit zur Implementierung von Plugins und Themes, die zu tausenden schon entstanden und meist frei verfügbar sind. Dieser Blog basiert auch auf WordPress.

Um die technische Kommunikation zwischen dem Browser des Bloggers und der WordPress-Installation auf einem Server zu sichern bedient sich WordPress SSL4, zu erkennen an dem „https“ in der URL. Ein entsprechend gültiges und auf dem Server installiertes SSL-Zertifikat vorausgesetzt, lässt sich mit folgenden Konfigurationseinstellungen5 eine sichere Verbindung für das Login oder den gesamten Administrationszugang von WordPress erzwingen.

Für ein gesichertes Login:

1
define('FORCE_SSL_LOGIN', true);
define('FORCE_SSL_LOGIN', true);

Für den gesicherten Administrationsbereich (schließt Login mit ein):

1
define('FORCE_SSL_ADMIN', true);
define('FORCE_SSL_ADMIN', true);

Sind diese Einstellungen in der Konfigurationsdatei wp-config.php gesetzt, „überwacht“ WordPress die eingehenden Anfragen und leitet diese ggf. auf https um (redirect) bzw. wandelt alle angezeigten Links in https-Links um. Diese Funktionalität funktioniert ohne Plugins. Sie ist Bestandteil des WordPress-Core.

Problematisch wird dies, wenn eine WordPress-Installation (zumindest Version 3.1) über einen Load-Balancer6 ausgeliefert wird, auf dem direkt das SSL-Zertifikat installiert ist und der „nach hinten“ zu den eigentlichen WordPress-Installation (-Servern) nur http spricht. Dann ergibt sich nämlich folgendes Problem einer Totschleife:

  1. wordpress_lb_sslDer Browser fragt per https und landet mit seiner Anfrage beim Load-Balancer.
  2. Dieser gibt die Anfrage per http an eine der parallelen WordPress-Installationen weiter.
  3. Die WordPress-Installation merkt, dass hier nicht per https angefragt wird und „befiehlt“ (redirect) dem Browser auf https anzufragen.
  4. Der Browser fragt per https und … siehe 1.

Damit ist ein per SSL abgesicherter Administrationsbereich von WordPress mit dieser Load-Balancing-Konfiguration nicht zu erreichen.

Die Installation des SSL-Zertifikates auf den einzelnen WordPress-Servern war für uns keine Option, da u.a. die Server einerseits mit verschiedenen anderen Domänen auch per https umgehen müssen und dies ein Wildcard-SSL-Zertifikat voraussetzen würde, welches wir (zumindest zum damaligen Zeitpunkt) nicht erhalten haben. Darüber hinaus hätte dies den Wartungs- und Konfigurationsaufwand der Webserver unverhältnismäßig erhöht. Was also tun?

Mit einem kleinen Eingriff (4 kleine Code-Änderungen in insgesamt 4 Dateien vom WordPress-Core), der im Folgenden beschrieben wird, haben wir das Problem gelöst. Die Idee der Problemlösung ist, dass WordPress zwar alle Links als https anbietet, aber kein Redirect erzwungen wird, wenn doch mal eine Anfrage auf http ankommt.

wp-config.php

Hier kann wahlweise eine der folgenden Konstanten bzw. Einstellungsoptionen definiert werden.

1
2
define('USE_SSL_LOGIN', true);
define('USE_SSL_ADMIN', true);
define('USE_SSL_LOGIN', true);
define('USE_SSL_ADMIN', true);

 

wp-includes/functions.php

In die functions.php müssen zwei neue Funktionen an beliebiger Stelle eingefügt werden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Whether SSL login should be used.
 *
 * @since patch 01.11.2010
 *
 * @param string|bool $use Optional.
 * @return bool True if used, false if not used.
 */
function use_ssl_login( $use = null ) {
    static $used = false;
 
    if ( !is_null( $use ) ) {
        $old_used = $used;
        $used = $use;
        return $old_used;
    }
 
    return $used;
}
 
/**
 * Whether to use SSL used for the Administration Panels.
 *
 * @since patch 01.11.2010
 *
 * @param string|bool $use
 * @return bool True if used, false if not used.
 */
function use_ssl_admin( $use = null ) {
    static $used = false;
 
    if ( !is_null( $use ) ) {
        $old_used = $used;
        $used = $use;
        return $old_used;
    }
 
    return $used;
}
/**
 * Whether SSL login should be used.
 *
 * @since patch 01.11.2010
 *
 * @param string|bool $use Optional.
 * @return bool True if used, false if not used.
 */
function use_ssl_login( $use = null ) {
    static $used = false;

    if ( !is_null( $use ) ) {
        $old_used = $used;
        $used = $use;
        return $old_used;
    }

    return $used;
}

/**
 * Whether to use SSL used for the Administration Panels.
 *
 * @since patch 01.11.2010
 *
 * @param string|bool $use
 * @return bool True if used, false if not used.
 */
function use_ssl_admin( $use = null ) {
    static $used = false;

    if ( !is_null( $use ) ) {
        $old_used = $used;
        $used = $use;
        return $old_used;
    }

    return $used;
}

 

wp-includes/default-constants.php

Hier gilt es die Funktion wp_ssl_constants anzupassen bzw. zu erweitern (siehe hervorgehobenen Bereich).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122232425262728293031323334
/**
 * Defines cookie related WordPress constants
 *
 * @since 3.0.0
 */
function wp_ssl_constants( ) {
   /**
    * @since 2.6.0
    */
   if ( !defined('FORCE_SSL_ADMIN') )
      define('FORCE_SSL_ADMIN', false);
   force_ssl_admin(FORCE_SSL_ADMIN);
 
   /**
    * @since 2.6.0
    */
   if ( !defined('FORCE_SSL_LOGIN') )
      define('FORCE_SSL_LOGIN', false);
   force_ssl_login(FORCE_SSL_LOGIN);
 
    /**     * @since patch 01.11.2010     */    if ( !defined('USE_SSL_LOGIN') )        define('USE_SSL_LOGIN', false);    use_ssl_login(USE_SSL_LOGIN);     /**     * @since patch 01.11.2010     */    if ( !defined('USE_SSL_ADMIN') )        define('USE_SSL_ADMIN', false);    use_ssl_admin(USE_SSL_ADMIN);}
/**
 * Defines cookie related WordPress constants
 *
 * @since 3.0.0
 */
function wp_ssl_constants( ) {
   /**
    * @since 2.6.0
    */
   if ( !defined('FORCE_SSL_ADMIN') )
      define('FORCE_SSL_ADMIN', false);
   force_ssl_admin(FORCE_SSL_ADMIN);

   /**
    * @since 2.6.0
    */
   if ( !defined('FORCE_SSL_LOGIN') )
      define('FORCE_SSL_LOGIN', false);
   force_ssl_login(FORCE_SSL_LOGIN);

    /**
     * @since patch 01.11.2010
     */
    if ( !defined('USE_SSL_LOGIN') )
        define('USE_SSL_LOGIN', false);
    use_ssl_login(USE_SSL_LOGIN);

    /**
     * @since patch 01.11.2010
     */
    if ( !defined('USE_SSL_ADMIN') )
        define('USE_SSL_ADMIN', false);
    use_ssl_admin(USE_SSL_ADMIN);
}

 

wp-includes/link-template.php

Hier muss die Funktion get_site_url beim ersten IF angepasst werden (siehe hervorgehobenen Bereich).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2021
2223
2425
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Retrieve the site url for a given site.
 *
 * Returns the 'site_url' option with the appropriate protocol,  'https' if
 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
 * overridden.
 *
 * @package WordPress
 * @since 3.0.0
 *
 * @param int $blog_id (optional) Blog ID. Defaults to current blog.
 * @param string $path Optional. Path relative to the site url.
 * @param string $scheme Optional. Scheme to give the site url context. Currently 'http','https', 'login', 'login_post', or 'admin'.
 * @return string Site url link with optional path appended.
*/
function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
   // should the list of allowed schemes be maintained elsewhere?
   $orig_scheme = $scheme;
   if ( !in_array( $scheme, array( 'http', 'https' ) ) ) {
      if ( ( 'login_post' == $scheme || 'rpc' == $scheme ) && ( force_ssl_login() || force_ssl_admin() || use_ssl_login() || use_ssl_admin() ) )         $scheme = 'https';
      elseif ( ( 'login' == $scheme ) && ( force_ssl_admin() || use_ssl_admin() ) )         $scheme = 'https';
      elseif ( ( 'admin' == $scheme ) && ( force_ssl_admin() || use_ssl_admin() ) )         $scheme = 'https';
      else
         $scheme = ( is_ssl() ? 'https' : 'http' );
   }
 
   if ( empty( $blog_id ) || !is_multisite() )
      $url = get_option( 'siteurl' );
   else
      $url = get_blog_option( $blog_id, 'siteurl' );
 
   if ( 'http' != $scheme )
      $url = str_replace( 'http://', "{$scheme}://", $url );
 
   if ( !empty( $path ) && is_string( $path ) && strpos( $path, '..' ) === false )
      $url .= '/' . ltrim( $path, '/' );
 
   return apply_filters( 'site_url', $url, $path, $orig_scheme, $blog_id );
}
/**
 * Retrieve the site url for a given site.
 *
 * Returns the 'site_url' option with the appropriate protocol,  'https' if
 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
 * overridden.
 *
 * @package WordPress
 * @since 3.0.0
 *
 * @param int $blog_id (optional) Blog ID. Defaults to current blog.
 * @param string $path Optional. Path relative to the site url.
 * @param string $scheme Optional. Scheme to give the site url context. Currently 'http','https', 'login', 'login_post', or 'admin'.
 * @return string Site url link with optional path appended.
*/
function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
   // should the list of allowed schemes be maintained elsewhere?
   $orig_scheme = $scheme;
   if ( !in_array( $scheme, array( 'http', 'https' ) ) ) {
      if ( ( 'login_post' == $scheme || 'rpc' == $scheme ) && ( force_ssl_login() || force_ssl_admin() || use_ssl_login() || use_ssl_admin() ) )
         $scheme = 'https';
      elseif ( ( 'login' == $scheme ) && ( force_ssl_admin() || use_ssl_admin() ) )
         $scheme = 'https';
      elseif ( ( 'admin' == $scheme ) && ( force_ssl_admin() || use_ssl_admin() ) )
         $scheme = 'https';
      else
         $scheme = ( is_ssl() ? 'https' : 'http' );
   }

   if ( empty( $blog_id ) || !is_multisite() )
      $url = get_option( 'siteurl' );
   else
      $url = get_blog_option( $blog_id, 'siteurl' );

   if ( 'http' != $scheme )
      $url = str_replace( 'http://', "{$scheme}://", $url );

   if ( !empty( $path ) && is_string( $path ) && strpos( $path, '..' ) === false )
      $url .= '/' . ltrim( $path, '/' );

   return apply_filters( 'site_url', $url, $path, $orig_scheme, $blog_id );
}

Das Problem und die Lösung hab ich (selbstverständlich) als Feature Request und Patch7 an die WordPress-OpenSource-Entwicklung geschickt.

Achtung: Grundsätzlich öffnet sich hier eine Sicherheitslücke, da WordPress ja trotz „nutze bitte SSL“ auch auf nur http reagiert; wobei der Nutzer selbständig immer das „s“ aus der URL entfernen muss bzw. automatisierte Skripte, die mit der Web-Oberfläche von WordPress arbeiten, ggf. nicht „abhör sicher“ sind. Diesem sollte man sich bewusst sein. In unserer Umgebung kommt diese Lücke nicht zum Tragen, da der Load-Balancer selbst alle http-Anfragen direkt auf https umlenkt.

  1. http://www.wordpress.org []
  2. http://de.wikipedia.org/wiki/Blog []
  3. http://de.wikipedia.org/wiki/WordPress []
  4. http://de.wikipedia.org/wiki/Transport_Layer_Security []
  5. http://codex.wordpress.org/Administration_Over_SSL []
  6. http://de.wikipedia.org/wiki/Load_Balancer []
  7. http://core.trac.wordpress.org/ticket/15277 []

2 thoughts on “WordPress, SSL und Load-Balancing

  1. S. Böttcher

    Hallo,

    wie sieht es bei dieser Lösung mit WordPress-Updates aus? werden die Änderungen damit überschrieben? Die Infrastruktur bei uns sieht ähnlich aus (LB leitet anfragen per http weiter), daher sind wir auf der Suche nach einer Lösung.

    Gruß,

    S. Böttcher

    Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.