Archived
1
0
Fork 0

Began implementing configuration fetching from an sshportal mysql database

This commit is contained in:
Julien Dessaux 2018-08-26 18:54:19 +02:00
parent beff818f25
commit fecf526ed3
11 changed files with 251 additions and 45 deletions

View file

@ -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]

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

165
src/mysql.c Normal file
View file

@ -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);
}

18
src/mysql.h Normal file
View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);