diff --git a/README.md b/README.md index a709bdd..0afc317 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ https://user-images.githubusercontent.com/8253488/220523348-76c3f5ea-419d-46a7-8 2. copy `conf.example.py` to `conf.py` 3. edit `conf.py`: * change `ssh_password` to be a good password. This will be the password used to log into all the clients + * if you are using an SSH key for authentication, fill out `ssh_key` and optionally `ssh_key_passphrase`. Comment out the `ssh_password` directive. * add key value pairs in `trackme` of the clients and IPs you want to manage. You can use domain names as well. 4. run `docker compose up -d` 5. go to `http://your-server-IP:8080` on your phone or desktop @@ -67,7 +68,8 @@ Follow these steps for on each client you want to control: * `ssh_user` - user to SSH into client machines as. Defaults to `timekpr-next-remote` * `ssh_password` - password to use wen SSHing into client machines. Defaults to `timekpr-next-remote` * `ssh_timekpra_bin` - path on clients where `timekpra` executable is. defaults to `/usr/bin/timekpra` -`ssh_key` - wtf - I don't know, SSH library wouldn't work with out this. don't touch this +* `ssh_key` - file location of SSH private key file. Supports OpenSSH private key format. +* `ssh_key_passphrase` - passphrase for encrypted private key file (optional) ### docker compose @@ -88,7 +90,7 @@ By design, this system is very secure as far as controlling clients over SSH, bu * [ ] add PIN protection in web GUI * [ ] add "refresh" button per client in web GUI -* [ ] support ssh keys +* [X] support ssh keys * [X] enable more than one user per machine * [X] better error handling when SSH fails etc. * [X] AJAX async loading diff --git a/conf.example.py b/conf.example.py index 12e04fb..4d10032 100644 --- a/conf.example.py +++ b/conf.example.py @@ -5,3 +5,7 @@ ssh_password = 'timekpr-next-remote' ssh_timekpra_bin = '/usr/bin/timekpra' ssh_key = './id_timekpr' + +### If your SSH private key does not have a passphrase, leave +### the ssk_key_passphrase configuration option commented out +#ssk_key_passphrase = 'mypassphrase' \ No newline at end of file diff --git a/main.py b/main.py index dda2b89..1b08193 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,15 @@ # Press Shift+F10 to execute it or replace it with your code. # Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. -import conf, re + +import conf, re, os from fabric import Connection from paramiko.ssh_exception import AuthenticationException from paramiko.ssh_exception import NoValidConnectionsError +from paramiko.ssh_exception import ChannelException +from paramiko.ssh_exception import PasswordRequiredException +from paramiko.ssh_exception import SSHException +from pathlib import Path def get_config(): @@ -49,24 +54,39 @@ def get_usage(user, computer, ssh): def get_connection(computer): global connection # todo handle SSH keys instead of forcing it to be passsword only - connect_kwargs = { + + connect_kwargs_common = { 'allow_agent': False, 'look_for_keys': False, - "password": conf.ssh_password } + if hasattr(conf,'ssh_key') and Path(conf.ssh_key).is_file() : + connect_kwargs_merged = connect_kwargs_common | { 'key_filename': conf.ssh_key } + if hasattr(conf, 'ssh_key_passphrase') : + connect_kwargs_merged = connect_kwargs_merged | { 'password': conf.ssh_key_passphrase } + elif hasattr(conf,'ssh_password') : + connect_kwargs_merged = connect_kwargs_common | {"password": conf.ssh_password} + + else : + quit(f"No SSH authentication configured") try: connection = Connection( host=computer, user=conf.ssh_user, - connect_kwargs=connect_kwargs + forward_agent=False, + connect_kwargs=connect_kwargs_merged ) + return connection except AuthenticationException as e: quit(f"Wrong credentials for user '{conf.ssh_user}' on host '{computer}'. " f"Check `ssh_user` and `ssh_password` credentials in conf.py.") + except ChannelException as e : + quit(f"Could not create a SSH channel for host '{computer}' \n Code: {code} Msg: {text} ") + except PasswordRequiredException as e: + quit(f"SSH Private key passphrase is unset or incorrect") + except SSHException as e : + quit(f"Failed to negotiate SSH connection or logic failure") except Exception as e: quit(f"Error logging in as user '{conf.ssh_user}' on host '{computer}', check conf.py. \n\n\t" + str(e)) - finally: - return connection def adjust_time(up_down_string, seconds, ssh, user): command = conf.ssh_timekpra_bin + ' --settimeleft ' + user + ' ' + up_down_string + ' ' + str(seconds) diff --git a/requirements.txt b/requirements.txt index 230d495..2608c8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ fabric -paramiko -flask \ No newline at end of file +paramiko > 1.6 +flask +pathlib \ No newline at end of file diff --git a/timekpr-next-web.py b/timekpr-next-web.py index 9e48fab..9f2c5e6 100644 --- a/timekpr-next-web.py +++ b/timekpr-next-web.py @@ -1,5 +1,6 @@ import main -import conf, re, os +import conf +import re, os from fabric import Connection from paramiko.ssh_exception import AuthenticationException from flask import Flask, render_template, request, send_from_directory