Bezpieczna zdalna konsola CLI

0

Witam,
Piszę sobie aplikację, w której chciałbym mieć możliwość podłączenia się do własnego CLI. Mogę to zrobić albo lokalnie (jest masa bibliotek), albo po nieszyfrowanym socketcie (bezpieczeństwo tego rozwiązania praktycznie nie istnieje), albo "jakoś inaczej". I tutaj mam pytanie. Ma ktoś może jakiś dobry pomysł?

Ogólnie chodzi o to, żeby podłączyć się do aplikacji po TCP i mieć dostęp do własnej, napisanej konsoli (fajnie byłoby podpiąć gotową bibliotekę, ale jeśli się nie da, to trudno). Niestety potrzebne będzie logowanie, więc zwykły socket nie wystarczy - można się podłączyć z wszystkiego (choćby telnet), ale hasełka latałyby bez szyfrowania. Dlatego też próbowałem SSH, ale własny serwer z własną konsolą, to dość karkołomne rozwiązanie.

0

A jak sie polaczysz za pomoca SSL?

0

Dzięki za zainteresowanie.
Właśnie dlatego myślałem o SSH, ponieważ można użyć jakiegoś zwyczajnego klienta do połączenia/zalogowania. Niestety nie radzę sobie z serwerem wykorzystującym własną konsolę.

0

W sumie nie do konca rozumiem co chcesz zrobic. Co to znaczy 'wlasne CLI'? Chcesz sie za pomoca Twojej aplikacji polaczyc z jakims shellem na serwerze, czy twoja aplikacja ma wbudowana konsole, ktora potrafi sie polaczyc z serwerem i wykonac jakies polecenia?

0

Jeszcze inaczej. Chciałbym, żeby moja aplikacja miała swoje własne CLI - odpalam aplikację na serwerze, łączę się z innego kompa z tą aplikacją i mam możliwość interakcji z aplikacją poprzez "zdalną linię poleceń".
Najprościej to zrobić po prostu przez sockety TCP. Otwieram port, na output stream wysyłam konsolę (znak zachęty, wyniki wykonywanych operacji), a z input stream pobieram polecenia. Są z tym jednak trzy problemy: konieczność obsłużenia sesji i logowania użytkowników, problemy z uzupełnianiem komand, no i najważniejsze - brak szyfrowania. Z tego ostatniego powodu chciałem zamiast tego wykorzystać SSH, ale niezbyt wiem, jak zmodyfikować jakąś bibliotekę potrafiącą stworzyć serwer SSH w Javie do tego, żeby po zalogowaniu dała mi input/output stream i możliwość dalszej zabawy na włąsną rękę, bez konsoli, np. bashowej, a jeszcze lepiej - żeby dało się to spiąć z jakąś biblioteką, jak java-cli-api, commons-cli, natural-cli, itp.

0

Nie wiem czy jest lib do javy zeby wystartowac serwer ssh, ale moze sprobuj inaczej - moze poczytaj o tulenowaniu przez ssh? Polega to na tym ze wywolujesz komende ssh z parametrami, i ten ssh tworzy taki jakby 'serwer' u ciebie lokalnie. Ty sie w Teojej aplikacji laczysz z tym serwerem, i wszystko co przeslesz do niego leci przez tunel ssh do twojego 'prawdziwego' serwera. Po strone serwera aplikacja chodzi tylko na lokalhoscie, a jak stworzysz tunel ssh to na serwerze on sie polaczy wlasnie z tym localhostem. W ten sposob poza ssh nikt sie nie polaczy z Twoja aplikacja. Takie wywolanie ssh wyglada mniej wiecej tak:

ssh -L 1234:host:4321

Co oznacza: laczysz sie z 'host' przez ssh, otwierasz lokalnie (na kliencie z ktorego sie laczysz) socket na porcie 1234, kazde polaczenie z tym socketem spowoduje wytworzenie tunelu do serwera 'host', a tam drugi koniec zostanie polaczony z portem 4321 - to bedzie Twoja aplikacja ktora czeka na polaczenia na localhoscie na porcie wlasnie 4321.
Plusy to to ze bezpieczenstwo i szyfrowanie masz za darmo - korzystasz z ssh. Minusy to np. to ze korzystasz z ssh, czyli jestes dosc mocno od tego zalezny. Moim zdaniem warto sie zainteresowac.
Albo jeszcze inaczej - po prostu zrob aplikacje ktora startuje lokalnie i nie wie nic na temat socketow, sieci i bezpieczenstwa - po prostu startujesz ja i mozesz uzywac na twoim kompie. Instalujesz ja na swerwerze, tam gdzie jest tez serwer ssh. Teraz klienckie aplikacje sie lacza ssh z serwerem, i mozesz uruchomic ta aplikacje. Takie 'poor man's tunneling' ;d Ssh mozesz skonfigurowac tak zeby domyslnie startowac twoja aplikacje. To jest najlatwiejsze co im przychodzi do glowy.
W zaleznosci od tego co to za aplikacja i dla kogo, moga to byc bardzo dobre rozwiazania. Przede wszystkim pomijasz wlasnoreczna implementacje bezpieczenstwa, szyfrowania itp co nie jest trywialne. No chyba ze wlasnie chcesz to sam implementowac, to wtedy nie ;d

0

Sory, zle podalem polaczenie, powinno byc:
ssh -L 12344444 host
Czyli: polacz sie z 'host' przez ssh; gdy na kliencie ktos sie polaczy z localhost:1234 to powstanie tunel do host, a tam ssh polaczy sie z localhost:4444. W ten sposob masz laczenie z ssh (np. za pomoca PKI), szyfrowanie (bo tunel ssh jest szyfrowany), nikt sie nie polaczy z aplikacja inaczej niz po localhost, czyli dosc bezpiecznie ;d

0

Wielkie dzięki
SSH nie jest dla mnie tajemnicą, więc raczej dam radę:).
Nie myślałem o tunelowaniu z prostego powodu - daję człowiekowi apkę spakowaną do paczki linuxowej (taki deb na przykład), a on ją instaluje. Mogę nawet nie dotykać tej maszyny. Właściwie, to da się zrobić taki numer - po instalacji tworzy się skrypt, który przed uruchomieniem mojego programu tworzy tunel SSH lub jakiś dziwny twór typu IPv6inIPv4 dla zmylenia przeciwnika.
Jest jeszcze jedno rozwiązanie - program uruchamia się w screen-ie (jak ktoś nie zna tego programu, to naprawdę polecam poznać), a później można wejść przez SSH na serwer i dopiąć się do screen-a, gdzie czeka CLI aplikacji. NIESTETY... oznacza to też ofiarowanie użytkownikowi dostępu do basha na serwerze, a tego wolałbym uniknąć. I znów jest to problem rozwiązywalny, tylko strasznie okrężną drogą... Zarządzanie uprawnieniami, oddzielny plik z danymi uwierzytelniania użytkownika stworzonego tylko w tym celu, itd.
Aplikacja i tak jest sieciowa, mam już nawet komunikację klient androidowy - serwer J2SE po JSSE (celowo nie użyłem J2EE, np. JBoss-a). Zdalną konsolę też chciałem zrobić "ładne", ale najwyraźniej się nie da.
Cóż.. w ostateczności użyję JSSE i napiszę sobie własnego klienta. Powinno pójść gładko. Jakbym wykombinował coś ładnego, to napiszę tutaj dla potomnych.

Raz jeszcze dzięki za pomoc.

0

No tak, ja myslalem ze to ma byc jakas tylko aplikacja dla ciebie, nie jakis pakiet dla innych osob. Problem JSSE jest taki ze musisz sam wszystko implementowac, no ale jak trzeba...

0

O ile dobrze zrozumiałem, to ten projekt powinien Ci pomóc http://mina.apache.org/sshd-project/index.html

0

Masz rację - powinien. Problem pojawia się w momencie próby zmuszenia go do użycia takiego 'ShellFactory', który pozwoli przekazywać polecenia wydane zdalnie do programu, a nie do powłoki systemu operacyjnego.

0

Może to głupie, ale może połączenie TCP i same, przesyłane dane szyfrować?

0

Fajnie, ale to wymaga napisania klienta. Mało ludzi jest w stanie pisać komendy szyfrując je w locie w głowie :). Walczę z przekierowaniem portu z SSH na nie-SSH. Jak skończę, to wrzucę

0

A więc dla potomnych zdalne CLI nasłuchujące tylko lokalnie i zabezpieczony serwer SSH, który zajmuje się przekierowaniem połączenia na ten CLI:

Serwer nasłuchujący na połączenia od localhosta:

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.log4j.Logger;


public class ConsoleServer extends Thread {

	private final int port;
	private final int lenghtOfQuere;
	public AtomicBoolean listening;
	private static final Logger log = Logger.getLogger("Konsola zdalna");

	public boolean isListening() {
		return listening.get();
	}

	public void setListening(boolean listening) {
		this.listening.set(listening);
	}

	public ConsoleServer() {

		port = Integer.parseInt(ServerProperties.getProperty("console_port"));
		log.info("Uruchamiam serwer bez szyfrowania na porcie " + port);
		lenghtOfQuere = 100;
		listening = new AtomicBoolean(true);
	}

	@Override
	public void run() {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port, lenghtOfQuere, InetAddress.getByName("localhost"));
			while (listening.get()) {
				new RemoteConsoleHandler(serverSocket.accept()).start();
			}

			serverSocket.close();
		} catch (IOException e) {
			log.error("Nie mogę uruchomić serwera z powodu błędu", e);
			try {
				serverSocket.close();
			} catch (Exception ex) {
			}
			CriticalBehaviour.closeOnFatal(ConsoleServer.class, e);
		}

	}
}

Obsługa poleceń:


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.LinkedHashMap;

import org.apache.log4j.Logger;

public class RemoteConsoleHandler extends Thread {

	private Socket connection = null;
	private static final Logger log = Logger.getLogger("Obsługa konsoli zdalnej");
	private PrintWriter out;
	private BufferedReader in;
	private String prompt;
	private CommandStore commandStore;

	public RemoteConsoleHandler(Socket s) {
		this.connection = s;
		setPrompt("serwer");
		commandStore = new CommandStore();
	}

	private void setPrompt(String prompt) {
		this.prompt = prompt.concat("# ");
	}

	private String getPrompt() {
		return prompt;
	}

	@Override
	public void run() {

		try {
			InetAddress adr = connection.getInetAddress();
			int remotePort = connection.getPort();
			log.debug("Rozpoczynam obsługę połączenia do konsoli zdalnej z adresu " + adr + " oraz portu " + remotePort);

			out = new PrintWriter(connection.getOutputStream(), true);
			in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

			out.format("Nacisnij ENTER, aby rozpoczac"); // jeśli użytkownik łączy się telnetem, to pierwszy komunikat zaczyna się od śmieci, więc warto go ominąć

			in.readLine();

			out.format(getPrompt());

			String command = in.readLine();
			if (command != null) {
				while (!command.equalsIgnoreCase("exit")) {
					if (command.length() > 0) {
						log.trace("Użytkownik " + adr.getHostName() + "/" + remotePort + " wydał polecenie: " + command);
						String answer = handleCommand(command);
						send(answer);
					}
					out.format(getPrompt());
					command = in.readLine();
				}
				log.debug("Rozłączam użytkownika połączonego z adresu " + adr + " oraz portu " + remotePort);
			}
			out.close();
			in.close();
			connection.close();
		} catch (NullPointerException e) {
			log.warn("Użytkownik rozłączył się niespodziewanie");
		} catch (Exception e) {
			log.error("Błąd zdalnej konsoli użytkownika", e);
		}
	}

	private String handleCommand(String command) {
		String status = "Nie rozpoznalem polecenia :(";
		if (command.equalsIgnoreCase("help")) {
			commandHelp();
			status = "";
		} else {
			String newStatus = commandStore.execute(command);
			if (newStatus != null && newStatus.length() > 0) {
				status = newStatus;
			}
		}

		return status;
	}


	private void commandHelp() {
		LinkedHashMap<String, String> helpMessage = commandStore.getHelp();
		String value = "Dostepne sa nastepujace polecenia:\n\n";
		for (String key : helpMessage.keySet()) {
			value = value.concat(key + "\n");
			value = value.concat(helpMessage.get(key));
			value = value.concat("\n\n---------\n\n");
		}
		send(value);
	}

	void send(String msg) {
		try {
			if (msg != null && msg.length() > 0) {
				msg = msg.replaceAll("\n", "\n\r");
				log.trace("Wysyłam odpowiedź: \n" + msg);
				out.println(msg);
			}
		} catch (Exception ioException) {
			log.error("Nie mogę wysłać odpowiedzi z powodu błędu", ioException);
		}
	}
}

No i najciekawsze - tunel SSH:

import java.util.EnumSet;

import org.apache.log4j.Logger;
import org.apache.sshd.SshServer;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.shell.ProcessShellFactory;
import org.apache.sshd.server.shell.ProcessShellFactory.TtyOptions;

public class TunnelSSH implements PasswordAuthenticator {

	private static final Logger log = Logger.getLogger("Tunel SSH dla konsoli zdalnej");

	public void openTunnel() {
		try {
			SshServer sshd = SshServer.setUpDefaultServer();
			sshd.setPort(Integer.parseInt(ServerProperties.getProperty("secure_console_port")));
			sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(ServerProperties.getProperty("local_ssh_keyset_filename")));
			EnumSet<TtyOptions> enumSet = EnumSet.noneOf(TtyOptions.class);
			enumSet.add(TtyOptions.Echo);
			enumSet.add(TtyOptions.ONlCr);
			enumSet.add(TtyOptions.ICrNl);
			sshd.setShellFactory(new ProcessShellFactory(new String[] { "nc", "localhost", ServerProperties.getProperty("console_port") }, enumSet));
			sshd.setPasswordAuthenticator(this);
			sshd.start();
		} catch (Exception e) {
			log.error("Nie mogę uruchomić bezpiecznego przekierowania dla portu konsoli zdalnej", e);

		}

	}

	public boolean authenticate(String userName, String password, ServerSession session) {
		return UserCache.getInstance().checkUser(userName, password);
	}

}

Widziałem podobne wątki bez odpowiedzi, więc liczę, że się przyda.

1 użytkowników online, w tym zalogowanych: 0, gości: 1