From 1b9a3e8c4bdb99f7713958a3f284e06438b95bb1 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Thu, 14 Mar 2019 23:34:55 +0100 Subject: Made all settings customisable through a config file. --- CMakeLists.txt | 4 +-- README.md | 25 +++++++++++++-- bastion/CMakeLists.txt | 7 ++-- bastion/main.c | 23 ++++++++++---- bastion/recording.c | 12 +++---- common/config.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ common/config.h.in | 28 ++++++++++++---- 7 files changed, 159 insertions(+), 26 deletions(-) create mode 100644 common/config.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a131f0b..db4fc3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() set(CMAKE_C_FLAGS "-Wall -Werror -Wextra -pedantic") -set(CMAKE_C_FLAGS_DEBUG "-g -ggdb -pg -fsanitize=address") +set(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb -pg -fsanitize=address") set(CMAKE_C_FLAGS_RELEASE "-O2") set(CMAKE_C_FLAGS_MinSizeRel "-Os") set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g -ggdb -pg -fsanitize=address") @@ -34,7 +34,7 @@ set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_CONTACT "Julien Dessaux ") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_SECTION "net") -set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libssh-4") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libconfig9, libssh-4") set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") diff --git a/README.md b/README.md index 3b817cd..d2cf716 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ This bastion project does work properly with non interactive sessions, which all ## Contents - [Dependencies](#dependencies) -- [Installation and usage](#manual-installation) +- [Manual installation](#manual-installation) +- [Configuration](#configuration) - [Usage](#usage) - [Docker](#docker) - [Monitoring](#monitoring) @@ -20,7 +21,8 @@ This bastion project does work properly with non interactive sessions, which all ## Dependencies -This project has only one hard dependency : +This project has only two hard dependencies : +- the libconfig from http://www.hyperrealm.com/libconfig/libconfig.html - the libssh from https://www.libssh.org/. You should be able to use your distro's packages if they are recent enough. The following are optional dependencies : @@ -49,6 +51,25 @@ For exemple this disables session recording for a debug build and installs the b `cmake .. -DCMAKE_BUILD_TYPE=Debug -D CMAKE_INSTALL_PREFIX=$HOME/.local -DSESSION_RECORDING=OFF` +## Configuration + +Here is the default configuration : +``` +port = 2222; + +keys: +{ + dsa = "/home/julien/.local/etc/bastion/ssh_host_dsa_key"; + rsa = "/home/julien/.local/etc/bastion/ssh_host_rsa_key"; + ecdsa = "/home/julien/.local/etc/bastion/ssh_host_ecdsa_key"; +}; + +session_recording: +{ + path = "/home/julien/.local/var/log/bastion/$d/$h/$u/$i.gz"; # $d : date in iso format, $h : hostname, $u : username : $i session id +}; +``` + ## Usage ## Docker diff --git a/bastion/CMakeLists.txt b/bastion/CMakeLists.txt index 8176078..f07111b 100644 --- a/bastion/CMakeLists.txt +++ b/bastion/CMakeLists.txt @@ -8,6 +8,9 @@ target_link_libraries(bastion common) if (${SESSION_RECORDING}) target_link_libraries(bastion libtty) endif() -target_link_libraries(bastion bz2 lzma mysqlclient ssh z) +target_link_libraries(bastion bz2 config lzma mysqlclient ssh z) -install (TARGETS bastion DESTINATION bin) +install(TARGETS bastion DESTINATION bin) + +configure_file("bastion.conf.example.in" "bastion.conf.example") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/bastion.conf.example" DESTINATION etc/bastion) diff --git a/bastion/main.c b/bastion/main.c index 3ce7d5e..886bfac 100644 --- a/bastion/main.c +++ b/bastion/main.c @@ -50,6 +50,10 @@ int main() return 1; } + // Initializing configuration context + if (config_load() != 0) + fprintf(stderr, "Failed to load configuration file %s, using built-in defaults.\n", CONFIG_PATH); + // Initializing ssh context ssh_init(); @@ -57,19 +61,21 @@ int main() sshbind = ssh_bind_new(); if (sshbind == NULL) { fprintf(stderr, "Error initializing ssh_bind\n"); - exit(-1); + config_clean(); + return 3; } - int listen_port = LISTEN_PORT; + int listen_port = config_get_port(); ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &listen_port); - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, DSAKEY_PATH); - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, RSAKEY_PATH); - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, ECDSAKEY_PATH); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, config_get_key_dsa()); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, config_get_key_rsa()); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, config_get_key_ecdsa()); if (ssh_bind_listen(sshbind) < 0) { printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); ssh_bind_free(sshbind); ssh_finalize(); - return 1; + config_clean(); + return 4; } while (1) { @@ -109,6 +115,7 @@ child_cleaning: ssh_disconnect(session); ssh_free(session); ssh_finalize(); + config_clean(); return 0; case -1: @@ -120,7 +127,8 @@ child_cleaning: ssh_free(session); ssh_bind_free(sshbind); ssh_finalize(); - return 1; + config_clean(); + return 5; } /* Since the session has been passed to a child fork, do some cleaning up at the parent process. */ ssh_disconnect(session); @@ -128,6 +136,7 @@ child_cleaning: } ssh_bind_free(sshbind); ssh_finalize(); + config_clean(); db_clean(); return 0; } diff --git a/bastion/recording.c b/bastion/recording.c index 8a8c570..4891939 100644 --- a/bastion/recording.c +++ b/bastion/recording.c @@ -26,15 +26,15 @@ void clean_recorder(void) static char * // returns NULL if error, this char * is to be freed from the calling code make_filename(void) { - char * format = LOG_FILENAME_FORMAT; + const char * format = config_get_session_recording_path(); char * filename = NULL; unsigned int fname_pos = 0; unsigned int format_pos = 0; - filename = malloc(LOG_FILENAME_MAX_LEN+1); + filename = malloc(SESSION_RECORDING_FILENAME_MAX_LEN+1); size_t format_len = strlen(format); - while (format_pos < format_len + 1 && fname_pos < LOG_FILENAME_MAX_LEN +1) { + while (format_pos < format_len + 1 && fname_pos < SESSION_RECORDING_FILENAME_MAX_LEN +1) { if (format[format_pos] == '$') { format_pos++; if (format[format_pos] == 'd') { @@ -42,7 +42,7 @@ make_filename(void) struct tm * tm; time(&t); tm = localtime(&t); - fname_pos += strftime(filename + fname_pos, LOG_FILENAME_MAX_LEN - fname_pos, "%F", tm); + fname_pos += strftime(filename + fname_pos, SESSION_RECORDING_FILENAME_MAX_LEN - fname_pos, "%F", tm); } else if (format[format_pos] == 'h') { const char * hostname = state_get_ssh_destination(); size_t len = strlen(hostname); @@ -66,7 +66,7 @@ make_filename(void) if (dir) closedir(dir); else { - int ret = mkdir(filename, LOG_DIRECTORY_MODE); + int ret = mkdir(filename, SESSION_RECORDING_DIRECTORY_MODE); if (ret != 0) { fprintf(stderr, "Couldn't create log directory %s : %s\n", filename, strerror( errno )); } @@ -78,7 +78,7 @@ make_filename(void) } if (filename[fname_pos-1] != '\0') { - fprintf(stderr, "Log file name is too long, check LOG_FILENAME_FORMAT and LOG_FILENAME_MAX_LEN\n"); + fprintf(stderr, "Log file name is too long, check the log filename in the configuration file at %s. Alternatively you can change SESSION_RECORDING_FILENAME_MAX_LEN (current value is %d) in the sources and recompile bastion.\n", CONFIG_PATH, SESSION_RECORDING_FILENAME_MAX_LEN); free(filename); filename = NULL; } diff --git a/common/config.c b/common/config.c new file mode 100644 index 0000000..e8196ce --- /dev/null +++ b/common/config.c @@ -0,0 +1,86 @@ +#include +#include + +#include "config.h" + +config_t * config = NULL; + +char // returns 0 if ok, greater than 0 otherwise +config_load(void) +{ + config = malloc(sizeof(config_t)); + config_init(config); + config_set_tab_width(config, 4); + if (config_read_file(config, CONFIG_PATH) != CONFIG_TRUE) { + switch(config_error_type(config)) { + case CONFIG_ERR_NONE: + fprintf(stderr, "Configuration read error with none type reported... This shouldn't happen!\n"); + break; + case CONFIG_ERR_FILE_IO: + fprintf(stderr, "Configuration I/O error, the most common cause is a file not found at %s\n", CONFIG_PATH); + break; + case CONFIG_ERR_PARSE: + fprintf(stderr, "Configuration parse error\n"); + break; + } + fprintf(stderr, "Configuration read error occured at %s:%d %s\n", config_error_file(config), config_error_line(config), config_error_text(config)); + return 1; + } + return 0; +} + +int config_get_port(void) +{ + int port; + if (config_lookup_int(config, "port", &port) != CONFIG_TRUE) { + return DEFAULT_PORT; + } + return port; +} + +const char * config_get_key_dsa(void) +{ + const char * key; + if (config_lookup_string(config, "keys.dsa", &key) != CONFIG_TRUE) { + return DEFAULT_DSAKEY_PATH; + } + return key; +} + +const char * config_get_key_rsa(void) +{ + const char * key; + if (config_lookup_string(config, "keys.rsa", &key) != CONFIG_TRUE) { + return DEFAULT_RSAKEY_PATH; + } + return key; +} + +const char * config_get_key_ecdsa(void) +{ + const char * key; + if (config_lookup_string(config, "keys.ecdsa", &key) != CONFIG_TRUE) { + return DEFAULT_ECDSAKEY_PATH; + } + return key; +} + +#ifdef SESSION_RECORDING +const char * config_get_session_recording_path(void) +{ + const char * key; + if (config_lookup_string(config, "session_recording.path", &key) != CONFIG_TRUE) { + return DEFAULT_SESSION_RECORDING_PATH; + } + return key; +} +#endif + +void config_clean(void) +{ + if (config != NULL) { + config_destroy(config); + free(config); + config = NULL; + } +} diff --git a/common/config.h.in b/common/config.h.in index 1263c7c..189e725 100644 --- a/common/config.h.in +++ b/common/config.h.in @@ -1,21 +1,35 @@ #ifndef COMMON_CONFIG_H_ #define COMMON_CONFIG_H_ -#define LISTEN_PORT 2222 +#define CONFIG_PATH "@CMAKE_INSTALL_PREFIX@/etc/bastion/bastion.conf" + #define MAX_HOSTNAME_LENGTH 64 #define MAX_USERNAME_LENGTH 64 -#define DSAKEY_PATH "@CMAKE_INSTALL_PREFIX@/etc/ssh_host_dsa_key" -#define RSAKEY_PATH "@CMAKE_INSTALL_PREFIX@/etc/ssh_host_rsa_key" -#define ECDSAKEY_PATH "@CMAKE_INSTALL_PREFIX@/etc/ssh_host_ecdsa_key" +#define DEFAULT_PORT 2222 +#define DEFAULT_DSAKEY_PATH "@CMAKE_INSTALL_PREFIX@/etc/bastion/ssh_host_dsa_key" +#define DEFAULT_RSAKEY_PATH "@CMAKE_INSTALL_PREFIX@/etc/bastion/ssh_host_rsa_key" +#define DEFAULT_ECDSAKEY_PATH "@CMAKE_INSTALL_PREFIX@/etc/bastion/ssh_host_ecdsa_key" #define MYSQL_HOST "localhost" #define MYSQL_USER "sshportal" #define MYSQL_PASS "graou" #define MYSQL_DB "sshportal" -#define LOG_FILENAME_FORMAT "@CMAKE_INSTALL_PREFIX@/var/log/$d/$h/$u/$i.gz" // $d : date in iso format, $h : hostname, $u : username : $i session id -#define LOG_FILENAME_MAX_LEN 255 -#define LOG_DIRECTORY_MODE S_IRUSR | S_IWUSR | S_IXUSR +#ifdef SESSION_RECORDING +#define DEFAULT_SESSION_RECORDING_PATH "@CMAKE_INSTALL_PREFIX@/var/log/bastion/$d/$h/$u/$i.gz" +#define SESSION_RECORDING_FILENAME_MAX_LEN 255 +#define SESSION_RECORDING_DIRECTORY_MODE S_IRUSR | S_IWUSR | S_IXUSR +#endif + +char config_load(void); +int config_get_port(void); +const char * config_get_key_dsa(void); +const char * config_get_key_rsa(void); +const char * config_get_key_ecdsa(void); +#ifdef SESSION_RECORDING +const char * config_get_session_recording_path(void); +#endif +void config_clean(void); #endif -- cgit v1.2.3