From b558426da8e4c06824fb525b8f439ce1e428beb3 Mon Sep 17 00:00:00 2001 From: feiniks <36756310+feiniks@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:39:40 +0800 Subject: [PATCH] Add ca_path and skip_verify option for mysql (#610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add ca_path and skip_verify option for mysql * Add comment --------- Co-authored-by: 杨赫然 --- common/seaf-db.c | 27 ++++++++++++++++++++++++--- common/seaf-db.h | 2 ++ common/seaf-utils.c | 29 +++++++++++++++++++++++++++-- fileserver/fileserver.go | 38 +++++++++++++++++++++++++++++++++++--- 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/common/seaf-db.c b/common/seaf-db.c index fb2c08e..2687d19 100644 --- a/common/seaf-db.c +++ b/common/seaf-db.c @@ -68,6 +68,8 @@ mysql_db_new (const char *host, const char *db_name, const char *unix_socket, gboolean use_ssl, + gboolean skip_verify, + const char *ca_path, const char *charset); static DBConnection * mysql_db_get_connection (SeafDB *db); @@ -234,12 +236,14 @@ seaf_db_new_mysql (const char *host, const char *db_name, const char *unix_socket, gboolean use_ssl, + gboolean skip_verify, + const char *ca_path, const char *charset, int max_connections) { SeafDB *db; - db = mysql_db_new (host, port, user, passwd, db_name, unix_socket, use_ssl, charset); + db = mysql_db_new (host, port, user, passwd, db_name, unix_socket, use_ssl, skip_verify, ca_path, charset); if (!db) return NULL; db->type = SEAF_DB_TYPE_MYSQL; @@ -751,6 +755,8 @@ typedef struct MySQLDB { char *db_name; char *unix_socket; gboolean use_ssl; + gboolean skip_verify; + char *ca_path; char *charset; } MySQLDB; @@ -775,6 +781,8 @@ mysql_db_new (const char *host, const char *db_name, const char *unix_socket, gboolean use_ssl, + gboolean skip_verify, + const char *ca_path, const char *charset) { MySQLDB *db = g_new0 (MySQLDB, 1); @@ -786,6 +794,8 @@ mysql_db_new (const char *host, db->db_name = g_strdup(db_name); db->unix_socket = g_strdup(unix_socket); db->use_ssl = use_ssl; + db->skip_verify = skip_verify; + db->ca_path = g_strdup(ca_path); db->charset = g_strdup(charset); mysql_library_init (0, NULL, NULL); @@ -803,6 +813,7 @@ mysql_db_get_connection (SeafDB *vdb) int read_write_timeout = 5; MYSQL *db_conn; MySQLDBConnection *conn = NULL; + int ssl_mode; db_conn = mysql_init (NULL); if (!db_conn) { @@ -810,8 +821,18 @@ mysql_db_get_connection (SeafDB *vdb) return NULL; } - if (db->use_ssl) - mysql_ssl_set(db_conn, 0,0,0,0,0); + if (db->use_ssl && !db->skip_verify) { + // Set ssl_mode to SSL_MODE_VERIFY_IDENTITY to verify server cert. + // When ssl_mode is set to SSL_MODE_VERIFY_IDENTITY, MYSQL_OPT_SSL_CA is required to verify server cert. + // Refer to: https://dev.mysql.com/doc/c-api/5.7/en/mysql-options.html + ssl_mode = SSL_MODE_VERIFY_IDENTITY; + mysql_options(db_conn, MYSQL_OPT_SSL_MODE, &ssl_mode); + mysql_options(db_conn, MYSQL_OPT_SSL_CA, db->ca_path); + } else if (db->use_ssl && db->skip_verify) { + // Set ssl_mode to SSL_MODE_PREFERRED to skip verify server cert. + ssl_mode = SSL_MODE_PREFERRED; + mysql_options(db_conn, MYSQL_OPT_SSL_MODE, &ssl_mode); + } if (db->charset) mysql_options(db_conn, MYSQL_SET_CHARSET_NAME, db->charset); diff --git a/common/seaf-db.h b/common/seaf-db.h index 3f58a4c..f33eab8 100644 --- a/common/seaf-db.h +++ b/common/seaf-db.h @@ -25,6 +25,8 @@ seaf_db_new_mysql (const char *host, const char *db, const char *unix_socket, gboolean use_ssl, + gboolean skip_verify, + const char *ca_path, const char *charset, int max_connections); diff --git a/common/seaf-utils.c b/common/seaf-utils.c index 092b67e..a2bfc76 100644 --- a/common/seaf-utils.c +++ b/common/seaf-utils.c @@ -62,6 +62,8 @@ mysql_db_start (SeafileSession *session) char *host, *user, *passwd, *db, *unix_socket, *charset; int port; gboolean use_ssl = FALSE; + gboolean skip_verify = FALSE; + char *ca_path = NULL; int max_connections = 0; GError *error = NULL; @@ -100,6 +102,18 @@ mysql_db_start (SeafileSession *session) use_ssl = g_key_file_get_boolean (session->config, "database", "use_ssl", NULL); + skip_verify = g_key_file_get_boolean (session->config, + "database", "skip_verify", NULL); + + if (use_ssl && !skip_verify) { + ca_path = seaf_key_file_get_string (session->config, + "database", "ca_path", NULL); + if (!ca_path) { + seaf_warning ("ca_path is required if use ssl and don't skip verify.\n"); + return -1; + } + } + charset = seaf_key_file_get_string (session->config, "database", "connection_charset", NULL); @@ -113,7 +127,7 @@ mysql_db_start (SeafileSession *session) max_connections = DEFAULT_MAX_CONNECTIONS; } - session->db = seaf_db_new_mysql (host, port, user, passwd, db, unix_socket, use_ssl, charset, max_connections); + session->db = seaf_db_new_mysql (host, port, user, passwd, db, unix_socket, use_ssl, skip_verify, ca_path, charset, max_connections); if (!session->db) { seaf_warning ("Failed to start mysql db.\n"); return -1; @@ -252,6 +266,8 @@ ccnet_init_mysql_database (SeafileSession *session) char *host, *user, *passwd, *db, *unix_socket, *charset; int port; gboolean use_ssl = FALSE; + gboolean skip_verify = FALSE; + char *ca_path = NULL; int max_connections = 0; host = ccnet_key_file_get_string (session->ccnet_config, "Database", "HOST"); @@ -286,6 +302,15 @@ ccnet_init_mysql_database (SeafileSession *session) unix_socket = ccnet_key_file_get_string (session->ccnet_config, "Database", "UNIX_SOCKET"); use_ssl = g_key_file_get_boolean (session->ccnet_config, "Database", "USE_SSL", NULL); + skip_verify = g_key_file_get_boolean (session->ccnet_config, "Database", "SKIP_VERIFY", NULL); + if (use_ssl && !skip_verify) { + ca_path = seaf_key_file_get_string (session->ccnet_config, + "Database", "CA_PATH", NULL); + if (!ca_path) { + seaf_warning ("ca_path is required if use ssl and don't skip verify.\n"); + return -1; + } + } charset = ccnet_key_file_get_string (session->ccnet_config, "Database", "CONNECTION_CHARSET"); @@ -298,7 +323,7 @@ ccnet_init_mysql_database (SeafileSession *session) g_clear_error (&error); } - session->ccnet_db = seaf_db_new_mysql (host, port, user, passwd, db, unix_socket, use_ssl, charset, max_connections); + session->ccnet_db = seaf_db_new_mysql (host, port, user, passwd, db, unix_socket, use_ssl, skip_verify, ca_path, charset, max_connections); if (!session->ccnet_db) { seaf_warning ("Failed to open ccnet database.\n"); return -1; diff --git a/fileserver/fileserver.go b/fileserver/fileserver.go index ae2f4c4..f869fba 100644 --- a/fileserver/fileserver.go +++ b/fileserver/fileserver.go @@ -2,10 +2,13 @@ package main import ( + "crypto/tls" + "crypto/x509" "database/sql" "flag" "fmt" "io" + "io/ioutil" "net/http" "os" "os/signal" @@ -14,7 +17,7 @@ import ( "strings" "syscall" - _ "github.com/go-sql-driver/mysql" + "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" "github.com/haiwen/seafile-server/fileserver/blockmgr" "github.com/haiwen/seafile-server/fileserver/commitmgr" @@ -120,8 +123,15 @@ func loadCcnetDB() { } var dsn string if unixSocket == "" { - if skipVerify { + if useTLS && skipVerify { dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify", user, password, host, port, dbName) + } else if useTLS && !skipVerify { + capath := "" + if key, err = section.GetKey("CA_PATH"); err == nil { + capath = key.String() + } + registerCA(capath) + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=custom", user, password, host, port, dbName) } else { dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=%t", user, password, host, port, dbName, useTLS) } @@ -143,6 +153,21 @@ func loadCcnetDB() { } } +// registerCA registers CA to verify server cert. +func registerCA(capath string) { + rootCertPool := x509.NewCertPool() + pem, err := ioutil.ReadFile(capath) + if err != nil { + log.Fatal(err) + } + if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { + log.Fatal("Failed to append PEM.") + } + mysql.RegisterTLSConfig("custom", &tls.Config{ + RootCAs: rootCertPool, + }) +} + func loadSeafileDB() { seafileConfPath := filepath.Join(centralDir, "seafile.conf") @@ -197,8 +222,15 @@ func loadSeafileDB() { var dsn string if unixSocket == "" { - if skipVerify { + if useTLS && skipVerify { dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify", user, password, host, port, dbName) + } else if useTLS && !skipVerify { + capath := "" + if key, err = section.GetKey("ca_path"); err == nil { + capath = key.String() + } + registerCA(capath) + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=custom", user, password, host, port, dbName) } else { dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=%t", user, password, host, port, dbName, useTLS) }