aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Dessaux2018-08-26 18:54:19 +0200
committerJulien Dessaux2018-11-02 18:32:59 +0100
commitfecf526ed334c8b987b37a3702e85a08bb7a9e68 (patch)
treeb70f7ba78eb409f12a63c5ac69ef0ba97cf7b269
parentAdded session recording with https://github.com/kilobyte/termrec (diff)
downloadbastion-fecf526ed334c8b987b37a3702e85a08bb7a9e68.tar.gz
bastion-fecf526ed334c8b987b37a3702e85a08bb7a9e68.tar.bz2
bastion-fecf526ed334c8b987b37a3702e85a08bb7a9e68.zip
Began implementing configuration fetching from an sshportal mysql database
-rw-r--r--GNUmakefile2
-rw-r--r--config.h13
-rw-r--r--src/client.c43
-rw-r--r--src/main.c7
-rw-r--r--src/mysql.c165
-rw-r--r--src/mysql.h18
-rw-r--r--src/proxy.c3
-rw-r--r--src/recording.c4
-rw-r--r--src/session.c17
-rw-r--r--src/state.c20
-rw-r--r--src/state.h4
11 files changed, 251 insertions, 45 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 52ee070..2de00cf 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -8,7 +8,7 @@ OBJ=$(sources:.c=.o)
all: bastion
bastion: $(OBJ)
- $(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -ltty
+ $(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -ltty -lmysqlclient
clean:
$(RM) bastion *.[do] src/*.[do]
diff --git a/config.h b/config.h
index 6e93bf4..18815e8 100644
--- a/config.h
+++ b/config.h
@@ -1,18 +1,19 @@
#ifndef CONFIG_H_
#define CONFIG_H_
-#define USER_TO_LOGIN_AS "root"
-
#define LISTEN_PORT 2222
-#define MAX_HOSTNAME_LENGTH 255
-#define MAX_USERNAME_LENGTH 255
-#define USER_RSA_PUBKEY "AAAAB3NzaC1yc2EAAAADAQABAAACAQDMdBAFjENiPMTtq90GT3+NZ68nfGxQiRExaYYnLzm1ecmulCvsuA4AOpeLY6f+FWe+ludiw7nhrXzssDdsKBy0QL+XQyvjjjW4X+k9MYhP1gAWXEOGJnjJ/1ovEsMt++6fLyNKLUTA46kErbEehDs22r+rIiEKatrn0BNrJcRI94H44oEL1/ImzVam0cSBL0tPiaJxe60sBs7M76zfyFtVdMGkeuBpS7ee+FLA58fsS3/sEZmkas8MT0QdvZz1y/66MknXYbIaqDSOUACXGF4yVKpogLRRJ1SgNo1Ujo/U3VOR1O4CiQczsZOcbSdjgl0x3fJb7BaIxrZy9iW2I7G/L/chfTvRws+x1s1y5FNZOOiXMCdZjhgLaRwb6p5gMsMVn9sJbhDjmejcAkBKQDkzbvxxhfVkH225FoVXA9YF0msWLyOEyZQYbA8autLDJsAOT5RDfw/G82DQBufAPEBR/bPby0Hl5kjqW75bpSVxDvzmKwt3EpITg9iuYEhvYZ/Zq5qC1UJ54ZfOvaf0PsTUzFePty6ve/JzfxCV1XgFQ+B8l4NSz11loDfNXSUngf7lL4qu5X4aN6WmLFO1YbyFlfpvt3K1CekJmWVeE5mV9EFTUJ4ParVWRGiA4W+zaCOsHgRkcGkp4eYGyWW8gOR/lVxYU2IFl9mbMrC9bkdRbQ=="
-#define PRIVKEY_PATH "./id_rsa"
+#define MAX_HOSTNAME_LENGTH 64
+#define MAX_USERNAME_LENGTH 64
#define DSAKEY_PATH "./ssh_host_dsa_key"
#define RSAKEY_PATH "./ssh_host_rsa_key"
#define ECDSAKEY_PATH "./ssh_host_ecdsa_key"
+#define MYSQL_HOST "::"
+#define MYSQL_USER "root"
+#define MYSQL_PASS "graou"
+#define MYSQL_DB "sshportal"
+
#define SESSION_RECORDING // comment this to deactivate
#define LOG_FILENAME_FORMAT "./log/$d/$h/$u/$i.gz" // $d : date in iso format, $h : hostname, $u : username : $i session id
#define LOG_FILENAME_MAX_LEN 255
diff --git a/src/client.c b/src/client.c
index 6c3cbd0..11757fc 100644
--- a/src/client.c
+++ b/src/client.c
@@ -7,6 +7,7 @@
#ifdef SESSION_RECORDING
#include "recording.h"
#endif
+#include "mysql.h"
#include "state.h"
// callback function for channel data and exceptions
@@ -87,8 +88,12 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha
cdata->client_channel_cb = NULL;
/* First we try to add the private key that the server will accept */
- ssh_key privkey;
- if (ssh_pki_import_privkey_file(PRIVKEY_PATH, NULL, NULL, NULL, &privkey) != SSH_OK) {
+ struct db_host_info * info = db_get_host_info(hostname);
+ if (info == NULL)
+ goto host_info_clean;
+
+ ssh_key privkey = NULL;
+ if (ssh_pki_import_privkey_base64(info->privkeytxt, NULL, NULL, NULL, &privkey) != SSH_OK) {
printf("Error importing private key");
goto privkey_clean;
}
@@ -97,8 +102,8 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha
printf("Connecting to %s\n", hostname);
cdata->my_session = ssh_new();
- ssh_options_set(cdata->my_session, SSH_OPTIONS_HOST, hostname);
- ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, state_get_username());
+ ssh_options_set(cdata->my_session, SSH_OPTIONS_HOST, info->address);
+ ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, info->username);
#ifdef LIBSSH_VERBOSE_OUTPOUT
int verbosity = SSH_LOG_PROTOCOL;
ssh_options_set(cdata->my_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
@@ -110,7 +115,7 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha
}
/* We now validate the remote server's public key */
- ssh_key server_pub_key;
+ ssh_key server_pub_key = NULL;
unsigned char * hash = NULL;
size_t hlen;
char * hexa = NULL;
@@ -123,15 +128,26 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha
goto pubkey_hash_clean;
}
hexa = ssh_get_hexa(hash, hlen);
- printf("Server public key hash : %s\n", hexa); // TODO validate the key
- // if error goto pubkey_nomatch_clean
+ if (strlen(info->hostkeyhash) > 0) {
+ if (strcmp(hexa, info->hostkeyhash) != 0) {
+ fprintf(stderr, "Error invalid host key for %s\n", hostname);
+ goto pubkey_hexa_clean;
+ }
+ } else {
+ // TODO we got a broken sshportal record, we need to fix it but only
+ // after we completed the migration from sshportal
+ //db_set_host_publickey_hash(hostname, hexa);
+ }
+ ssh_string_free_char(hexa);
+ ssh_clean_pubkey_hash(&hash);
+ ssh_key_free(server_pub_key);
/* With the server checked, we can authenticate */
if(ssh_userauth_publickey(cdata->my_session, NULL, privkey) == SSH_AUTH_SUCCESS){
printf("Authentication success\n");
} else {
printf("Error private key was rejected\n");
- goto auth_clean;
+ goto session_clean;
}
/* we open the client channel */
@@ -170,17 +186,14 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha
}
#endif
- ssh_string_free_char(hexa);
- ssh_clean_pubkey_hash(&hash);
- ssh_key_free(server_pub_key);
ssh_key_free(privkey);
+ db_free_host_info(info);
return cdata;
channel_clean:
ssh_channel_free(cdata->my_channel);
-auth_clean:
- // TODO when pubkey match implemented fix this
-//pubkey_nomatch_clean:
+ goto session_clean;
+pubkey_hexa_clean:
ssh_string_free_char(hexa);
pubkey_hash_clean:
ssh_clean_pubkey_hash(&hash);
@@ -189,8 +202,10 @@ pubkey_clean:
session_clean:
ssh_disconnect(cdata->my_session);
ssh_free(cdata->my_session);
+ db_free_host_info(info);
privkey_clean:
ssh_key_free(privkey);
+host_info_clean:
free(cdata);
return NULL;
}
diff --git a/src/main.c b/src/main.c
index 4f475ee..6ea6bcb 100644
--- a/src/main.c
+++ b/src/main.c
@@ -5,6 +5,7 @@
#include <sys/wait.h>
#include "../config.h"
+#include "mysql.h"
#include "session.h"
/* SIGCHLD handler for cleaning up dead children. */
@@ -24,6 +25,7 @@ __attribute__((noreturn)) static void sigint_handler(int signo)
ssh_free(session);
ssh_bind_free(sshbind);
ssh_finalize();
+ db_clean();
exit(0);
}
@@ -92,6 +94,9 @@ int main()
ssh_bind_free(sshbind);
sshbind = NULL;
+ if (db_init() !=0)
+ goto child_cleaning;
+
ssh_event event = ssh_event_new();
if (event != NULL) {
/* Blocks until the SSH session ends */
@@ -100,6 +105,7 @@ int main()
} else {
fprintf(stderr, "Could not create polling context\n");
}
+child_cleaning:
ssh_disconnect(session);
ssh_free(session);
ssh_finalize();
@@ -122,5 +128,6 @@ int main()
}
ssh_bind_free(sshbind);
ssh_finalize();
+ db_clean();
return 0;
}
diff --git a/src/mysql.c b/src/mysql.c
new file mode 100644
index 0000000..b2a271e
--- /dev/null
+++ b/src/mysql.c
@@ -0,0 +1,165 @@
+#include <libssh/server.h>
+#include <mysql/mysql.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../config.h"
+#include "mysql.h"
+
+static MYSQL *db;
+
+char // returns 0 if ok, greater than 0 otherwise
+db_init(void)
+{
+ printf("MySQL client version: %s\n", mysql_get_client_info());
+ db = mysql_init(NULL);
+ if (db == NULL) {
+ fprintf(stderr, "%s\n", mysql_error(db));
+ return 1;
+ }
+ if (mysql_real_connect(db, MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB, 0, NULL, 0) == NULL) {
+ fprintf(stderr, "%s\n", mysql_error(db));
+ mysql_close(db);
+ return 1;
+ }
+ return 0;
+}
+
+void db_clean(void)
+{
+ mysql_close(db);
+ db = NULL;
+}
+
+char * // returns NULL if no user found, this char * is to be freed from the calling code
+db_get_username_from_pubkey(ssh_key pubkey)
+{
+ int res = mysql_query(db, "SELECT name, authorized_key FROM users, user_keys WHERE users.id = user_keys.user_id");
+ if (res != 0) {
+ fprintf(stderr, "WARNING: Couldn't get usernames from database.\n");
+ return NULL;
+ }
+ MYSQL_RES *result = mysql_store_result(db);
+ if (result == NULL) {
+ fprintf(stderr, "FATAL: Couldn't retrieve public keys from database.\n");
+ return NULL;
+ }
+
+ MYSQL_ROW row;
+ while ((row = mysql_fetch_row(result))) {
+ char * rsa = "ssh-rsa ";
+ if (strncmp (row[1], rsa, strlen(rsa)) != 0) {
+ fprintf(stderr, "Unsupported public key type for user %s : %s\n", row[0], row[1]);
+ } else {
+ ssh_key tmp_key;
+ if (ssh_pki_import_pubkey_base64(row[1] + strlen(rsa), SSH_KEYTYPE_RSA, &tmp_key) != SSH_OK) {
+ fprintf(stderr, "Error importing public key for user %s : %s\n", row[0], row[1]);
+ } else if (!ssh_key_cmp(pubkey, tmp_key, SSH_KEY_CMP_PUBLIC)) {
+ size_t len = strlen(row[0]);
+ char * username = malloc(len+1);
+ strcpy(username, row[0]);
+ ssh_key_free(tmp_key);
+ mysql_free_result(result);
+ return username;
+ } else {
+ ssh_key_free(tmp_key);
+ }
+ }
+ }
+
+ fprintf(stderr, "ERROR: Didn't find public key in database.\n");
+ mysql_free_result(result);
+ return NULL;
+}
+
+struct db_host_info * // returns NULL if no key found, this char * is to be freed from the calling code
+db_get_host_info(const char * hostname)
+{
+ char buff[255];
+ sprintf(buff, "SELECT priv_key, url, host_key FROM ssh_keys, hosts WHERE ssh_keys.id = hosts.ssh_key_id and hosts.name = \"%s\"", hostname);
+ int res = mysql_query(db, buff);
+ if (res != 0) {
+ fprintf(stderr, "WARNING: Couldn't query db for server infos for host %s\n", hostname);
+ return NULL;
+ }
+ MYSQL_RES *result = mysql_store_result(db);
+ if (result == NULL) {
+ fprintf(stderr, "FATAL: Couldn't retrieve server infos for %s from database.\n", hostname);
+ return NULL;
+ }
+
+ MYSQL_ROW row = mysql_fetch_row(result);
+ if (row == NULL) {
+ fprintf(stderr, "FATAL: Couldn't retrieve server db results for %s from database.\n", hostname);
+ mysql_free_result(result);
+ return NULL;
+ }
+
+ struct db_host_info * info = malloc(sizeof(struct db_host_info));
+ memset(info, 0, sizeof(struct db_host_info));
+
+ size_t len = strlen(row[0]);
+ info->privkeytxt = malloc(len+1);
+ strcpy(info->privkeytxt, row[0]);
+
+ if (strncmp(row[1], "ssh://", 6) != 0) {
+ fprintf(stderr, "FATAL: invalid host url %s\n", row[1]);
+ return NULL;
+ }
+ size_t at_pos = 0;
+ char done = 0;
+ for(size_t i = 6; !done; ++i) {
+ switch(*(row[1]+i)) {
+ case '@':
+ info->username = malloc(i-6+1);
+ strncpy(info->username, row[1]+6, i-6);
+ info->username[i-6] = '\0';
+ at_pos = i;
+ break;
+ case '\0':
+ info->address = malloc(i-at_pos);
+ strncpy(info->address, row[1]+at_pos+1, i-at_pos-1);
+ info->address[i-at_pos-1] = '\0';
+ done = 1;
+ break;
+ }
+ if (i > MAX_HOSTNAME_LENGTH + MAX_USERNAME_LENGTH + 6 + 1) {
+ fprintf(stderr, "FATAL: Couldn't parse host url for host %s, too long.\n", hostname);
+ if (info->username != NULL)
+ free(info->username);
+ return NULL;
+ }
+ }
+
+ len = strlen(row[2]);
+ info->hostkeyhash = malloc(len+1);
+ strcpy(info->hostkeyhash, row[2]);
+
+ mysql_free_result(result);
+ return info;
+}
+
+void db_set_host_publickey_hash(const char * hostname, const char * hash)
+{
+ char buff[255];
+ sprintf(buff, "UPDATE ssh_keys, hosts SET host_key = \"%s\" WHERE ssh_keys.id = hosts.ssh_key_id and hosts.name = \"%s\"", hash, hostname);
+ int res = mysql_query(db, buff);
+ if (res != 0) {
+ fprintf(stderr, "WARNING: Couldn't set host key for host %s: %s\n", hostname, hash);
+ return;
+ }
+ res = mysql_commit(db);
+ if (res != 0) {
+ fprintf(stderr, "WARNING: Couldn't commit after setting host key for host %s: %s\n", hostname, hash);
+ }
+}
+
+void db_free_host_info(struct db_host_info * info)
+{
+ free(info->privkeytxt);
+ free(info->address);
+ free(info->username);
+ free(info->hostkeyhash);
+ free(info);
+}
diff --git a/src/mysql.h b/src/mysql.h
new file mode 100644
index 0000000..46e607e
--- /dev/null
+++ b/src/mysql.h
@@ -0,0 +1,18 @@
+#ifndef MYSQL_H_
+#define MYSQL_H_
+
+struct db_host_info {
+ char * privkeytxt;
+ char * address;
+ char * username;
+ char * hostkeyhash;
+};
+
+char db_init(void);
+void db_clean(void);
+char * db_get_username_from_pubkey(ssh_key pubkey);
+struct db_host_info * db_get_host_info(const char * hostname);
+void db_free_host_info(struct db_host_info * info);
+void db_set_host_publickey_hash(const char * hostname, const char * hash);
+
+#endif
diff --git a/src/proxy.c b/src/proxy.c
index 5718a8a..7d410db 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -3,6 +3,7 @@
#include <stdlib.h>
#include "client.h"
+#include "mysql.h"
#include "proxy.h"
#include "state.h"
@@ -201,6 +202,8 @@ void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_c
ssh_callbacks_init(&channel_cb);
ssh_set_channel_callbacks(my_channel, &channel_cb);
+ db_clean(); // we close the mysql connection before the main loop, as to not waste ressources
+
do {
/* Poll the main event which takes care of the sessions and channels */
if (ssh_event_dopoll(event, -1) == SSH_ERROR) {
diff --git a/src/recording.c b/src/recording.c
index 6c0cdab..a001607 100644
--- a/src/recording.c
+++ b/src/recording.c
@@ -23,7 +23,7 @@ void clean_recorder(void)
recorder_handle = NULL;
}
-static char * // this char * is to be freed from the calling code
+static char * // returns NULL if error, this char * is to be freed from the calling code
make_filename(void)
{
char * format = LOG_FILENAME_FORMAT;
@@ -49,7 +49,7 @@ make_filename(void)
strcpy(filename + fname_pos, hostname);
fname_pos += len;
} else if (format[format_pos] == 'u') {
- const char * username = state_get_username();
+ const char * username = state_get_bastion_username();
size_t len = strlen(username);
strcpy(filename + fname_pos, username);
fname_pos += len;
diff --git a/src/session.c b/src/session.c
index f5827e8..2261242 100644
--- a/src/session.c
+++ b/src/session.c
@@ -9,13 +9,13 @@
#include <sys/wait.h>
#include "../config.h"
+#include "mysql.h"
#include "proxy.h"
#include "session.h"
#include "state.h"
-static int auth_pubkey(ssh_session session, const char *user,
- struct ssh_key_struct *pubkey,
- char signature_state, void *userdata) {
+static int auth_pubkey(ssh_session session, const char *user, ssh_key pubkey, char signature_state,
+ void *userdata) {
struct session_data_struct *sdata = (struct session_data_struct *) userdata;
(void) session;
@@ -31,20 +31,19 @@ static int auth_pubkey(ssh_session session, const char *user,
// TODO check for an invite
- ssh_key reference_key;
- ssh_pki_import_pubkey_base64(USER_RSA_PUBKEY, SSH_KEYTYPE_RSA, &reference_key); // TODO fetch all pubkeys from db
- if (!ssh_key_cmp(pubkey, reference_key, SSH_KEY_CMP_PUBLIC)) {
+ char * bastion_username = db_get_username_from_pubkey(pubkey);
+ if (bastion_username != NULL) {
sdata->authenticated = 1;
- ssh_key_free(reference_key);
if (state_set_ssh_destination(user) != 0)
return SSH_ERROR;
// TODO check access rights and host configs
- state_set_username(USER_TO_LOGIN_AS);
+ state_set_bastion_username(bastion_username);
+ free(bastion_username);
// TODO log session creation in db
state_set_session_id(1337);
return SSH_AUTH_SUCCESS;
} else {
- ssh_key_free(reference_key);
+ free(bastion_username);
sdata->auth_attempts++;
return SSH_AUTH_DENIED;
}
diff --git a/src/state.c b/src/state.c
index 646fabe..d9577e7 100644
--- a/src/state.c
+++ b/src/state.c
@@ -7,9 +7,9 @@
struct state {
char * destination;
- char * username;
+ char * bastion_username;
int session_id;
- int padding;
+ int padding; // makes compiler happy
};
static struct state state = {0};
@@ -37,10 +37,10 @@ const char * state_get_ssh_destination(void)
}
char // return 0 if ok, greater than 0 otherwise
-state_set_username(const char * name)
+state_set_bastion_username(const char * name)
{
- if (state.username != NULL) {
- fprintf(stderr, "BUG found, attempting to overwrite state.username that has already been set\n");
+ if (state.bastion_username != NULL) {
+ fprintf(stderr, "BUG found, attempting to overwrite state.bastion_username that has already been set\n");
return 1;
}
size_t len = strnlen(name, MAX_USERNAME_LENGTH + 1);
@@ -48,14 +48,14 @@ state_set_username(const char * name)
fprintf(stderr, "Username too long, max length is %d.\n", MAX_USERNAME_LENGTH);
return 1;
}
- state.username = malloc(len+1);
- strncpy(state.username, name, len+1);
+ state.bastion_username = malloc(len+1);
+ strncpy(state.bastion_username, name, len+1);
return 0;
}
-const char * state_get_username(void)
+const char * state_get_bastion_username(void)
{
- return state.username;
+ return state.bastion_username;
}
char // return 0 if ok, greater than 0 otherwise
@@ -78,6 +78,4 @@ void state_clean(void)
{
free(state.destination);
state.destination = NULL;
- free(state.username);
- state.username = NULL;
}
diff --git a/src/state.h b/src/state.h
index 79c5861..df5bd6e 100644
--- a/src/state.h
+++ b/src/state.h
@@ -3,8 +3,8 @@
char state_set_ssh_destination(const char * dest);
const char * state_get_ssh_destination(void);
-char state_set_username(const char * name);
-const char * state_get_username(void);
+char state_set_bastion_username(const char * name);
+const char * state_get_bastion_username(void);
char state_set_session_id(const int id);
int state_get_session_id(void);
void state_clean(void);