From bf44cd0d14d250f7ef4a63b2b589dc93c59e3773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Tue, 2 May 2017 07:12:44 +0300 Subject: [PATCH] MXS-1220: Add HTTPS support The REST API now supports encryption. The user needs to configure certificates for the REST API before encryption is used. --- .../Getting-Started/Configuration-Guide.md | 83 ++++++++++++------- include/maxscale/config.h | 6 ++ server/core/admin.cc | 80 +++++++++++++++++- server/core/config.cc | 18 ++++ server/core/httprequest.cc | 5 +- server/core/maxscale/admin.hh | 7 ++ 6 files changed, 167 insertions(+), 32 deletions(-) diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index 524a1dbbb..b7d22f438 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -172,35 +172,6 @@ write or modify the data in the backend server. The default is 2 seconds. auth_write_timeout=10 ``` -#### `admin_host` - -The network interface where the HTTP admin interface listens on. The default -value is the IPv6 address `::` which listens on all available network -interfaces. - -#### `admin_port` - -The port where the HTTP admin interface listens on. The default value is port -8080. - -#### `admin_auth` - -Enable HTTP admin interface authentication using HTTP Basic Access -authentication. This is not a secure method of authentication but it does add a -small layer of security. This option id disabled by default. - -#### `admin_user` - -The HTTP admin interface username. This is the username which is used when -_admin_auth_ is enabled. The default user for the HTTP admin interface is -`admin`. - -#### `admin_password` - -The HTTP admin interface password. This is the which which is used when -_admin_auth_ is enabled. The default password for the HTTP admin interface is -`mariadb`. - #### `ms_timestamp` Enable or disable the high precision timestamps in logfiles. Enabling this adds @@ -548,6 +519,60 @@ This will log all statements that cannot be parsed completely. This may be useful if you suspect that MariaDB MaxScale routes statements to the wrong server (e.g. to a slave instead of to a master). +### REST API Configuration + +The MaxScale REST API is an HTTP interface that provides JSON format data +intended to be consumed by monitoring appllications and visualization tools. + +The following options must be defined under the `[maxscale]` section in the +configuration file. + +#### `admin_host` + +The network interface where the HTTP admin interface listens on. The default +value is the IPv6 address `::` which listens on all available network +interfaces. + +#### `admin_port` + +The port where the HTTP admin interface listens on. The default value is port +8080. + +#### `admin_auth` + +Enable HTTP admin interface authentication using HTTP Basic Access +authentication. This is not a secure method of authentication but it does add a +small layer of security. This option id disabled by default. + +#### `admin_user` + +The HTTP admin interface username. This is the username which is used when +_admin_auth_ is enabled. The default user for the HTTP admin interface is +`admin`. + +#### `admin_password` + +The HTTP admin interface password. This is the which which is used when +_admin_auth_ is enabled. The default password for the HTTP admin interface is +`mariadb`. + +#### `admin_ssl_key` + +The path to the TLS private key in PEM format for the admin interface. + +If the `admin_ssl_key`, `admin_ssl_cert` and `admin_ssl_ca_cert` options are all +defined, the admin interface will use encrypted HTTPS instead of plain HTTP. + +#### `admin_ssl_cert` + +The path to the TLS public certificate in PEM format. See `admin_ssl_key` +documentation for more details. + +#### `admin_ssl_ca_cert` + +The path to the TLS CA certificate in PEM format. See `admin_ssl_key` +documentation for more details. + ### Service A service represents the database service that MariaDB MaxScale offers to the diff --git a/include/maxscale/config.h b/include/maxscale/config.h index 4a25ff04e..b6f3b38f8 100644 --- a/include/maxscale/config.h +++ b/include/maxscale/config.h @@ -48,6 +48,9 @@ extern const char CN_ADMIN_HOST[]; extern const char CN_ADMIN_PASSWORD[]; extern const char CN_ADMIN_PORT[]; extern const char CN_ADMIN_USER[]; +extern const char CN_ADMIN_SSL_KEY[]; +extern const char CN_ADMIN_SSL_CERT[]; +extern const char CN_ADMIN_SSL_CA_CERT[]; extern const char CN_AUTHENTICATOR[]; extern const char CN_AUTHENTICATOR_OPTIONS[]; extern const char CN_AUTH_ALL_SERVERS[]; @@ -162,6 +165,9 @@ typedef struct char admin_host[MAX_ADMIN_HOST_LEN]; /**< Admin interface host */ uint16_t admin_port; /**< Admin interface port */ bool admin_auth; /**< Admin interface authentication */ + char admin_ssl_key[PATH_MAX]; /**< Admin SSL key */ + char admin_ssl_cert[PATH_MAX]; /**< Admin SSL cert */ + char admin_ssl_ca_cert[PATH_MAX]; /**< Admin SSL CA cert */ } MXS_CONFIG; /** diff --git a/server/core/admin.cc b/server/core/admin.cc index 6d9639aea..c54072b85 100644 --- a/server/core/admin.cc +++ b/server/core/admin.cc @@ -18,7 +18,9 @@ #include #include +#include #include +#include #include #include #include @@ -29,14 +31,23 @@ #include #include #include +#include #include "maxscale/resource.hh" #include "maxscale/http.hh" using std::string; +using std::ifstream; static struct MHD_Daemon* http_daemon = NULL; +/** In-memory certificates in PEM format */ +static char* admin_ssl_key = NULL; +static char* admin_ssl_cert = NULL; +static char* admin_ssl_ca_cert = NULL; + +static bool using_ssl = false; + int kv_iter(void *cls, enum MHD_ValueKind kind, const char *key, @@ -221,6 +232,58 @@ static bool host_to_sockaddr(const char* host, uint16_t port, struct sockaddr_st return true; } +static char* load_cert(const char* file) +{ + char* rval = NULL; + ifstream infile(file); + struct stat st; + + if (stat(file, &st) == 0 && + (rval = new (std::nothrow) char[st.st_size + 1])) + { + infile.read(rval, st.st_size); + rval[st.st_size] = '\0'; + + if (!infile.good()) + { + MXS_ERROR("Failed to load certificate file: %s", file); + delete rval; + rval = NULL; + } + } + + return rval; +} + +static bool load_ssl_certificates() +{ + bool rval = false; + const char* key = config_get_global_options()->admin_ssl_key; + const char* cert = config_get_global_options()->admin_ssl_cert; + const char* ca = config_get_global_options()->admin_ssl_ca_cert; + + if (*key && *cert && *ca) + { + if ((admin_ssl_key = load_cert(key)) && + (admin_ssl_cert = load_cert(cert)) && + (admin_ssl_ca_cert = load_cert(ca))) + { + rval = true; + } + else + { + delete admin_ssl_key; + delete admin_ssl_cert; + delete admin_ssl_ca_cert; + admin_ssl_key = NULL; + admin_ssl_cert = NULL; + admin_ssl_ca_cert = NULL; + } + } + + return rval; +} + bool mxs_admin_init() { struct sockaddr_storage addr; @@ -236,10 +299,20 @@ bool mxs_admin_init() options |= MHD_USE_DUAL_STACK; } + if (load_ssl_certificates()) + { + using_ssl = true; + options |= MHD_USE_SSL; + } + // The port argument is ignored and the port in the struct sockaddr is used instead - http_daemon = MHD_start_daemon(options, 0, NULL, NULL, handle_client, NULL, + http_daemon = MHD_start_daemon(options, 0, NULL, NULL, handle_client, NULL, MHD_OPTION_NOTIFY_COMPLETED, close_client, NULL, MHD_OPTION_SOCK_ADDR, &addr, + !using_ssl ? MHD_OPTION_END : + MHD_OPTION_HTTPS_MEM_KEY, admin_ssl_key, + MHD_OPTION_HTTPS_MEM_CERT, admin_ssl_cert, + MHD_OPTION_HTTPS_MEM_TRUST, admin_ssl_cert, MHD_OPTION_END); } @@ -250,3 +323,8 @@ void mxs_admin_shutdown() { MHD_stop_daemon(http_daemon); } + +bool mxs_admin_https_enabled() +{ + return using_ssl; +} diff --git a/server/core/config.cc b/server/core/config.cc index 2e68d8534..b8c8f47c1 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -57,6 +57,9 @@ const char CN_ADMIN_HOST[] = "admin_host"; const char CN_ADMIN_PASSWORD[] = "admin_password"; const char CN_ADMIN_PORT[] = "admin_port"; const char CN_ADMIN_USER[] = "admin_user"; +const char CN_ADMIN_SSL_KEY[] = "admin_ssl_key"; +const char CN_ADMIN_SSL_CERT[] = "admin_ssl_cert"; +const char CN_ADMIN_SSL_CA_CERT[] = "admin_ssl_ca_cert"; const char CN_AUTHENTICATOR[] = "authenticator"; const char CN_AUTHENTICATOR_OPTIONS[] = "authenticator_options"; const char CN_AUTH_ALL_SERVERS[] = "auth_all_servers"; @@ -1525,6 +1528,18 @@ handle_global_item(const char *name, const char *value) { strcpy(gateway.admin_host, value); } + else if (strcmp(name, CN_ADMIN_SSL_KEY) == 0) + { + strcpy(gateway.admin_ssl_key, value); + } + else if (strcmp(name, CN_ADMIN_SSL_CERT) == 0) + { + strcpy(gateway.admin_ssl_cert, value); + } + else if (strcmp(name, CN_ADMIN_SSL_CA_CERT) == 0) + { + strcpy(gateway.admin_ssl_ca_cert, value); + } else if (strcmp(name, CN_ADMIN_AUTH) == 0) { gateway.admin_auth = config_truth_value(value); @@ -1754,6 +1769,9 @@ global_defaults() strcpy(gateway.admin_host, DEFAULT_ADMIN_HOST); strcpy(gateway.admin_user, INET_DEFAULT_USERNAME); strcpy(gateway.admin_password, INET_DEFAULT_PASSWORD); + gateway.admin_ssl_key[0] = '\0'; + gateway.admin_ssl_cert[0] = '\0'; + gateway.admin_ssl_ca_cert[0] = '\0'; if (version_string != NULL) { diff --git a/server/core/httprequest.cc b/server/core/httprequest.cc index b5a444203..12301c0f8 100644 --- a/server/core/httprequest.cc +++ b/server/core/httprequest.cc @@ -12,6 +12,7 @@ */ #include "maxscale/httprequest.hh" +#include "maxscale/admin.hh" #include #include @@ -98,8 +99,8 @@ HttpRequest::HttpRequest(struct MHD_Connection *connection, string url, string m m_connection(connection) { process_uri(url, m_resource_parts); - // TODO: Add https support - m_hostname = HttpRequest::HTTP_PREFIX; + + m_hostname = mxs_admin_https_enabled() ? HttpRequest::HTTPS_PREFIX : HttpRequest::HTTP_PREFIX; m_hostname += get_header(HTTP_HOST_HEADER); } diff --git a/server/core/maxscale/admin.hh b/server/core/maxscale/admin.hh index d9bc3f765..46959145a 100644 --- a/server/core/maxscale/admin.hh +++ b/server/core/maxscale/admin.hh @@ -68,3 +68,10 @@ bool mxs_admin_init(); * @brief Shutdown the administrative interface */ void mxs_admin_shutdown(); + +/** + * @brief Check if admin interface uses HTTPS protocol + * + * @return True if HTTPS is enabled + */ +bool mxs_admin_https_enabled();