mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge branch '3' into 4
This commit is contained in:
commit
c0211927aa
@ -0,0 +1,169 @@
|
||||
title: MySQL SSL Support
|
||||
summary: Setting up MySQL SSL certificates to work with Silverstripe
|
||||
|
||||
# MySQL SSL Support: Why do I need it?
|
||||
|
||||
In a typical Silverstripe set up, you will only need to use a single host to function as the web server, email server, database server, among others.
|
||||
|
||||
In some cases, however, you may be required to connect to a database on a remote host. Connecting to a remote host without SSL encryption exposes your data to [packet sniffing](http://www.linuxjournal.com/content/packet-sniffing-basics) and may compromise the security of your Silverstripe instance.
|
||||
|
||||
This article demonstrates how to generate SSL certificates using MySQL and implementing them in Silverstripe.
|
||||
|
||||
<div class="notice" markdown='1'>
|
||||
This article assumes that you have `MySQL` and `OpenSSL` installed.
|
||||
</div>
|
||||
|
||||
|
||||
## Generating Certificates
|
||||
|
||||
There are three components to an SSL certificate implementation. The first two components are the ***private key***, and the ***public certificate***, which are mathematically-generated, symetrical pieces of the puzzle that allow [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) to work. The third component is the [Certificate Authority (CA) certificate](https://en.wikipedia.org/wiki/Certificate_authority) that signs the pubic key to prove its validity.
|
||||
|
||||
In the case of MySQL, we will need to generate three sets of certificates, namely:
|
||||
|
||||
- the CA key and certificate
|
||||
- the server key and certificate
|
||||
- the client key and certificate
|
||||
|
||||
We also need to sign the certificates with our generated CA.
|
||||
|
||||
The commands below illustrate how to do so on your MySQL host.
|
||||
|
||||
<div class="notice" markdown='1'>
|
||||
The following commands will work on Linux/Unix based servers. For other servers such as windows, refer to the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-ssl-files-using-openssl.html)
|
||||
</div>
|
||||
|
||||
|
||||
:::bash
|
||||
|
||||
# Create directory
|
||||
sudo mkdir ssl
|
||||
cd ssl
|
||||
|
||||
# Generate CA key and CA cert
|
||||
sudo openssl genrsa 2048 | sudo tee -a ca-key.pem
|
||||
sudo openssl req -new -x509 -nodes -days 365000 -key ca-key.pem -out ca-cert.pem
|
||||
|
||||
# Generate SERVER key and server certificate signing request
|
||||
# IMPORTANT: the common name of the certificate should match the domain name of your host!
|
||||
sudo openssl rsa -in server-key.pem -out server-key.pem
|
||||
sudo openssl req -newkey rsa:2048 -days 365000 -nodes -keyout server-key.pem -out server-req.pem
|
||||
|
||||
# Generate and sign SERVER certificate
|
||||
sudo openssl x509 -req -in server-req.pem -days 365000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
|
||||
|
||||
# Generate CLIENT key and certificate signing request
|
||||
sudo openssl rsa -in client-key.pem -out client-key.pem
|
||||
sudo openssl req -newkey rsa:2048 -days 365000 -nodes -keyout client-key.pem -out client-req.pem
|
||||
|
||||
# Generate and sign CLIENT certificate
|
||||
sudo openssl x509 -req -in client-req.pem -days 365000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem
|
||||
|
||||
# Verify validity of generated certificates
|
||||
sudo openssl verify -CAfile ca-cert.pem server-cert.pem client-cert.pem
|
||||
|
||||
<div class="warning" markdown='1'>
|
||||
After generating the certificates, make sure to set the correct permissions to prevent unauthorized access to your keys!
|
||||
|
||||
It is critical that the key files (files ending in *key.pem) are kept secret. Once these files are exposed, you will need to regenerate the certificates to prevent exposing your data traffic.
|
||||
</div>
|
||||
|
||||
:::bash
|
||||
# Set permissions readonly permissions and change owner to root
|
||||
sudo chown root:root *.pem
|
||||
sudo chmod 440 *.pem
|
||||
|
||||
# Server certificates need to be readable by mysql
|
||||
sudo chgrp mysql server*.pem
|
||||
sudo mv *.pem /etc/mysql/ssl
|
||||
|
||||
|
||||
## Setting up MySQL to use SSL certificates
|
||||
|
||||
<div class="notice" markdown='1'>
|
||||
For Debian/Ubuntu instances, the configuration file is usually in `/etc/mysql/my.cnf`. Refer to your MySQL manual for more information
|
||||
</div>
|
||||
|
||||
We must edit the MySQL configuration to use the newly generated certificates.
|
||||
|
||||
Edit your MySQL configuration file as follows.
|
||||
|
||||
|
||||
[mysqld]
|
||||
...
|
||||
ssl-ca=/etc/mysql/ca-cert.pem
|
||||
ssl-cert=/etc/mysql/server-cert.pem
|
||||
ssl-key=/etc/mysql/server-key.pem
|
||||
|
||||
# IMPORTANT! When enabling MySQL remote connections, make sure to take adequate steps to secure your machine from unathorized access!
|
||||
bind-address=0.0.0.0
|
||||
|
||||
<div class="warning" markdown='1'>
|
||||
Enabling remote connections to your MySQL instance introduces various security risks. Make sure to take appropriate steps to secure your instance by using a strong password, disabling MySQL root access, and using a firewall to only accept qualified hosts, for example.
|
||||
</div>
|
||||
|
||||
Make sure to restart your MySQL instance to reflect the changes.
|
||||
|
||||
:::bash
|
||||
sudo service mysql restart
|
||||
|
||||
|
||||
## Setting up Silverstripe to connect to MySQL
|
||||
|
||||
Now that we have successfully setup the SSL your MySQL host, we now need to configure Silverstripe to use the certificates.
|
||||
|
||||
### Copying SSL Certificates
|
||||
|
||||
First we need to copy the client certificate files to the Silverstripe instance. You will need to copy:
|
||||
|
||||
- `client-key.pem`
|
||||
- `client-cert.pem`
|
||||
- `ca-cert.pem`
|
||||
|
||||
<div class="warning" markdown='1'>
|
||||
Make sure to only copy `client-key.pem`, `client-cert.pem`, and `ca-cert.pem` to avoid leaking your credentials!
|
||||
</div>
|
||||
|
||||
On your Silverstripe instance:
|
||||
|
||||
:::bash
|
||||
# Secure copy over SSH via rsync command. You may use an alternative method if desired.
|
||||
rsync -avP user@db1.example.com:/path/to/client/certs /path/to/secure/folder
|
||||
|
||||
# Depending on your web server configuration, allow web server to read to SSL files
|
||||
sudo chown -R www-data:www-data /path/to/secure/folder
|
||||
sudo chmod 750 /path/to/secure/folder
|
||||
sudo chmod 400 /path/to/secure/folder/*
|
||||
|
||||
### Setting up _ss_environment.php to use SSL certificates
|
||||
|
||||
<div class="notice" markdown='1'>
|
||||
`SS_DATABASE_SERVER does not accept IP-based hostnames. Also, if the domain name of the host does not match the common name you used to generate the server certificate, you will get an `SSL certificate mismatch error`.
|
||||
</div>
|
||||
|
||||
Add or edit your `_ss_environment.php` configuration file. (See [Environment Management](/getting_started/environment_management) for more information.)
|
||||
|
||||
:::php
|
||||
<?php
|
||||
|
||||
// These four define set the database connection details.
|
||||
define('SS_DATABASE_CLASS', 'MySQLPDODatabase');
|
||||
|
||||
define('SS_DATABASE_SERVER', 'db1.example.com');
|
||||
define('SS_DATABASE_USERNAME', 'dbuser');
|
||||
define('SS_DATABASE_PASSWORD', '<password>');
|
||||
|
||||
// These define the paths to the SSL key, certificate, and CA certificate bundle.
|
||||
define('SS_DATABASE_SSL_KEY', '/home/newdrafts/mysqlssltest/client-key.pem');
|
||||
define('SS_DATABASE_SSL_CERT', '/home/newdrafts/mysqlssltest/client-cert.pem');
|
||||
define('SS_DATABASE_SSL_CA', '/home/newdrafts/mysqlssltest/ca- cert.pem');
|
||||
|
||||
// When using SSL connections, you also need to supply a username and password to override the default settings
|
||||
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
||||
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
||||
|
||||
|
||||
When running the installer, make sure to check on the `Use _ss_environment file for configuration` option under the `Database Configuration` section to use the environment file.
|
||||
|
||||
## Conclusion
|
||||
|
||||
That's it! We hope that this article was able to help you configure your remote MySQL SSL secure database connection.
|
@ -85,3 +85,8 @@ SilverStripe core environment variables are listed here, though you're free to d
|
||||
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name |
|
||||
| `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file |
|
||||
| `SS_BASE_URL` | The url to use when it isn't determinable by other means (eg: for CLI commands) |
|
||||
| `SS_CONFIGSTATICMANIFEST` | Set to `SS_ConfigStaticManifest_Reflection` to use the Silverstripe 4 Reflection config manifest (speed improvement during dev/build and ?flush) |
|
||||
| `SS_DATABASE_SSL_KEY` | Absolute path to SSL key file |
|
||||
| `SS_DATABASE_SSL_CERT` | Absolute path to SSL certificate file |
|
||||
| `SS_DATABASE_SSL_CA` | Absolute path to SSL Certificate Authority bundle file |
|
||||
| `SS_DATABASE_SSL_CIPHER` | Optional setting for custom SSL cipher |
|
||||
|
@ -6,6 +6,9 @@ use mysqli;
|
||||
use PDO;
|
||||
use Exception;
|
||||
use mysqli_result;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\ORM\Connect\MySQLiConnector;
|
||||
use SilverStripe\ORM\Connect\PDOConnector;
|
||||
|
||||
/**
|
||||
* This is a helper class for the SS installer.
|
||||
@ -30,11 +33,29 @@ class MySQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
|
||||
try {
|
||||
switch ($databaseConfig['type']) {
|
||||
case 'MySQLDatabase':
|
||||
$conn = @new MySQLi(
|
||||
$conn = mysqli_init();
|
||||
|
||||
// Set SSL parameters if they exist. All parameters are required.
|
||||
if (array_key_exists('ssl_key', $databaseConfig) &&
|
||||
array_key_exists('ssl_cert', $databaseConfig) &&
|
||||
array_key_exists('ssl_ca', $databaseConfig)
|
||||
) {
|
||||
$conn->ssl_set(
|
||||
$databaseConfig['ssl_key'],
|
||||
$databaseConfig['ssl_cert'],
|
||||
$databaseConfig['ssl_ca'],
|
||||
dirname($databaseConfig['ssl_ca']),
|
||||
array_key_exists('ssl_cipher', $databaseConfig)
|
||||
? $databaseConfig['ssl_cipher']
|
||||
: Config::inst()->get(MySQLiConnector::class, 'ssl_cipher_default')
|
||||
);
|
||||
}
|
||||
@$conn->real_connect(
|
||||
$databaseConfig['server'],
|
||||
$databaseConfig['username'],
|
||||
$databaseConfig['password']
|
||||
);
|
||||
|
||||
if ($conn && empty($conn->connect_errno)) {
|
||||
$conn->query("SET sql_mode = 'ANSI'");
|
||||
return $conn;
|
||||
@ -46,10 +67,31 @@ class MySQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
|
||||
}
|
||||
case 'MySQLPDODatabase':
|
||||
// May throw a PDOException if fails
|
||||
|
||||
// Set SSL parameters
|
||||
$ssl = null;
|
||||
|
||||
if (array_key_exists('ssl_key', $databaseConfig) &&
|
||||
array_key_exists('ssl_cert', $databaseConfig)
|
||||
) {
|
||||
$ssl = array(
|
||||
PDO::MYSQL_ATTR_SSL_KEY => $databaseConfig['ssl_key'],
|
||||
PDO::MYSQL_ATTR_SSL_CERT => $databaseConfig['ssl_cert'],
|
||||
);
|
||||
if (array_key_exists('ssl_ca', $databaseConfig)) {
|
||||
$ssl[PDO::MYSQL_ATTR_SSL_CA] = $databaseConfig['ssl_ca'];
|
||||
}
|
||||
// use default cipher if not provided
|
||||
$ssl[PDO::MYSQL_ATTR_SSL_CA] = array_key_exists('ssl_ca', $databaseConfig)
|
||||
? $databaseConfig['ssl_ca']
|
||||
: Config::inst()->get(PDOConnector::class, 'ssl_cipher_default');
|
||||
}
|
||||
|
||||
$conn = @new PDO(
|
||||
'mysql:host='.$databaseConfig['server'],
|
||||
$databaseConfig['username'],
|
||||
$databaseConfig['password']
|
||||
$databaseConfig['password'],
|
||||
$ssl
|
||||
);
|
||||
if ($conn) {
|
||||
$conn->query("SET sql_mode = 'ANSI'");
|
||||
|
@ -138,6 +138,18 @@ if (isset($_REQUEST['db'])) {
|
||||
"password" => getenv('SS_DATABASE_PASSWORD') ?: "",
|
||||
"database" => $_REQUEST['db'][$type]['database'],
|
||||
);
|
||||
|
||||
// Set SSL parameters if they exist
|
||||
if (getenv('SS_DATABASE_SSL_KEY') && getenv('SS_DATABASE_SSL_CERT')) {
|
||||
$databaseConfig['ssl_key'] = getenv('SS_DATABASE_SSL_KEY');
|
||||
$databaseConfig['ssl_cert'] = getenv('SS_DATABASE_SSL_CERT');
|
||||
}
|
||||
if (getenv('SS_DATABASE_SSL_CA')) {
|
||||
$databaseConfig['ssl_ca'] = getenv('SS_DATABASE_SSL_CA');
|
||||
}
|
||||
if (getenv('SS_DATABASE_SSL_CIPHER')) {
|
||||
$databaseConfig['ssl_ca'] = getenv('SS_DATABASE_SSL_CIPHER');
|
||||
}
|
||||
} else {
|
||||
// Normal behaviour without the environment
|
||||
$databaseConfig = $_REQUEST['db'][$type];
|
||||
|
@ -440,7 +440,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
|
||||
{
|
||||
$filename = ClassLoader::inst()->getItemPath(static::class);
|
||||
if (!$filename) {
|
||||
throw new LogicException("getItemPath returned null for " . static::class);
|
||||
throw new LogicException("getItemPath returned null for " . static::class
|
||||
. ". Try adding flush=1 to the test run.");
|
||||
}
|
||||
return dirname($filename);
|
||||
}
|
||||
|
@ -69,6 +69,13 @@ class TreeDropdownField extends FormField
|
||||
'tree'
|
||||
);
|
||||
|
||||
/**
|
||||
* @config
|
||||
* @var int
|
||||
* @see {@link Hierarchy::$node_threshold_total}.
|
||||
*/
|
||||
private static $node_threshold_total = 30;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -488,7 +495,12 @@ class TreeDropdownField extends FormField
|
||||
}
|
||||
|
||||
// Create marking set
|
||||
$markingSet = MarkedSet::create($obj, $this->getChildrenMethod(), $this->getNumChildrenMethod(), 30);
|
||||
$markingSet = MarkedSet::create(
|
||||
$obj,
|
||||
$this->getChildrenMethod(),
|
||||
$this->getNumChildrenMethod(),
|
||||
$this->config()->get('node_threshold_total')
|
||||
);
|
||||
|
||||
// Set filter on searched nodes
|
||||
if ($this->getFilterFunction() || $this->search) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\ORM\Connect;
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\View\Parsers\SQLFormatter;
|
||||
|
||||
/**
|
||||
@ -11,6 +12,8 @@ use SilverStripe\View\Parsers\SQLFormatter;
|
||||
abstract class DBConnector
|
||||
{
|
||||
|
||||
use Configurable;
|
||||
|
||||
/**
|
||||
* List of operations to treat as write
|
||||
* Implicitly includes all ddl_operations
|
||||
|
@ -12,6 +12,14 @@ use mysqli_stmt;
|
||||
class MySQLiConnector extends DBConnector
|
||||
{
|
||||
|
||||
/**
|
||||
* Default strong SSL cipher to be used
|
||||
*
|
||||
* @config
|
||||
* @var string
|
||||
*/
|
||||
private static $ssl_cipher_default = 'DHE-RSA-AES256-SHA';
|
||||
|
||||
/**
|
||||
* Connection to the MySQL database
|
||||
*
|
||||
@ -68,23 +76,31 @@ class MySQLiConnector extends DBConnector
|
||||
$connCharset = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'connection_charset');
|
||||
$connCollation = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'connection_collation');
|
||||
|
||||
if (!empty($parameters['port'])) {
|
||||
$this->dbConn = new MySQLi(
|
||||
$parameters['server'],
|
||||
$parameters['username'],
|
||||
$parameters['password'],
|
||||
$selectedDB,
|
||||
$parameters['port']
|
||||
);
|
||||
} else {
|
||||
$this->dbConn = new MySQLi(
|
||||
$parameters['server'],
|
||||
$parameters['username'],
|
||||
$parameters['password'],
|
||||
$selectedDB
|
||||
$this->dbConn = mysqli_init();
|
||||
|
||||
// Set SSL parameters if they exist. All parameters are required.
|
||||
if (array_key_exists('ssl_key', $parameters) &&
|
||||
array_key_exists('ssl_cert', $parameters) &&
|
||||
array_key_exists('ssl_ca', $parameters)) {
|
||||
$this->dbConn->ssl_set(
|
||||
$parameters['ssl_key'],
|
||||
$parameters['ssl_cert'],
|
||||
$parameters['ssl_ca'],
|
||||
dirname($parameters['ssl_ca']),
|
||||
array_key_exists('ssl_cipher', $parameters)
|
||||
? $parameters['ssl_cipher']
|
||||
: self::config()->get('ssl_cipher_default')
|
||||
);
|
||||
}
|
||||
|
||||
$this->dbConn->real_connect(
|
||||
$parameters['server'],
|
||||
$parameters['username'],
|
||||
$parameters['password'],
|
||||
$selectedDB,
|
||||
!empty($parameters['port']) ? $parameters['port'] : ini_get("mysqli.default_port")
|
||||
);
|
||||
|
||||
if ($this->dbConn->connect_error) {
|
||||
$this->databaseError("Couldn't connect to MySQL database | " . $this->dbConn->connect_error);
|
||||
}
|
||||
|
@ -21,6 +21,14 @@ class PDOConnector extends DBConnector
|
||||
*/
|
||||
private static $emulate_prepare = false;
|
||||
|
||||
/**
|
||||
* Default strong SSL cipher to be used
|
||||
*
|
||||
* @config
|
||||
* @var string
|
||||
*/
|
||||
private static $ssl_cipher_default = 'DHE-RSA-AES256-SHA';
|
||||
|
||||
/**
|
||||
* The PDO connection instance
|
||||
*
|
||||
@ -171,6 +179,20 @@ class PDOConnector extends DBConnector
|
||||
$options = array(
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . $charset . ' COLLATE ' . $connCollation
|
||||
);
|
||||
|
||||
// Set SSL options if they are defined
|
||||
if (array_key_exists('ssl_key', $parameters) &&
|
||||
array_key_exists('ssl_cert', $parameters)
|
||||
) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_KEY] = $parameters['ssl_key'];
|
||||
$options[PDO::MYSQL_ATTR_SSL_CERT] = $parameters['ssl_cert'];
|
||||
if (array_key_exists('ssl_ca', $parameters)) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CA] = $parameters['ssl_ca'];
|
||||
}
|
||||
// use default cipher if not provided
|
||||
$options[PDO::MYSQL_ATTR_SSL_CIPHER] = array_key_exists('ssl_cipher', $parameters) ? $parameters['ssl_cipher'] : self::config()->get('ssl_cipher_default');
|
||||
}
|
||||
|
||||
if (self::is_emulate_prepare()) {
|
||||
$options[PDO::ATTR_EMULATE_PREPARES] = true;
|
||||
}
|
||||
|
5
templates/forms/HtmlEditorField_holder_small.ss
Normal file
5
templates/forms/HtmlEditorField_holder_small.ss
Normal file
@ -0,0 +1,5 @@
|
||||
<div class="fieldholder-small field htmleditor">
|
||||
<% if $Title %><label class="fieldholder-small-label" <% if $ID %>for="$ID"<% end_if %>>$Title</label><% end_if %>
|
||||
$Field
|
||||
<% if $RightTitle %><label class="right fieldholder-small-label" <% if $ID %>for="$ID"<% end_if %>>$RightTitle</label><% end_if %>
|
||||
</div>
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\Security\Tests;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\NullHTTPRequest;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
@ -42,7 +43,6 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
$this->defaultUsername = null;
|
||||
$this->defaultPassword = null;
|
||||
}
|
||||
DefaultAdminService::clearDefaultAdmin();
|
||||
DefaultAdminService::setDefaultAdmin('admin', 'password');
|
||||
}
|
||||
|
||||
@ -149,6 +149,37 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
);
|
||||
}
|
||||
|
||||
public function testExpiredTempID()
|
||||
{
|
||||
DefaultAdminService::clearDefaultAdmin();
|
||||
|
||||
$authenticator = new CMSMemberAuthenticator();
|
||||
|
||||
// Make member with expired TempID
|
||||
$member = new Member();
|
||||
$member->Email = 'test1@test.com';
|
||||
$member->PasswordEncryption = "sha1";
|
||||
$member->Password = "mypassword";
|
||||
$member->TempIDExpired = '2016-05-22 00:00:00';
|
||||
$member->write();
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($member, true);
|
||||
|
||||
$tempID = $member->TempIDHash;
|
||||
|
||||
DBDatetime::set_mock_now('2016-05-29 00:00:00');
|
||||
|
||||
$this->assertNotEmpty($tempID);
|
||||
$this->assertFalse(DefaultAdminService::hasDefaultAdmin());
|
||||
|
||||
$result = $authenticator->authenticate(array(
|
||||
'tempid' => $tempID,
|
||||
'Password' => 'notmypassword'
|
||||
), Controller::curr()->getRequest(), $validationResult);
|
||||
|
||||
$this->assertNull($result);
|
||||
$this->assertFalse($validationResult->isValid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the default admin can be authenticated
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user