From 410d78890e5a7c5a45eff5bef943e9a10537b53e Mon Sep 17 00:00:00 2001 From: Geoffroy Arnoud Date: Tue, 8 Dec 2020 11:53:06 +0100 Subject: [PATCH 1/6] Added : - support to tomcat (through snapshot version of lmvc) - support to liquibase (and virtually to any database) - build a war for MariaDB, through a maven profile - support for heartbeat messages sent --- pom.xml | 259 +++++----- .../bastillion/common/db/DBInitServlet.java | 137 ++--- .../io/bastillion/manage/db/ProfileDB.java | 470 +++++++++--------- .../manage/socket/SecureShellWS.java | 452 ++++++++--------- .../resources/BastillionConfig.properties | 13 +- .../changelog/3.11.00_initial_schema.xml | 291 +++++++++++ .../resources/config/liquibase/master.xml | 10 + src/main/resources/log4j2.xml | 2 +- 8 files changed, 993 insertions(+), 641 deletions(-) create mode 100644 src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml create mode 100644 src/main/resources/config/liquibase/master.xml diff --git a/pom.xml b/pom.xml index 093d9bf8..128c5907 100755 --- a/pom.xml +++ b/pom.xml @@ -1,114 +1,145 @@ - - - 4.0.0 - io.bastillion - bastillion - 3.11.00-SNAPSHOT - war - Bastillion - - UTF-8 - 1.9 - 1.9 - 9.4.29.v20200521 - - - - loophole.mvc - lmvc - 1.06.00 - - - com.h2database - h2 - 1.4.200 - - - com.jcraft - jsch - 0.1.55 - - - com.google.code.gson - gson - 2.8.6 - - - commons-fileupload - commons-fileupload - 1.4 - - - commons-codec - commons-codec - 1.14 - - - org.apache.commons - commons-dbcp2 - 2.7.0 - - - commons-configuration - commons-configuration - 1.10 - - - com.google.zxing - core - 3.4.0 - - - - - org.eclipse.jetty - jetty-jaas - ${jetty-version} - provided - - - org.eclipse.jetty.websocket - javax-websocket-server-impl - ${jetty-version} - provided - - - - - - src/main/resources - - VERSION.txt - - true - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.0 - - - org.apache.maven.plugins - maven-war-plugin - 3.2.2 - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty-version} - - - ${basedir}/src/test/resources/jetty.xml,${basedir}/src/test/resources/jetty-ssl.xml,${basedir}/src/test/resources/jetty-https.xml - - -1 - - - - - + + + 4.0.0 + io.bastillion + bastillion + 3.11.00-SNAPSHOT + war + Bastillion + + UTF-8 + 1.9 + 1.9 + 9.4.29.v20200521 + + + + loophole.mvc + lmvc + 1.06.01-SNAPSHOT + + + + com.jcraft + jsch + 0.1.55 + + + com.google.code.gson + gson + 2.8.6 + + + commons-fileupload + commons-fileupload + 1.4 + + + commons-codec + commons-codec + 1.14 + + + org.apache.commons + commons-dbcp2 + 2.7.0 + + + commons-configuration + commons-configuration + 1.10 + + + com.google.zxing + core + 3.4.0 + + + org.liquibase + liquibase-core + 4.2.1 + + + + + org.eclipse.jetty + jetty-jaas + ${jetty-version} + provided + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty-version} + provided + + + + + + src/main/resources + + VERSION.txt + + true + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.0 + + + org.apache.maven.plugins + maven-war-plugin + 3.2.2 + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty-version} + + + ${basedir}/src/test/resources/jetty.xml,${basedir}/src/test/resources/jetty-ssl.xml,${basedir}/src/test/resources/jetty-https.xml + + -1 + + + + + + + h2 + + true + + + + com.h2database + h2 + 1.4.200 + + + + + mariadb + + false + + + + + org.mariadb.jdbc + mariadb-java-client + 2.6.2 + + + + + diff --git a/src/main/java/io/bastillion/common/db/DBInitServlet.java b/src/main/java/io/bastillion/common/db/DBInitServlet.java index 67569003..684a7161 100755 --- a/src/main/java/io/bastillion/common/db/DBInitServlet.java +++ b/src/main/java/io/bastillion/common/db/DBInitServlet.java @@ -27,13 +27,6 @@ */ package io.bastillion.common.db; -import io.bastillion.common.util.AppConfig; -import io.bastillion.manage.model.Auth; -import io.bastillion.manage.util.*; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; import java.io.File; import java.sql.Connection; import java.sql.PreparedStatement; @@ -41,19 +34,35 @@ import java.sql.Statement; import java.util.Scanner; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; + import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.bastillion.common.util.AppConfig; +import io.bastillion.manage.model.Auth; +import io.bastillion.manage.util.DBUtils; +import io.bastillion.manage.util.EncryptionUtil; +import io.bastillion.manage.util.RefreshAuthKeyUtil; +import io.bastillion.manage.util.SSHUtil; +import liquibase.Contexts; +import liquibase.LabelExpression; +import liquibase.Liquibase; +import liquibase.database.Database; +import liquibase.database.DatabaseFactory; +import liquibase.database.jvm.JdbcConnection; +import liquibase.resource.ClassLoaderResourceAccessor; + /** - * Initial startup task. Creates an SQLite DB and generates - * the system public/private key pair if none exists + * Initial startup task. Creates an SQLite DB and generates the system + * public/private key pair if none exists */ -@WebServlet(name = "DBInitServlet", - urlPatterns = {"/config"}, - loadOnStartup = 1) +@WebServlet(name = "DBInitServlet", urlPatterns = { "/config" }, loadOnStartup = 1) public class DBInitServlet extends javax.servlet.http.HttpServlet { private static Logger log = LoggerFactory.getLogger(DBInitServlet.class); @@ -69,15 +78,15 @@ public void init(ServletConfig config) throws ServletException { Connection connection = null; Statement statement = null; - //check if reset ssh application key is set + // check if reset ssh application key is set boolean resetSSHKey = "true".equals(AppConfig.getProperty("resetApplicationSSHKey")); - //if DB password is empty generate a random - if(StringUtils.isEmpty(AppConfig.getProperty("dbPassword"))) { + // if DB password is empty generate a random + if (StringUtils.isEmpty(AppConfig.getProperty("dbPassword"))) { String dbPassword = null; String dbPasswordConfirm = null; - if(!"true".equals(System.getProperty("GEN_DB_PASS"))) { - //prompt for password and confirmation + if (!"true".equals(System.getProperty("GEN_DB_PASS"))) { + // prompt for password and confirmation while (dbPassword == null || !dbPassword.equals(dbPasswordConfirm)) { if (System.console() == null) { Scanner in = new Scanner(System.in); @@ -87,22 +96,23 @@ public void init(ServletConfig config) throws ServletException { dbPasswordConfirm = in.nextLine(); } else { dbPassword = new String(System.console().readPassword("Please enter database password: ")); - dbPasswordConfirm = new String(System.console().readPassword("Please confirm database password: ")); + dbPasswordConfirm = new String( + System.console().readPassword("Please confirm database password: ")); } if (!dbPassword.equals(dbPasswordConfirm)) { System.out.println("Passwords do not match"); } } } - //set password - if(StringUtils.isNotEmpty(dbPassword)) { + // set password + if (StringUtils.isNotEmpty(dbPassword)) { AppConfig.encryptProperty("dbPassword", dbPassword); - //if password not set generate a random + // if password not set generate a random } else { System.out.println("Generating random database password"); AppConfig.encryptProperty("dbPassword", RandomStringUtils.random(32, true, true)); } - //else encrypt password if plain-text + // else encrypt password if plain-text } else if (!AppConfig.isPropertyEncrypted("dbPassword")) { AppConfig.encryptProperty("dbPassword", AppConfig.getProperty("dbPassword")); } @@ -111,43 +121,59 @@ public void init(ServletConfig config) throws ServletException { connection = DBUtils.getConn(); statement = connection.createStatement(); - ResultSet rs = statement.executeQuery("select * from information_schema.tables where upper(table_name) = 'USERS' and table_schema='PUBLIC'"); - if (!rs.next()) { - resetSSHKey = true; + boolean databaseExists = false; + + ResultSet rs = null; + try { + rs = statement.executeQuery("select * from users"); + databaseExists = true; + } finally { + DBUtils.closeRs(rs); + } - //create DB objects - statement.executeUpdate("create table if not exists users (id INTEGER PRIMARY KEY AUTO_INCREMENT, first_nm varchar, last_nm varchar, email varchar, username varchar not null unique, password varchar, auth_token varchar, auth_type varchar not null default '" + Auth.AUTH_BASIC+ "', user_type varchar not null default '" + Auth.ADMINISTRATOR + "', salt varchar, otp_secret varchar, last_login_tm timestamp, expiration_tm timestamp)"); - statement.executeUpdate("create table if not exists user_theme (user_id INTEGER PRIMARY KEY, bg varchar(7), fg varchar(7), d1 varchar(7), d2 varchar(7), d3 varchar(7), d4 varchar(7), d5 varchar(7), d6 varchar(7), d7 varchar(7), d8 varchar(7), b1 varchar(7), b2 varchar(7), b3 varchar(7), b4 varchar(7), b5 varchar(7), b6 varchar(7), b7 varchar(7), b8 varchar(7), foreign key (user_id) references users(id) on delete cascade) "); - statement.executeUpdate("create table if not exists system (id INTEGER PRIMARY KEY AUTO_INCREMENT, display_nm varchar not null, user varchar not null, host varchar not null, port INTEGER not null, authorized_keys varchar not null, status_cd varchar not null default 'INITIAL')"); - statement.executeUpdate("create table if not exists profiles (id INTEGER PRIMARY KEY AUTO_INCREMENT, nm varchar not null, desc varchar not null)"); - statement.executeUpdate("create table if not exists system_map (profile_id INTEGER, system_id INTEGER, foreign key (profile_id) references profiles(id) on delete cascade , foreign key (system_id) references system(id) on delete cascade, primary key (profile_id, system_id))"); - statement.executeUpdate("create table if not exists user_map (user_id INTEGER, profile_id INTEGER, foreign key (user_id) references users(id) on delete cascade, foreign key (profile_id) references profiles(id) on delete cascade, primary key (user_id, profile_id))"); - statement.executeUpdate("create table if not exists application_key (id INTEGER PRIMARY KEY AUTO_INCREMENT, public_key varchar not null, private_key varchar not null, passphrase varchar)"); + // Automatically create or update database + if (Boolean.valueOf(AppConfig.getProperty("dbCreate"))) { - statement.executeUpdate("create table if not exists status (id INTEGER, user_id INTEGER, status_cd varchar not null default 'INITIAL', foreign key (id) references system(id) on delete cascade, foreign key (user_id) references users(id) on delete cascade, primary key(id, user_id))"); - statement.executeUpdate("create table if not exists scripts (id INTEGER PRIMARY KEY AUTO_INCREMENT, user_id INTEGER, display_nm varchar not null, script varchar not null, foreign key (user_id) references users(id) on delete cascade)"); + log.info("Creating database with Liquibase"); + + // Create DB objects with liquibase + Database database = DatabaseFactory.getInstance() + .findCorrectDatabaseImplementation(new JdbcConnection(connection)); + + try (Liquibase liquibase = new liquibase.Liquibase("config/liquibase/master.xml", + new ClassLoaderResourceAccessor(), database)) { + liquibase.update(new Contexts(), new LabelExpression()); + } catch (Exception e) { + log.error("Error creating/updating database", e); + System.exit(1); + } + } - statement.executeUpdate("create table if not exists public_keys (id INTEGER PRIMARY KEY AUTO_INCREMENT, key_nm varchar not null, type varchar, fingerprint varchar, public_key varchar, enabled boolean not null default true, create_dt timestamp not null default CURRENT_TIMESTAMP(), user_id INTEGER, profile_id INTEGER, foreign key (profile_id) references profiles(id) on delete cascade, foreign key (user_id) references users(id) on delete cascade)"); + // Reset SSH Key and create admin if database didn't exist + if (!databaseExists) { - statement.executeUpdate("create table if not exists session_log (id BIGINT PRIMARY KEY AUTO_INCREMENT, session_tm timestamp default CURRENT_TIMESTAMP, first_nm varchar, last_nm varchar, username varchar not null, ip_address varchar)"); - statement.executeUpdate("create table if not exists terminal_log (session_id BIGINT, instance_id INTEGER, output varchar not null, log_tm timestamp default CURRENT_TIMESTAMP, display_nm varchar not null, user varchar not null, host varchar not null, port INTEGER not null, foreign key (session_id) references session_log(id) on delete cascade)"); + resetSSHKey = true; - //if exists readfile to set default password + // if exists readfile to set default password String salt = EncryptionUtil.generateSalt(); String defaultPassword = EncryptionUtil.hash("changeme" + salt); - //set password if running in EC2 + // set password if running in EC2 File file = new File("/opt/bastillion/instance_id"); if (file.exists()) { String str = FileUtils.readFileToString(file, "UTF-8"); - if(StringUtils.isNotEmpty(str)) { + if (StringUtils.isNotEmpty(str)) { defaultPassword = EncryptionUtil.hash(str.trim() + salt); } } - //insert default admin user - PreparedStatement pStmt = connection.prepareStatement("insert into users (username, password, user_type, salt) values(?,?,?,?)"); + // insert default admin user + if (connection == null || connection.isClosed()) { + connection = DBUtils.getConn(); + } + PreparedStatement pStmt = connection + .prepareStatement("insert into users (username, password, user_type, salt) values(?,?,?,?)"); pStmt.setString(1, "admin"); pStmt.setString(2, defaultPassword); pStmt.setString(3, Auth.MANAGER); @@ -156,26 +182,26 @@ public void init(ServletConfig config) throws ServletException { DBUtils.closeStmt(pStmt); } - DBUtils.closeRs(rs); - //if reset ssh application key then generate new key + // if reset ssh application key then generate new key if (resetSSHKey) { - //delete old key entry + // delete old key entry PreparedStatement pStmt = connection.prepareStatement("delete from application_key"); pStmt.execute(); DBUtils.closeStmt(pStmt); - //generate new key and insert passphrase + // generate new key and insert passphrase System.out.println("Setting Bastillion SSH public/private key pair"); - //generate application pub/pvt key and get values + // generate application pub/pvt key and get values String passphrase = SSHUtil.keyGen(); String publicKey = SSHUtil.getPublicKey(); String privateKey = SSHUtil.getPrivateKey(); - //insert new keys - pStmt = connection.prepareStatement("insert into application_key (public_key, private_key, passphrase) values(?,?,?)"); + // insert new keys + pStmt = connection.prepareStatement( + "insert into application_key (public_key, private_key, passphrase) values(?,?,?)"); pStmt.setString(1, publicKey); pStmt.setString(2, EncryptionUtil.encrypt(privateKey)); pStmt.setString(3, EncryptionUtil.encrypt(passphrase)); @@ -185,29 +211,26 @@ public void init(ServletConfig config) throws ServletException { System.out.println("Bastillion Generated Global Public Key:"); System.out.println(publicKey); - //set config to default + // set config to default AppConfig.updateProperty("publicKey", ""); AppConfig.updateProperty("privateKey", ""); AppConfig.updateProperty("defaultSSHPassphrase", "${randomPassphrase}"); - //set to false + // set to false AppConfig.updateProperty("resetApplicationSSHKey", "false"); } - //delete ssh keys + // delete ssh keys SSHUtil.deletePvtGenSSHKey(); - } catch (Exception ex) { log.error(ex.toString(), ex); - } - finally { + } finally { DBUtils.closeStmt(statement); DBUtils.closeConn(connection); } - RefreshAuthKeyUtil.startRefreshAllSystemsTimerTask(); } diff --git a/src/main/java/io/bastillion/manage/db/ProfileDB.java b/src/main/java/io/bastillion/manage/db/ProfileDB.java index 48456a6e..69c4bf74 100755 --- a/src/main/java/io/bastillion/manage/db/ProfileDB.java +++ b/src/main/java/io/bastillion/manage/db/ProfileDB.java @@ -27,10 +27,6 @@ */ package io.bastillion.manage.db; -import io.bastillion.manage.model.Profile; -import io.bastillion.manage.model.SortedSet; -import io.bastillion.manage.util.DBUtils; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -41,247 +37,241 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.bastillion.manage.model.Profile; +import io.bastillion.manage.model.SortedSet; +import io.bastillion.manage.util.DBUtils; /** * DAO to manage profile */ public class ProfileDB { - private static Logger log = LoggerFactory.getLogger(ProfileDB.class); - - public static final String FILTER_BY_SYSTEM = "system"; - public static final String FILTER_BY_USER = "user"; - public static final String SORT_BY_PROFILE_NM="nm"; - - private ProfileDB() { - } - - /** - * method to do order by based on the sorted set object for profiles - * @return list of profiles - */ - public static SortedSet getProfileSet(SortedSet sortedSet) { - - ArrayList profileList = new ArrayList<>(); - - String orderBy = ""; - if (sortedSet.getOrderByField() != null && !sortedSet.getOrderByField().trim().equals("")) { - orderBy = " order by " + sortedSet.getOrderByField() + " " + sortedSet.getOrderByDirection(); - } - String sql = "select distinct p.* from profiles p "; - if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_SYSTEM))) { - sql = sql + ", system_map m, system s where m.profile_id = p.id and m.system_id = s.id" + - " and (lower(s.display_nm) like ? or lower(s.host) like ?)"; - } else if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_USER))) { - sql = sql + ", user_map m, users u where m.profile_id = p.id and m.user_id = u.id" + - " and (lower(u.first_nm) like ? or lower(u.last_nm) like ?" + - " or lower(u.email) like ? or lower(u.username) like ?)"; - } - sql = sql + orderBy; - - Connection con = null; - try { - con = DBUtils.getConn(); - PreparedStatement stmt = con.prepareStatement(sql); - if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_SYSTEM))) { - stmt.setString(1, "%" + sortedSet.getFilterMap().get(FILTER_BY_SYSTEM).toLowerCase() + "%"); - stmt.setString(2, "%" + sortedSet.getFilterMap().get(FILTER_BY_SYSTEM).toLowerCase() + "%"); - } else if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_USER))) { - stmt.setString(1, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); - stmt.setString(2, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); - stmt.setString(3, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); - stmt.setString(4, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); - } - ResultSet rs = stmt.executeQuery(); - - while (rs.next()) { - Profile profile = new Profile(); - profile.setId(rs.getLong("id")); - profile.setNm(rs.getString("nm")); - profile.setDesc(rs.getString("desc")); - profileList.add(profile); - - } - DBUtils.closeRs(rs); - DBUtils.closeStmt(stmt); - - } catch (Exception e) { - log.error(e.toString(), e); - } - finally { - DBUtils.closeConn(con); - } - - sortedSet.setItemList(profileList); - return sortedSet; - } - - - /** - * returns all profile information - * - * @return list of profiles - */ - public static List getAllProfiles() { - - ArrayList profileList = new ArrayList<>(); - Connection con = null; - try { - con = DBUtils.getConn(); - PreparedStatement stmt = con.prepareStatement("select * from profiles order by nm asc"); - ResultSet rs = stmt.executeQuery(); - - while (rs.next()) { - Profile profile = new Profile(); - profile.setId(rs.getLong("id")); - profile.setNm(rs.getString("nm")); - profile.setDesc(rs.getString("desc")); - profileList.add(profile); - - } - DBUtils.closeRs(rs); - DBUtils.closeStmt(stmt); - - } catch (Exception e) { - log.error(e.toString(), e); - } - finally { - DBUtils.closeConn(con); - } - - return profileList; - } - - /** - * returns profile based on id - * - * @param profileId profile id - * @return profile - */ - public static Profile getProfile(Long profileId) { - - Profile profile = null; - Connection con = null; - try { - con = DBUtils.getConn(); - profile=getProfile(con, profileId); - } catch (Exception e) { - log.error(e.toString(), e); - } - finally { - DBUtils.closeConn(con); - } - - return profile; - } - - /** - * returns profile based on id - * - * @param con db connection object - * @param profileId profile id - * @return profile - */ - public static Profile getProfile(Connection con, Long profileId) { - - Profile profile = null; - try { - PreparedStatement stmt = con.prepareStatement("select * from profiles where id=?"); - stmt.setLong(1, profileId); - ResultSet rs = stmt.executeQuery(); - - while (rs.next()) { - profile = new Profile(); - profile.setId(rs.getLong("id")); - profile.setNm(rs.getString("nm")); - profile.setDesc(rs.getString("desc")); - profile.setHostSystemList(ProfileSystemsDB.getSystemsByProfile(con, profileId)); - - } - DBUtils.closeRs(rs); - DBUtils.closeStmt(stmt); - - } catch (Exception e) { - log.error(e.toString(), e); - } - - return profile; - } - - /** - * inserts new profile - * - * @param profile profile object - */ - public static void insertProfile(Profile profile) { - - - Connection con = null; - try { - con = DBUtils.getConn(); - PreparedStatement stmt = con.prepareStatement("insert into profiles (nm, desc) values (?,?)"); - stmt.setString(1, profile.getNm()); - stmt.setString(2, profile.getDesc()); - stmt.execute(); - DBUtils.closeStmt(stmt); - - } catch (Exception e) { - log.error(e.toString(), e); - } - finally { - DBUtils.closeConn(con); - } - } - - /** - * updates profile - * - * @param profile profile object - */ - public static void updateProfile(Profile profile) { - - - Connection con = null; - try { - con = DBUtils.getConn(); - PreparedStatement stmt = con.prepareStatement("update profiles set nm=?, desc=? where id=?"); - stmt.setString(1, profile.getNm()); - stmt.setString(2, profile.getDesc()); - stmt.setLong(3, profile.getId()); - stmt.execute(); - DBUtils.closeStmt(stmt); - - } catch (Exception e) { - log.error(e.toString(), e); - } - finally { - DBUtils.closeConn(con); - } - } - - /** - * deletes profile - * - * @param profileId profile id - */ - public static void deleteProfile(Long profileId) { - - - Connection con = null; - try { - con = DBUtils.getConn(); - PreparedStatement stmt = con.prepareStatement("delete from profiles where id=?"); - stmt.setLong(1, profileId); - stmt.execute(); - DBUtils.closeStmt(stmt); - - } catch (Exception e) { - log.error(e.toString(), e); - } - finally { - DBUtils.closeConn(con); - } - } - + private static Logger log = LoggerFactory.getLogger(ProfileDB.class); + + public static final String FILTER_BY_SYSTEM = "system"; + public static final String FILTER_BY_USER = "user"; + public static final String SORT_BY_PROFILE_NM = "nm"; + + private ProfileDB() { + } + + /** + * method to do order by based on the sorted set object for profiles + * + * @return list of profiles + */ + public static SortedSet getProfileSet(SortedSet sortedSet) { + + ArrayList profileList = new ArrayList<>(); + + String orderBy = ""; + if (sortedSet.getOrderByField() != null && !sortedSet.getOrderByField().trim().equals("")) { + orderBy = " order by " + sortedSet.getOrderByField() + " " + sortedSet.getOrderByDirection(); + } + String sql = "select distinct p.* from profiles p "; + if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_SYSTEM))) { + sql = sql + ", system_map m, system s where m.profile_id = p.id and m.system_id = s.id" + + " and (lower(s.display_nm) like ? or lower(s.host) like ?)"; + } else if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_USER))) { + sql = sql + ", user_map m, users u where m.profile_id = p.id and m.user_id = u.id" + + " and (lower(u.first_nm) like ? or lower(u.last_nm) like ?" + + " or lower(u.email) like ? or lower(u.username) like ?)"; + } + sql = sql + orderBy; + + Connection con = null; + try { + con = DBUtils.getConn(); + PreparedStatement stmt = con.prepareStatement(sql); + if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_SYSTEM))) { + stmt.setString(1, "%" + sortedSet.getFilterMap().get(FILTER_BY_SYSTEM).toLowerCase() + "%"); + stmt.setString(2, "%" + sortedSet.getFilterMap().get(FILTER_BY_SYSTEM).toLowerCase() + "%"); + } else if (StringUtils.isNotEmpty(sortedSet.getFilterMap().get(FILTER_BY_USER))) { + stmt.setString(1, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); + stmt.setString(2, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); + stmt.setString(3, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); + stmt.setString(4, "%" + sortedSet.getFilterMap().get(FILTER_BY_USER).toLowerCase() + "%"); + } + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + Profile profile = new Profile(); + profile.setId(rs.getLong("id")); + profile.setNm(rs.getString("nm")); + profile.setDesc(rs.getString("desc")); + profileList.add(profile); + + } + DBUtils.closeRs(rs); + DBUtils.closeStmt(stmt); + + } catch (Exception e) { + log.error(e.toString(), e); + } finally { + DBUtils.closeConn(con); + } + + sortedSet.setItemList(profileList); + return sortedSet; + } + + /** + * returns all profile information + * + * @return list of profiles + */ + public static List getAllProfiles() { + + ArrayList profileList = new ArrayList<>(); + Connection con = null; + try { + con = DBUtils.getConn(); + PreparedStatement stmt = con.prepareStatement("select * from profiles order by nm asc"); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + Profile profile = new Profile(); + profile.setId(rs.getLong("id")); + profile.setNm(rs.getString("nm")); + profile.setDesc(rs.getString("desc")); + profileList.add(profile); + + } + DBUtils.closeRs(rs); + DBUtils.closeStmt(stmt); + + } catch (Exception e) { + log.error(e.toString(), e); + } finally { + DBUtils.closeConn(con); + } + + return profileList; + } + + /** + * returns profile based on id + * + * @param profileId profile id + * @return profile + */ + public static Profile getProfile(Long profileId) { + + Profile profile = null; + Connection con = null; + try { + con = DBUtils.getConn(); + profile = getProfile(con, profileId); + } catch (Exception e) { + log.error(e.toString(), e); + } finally { + DBUtils.closeConn(con); + } + + return profile; + } + + /** + * returns profile based on id + * + * @param con db connection object + * @param profileId profile id + * @return profile + */ + public static Profile getProfile(Connection con, Long profileId) { + + Profile profile = null; + try { + PreparedStatement stmt = con.prepareStatement("select * from profiles where id=?"); + stmt.setLong(1, profileId); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + profile = new Profile(); + profile.setId(rs.getLong("id")); + profile.setNm(rs.getString("nm")); + profile.setDesc(rs.getString("desc")); + profile.setHostSystemList(ProfileSystemsDB.getSystemsByProfile(con, profileId)); + + } + DBUtils.closeRs(rs); + DBUtils.closeStmt(stmt); + + } catch (Exception e) { + log.error(e.toString(), e); + } + + return profile; + } + + /** + * inserts new profile + * + * @param profile profile object + */ + public static void insertProfile(Profile profile) { + + Connection con = null; + try { + con = DBUtils.getConn(); + + PreparedStatement stmt = con.prepareStatement("insert into profiles (nm, `desc`) values (?,?)"); + stmt.setString(1, profile.getNm()); + stmt.setString(2, profile.getDesc()); + stmt.execute(); + DBUtils.closeStmt(stmt); + + } catch (Exception e) { + log.error(e.toString(), e); + } finally { + DBUtils.closeConn(con); + } + } + + /** + * updates profile + * + * @param profile profile object + */ + public static void updateProfile(Profile profile) { + + Connection con = null; + try { + con = DBUtils.getConn(); + PreparedStatement stmt = con.prepareStatement("update profiles set nm=?, desc=? where id=?"); + stmt.setString(1, profile.getNm()); + stmt.setString(2, profile.getDesc()); + stmt.setLong(3, profile.getId()); + stmt.execute(); + DBUtils.closeStmt(stmt); + + } catch (Exception e) { + log.error(e.toString(), e); + } finally { + DBUtils.closeConn(con); + } + } + + /** + * deletes profile + * + * @param profileId profile id + */ + public static void deleteProfile(Long profileId) { + + Connection con = null; + try { + con = DBUtils.getConn(); + PreparedStatement stmt = con.prepareStatement("delete from profiles where id=?"); + stmt.setLong(1, profileId); + stmt.execute(); + DBUtils.closeStmt(stmt); + + } catch (Exception e) { + log.error(e.toString(), e); + } finally { + DBUtils.closeConn(con); + } + } } diff --git a/src/main/java/io/bastillion/manage/socket/SecureShellWS.java b/src/main/java/io/bastillion/manage/socket/SecureShellWS.java index c64287cb..6f89a3e8 100755 --- a/src/main/java/io/bastillion/manage/socket/SecureShellWS.java +++ b/src/main/java/io/bastillion/manage/socket/SecureShellWS.java @@ -1,6 +1,25 @@ package io.bastillion.manage.socket; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpSession; +import javax.websocket.EndpointConfig; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.google.gson.Gson; + import io.bastillion.common.util.AppConfig; import io.bastillion.common.util.AuthUtil; import io.bastillion.manage.control.SecureShellKtrl; @@ -9,17 +28,6 @@ import io.bastillion.manage.model.UserSchSessions; import io.bastillion.manage.task.SentOutputTask; import io.bastillion.manage.util.SessionOutputUtil; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.http.HttpSession; -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; /** * class to run commands and start thread to send web socket terminal output @@ -28,219 +36,213 @@ @SuppressWarnings("unchecked") public class SecureShellWS { - private static Logger log = LoggerFactory.getLogger(SecureShellWS.class); - - private HttpSession httpSession = null; - private Session session = null; - private Long sessionId = null; - private Long count = 0L; - - - - @OnOpen - public void onOpen(Session session, EndpointConfig config) { - - - //set websocket timeout - if(StringUtils.isNotEmpty(AppConfig.getProperty("websocketTimeout"))){ - session.setMaxIdleTimeout( Long.parseLong(AppConfig.getProperty("websocketTimeout"))* 60000); - } else { - session.setMaxIdleTimeout(0); - } - - if (this.httpSession == null) { - this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); - } - this.sessionId = AuthUtil.getSessionId(httpSession); - this.session = session; - - Runnable run=new SentOutputTask(sessionId, session, UserDB.getUser(AuthUtil.getUserId(httpSession))); - Thread thread = new Thread(run); - thread.start(); - - } - - - @OnMessage - public void onMessage(String message) { - - if (session.isOpen() && StringUtils.isNotEmpty(message)) { - - Map jsonRoot = new Gson().fromJson(message, Map.class); - - String command = (String) jsonRoot.get("command"); - - Integer keyCode = null; - Double keyCodeDbl = (Double) jsonRoot.get("keyCode"); - if (keyCodeDbl != null) { - keyCode = keyCodeDbl.intValue(); - } - - for (String idStr : (ArrayList) jsonRoot.get("id")) { - Integer id = Integer.parseInt(idStr); - - //get servletRequest.getSession() for user - UserSchSessions userSchSessions = SecureShellKtrl.getUserSchSessionMap().get(sessionId); - if (userSchSessions != null) { - SchSession schSession = userSchSessions.getSchSessionMap().get(id); - if (keyCode != null) { - if (keyMap.containsKey(keyCode)) { - try { - schSession.getCommander().write(keyMap.get(keyCode)); - } catch (IOException ex) { - log.error(ex.toString(), ex); - } - } - } else { - schSession.getCommander().print(command); - } - } - - } - //update timeout - AuthUtil.setTimeout(httpSession); - - } - - - - } - @OnError - public void onError(Session session, Throwable t) { - log.error(t.toString(), t); - } - - - @OnClose - public void onClose() { - - if (SecureShellKtrl.getUserSchSessionMap() != null) { - UserSchSessions userSchSessions = SecureShellKtrl.getUserSchSessionMap().get(sessionId); - if (userSchSessions != null) { - Map schSessionMap = userSchSessions.getSchSessionMap(); - - for (Integer sessionKey : schSessionMap.keySet()) { - - SchSession schSession = schSessionMap.get(sessionKey); - - //disconnect ssh session - schSession.getChannel().disconnect(); - schSession.getSession().disconnect(); - schSession.setChannel(null); - schSession.setSession(null); - schSession.setInputToChannel(null); - schSession.setCommander(null); - schSession.setOutFromChannel(null); - schSession = null; - //remove from map - schSessionMap.remove(sessionKey); - } - - - //clear and remove session map for user - schSessionMap.clear(); - SecureShellKtrl.getUserSchSessionMap().remove(sessionId); - SessionOutputUtil.removeUserSession(sessionId); - } - } - - - } - - - /** - * Maps key press events to the ascii values - */ - static Map keyMap = new HashMap<>(); - - static { - //ESC - keyMap.put(27, new byte[]{(byte) 0x1b}); - //ENTER - keyMap.put(13, new byte[]{(byte) 0x0d}); - //LEFT - keyMap.put(37, new byte[]{(byte) 0x1b, (byte) 0x4f, (byte) 0x44}); - //UP - keyMap.put(38, new byte[]{(byte) 0x1b, (byte) 0x4f, (byte) 0x41}); - //RIGHT - keyMap.put(39, new byte[]{(byte) 0x1b, (byte) 0x4f, (byte) 0x43}); - //DOWN - keyMap.put(40, new byte[]{(byte) 0x1b, (byte) 0x4f, (byte) 0x42}); - //BS - keyMap.put(8, new byte[]{(byte) 0x7f}); - //TAB - keyMap.put(9, new byte[]{(byte) 0x09}); - //CTR - keyMap.put(17, new byte[]{}); - //DEL - keyMap.put(46, "\033[3~".getBytes()); - //CTR-A - keyMap.put(65, new byte[]{(byte) 0x01}); - //CTR-B - keyMap.put(66, new byte[]{(byte) 0x02}); - //CTR-C - keyMap.put(67, new byte[]{(byte) 0x03}); - //CTR-D - keyMap.put(68, new byte[]{(byte) 0x04}); - //CTR-E - keyMap.put(69, new byte[]{(byte) 0x05}); - //CTR-F - keyMap.put(70, new byte[]{(byte) 0x06}); - //CTR-G - keyMap.put(71, new byte[]{(byte) 0x07}); - //CTR-H - keyMap.put(72, new byte[]{(byte) 0x08}); - //CTR-I - keyMap.put(73, new byte[]{(byte) 0x09}); - //CTR-J - keyMap.put(74, new byte[]{(byte) 0x0A}); - //CTR-K - keyMap.put(75, new byte[]{(byte) 0x0B}); - //CTR-L - keyMap.put(76, new byte[]{(byte) 0x0C}); - //CTR-M - keyMap.put(77, new byte[]{(byte) 0x0D}); - //CTR-N - keyMap.put(78, new byte[]{(byte) 0x0E}); - //CTR-O - keyMap.put(79, new byte[]{(byte) 0x0F}); - //CTR-P - keyMap.put(80, new byte[]{(byte) 0x10}); - //CTR-Q - keyMap.put(81, new byte[]{(byte) 0x11}); - //CTR-R - keyMap.put(82, new byte[]{(byte) 0x12}); - //CTR-S - keyMap.put(83, new byte[]{(byte) 0x13}); - //CTR-T - keyMap.put(84, new byte[]{(byte) 0x14}); - //CTR-U - keyMap.put(85, new byte[]{(byte) 0x15}); - //CTR-V - keyMap.put(86, new byte[]{(byte) 0x16}); - //CTR-W - keyMap.put(87, new byte[]{(byte) 0x17}); - //CTR-X - keyMap.put(88, new byte[]{(byte) 0x18}); - //CTR-Y - keyMap.put(89, new byte[]{(byte) 0x19}); - //CTR-Z - keyMap.put(90, new byte[]{(byte) 0x1A}); - //CTR-[ - keyMap.put(219, new byte[]{(byte) 0x1B}); - //CTR-] - keyMap.put(221, new byte[]{(byte) 0x1D}); - //INSERT - keyMap.put(45, "\033[2~".getBytes()); - //PG UP - keyMap.put(33, "\033[5~".getBytes()); - //PG DOWN - keyMap.put(34, "\033[6~".getBytes()); - //END - keyMap.put(35, "\033[4~".getBytes()); - //HOME - keyMap.put(36, "\033[1~".getBytes()); - - } + private static Logger log = LoggerFactory.getLogger(SecureShellWS.class); + + private HttpSession httpSession = null; + private Session session = null; + private Long sessionId = null; + private Long count = 0L; + + @OnOpen + public void onOpen(Session session, EndpointConfig config) { + + // set websocket timeout + if (StringUtils.isNotEmpty(AppConfig.getProperty("websocketTimeout"))) { + session.setMaxIdleTimeout(Long.parseLong(AppConfig.getProperty("websocketTimeout")) * 60000); + } else { + session.setMaxIdleTimeout(0); + } + + if (this.httpSession == null) { + this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); + } + this.sessionId = AuthUtil.getSessionId(httpSession); + this.session = session; + + Runnable run = new SentOutputTask(sessionId, session, UserDB.getUser(AuthUtil.getUserId(httpSession))); + Thread thread = new Thread(run); + thread.start(); + + } + + @OnMessage + public void onMessage(String message) { + + if (session.isOpen() && StringUtils.isNotEmpty(message)) { + + if (!StringUtils.equals(message, "heartbeat")) { + + Map jsonRoot = new Gson().fromJson(message, Map.class); + + String command = (String) jsonRoot.get("command"); + + Integer keyCode = null; + Double keyCodeDbl = (Double) jsonRoot.get("keyCode"); + if (keyCodeDbl != null) { + keyCode = keyCodeDbl.intValue(); + } + + for (String idStr : (ArrayList) jsonRoot.get("id")) { + Integer id = Integer.parseInt(idStr); + + // get servletRequest.getSession() for user + UserSchSessions userSchSessions = SecureShellKtrl.getUserSchSessionMap().get(sessionId); + if (userSchSessions != null) { + SchSession schSession = userSchSessions.getSchSessionMap().get(id); + if (keyCode != null) { + if (keyMap.containsKey(keyCode)) { + try { + schSession.getCommander().write(keyMap.get(keyCode)); + } catch (IOException ex) { + log.error(ex.toString(), ex); + } + } + } else { + schSession.getCommander().print(command); + } + } + + } + } + // update timeout + AuthUtil.setTimeout(httpSession); + + } + + } + + @OnError + public void onError(Session session, Throwable t) { + log.error(t.toString(), t); + } + + @OnClose + public void onClose() { + + if (SecureShellKtrl.getUserSchSessionMap() != null) { + UserSchSessions userSchSessions = SecureShellKtrl.getUserSchSessionMap().get(sessionId); + if (userSchSessions != null) { + Map schSessionMap = userSchSessions.getSchSessionMap(); + + for (Integer sessionKey : schSessionMap.keySet()) { + + SchSession schSession = schSessionMap.get(sessionKey); + + // disconnect ssh session + schSession.getChannel().disconnect(); + schSession.getSession().disconnect(); + schSession.setChannel(null); + schSession.setSession(null); + schSession.setInputToChannel(null); + schSession.setCommander(null); + schSession.setOutFromChannel(null); + schSession = null; + // remove from map + schSessionMap.remove(sessionKey); + } + + // clear and remove session map for user + schSessionMap.clear(); + SecureShellKtrl.getUserSchSessionMap().remove(sessionId); + SessionOutputUtil.removeUserSession(sessionId); + } + } + + } + + /** + * Maps key press events to the ascii values + */ + static Map keyMap = new HashMap<>(); + + static { + // ESC + keyMap.put(27, new byte[] { (byte) 0x1b }); + // ENTER + keyMap.put(13, new byte[] { (byte) 0x0d }); + // LEFT + keyMap.put(37, new byte[] { (byte) 0x1b, (byte) 0x4f, (byte) 0x44 }); + // UP + keyMap.put(38, new byte[] { (byte) 0x1b, (byte) 0x4f, (byte) 0x41 }); + // RIGHT + keyMap.put(39, new byte[] { (byte) 0x1b, (byte) 0x4f, (byte) 0x43 }); + // DOWN + keyMap.put(40, new byte[] { (byte) 0x1b, (byte) 0x4f, (byte) 0x42 }); + // BS + keyMap.put(8, new byte[] { (byte) 0x7f }); + // TAB + keyMap.put(9, new byte[] { (byte) 0x09 }); + // CTR + keyMap.put(17, new byte[] {}); + // DEL + keyMap.put(46, "\033[3~".getBytes()); + // CTR-A + keyMap.put(65, new byte[] { (byte) 0x01 }); + // CTR-B + keyMap.put(66, new byte[] { (byte) 0x02 }); + // CTR-C + keyMap.put(67, new byte[] { (byte) 0x03 }); + // CTR-D + keyMap.put(68, new byte[] { (byte) 0x04 }); + // CTR-E + keyMap.put(69, new byte[] { (byte) 0x05 }); + // CTR-F + keyMap.put(70, new byte[] { (byte) 0x06 }); + // CTR-G + keyMap.put(71, new byte[] { (byte) 0x07 }); + // CTR-H + keyMap.put(72, new byte[] { (byte) 0x08 }); + // CTR-I + keyMap.put(73, new byte[] { (byte) 0x09 }); + // CTR-J + keyMap.put(74, new byte[] { (byte) 0x0A }); + // CTR-K + keyMap.put(75, new byte[] { (byte) 0x0B }); + // CTR-L + keyMap.put(76, new byte[] { (byte) 0x0C }); + // CTR-M + keyMap.put(77, new byte[] { (byte) 0x0D }); + // CTR-N + keyMap.put(78, new byte[] { (byte) 0x0E }); + // CTR-O + keyMap.put(79, new byte[] { (byte) 0x0F }); + // CTR-P + keyMap.put(80, new byte[] { (byte) 0x10 }); + // CTR-Q + keyMap.put(81, new byte[] { (byte) 0x11 }); + // CTR-R + keyMap.put(82, new byte[] { (byte) 0x12 }); + // CTR-S + keyMap.put(83, new byte[] { (byte) 0x13 }); + // CTR-T + keyMap.put(84, new byte[] { (byte) 0x14 }); + // CTR-U + keyMap.put(85, new byte[] { (byte) 0x15 }); + // CTR-V + keyMap.put(86, new byte[] { (byte) 0x16 }); + // CTR-W + keyMap.put(87, new byte[] { (byte) 0x17 }); + // CTR-X + keyMap.put(88, new byte[] { (byte) 0x18 }); + // CTR-Y + keyMap.put(89, new byte[] { (byte) 0x19 }); + // CTR-Z + keyMap.put(90, new byte[] { (byte) 0x1A }); + // CTR-[ + keyMap.put(219, new byte[] { (byte) 0x1B }); + // CTR-] + keyMap.put(221, new byte[] { (byte) 0x1D }); + // INSERT + keyMap.put(45, "\033[2~".getBytes()); + // PG UP + keyMap.put(33, "\033[5~".getBytes()); + // PG DOWN + keyMap.put(34, "\033[6~".getBytes()); + // END + keyMap.put(35, "\033[4~".getBytes()); + // HOME + keyMap.put(36, "\033[1~".getBytes()); + + } } diff --git a/src/main/resources/BastillionConfig.properties b/src/main/resources/BastillionConfig.properties index f274b70b..271f6dfb 100755 --- a/src/main/resources/BastillionConfig.properties +++ b/src/main/resources/BastillionConfig.properties @@ -25,7 +25,7 @@ websocketTimeout=0 #enable SSH agent forwarding agentForwarding=false #enable two-factor authentication with a one-time password - 'required', 'optional', or 'disabled' -oneTimePassword=optional +oneTimePassword=disabled #set to false to disable key management. If false, the Bastillion public key will be appended to the authorized_keys file (instead of it being overwritten completely). keyManagementEnabled=true #set to true to generate keys when added/managed by users and enforce strong passphrases set to false to allow users to set their own public key @@ -51,11 +51,15 @@ sessionTimeout=15 #Database user dbUser=bastillion #Database password -dbPassword= +dbPassword=bastillion #Database JDBC driver -dbDriver=org.h2.Driver +#dbDriver=org.h2.Driver +dbDriver=org.mariadb.jdbc.Driver #Connection URL to the DB -dbConnectionURL=jdbc:h2:keydb/bastillion;CIPHER=AES; +#dbConnectionURL=jdbc:h2:keydb/bastillion;CIPHER=AES; +dbConnectionURL=jdbc:mariadb://localhost:3306/bastillion +#Database schema transparent creation +dbCreate=true #Max connections in the connection pool maxActive=25 #When true, objects will be validated before being returned by the connection pool @@ -65,3 +69,4 @@ minIdle=2 #The maximum amount of time (in milliseconds) to block before throwing an exception when the connection pool is exhausted maxWait=15000 + diff --git a/src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml b/src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml new file mode 100644 index 00000000..7afbaa6c --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml new file mode 100644 index 00000000..c020c52c --- /dev/null +++ b/src/main/resources/config/liquibase/master.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 1e9ebadd..6b163480 100755 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -27,7 +27,7 @@ - + From 140b683f0e41c64e2560e82e367c3c9ac0802f8c Mon Sep 17 00:00:00 2001 From: Geoffroy Arnoud Date: Tue, 8 Dec 2020 22:38:48 +0100 Subject: [PATCH 2/6] Fixed an issue on sql request (syntax not supported on MariaDB) --- .../java/io/bastillion/manage/db/PublicKeyDB.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/bastillion/manage/db/PublicKeyDB.java b/src/main/java/io/bastillion/manage/db/PublicKeyDB.java index d1ce9a4d..e858ee1e 100755 --- a/src/main/java/io/bastillion/manage/db/PublicKeyDB.java +++ b/src/main/java/io/bastillion/manage/db/PublicKeyDB.java @@ -27,12 +27,6 @@ */ package io.bastillion.manage.db; -import io.bastillion.manage.model.PublicKey; -import io.bastillion.manage.model.SortedSet; -import io.bastillion.manage.util.DBUtils; -import io.bastillion.manage.util.SSHUtil; -import org.apache.commons.lang3.StringUtils; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -41,9 +35,16 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; + +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.bastillion.manage.model.PublicKey; +import io.bastillion.manage.model.SortedSet; +import io.bastillion.manage.util.DBUtils; +import io.bastillion.manage.util.SSHUtil; + /** * DAO to manage public keys */ @@ -572,7 +573,7 @@ public static boolean isKeyRegistered(Long userId, PublicKey publicKey) { try { con = DBUtils.getConn(); - stmt = con.prepareStatement("select * from public_keys where user_id=? and fingerprint like ? and profile_id is ? and id is not ?"); + stmt = con.prepareStatement("select * from public_keys where user_id=? and fingerprint like ? and profile_id = ? and id != ?"); stmt.setLong(1, userId); stmt.setString(2, SSHUtil.getFingerprint(publicKey.getPublicKey())); if(publicKey.getProfile()!=null && publicKey.getProfile().getId()!=null){ From 72ab87329b2d065601b96fa4062b157ddbc9362d Mon Sep 17 00:00:00 2001 From: Geoffroy ARNOUD Date: Tue, 8 Dec 2020 23:03:45 +0100 Subject: [PATCH 3/6] Update README.md Added a litlle word about running in Tomcat and building with MariaDB support --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index cc12e845..c4b86400 100755 --- a/README.md +++ b/README.md @@ -67,6 +67,16 @@ for Windows More Documentation at: https://www.bastillion.io/docs/index.html +To run with Tomcat +------ + +Download Tomcat 8.5.x or above. + +Build war: + mvn clean install + +Put the war file in **webapps/** folder + Build from Source ------ Install Maven 3 or greater @@ -90,6 +100,19 @@ In the directory that contains the pom.xml run *Note: Doing a mvn clean will delete the H2 DB and wipe out all the data.* +Build to run with Mariadb +------ + + mvn clean install -Pmariadb + +Database management +------ +The database schema is managed with Liquibase (https://www.liquibase.org/). + +Resources are under src/main/resources/config/liquibase/ + +The **dbCreate** param conntrols whether or not the schema creation/update should be done when Bastillion starts. + Using Bastillion ------ Open browser to https://\:8443 From 0d91c26e75ca16c45139fb5eb48860c25aa4bb4c Mon Sep 17 00:00:00 2001 From: Geoffroy Arnoud Date: Tue, 8 Dec 2020 23:05:32 +0100 Subject: [PATCH 4/6] Fixed a bug in changelog --- .../config/liquibase/changelog/3.11.00_initial_schema.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml b/src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml index 7afbaa6c..8db6c60a 100644 --- a/src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml +++ b/src/main/resources/config/liquibase/changelog/3.11.00_initial_schema.xml @@ -208,12 +208,12 @@ - + - + From 5ff69a09aa4250bd66d0592d37794d36317054aa Mon Sep 17 00:00:00 2001 From: Geoffroy Arnoud Date: Wed, 9 Dec 2020 16:09:28 +0100 Subject: [PATCH 5/6] Fixed bug when database doesn't exist --- src/main/java/io/bastillion/common/db/DBInitServlet.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/bastillion/common/db/DBInitServlet.java b/src/main/java/io/bastillion/common/db/DBInitServlet.java index 684a7161..63e817d2 100755 --- a/src/main/java/io/bastillion/common/db/DBInitServlet.java +++ b/src/main/java/io/bastillion/common/db/DBInitServlet.java @@ -127,7 +127,10 @@ public void init(ServletConfig config) throws ServletException { try { rs = statement.executeQuery("select * from users"); databaseExists = true; - } finally { + } catch(Exception e) { + // This is not an issue. It just means the database doesn't exists + } + finally { DBUtils.closeRs(rs); } From 5a8a6e7fcf402bb413522965aa0a28ef2a530de7 Mon Sep 17 00:00:00 2001 From: Geoffroy Arnoud Date: Wed, 9 Dec 2020 16:21:46 +0100 Subject: [PATCH 6/6] Added details for MariaDB and Tomcat --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c4b86400..52d5cb3e 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ or purchase from the AWS marketplace https://aws.amazon.com/marketplace/pp/Loophole-LLC-Bastillion/B076PNFPCL Also, Bastillion can be installed on FreeBSD via the FreeBSD ports system. To install via the binary package, simply run: - + pkg install security/bastillion Prerequisites @@ -32,7 +32,7 @@ Prerequisites **Install [Authy](https://authy.com/) or [Google Authenticator](https://github.com/google/google-authenticator)** to enable two-factor authentication with Android or iOS -| Application | Android | iOS | +| Application | Android | iOS | |----------------------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------| | Authy | [Google Play](https://play.google.com/store/apps/details?id=com.authy.authy) | [iTunes](https://itunes.apple.com/us/app/authy/id494168017) | | Google Authenticator | [Google Play](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2) | [iTunes](https://itunes.apple.com/us/app/google-authenticator/id388497605) | @@ -64,25 +64,60 @@ for Linux/Unix/OSX for Windows startBastillion.bat - + More Documentation at: https://www.bastillion.io/docs/index.html - + To run with Tomcat ------ Download Tomcat 8.5.x or above. -Build war: - mvn clean install - +Build war with MariaDB support: + mvn clean install -Pmariadb + Put the war file in **webapps/** folder + cp target/bastillion-3.xx.xx.war /opt/tomcat/webapps/bastillion.war + +Externalise Bastillion config in **/etc/bastillion/** + +```bash +$ mkdir /etc/bastillion +$ chown tomcat /etc/bastillion +# put you config file into +# add property pointing to /etc/bastillion/ +$ echo 'JAVA_OPTS="$JAVA_OPTS -DCONFIG_DIR=/etc/bastillion/"' >> /opt/tomcat/bin/setenv.sh +# Create database and mariadb user +$ mysql -u root + +MariaDB > CREATE DATABASE IF NOT EXISTS bastillion CHARACTER SET=utf8; +MariaDB > create user bastillion identified by 'password'; +MariaDB > grant all privileges on bastillion.* to 'bastillion'@'localhost' identified by 'password'; +MariaDB > flush privileges; + +# Start Tomcat +$ systemctl start tomcat +``` + +**Sample httpd config:** + +``` + + ProxyPass http://127.0.0.1:8080/bastillion + ProxyPass http://127.0.0.1:8080/bastillion + + + ProxyPass ws://127.0.0.1:8080/bastillion/admin/$1 + ProxyPassReverse ws://127.0.0.1:8080/bastillion/admin/$1 + +``` + Build from Source ------ Install Maven 3 or greater -*apt-get install maven* -> http://maven.apache.org +*apt-get install maven* +> http://maven.apache.org Install Loophole MVC @@ -121,7 +156,7 @@ Login with username:admin password:changeme - + *Note: When using the AMI instance, the password is defaulted to the \. Also, the AMI uses port 443 as in https://\:443* Managing SSH Keys @@ -158,10 +193,10 @@ For example: #public key --set pub key publicKey=/Users/kavanagh/.ssh/id_rsa.pub - + #default passphrase --leave blank if passphrase is empty defaultSSHPassphrase=myPa$$w0rd - + After startup and once the key has been registered it can then be removed from the system. The passphrase and the key paths will be removed from the configuration file. Adjusting Database Settings @@ -190,7 +225,7 @@ For example: #specify a external authentication module (ex: ldap-ol, ldap-ad). Edit the jaas.conf to set connection details jaasModule=ldap-ol - + Connection details need to be set in the jaas.conf file ldap-ol { @@ -201,7 +236,7 @@ Connection details need to be set in the jaas.conf file useSSL=false debug=false; }; - + Administrators will be added as they are authenticated and profiles of systems may be assigned by full-privileged users. @@ -237,7 +272,7 @@ Auditing Auditing is disabled by default. Audit logs can be enabled through the **log4j2.xml** by uncommenting the **io.bastillion.manage.util.SystemAudit** and the **audit-appender** definitions. > https://github.com/bastillion-io/Bastillion/blob/master/src/main/resources/log4j2.xml#L19-L22 - + Auditing through the application is only a proof of concept. It can be enabled in the BastillionConfig.properties. #enable audit --set to true to enable @@ -278,4 +313,3 @@ Author + sean.p.kavanagh6@gmail.com + https://twitter.com/spkavanagh6 -