summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorthiagoftsm <49162938+thiagoftsm@users.noreply.github.com>2019-05-31 14:27:35 +0000
committerChris Akritidis <43294513+cakrit@users.noreply.github.com>2019-05-31 16:27:35 +0200
commitb6088e08a7dcf40c89dc859f85be11b2f9883a23 (patch)
treeb3a97848aa65905243839f8a09855e667ae3ec0e
parent0c39c626ff1cba2d3ebde70c175fee12bade45f8 (diff)
SSL implementation for Netdata (#5956)
* SSL implementation for Netdata * Upload of fixes asked by @paulkatsoulakis and @cakrit * Fix local computer * Adding openssl to webserver * fixing.. * HTTPS almost there * Codacity * HTTPS day 3 * HTTPS without Bio step 1 * HTTPS without Bio step 2 * HTTPS without Bio step 3 * HTTPS without Bio step 4 * HTTPS without Bio step 5 * HTTPS without Bio step 6 * HTTPS without Bio step 7 * HTTPS without Bio step 8 * HTTPS without Bio step 9 * HTTPS without Bio step 10 * SSL on streaming 1 * Daily pull * HTTPS without Bio step 11 * HTTPS without Bio step 12 * HTTPS without Bio step 13 * HTTPS without Bio step 14 * SSL_Interception change documentation * HTTPS without Bio step 15 * HTTPS without Bio step 16 * SSL_Interception fix codacity * SSL_Interception fix doc * SSL_Interception comments * SSL_Interception fixing problems! * SSL_Interception killing bugs * SSL_Interception changing parameter * SSL_Implementation documentation and script * SSL_Implementation multiple fixes * SSL_Implementation installer and cipher * SSL_Implementation Redirect 301 * SSL_Implementation webserver doc and install-or-update.sh * SSL_Implementation error 00000001:lib(0):func(0):reason(1) * SSL_Implementation web server doc * SSL_Implementation SEGFAULT on Fedora * SSL_Implementation fix ^SSL=force|optional * SSL_Implementation Redirect and Ciphers * SSL_Implementation race condition 1 * SSL_Implementation Fix Location * SSL_Implementation Fix Location 2 * SSL_Implementation Fix stream * SSL_Implementation Fix stream 2 * SSL_Implementation Fix stream 3 * SSL_Implementation last problems! * SSL_Implementation adjusts to commit! * SSL_Implementation documentation permission! * SSL_Implementation documentation permission 2! * SSL_Implementation documentation permission 3!
-rw-r--r--.gitignore3
-rw-r--r--CMakeLists.txt3
-rw-r--r--Makefile.am3
-rw-r--r--configure.ac20
-rw-r--r--daemon/main.c28
-rw-r--r--database/rrd.h4
-rw-r--r--database/rrdhost.c4
-rw-r--r--docs/Running-behind-nginx.md14
-rw-r--r--libnetdata/libnetdata.h1
-rw-r--r--libnetdata/socket/security.c215
-rw-r--r--libnetdata/socket/security.h38
-rw-r--r--libnetdata/socket/socket.c51
-rw-r--r--libnetdata/socket/socket.h6
-rwxr-xr-xnetdata-installer.sh2
-rwxr-xr-xpackaging/makeself/install-or-update.sh2
-rw-r--r--streaming/README.md39
-rw-r--r--streaming/rrdpush.c118
-rw-r--r--streaming/stream.conf13
-rw-r--r--web/server/README.md71
-rw-r--r--web/server/static/static-threaded.c66
-rw-r--r--web/server/web_client.c180
-rw-r--r--web/server/web_client.h4
-rw-r--r--web/server/web_client_cache.c38
-rw-r--r--web/server/web_server.c2
24 files changed, 888 insertions, 37 deletions
diff --git a/.gitignore b/.gitignore
index 207baca8d5..1d9cd173d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -171,3 +171,6 @@ docs/generator/build
docs/generator/mkdocs.yml
.environment.sh
+
+#CLion files
+netdata.cbp
diff --git a/CMakeLists.txt b/CMakeLists.txt
index af4b324ce1..e774bead93 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -284,7 +284,8 @@ set(LIBNETDATA_FILES
libnetdata/threads/threads.h
libnetdata/url/url.c
libnetdata/url/url.h
- )
+ libnetdata/socket/security.c
+ libnetdata/socket/security.h)
add_library(libnetdata OBJECT ${LIBNETDATA_FILES})
diff --git a/Makefile.am b/Makefile.am
index 2fcd9d9702..d460fc7679 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -154,6 +154,8 @@ LIBNETDATA_FILES = \
libnetdata/simple_pattern/simple_pattern.h \
libnetdata/socket/socket.c \
libnetdata/socket/socket.h \
+ libnetdata/socket/security.c \
+ libnetdata/socket/security.h \
libnetdata/statistical/statistical.c \
libnetdata/statistical/statistical.h \
libnetdata/storage_number/storage_number.c \
@@ -496,6 +498,7 @@ endif
NETDATA_COMMON_LIBS = \
$(OPTIONAL_MATH_LIBS) \
$(OPTIONAL_ZLIB_LIBS) \
+ $(OPTIONAL_SSL_LIBS) \
$(OPTIONAL_UUID_LIBS) \
$(OPTIONAL_UV_LIBS) \
$(OPTIONAL_LZ4_LIBS) \
diff --git a/configure.ac b/configure.ac
index 6e17b6078c..59bab84353 100644
--- a/configure.ac
+++ b/configure.ac
@@ -132,13 +132,18 @@ AC_ARG_ENABLE(
[enable_lto="detect"]
)
AC_ARG_ENABLE(
+ [https],
+ [AS_HELP_STRING([--enable-https], [Enable SSL support @<:@default autodetect@:>@])],
+ ,
+ [enable_https="detect"]
+)
+AC_ARG_ENABLE(
[dbengine],
[AS_HELP_STRING([--disable-dbengine], [disable netdata dbengine @<:@default autodetect@:>@])],
,
[enable_dbengine="detect"]
)
-
# -----------------------------------------------------------------------------
# netdata required checks
@@ -338,7 +343,7 @@ OPTIONAL_SSL_CFLAGS="${SSL_CFLAGS}"
OPTIONAL_SSL_LIBS="${SSL_LIBS}"
# -----------------------------------------------------------------------------
-# DB engine
+# DB engine and HTTPS
test "${enable_dbengine}" = "yes" -a -z "${UV_LIBS}" && \
AC_MSG_ERROR([libuv required but not found. Try installing 'libuv1-dev' or 'libuv-devel'.])
@@ -348,7 +353,7 @@ test "${enable_dbengine}" = "yes" -a -z "${LZ4_LIBS}" && \
test "${enable_dbengine}" = "yes" -a -z "${JUDY_LIBS}" && \
AC_MSG_ERROR([libJudy required but not found. Try installing 'libjudy-dev' or 'Judy-devel'.])
-test "${enable_dbengine}" = "yes" -a -z "${SSL_LIBS}" && \
+test "${enable_dbengine}" = "yes" -o "${enable_https}" = "yes" -a -z "${SSL_LIBS}" && \
AC_MSG_ERROR([OpenSSL required but not found. Try installing 'libssl-dev' or 'openssl-devel'.])
AC_MSG_CHECKING([if netdata dbengine should be used])
@@ -361,6 +366,15 @@ fi
AC_MSG_RESULT([${enable_dbengine}])
AM_CONDITIONAL([ENABLE_DBENGINE], [test "${enable_dbengine}" = "yes"])
+AC_MSG_CHECKING([if netdata https should be used])
+if test "${enable_https}" != "no" -a "${SSL_LIBS}"; then
+ enable_https="yes"
+ AC_DEFINE([ENABLE_HTTPS], [1], [netdata HTTPS usability])
+else
+ enable_https="no"
+fi
+AC_MSG_RESULT([${enable_https}])
+AM_CONDITIONAL([ENABLE_HTTPS], [test "${enable_https}" = "yes"])
# -----------------------------------------------------------------------------
# compiler options
diff --git a/daemon/main.c b/daemon/main.c
index 83e264735b..0478e37a6d 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -49,6 +49,10 @@ void netdata_cleanup_and_exit(int ret) {
error("EXIT: cannot unlink pidfile '%s'.", pidfile);
}
+#ifdef ENABLE_HTTPS
+ security_clean_openssl();
+#endif
+
info("EXIT: all done - netdata is now exiting - bye bye...");
exit(ret);
}
@@ -345,7 +349,20 @@ static const char *verify_required_directory(const char *dir) {
return dir;
}
-void log_init(void) {
+#ifdef ENABLE_HTTPS
+static void security_init(){
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/ssl/key.pem",netdata_configured_user_config_dir);
+ security_key = config_get(CONFIG_SECTION_WEB, "ssl key", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s/ssl/cert.pem",netdata_configured_user_config_dir);
+ security_cert = config_get(CONFIG_SECTION_WEB, "ssl certificate", filename);
+
+ security_openssl_library();
+}
+#endif
+
+static void log_init(void) {
char filename[FILENAME_MAX + 1];
snprintfz(filename, FILENAME_MAX, "%s/debug.log", netdata_configured_log_dir);
stdout_filename = config_get(CONFIG_SECTION_GLOBAL, "debug log", filename);
@@ -420,8 +437,9 @@ static void get_netdata_configured_variables() {
// get the hostname
char buf[HOSTNAME_MAX + 1];
- if(gethostname(buf, HOSTNAME_MAX) == -1)
+ if(gethostname(buf, HOSTNAME_MAX) == -1){
error("Cannot get machine hostname.");
+ }
netdata_configured_hostname = config_get(CONFIG_SECTION_GLOBAL, "hostname", buf);
debug(D_OPTIONS, "hostname set to '%s'", netdata_configured_hostname);
@@ -1081,6 +1099,12 @@ int main(int argc, char **argv) {
error_log_limit_unlimited();
// --------------------------------------------------------------------
+ // get the certificate and start security
+#ifdef ENABLE_HTTPS
+ security_init();
+#endif
+
+ // --------------------------------------------------------------------
// setup process signals
// block signals while initializing threads.
diff --git a/database/rrd.h b/database/rrd.h
index d2210e3141..9baba4192a 100644
--- a/database/rrd.h
+++ b/database/rrd.h
@@ -723,6 +723,10 @@ struct rrdhost {
struct rrdengine_instance *rrdeng_ctx; // DB engine instance for this host
#endif
+#ifdef ENABLE_HTTPS
+ struct netdata_ssl ssl; //Structure used to encrypt the connection
+#endif
+
struct rrdhost *next;
};
extern RRDHOST *localhost;
diff --git a/database/rrdhost.c b/database/rrdhost.c
index d33e7af8f5..8dab646305 100644
--- a/database/rrdhost.c
+++ b/database/rrdhost.c
@@ -147,6 +147,10 @@ RRDHOST *rrdhost_create(const char *hostname,
host->rrdpush_sender_pipe[0] = -1;
host->rrdpush_sender_pipe[1] = -1;
host->rrdpush_sender_socket = -1;
+#ifdef ENABLE_HTTPS
+ host->ssl.conn = NULL;
+ host->ssl.flags = NETDATA_SSL_START;
+#endif
netdata_mutex_init(&host->rrdpush_sender_buffer_mutex);
netdata_rwlock_init(&host->rrdhost_rwlock);
diff --git a/docs/Running-behind-nginx.md b/docs/Running-behind-nginx.md
index 9bed87e870..b38d27fa9a 100644
--- a/docs/Running-behind-nginx.md
+++ b/docs/Running-behind-nginx.md
@@ -117,6 +117,19 @@ Using the above, you access Netdata on the backend servers, like this:
- `http://nginx.server/netdata/server1/` to reach `backend-server1`
- `http://nginx.server/netdata/server2/` to reach `backend-server2`
+### Using TLS communication
+
+In case the Netdata web server has been [configured to use TLS](../web/server/#enabling-tls-support),
+you must also encrypt the communication between Nginx and Netdata.
+
+To enable encryption, first [enable SSL on nginx](http://nginx.org/en/docs/http/configuring_https_servers.html) and then put the following in the location section of the Nginx configuration:
+
+```
+proxy_set_header X-Forwarded-Proto https;
+proxy_pass https://localhost:19999;
+```
+
+If nginx is not configured as described here, you will probably receive the error `SSL_ERROR_RX_RECORD_TOO_LONG`.
### Enable authentication
@@ -201,4 +214,5 @@ If you get an 502 Bad Gateway error you might check your nginx error log:
If you see something like the above, chances are high that SELinux prevents nginx from connecting to the backend server. To fix that, just use this policy: `setsebool -P httpd_can_network_connect true`.
+
[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdocs%2FRunning-behind-nginx&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)]()
diff --git a/libnetdata/libnetdata.h b/libnetdata/libnetdata.h
index 230dc24422..eaedfe7036 100644
--- a/libnetdata/libnetdata.h
+++ b/libnetdata/libnetdata.h
@@ -298,6 +298,7 @@ extern char *netdata_configured_host_prefix;
#include "clocks/clocks.h"
#include "popen/popen.h"
#include "simple_pattern/simple_pattern.h"
+#include "socket/security.h"
#include "socket/socket.h"
#include "config/appconfig.h"
#include "log/log.h"
diff --git a/libnetdata/socket/security.c b/libnetdata/socket/security.c
new file mode 100644
index 0000000000..74a07a573b
--- /dev/null
+++ b/libnetdata/socket/security.c
@@ -0,0 +1,215 @@
+#include "../libnetdata.h"
+
+SSL_CTX *netdata_cli_ctx=NULL;
+SSL_CTX *netdata_srv_ctx=NULL;
+const char *security_key=NULL;
+const char *security_cert=NULL;
+int netdata_use_ssl_on_stream = NETDATA_SSL_OPTIONAL;
+int netdata_use_ssl_on_http = NETDATA_SSL_FORCE; //We force SSL due safety reasons
+int netdata_validate_server = NETDATA_SSL_VALID_CERTIFICATE;
+
+static void security_info_callback(const SSL *ssl, int where, int ret) {
+ (void)ssl;
+ if ( where & SSL_CB_ALERT ) {
+ debug(D_WEB_CLIENT,"SSL INFO CALLBACK %s %s",SSL_alert_type_string( ret ),SSL_alert_desc_string_long(ret));
+ }
+}
+
+void security_openssl_library()
+{
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+# if (SSLEAY_VERSION_NUMBER >= 0x0907000L)
+ OPENSSL_config(NULL);
+# endif
+
+# if OPENSSL_API_COMPAT < 0x10100000L
+ SSL_load_error_strings();
+# endif
+
+ SSL_library_init();
+#else
+ if ( OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG,NULL) != 1 ){
+ error("SSL library cannot be initialized.");
+ }
+#endif
+}
+
+void security_openssl_common_options(SSL_CTX *ctx){
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ static char *ciphers = {"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"};
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ SSL_CTX_set_options (ctx,SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION);
+#else
+ SSL_CTX_set_min_proto_version(ctx,TLS1_2_VERSION);
+ //We are avoiding the TLS v1.3 for while, because Google Chrome
+ //is giving the message net::ERR_SSL_VERSION_INTERFERENCE with it.
+ SSL_CTX_set_max_proto_version(ctx,TLS1_2_VERSION);
+#endif
+ SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ if (!SSL_CTX_set_cipher_list(ctx,ciphers) ){
+ error("SSL error. cannot set the cipher list");
+ }
+#endif
+
+
+}
+
+static SSL_CTX * security_initialize_openssl_client() {
+ SSL_CTX *ctx;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ ctx = SSL_CTX_new(SSLv23_client_method());
+#else
+ ctx = SSL_CTX_new(TLS_client_method());
+#endif
+ security_openssl_common_options(ctx);
+
+ return ctx;
+}
+
+static SSL_CTX * security_initialize_openssl_server(){
+ SSL_CTX *ctx;
+ char lerror[512];
+ static int netdata_id_context = 1;
+
+ //TO DO: Confirm the necessity to check return for other OPENSSL function
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ ctx = SSL_CTX_new(SSLv23_server_method());
+ if ( !ctx ) {
+ error("Cannot create a new SSL context, netdata won't encrypt communication");
+ return NULL;
+ }
+
+ SSL_CTX_use_certificate_file(ctx, security_cert, SSL_FILETYPE_PEM);
+#else
+ ctx = SSL_CTX_new(TLS_server_method());
+ if ( !ctx ){
+ error("Cannot create a new SSL context, netdata won't encrypt communication");
+ return NULL;
+ }
+
+ SSL_CTX_use_certificate_chain_file(ctx, security_cert );
+#endif
+ security_openssl_common_options(ctx);
+
+ SSL_CTX_use_PrivateKey_file(ctx,security_key,SSL_FILETYPE_PEM);
+
+ if ( !SSL_CTX_check_private_key(ctx) ){
+ ERR_error_string_n(ERR_get_error(),lerror,sizeof(lerror));
+ error("SSL cannot check the private key: %s",lerror);
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+
+ SSL_CTX_set_session_id_context(ctx,(void*)&netdata_id_context,(unsigned int)sizeof(netdata_id_context));
+ SSL_CTX_set_info_callback(ctx,security_info_callback);
+
+#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
+ SSL_CTX_set_verify_depth(ctx,1);
+#endif
+ debug(D_WEB_CLIENT,"SSL GLOBAL CONTEXT STARTED\n");
+
+ return ctx;
+}
+
+void security_start_ssl(int type){
+ if ( !type){
+ struct stat statbuf;
+ if ( (stat(security_key,&statbuf)) || (stat(security_cert,&statbuf)) ){
+ info("To use encryption it is necessary to set \"ssl certificate\" and \"ssl key\" in [web] !\n");
+ return;
+ }
+
+ netdata_srv_ctx = security_initialize_openssl_server();
+ }
+ else {
+ netdata_cli_ctx = security_initialize_openssl_client();
+ }
+}
+
+void security_clean_openssl(){
+ if ( netdata_srv_ctx )
+ {
+ SSL_CTX_free(netdata_srv_ctx);
+ }
+
+ if ( netdata_cli_ctx )
+ {
+ SSL_CTX_free(netdata_cli_ctx);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ ERR_free_strings();
+#endif
+}
+
+int security_process_accept(SSL *ssl,int msg) {
+ int sock = SSL_get_fd(ssl);
+ int test;
+ if (msg > 0x17)
+ {
+ return NETDATA_SSL_NO_HANDSHAKE;
+ }
+
+ ERR_clear_error();
+ if ((test = SSL_accept(ssl)) <= 0) {
+ int sslerrno = SSL_get_error(ssl, test);
+ switch(sslerrno) {
+ case SSL_ERROR_WANT_READ:
+ {
+ error("SSL handshake did not finish and it wanna read on socket %d!",sock);
+ return NETDATA_SSL_WANT_READ;
+ }
+ case SSL_ERROR_WANT_WRITE:
+ {
+ error("SSL handshake did not finish and it wanna read on socket %d!",sock);
+ return NETDATA_SSL_WANT_WRITE;
+ }
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_SSL:
+ case SSL_ERROR_SYSCALL:
+ default:
+ {
+ u_long err;
+ char buf[256];
+ int counter = 0;
+ while ((err = ERR_get_error()) != 0){
+ ERR_error_string_n(err, buf, sizeof(buf));
+ info("%d SSL Handshake error (%s) on socket %d ",counter++,ERR_error_string((long)SSL_get_error(ssl,test),NULL),sock);
+ }
+ return NETDATA_SSL_NO_HANDSHAKE;
+ }
+ }
+ }
+
+ if ( SSL_is_init_finished(ssl) )
+ {
+ debug(D_WEB_CLIENT_ACCESS,"SSL Handshake finished %s errno %d on socket fd %d",ERR_error_string((long)SSL_get_error(ssl,test),NULL),errno,sock);
+ }
+
+ return 0;
+}
+
+int security_test_certificate(SSL *ssl){
+ X509* cert = SSL_get_peer_certificate(ssl);
+ int ret;
+ long status;
+ if (!cert){
+ return -1;
+ }
+
+ status = SSL_get_verify_result(ssl);
+ if((X509_V_OK != status))
+ {
+ char error[512];
+ ERR_error_string_n(ERR_get_error(),error,sizeof(error));
+ error("SSL RFC4158 check: We have a invalid certificate, the tests result with %ld and message %s",status,error);
+ ret = -1;
+ }
+ else {
+ ret = 0;
+ }
+ return ret;
+} \ No newline at end of file
diff --git a/libnetdata/socket/security.h b/libnetdata/socket/security.h
new file mode 100644
index 0000000000..f4771414f7
--- /dev/null
+++ b/libnetdata/socket/security.h
@@ -0,0 +1,38 @@
+#ifndef NETDATA_SECURITY_H
+# define NETDATA_SECURITY_H
+
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# if (SSLEAY_VERSION_NUMBER >= 0x0907000L) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
+# include <openssl/conf.h>
+# endif
+
+#define NETDATA_SSL_HANDSHAKE_COMPLETE 0 //All the steps were successful
+#define NETDATA_SSL_START 1 //Starting handshake, conn variable is NULL
+#define NETDATA_SSL_WANT_READ 2 //The connection wanna read from socket
+#define NETDATA_SSL_WANT_WRITE 4 //The connection wanna write on socket
+#define NETDATA_SSL_NO_HANDSHAKE 8 //Continue without encrypt connection.
+#define NETDATA_SSL_OPTIONAL 16 //Flag to define the HTTP request
+#define NETDATA_SSL_FORCE 32 //We only accepts HTTPS request
+#define NETDATA_SSL_INVALID_CERTIFICATE 64 //Accepts invalid certificate
+#define NETDATA_SSL_VALID_CERTIFICATE 128 //Accepts invalid certificate
+struct netdata_ssl{
+ SSL *conn; //SSL connection
+ int flags;
+};
+
+extern SSL_CTX *netdata_cli_ctx;
+extern SSL_CTX *netdata_srv_ctx;
+extern const char *security_key;
+extern const char *security_cert;
+extern int netdata_use_ssl_on_stream;
+extern int netdata_use_ssl_on_http;
+extern int netdata_validate_server;
+
+void security_openssl_library();
+void security_clean_openssl();
+void security_start_ssl(int type);
+int security_process_accept(SSL *ssl,int msg);
+int security_test_certificate(SSL *ssl);
+
+#endif //NETDATA_SECURITY_H
diff --git a/libnetdata/socket/socket.c b/libnetdata/socket/socket.c
index bf9c60ea14..3ac5a26e5a 100644
--- a/libnetdata/socket/socket.c
+++ b/libnetdata/socket/socket.c
@@ -301,14 +301,37 @@ void listen_sockets_close(LISTEN_SOCKETS *sockets) {
sockets->failed = 0;
}
+WEB_CLIENT_ACL socket_ssl_acl(char *ssl){
+ if (!strcmp(ssl,"optional")){
+ netdata_use_ssl_on_http = NETDATA_SSL_OPTIONAL;
+ return WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_REGISTRY | WEB_CLIENT_ACL_BADGE | WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_NETDATACONF | WEB_CLIENT_ACL_STREAMING;
+ }
+ else if (!strcmp(ssl,"force")){
+ netdata_use_ssl_on_stream = NETDATA_SSL_FORCE;
+ return WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_REGISTRY | WEB_CLIENT_ACL_BADGE | WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_NETDATACONF | WEB_CLIENT_ACL_STREAMING;
+ }
+
+ return WEB_CLIENT_ACL_NONE;
+}
+
WEB_CLIENT_ACL read_acl(char *st) {
+ char *ssl = strchr(st,'^');
+ if (ssl){
+ ssl++;
+ if ( !strncmp("SSL=",ssl,4)){
+ ssl += 4;
+ }
+ socket_ssl_acl(ssl);
+ }
+
if (!strcmp(st,"dashboard")) return WEB_CLIENT_ACL_DASHBOARD;
if (!strcmp(st,"registry")) return WEB_CLIENT_ACL_REGISTRY;
if (!strcmp(st,"badges")) return WEB_CLIENT_ACL_BADGE;
if (!strcmp(st,"management")) return WEB_CLIENT_ACL_MGMT;
if (!strcmp(st,"streaming")) return WEB_CLIENT_ACL_STREAMING;
if (!strcmp(st,"netdata.conf")) return WEB_CLIENT_ACL_NETDATACONF;
- return WEB_CLIENT_ACL_NONE;
+
+ return socket_ssl_acl(st);
}
static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition, uint16_t default_port, int listen_backlog) {
@@ -824,7 +847,12 @@ int connect_to_one_of(const char *destination, int default_port, struct timeval
// --------------------------------------------------------------------------------------------------------------------
// helpers to send/receive data in one call, in blocking mode, with a timeout
+#ifdef ENABLE_HTTPS
+ssize_t recv_timeout(struct netdata_ssl *ssl,int sockfd, void *buf, size_t len, int flags, int timeout) {
+#else
ssize_t recv_timeout(int sockfd, void *buf, size_t len, int flags, int timeout) {
+#endif
+
for(;;) {
struct pollfd fd = {
.fd = sockfd,
@@ -852,10 +880,22 @@ ssize_t recv_timeout(int sockfd, void *buf, size_t len, int flags, int timeout)
if(fd.events & POLLIN) break;
}
+#ifdef ENABLE_HTTPS
+ if (ssl->conn){
+ if (!ssl->flags){
+ return SSL_read(ssl->conn,buf,len);
+ }
+ }
+#endif
return recv(sockfd, buf, len, flags);
}
+#ifdef ENABLE_HTTPS
+ssize_t send_timeout(struct netdata_ssl *ssl,int sockfd, void *buf, size_t len, int flags, int timeout) {
+#else
ssize_t send_timeout(int sockfd, void *buf, size_t len, int flags, int timeout) {
+#endif
+
for(;;) {
struct pollfd fd = {
.fd = sockfd,
@@ -883,6 +923,13 @@ ssize_t send_timeout(int sockfd, void *buf, size_t len, int flags, int timeout)
if(fd.events & POLLOUT) break;
}
+#ifdef ENABLE_HTTPS
+ if(ssl->conn){
+ if (!ssl->flags){
+ return SSL_write(ssl->conn, buf, len);
+ }
+ }
+#endif
return send(sockfd, buf, len, flags);
}
@@ -1291,6 +1338,8 @@ static void poll_events_process(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, sho
do {
char client_ip[NI_MAXHOST + 1];
char client_port[NI_MAXSERV + 1];
+ client_ip[0] = 0x00;
+ client_port[0] = 0x00;
debug(D_POLLFD, "POLLFD: LISTENER: calling accept4() slot %zu (fd %d)", i, fd);
nfd = accept_socket(fd, SOCK_NONBLOCK, client_ip, NI_MAXHOST + 1, client_port, NI_MAXSERV + 1, p->access_list);
diff --git a/libnetdata/socket/socket.h b/libnetdata/socket/socket.h
index c69d4897f3..1356765168 100644
--- a/libnetdata/socket/socket.h
+++ b/libnetdata/socket/socket.h
@@ -3,6 +3,7 @@
#ifndef NETDATA_SOCKET_H
#define NETDATA_SOCKET_H
+#include <openssl/ossl_typ.h>
#include "../libnetdata.h"
#ifndef MAX_LISTEN_FDS
@@ -51,8 +52,13 @@ extern void listen_sockets_close(LISTEN_SOCKETS *sockets);
extern int connect_to_this(const char *definition, int default_port, struct timeval *timeout);
extern int connect_to_one_of(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size);
+#ifdef ENABLE_HTTPS
+extern ssize_t recv_timeout(struct netdata_ssl *ssl,int sockfd, void *buf, size_t len, int flags, int timeout);
+extern ssize_t send_timeout(struct netdata_ssl *ssl,int sockfd, void *buf, size_t len, int flags, int timeout);
+#else
extern ssize_t recv_timeout(int sockfd, void *buf, size_t len, int flags, int timeout);
extern ssize_t send_timeout(int sockfd, void *buf, size_t len, int flags, int timeout);
+#endif
extern int sock_setnonblock(int fd);
extern int sock_delnonblock(int fd);
diff --git a/netdata-installer.sh b/netdata-installer.sh
index 41264c6485..bea4abdf75 100755
--- a/netdata-installer.sh
+++ b/netdata-installer.sh
@@ -635,7 +635,7 @@ fi
# --- conf dir ----
-for x in "python.d" "charts.d" "node.d" "health.d" "statsd.d" "go.d" "custom-plugins.d"; do
+for x in "python.d" "charts.d" "node.d" "health.d" "statsd.d" "go.d" "custom-plugins.d" "ssl"; do
if [ ! -d "${NETDATA_USER_CONFIG_DIR}/${x}" ]; then
echo >&2 "Creating directory '${NETDATA_USER_CONFIG_DIR}/${x}'"
run mkdir -p "${NETDATA_USER_CONFIG_DIR}/${x}" || exit 1
diff --git a/packaging/makeself/install-or-update.sh b/packaging/makeself/install-or-update.sh
index 9dd70629d2..165e7920bd 100755
--- a/packaging/makeself/install-or-update.sh
+++ b/packaging/makeself/install-or-update.sh
@@ -175,7 +175,7 @@ fi
progress "create user config directories"
-for x in "python.d" "charts.d" "node.d" "health.d" "statsd.d" "custom-plugins.d"
+for x in "python.d" "charts.d" "node.d" "health.d" "statsd.d" "custom-plugins.d" "ssl"
do
if [ ! -d "etc/netdata/${x}" ]
then
diff --git a/streaming/README.md b/streaming/README.md
index dd616cab4f..e5c35fc7e7 100644
--- a/streaming/README.md
+++ b/streaming/README.md
@@ -123,7 +123,7 @@ a `proxy`).
```
[stream]
enabled = yes | no
- destination = IP:PORT ...
+ destination = IP:PORT[:SSL] ...
api key = XXXXXXXXXXX
```
@@ -136,6 +136,8 @@ headless proxy|`none`|not `none`|`yes`|only for `data source = as collected`|not
proxy with db|not `none`|not `none`|`yes`|possible|possible|yes
central netdata|not `none`|not `none`|`no`|possible|possible|yes
+For the options to encrypt the data stream between the slave and the master, refer to [securing the communication](#securing-the-communication)
+
##### options for the receiving node
`stream.conf` looks like this:
@@ -209,6 +211,41 @@ The receiving end (`proxy` or `master`) logs entries like these:
For netdata v1.9+, streaming can also be monitored via `access.log`.
+### Securing the communication
+
+Netdata does not activate TLS encryption by default. To encrypt the connection, you first need to [enable TLS support](../web/server/#enabling-tls-support) on the master. With encryption enabled on the receiving side, we need to instruct the slave to use SSL as well. On the slave's `stream.conf`, configure the destination as follows:
+
+```
+[stream]
+ destination = host:port:SSL
+```
+
+The word SSL appended to the end of the destination tells the slave that the connection must be encrypted.
+
+#### Certificate verification
+
+When SSL is enabled on the slave, the default behavior will be do not connect with the master unless the server's certificate can be verified via the default chain. In case you want to avoid this check, add to the slave's `stream.conf` the following:
+
+```
+[stream]
+ ssl skip certificate verification = yes
+```
+
+#### Expected behaviors
+
+With the introduction of SSL, the master-slave communication behaves as shown in the table below, depending on the following configurations:
+- Master TLS (Yes/No): Whether the `[web]` section in `netdata.conf` has `ssl key` and `ssl certificate`.
+- Master port SSL (-/force/optional): Depends on whether the `[web]` section `bind to` contains a `^SSL=force` or `^SSL=optional` directive on the port(s) used for streaming.
+- Slave TLS (Yes/No): Whether the destination in the slave's `stream.conf` has `:SSL` at the end.
+- Slave SSL Verification (yes/no): Value of the slave's `stream.conf` `ssl skip certificate verification` parameter (default is no).
+
+ Master TLS enabled | Master port SSL | Slave TLS | Slave SSL Ver. | Behavior
+:------:|:-----:|:-----:|:-----:|:--------
+No | - | No | no | Legacy behavior. The master-slave stream is unencrypted.
+Yes | force | No | no | The master rejects the slave connection.
+Yes | -/optional | No | no | The master-slave stream is unencrypted (expected situation for legacy slaves and newer masters)
+Yes | -/force/optional | Yes | no | The master-slave stream is encrypted, provided that the master has a valid SSL certificate. Otherwise, the slave refuses to connect.
+Yes | -/force/optional | Yes | yes | The master-slave stream is encrypted.
## Viewing remote host dashboards, using mirrored databases
diff --git a/streaming/rrdpush.c b/streaming/rrdpush.c
index 6a7ebfee37..8c23750812 100644
--- a/