Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: MySQL database backends with SSL #15

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/guide/dhcp4-srv.xml
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,20 @@ be followed by a comma and another object definition.</para>
</screen>
If there is no password to the account, set the password to the empty string
"". (This is also the default.)</para>
Also supported for MySQL database backends are the following ssl parameters which directly apply to the corresponding parameters in
<ulink url="https://dev.mysql.com/doc/refman/5.6/en/mysql-ssl-set.html"/>:
<screen>
"Dhcp4": {
"lease-database": {
<userinput>"ssl_key": <replaceable>/etc/ssl/private/some.key</replaceable></userinput>,
<userinput>"ssl_cert": <replaceable>/etc/ssl/some.cert</replaceable></userinput>,
<userinput>"ssl_ca": <replaceable>/etc/ssl/certs/ca.pem</replaceable></userinput>,
<userinput>"ssl_capath": <replaceable>/etc/ssl/certs/</replaceable></userinput>,
<userinput>"ssl_cipher": <replaceable>some cipher string</replaceable></userinput>,
... },
... }
</screen>
SSL behavior is opt-in and the default values are <replaceable>NULL</replaceable>. So if none of the above is set, the connection is done in plain.
</section>
</section>

Expand Down
4 changes: 2 additions & 2 deletions src/lib/dhcpsrv/alloc_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,7 @@ AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeo
skipped = callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP;
}

/// @todo: Maybe add support for DROP stauts?
/// @todo: Maybe add support for DROP status?
/// Not sure if we need to support every possible status everywhere.

if (!skipped) {
Expand Down Expand Up @@ -1589,7 +1589,7 @@ AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeo
skipped = callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP;
}

/// @todo: Maybe add support for DROP stauts?
/// @todo: Maybe add support for DROP status?
/// Not sure if we need to support every possible status everywhere.

if (!skipped) {
Expand Down
75 changes: 59 additions & 16 deletions src/lib/dhcpsrv/mysql_connection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ const my_bool MLM_TRUE = 1; ///< True value

void
MySqlConnection::openDatabase() {

// Set up the values of the parameters
const char* host = "localhost";
string shost;
Expand Down Expand Up @@ -89,6 +88,31 @@ MySqlConnection::openDatabase() {
isc_throw(NoDatabaseName, "must specified a name for the database");
}

setupConnectionOptions();
setupSSL();
MYSQL* status;
status = realConnect(host, user, password, name);
if (status != mysql_) {
isc_throw(DbOpenError, mysql_error(mysql_));
}
}

MYSQL* MySqlConnection::realConnect(const char* host, const char* user,
const char* password, const char* name) {
// Open the database.
//
// The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
// the affected rows are the number of rows found that match the
// WHERE clause of the SQL statement, not the rows changed. The reason
// here is that MySQL apparently does not update a row if data has not
// changed and so the "affected rows" (retrievable from MySQL) is zero.
// This makes it hard to distinguish whether the UPDATE changed no rows
// because no row matching the WHERE clause was found, or because a
// row was found but no data was altered.
return mysql_real_connect(mysql_, host, user, password, name,
0, NULL, CLIENT_FOUND_ROWS);
}
void MySqlConnection::setupConnectionOptions() {
// Set options for the connection:
//
// Automatic reconnection: after a period of inactivity, the client will
Expand All @@ -111,25 +135,44 @@ MySqlConnection::openDatabase() {
isc_throw(DbOpenError, "unable to set SQL mode options: " <<
mysql_error(mysql_));
}
}

// Open the database.
//
// The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
// the affected rows are the number of rows found that match the
// WHERE clause of the SQL statement, not the rows changed. The reason
// here is that MySQL apparently does not update a row if data has not
// changed and so the "affected rows" (retrievable from MySQL) is zero.
// This makes it hard to distinguish whether the UPDATE changed no rows
// because no row matching the WHERE clause was found, or because a
// row was found but no data was altered.
MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
0, NULL, CLIENT_FOUND_ROWS);
if (status != mysql_) {
isc_throw(DbOpenError, mysql_error(mysql_));
void
MySqlConnection::setupSSL() {
const char* ssl_key = NULL;
const char* ssl_cert = NULL;
const char* ssl_ca = NULL;
const char* ssl_capath = NULL;
const char* ssl_cipher = NULL;

try {
ssl_key = getParameter("ssl_key").c_str();
} catch (...) {
// This SSL option might not be set.
}
try {
ssl_cert = getParameter("ssl_cert").c_str();
} catch (...) {
// This SSL option might not be set.
}
try {
ssl_ca = getParameter("ssl_ca").c_str();
} catch (...) {
// This SSL option might not be set.
}
try {
ssl_capath = getParameter("ssl_capath").c_str();
} catch (...) {
// This SSL option might not be set.
}
try {
ssl_cipher = getParameter("ssl_cipher").c_str();
} catch (...) {
// This SSL option might not be set.
}
mysql_ssl_set(mysql_,ssl_key,ssl_cert,ssl_ca,ssl_capath,ssl_cipher);
}


// Prepared statement setup. The textual form of an SQL statement is stored
// in a vector of strings (text_statements_) and is used in the output of
// error messages. The SQL statement is also compiled into a "prepared
Expand Down
41 changes: 34 additions & 7 deletions src/lib/dhcpsrv/mysql_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,23 +102,24 @@ class MySqlHolder : public boost::noncopyable {
/// Initialize MySql and store the associated context object.
///
/// @throw DbOpenError Unable to initialize MySql handle.
MySqlHolder() : mysql_(mysql_init(NULL)) {
if (mysql_ == NULL) {
isc_throw(DbOpenError, "unable to initialize MySQL");
}
MySqlHolder() {
setup();
}

/// @brief Destructor
///
/// Frees up resources allocated by the initialization of MySql.
~MySqlHolder() {
if (mysql_ != NULL) {
mysql_close(mysql_);
}
clean();
// The library itself shouldn't be needed anymore
mysql_library_end();
}

void reset() {
clean();
setup();
}

/// @brief Conversion Operator
///
/// Allows the MySqlHolder object to be passed as the context argument to
Expand All @@ -128,6 +129,18 @@ class MySqlHolder : public boost::noncopyable {
}

private:
void clean() {
if (mysql_ != NULL) {
mysql_close(mysql_);
}
}
void setup() {
mysql_ = mysql_init(NULL);
if (mysql_ == NULL) {
isc_throw(DbOpenError, "unable to initialize MySQL");
}
}

MYSQL* mysql_; ///< Initialization context
};

Expand Down Expand Up @@ -206,6 +219,20 @@ class MySqlConnection : public DatabaseConnection {
/// This field is public, because it is used heavily from MySqlConnection
/// and will be from MySqlHostDataSource.
MySqlHolder mysql_;

private:
/// @brief opens the actual connection using the a mysql function.
MYSQL* realConnect(const char* host, const char* user,
const char* password, const char* name);

/// @brief Setup options on the MySQL connection.
void setupConnectionOptions();

/// @brief Setup SSL for the database connection
///
/// When sufficient options are given in the objects parameterMap,
/// the connection is setup with SSL.
void setupSSL();
};


Expand Down