diff --git a/.gitignore b/.gitignore index d19b44c..d7253b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ .vagrant -build \ No newline at end of file +build +venv +dist +*.egg-info +__pycache__ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..863e987 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Benjamin Dweck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..37a8e83 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +wheel>=0.35.1 +setuptools>=44.0.0 +stem>=1.8.0 +paho-mqtt>=1.5.1 +PySocks>=1.7.1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8b4be3b --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="torch-agent", + version="0.0.1", + author="B.J. Dweck", + author_email="bjdweck@gmail.com", + description="TORch: Iluminate the Way to your Node", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://git.rudefox.io/bj/torch-agent", + packages=setuptools.find_packages(), + entry_points = { + 'console_scripts': ['torch-agent=torch_agent:main'], + }, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + ], + python_requires='>=3.6', +) + diff --git a/torch-agent.py b/torch-agent.py deleted file mode 100644 index 98fd542..0000000 --- a/torch-agent.py +++ /dev/null @@ -1,98 +0,0 @@ -from stem.control import Controller -import stem.connection -import paho.mqtt.client as mqtt -import ssl -import socks -import socket -import json -import configparser -import argparse -from datetime import datetime -from os import environ - -parser = argparse.ArgumentParser(description='Broadcast SSH hidden service hostname via MQTT') - -parser.add_argument('--config-dir', nargs='?', dest='configPath', default='/etc/torch', - help='configuration directory (default: /etc/torch)') - -args = parser.parse_args() - -configPath = args.configPath - -if "TORCH_CONFIG_DIR" in environ: - configPath = environ.get("TORCH_CONFIG_DIR") - -if not configPath.endswith("/"): - configPath = configPath + "/" - -print("Using torch configuration path: " + configPath) - -config = configparser.ConfigParser() -config.read(configPath + "torch.conf") - -torProxyPort = config['tor'].getint('ProxyPort', fallback = 9050) -torControllerPort = config['tor'].getint('ControllerPort', fallback = 9051) - -sshPort = config['ssh'].getint('Port', fallback = 22) - -mqttConfig = config['mqtt'] -mqttBrokerHost = mqttConfig.get('BrokerHost', fallback = "localhost") -mqttBrokerPort = mqttConfig.getint('BrokerPort', fallback = 1883) -clientID = mqttConfig.get('ClientID', fallback = socket.gethostname()) -mqttTopic = mqttConfig.get('Topic', fallback = "torch/%s/onion_url" % (clientID)) - -mqttRequireCertificate = mqttConfig.getboolean( - 'RequireCertificate', - fallback = False) - -mqttCaFile = configPath + mqttConfig.get('CaFile') -mqttCertFile = configPath + mqttConfig.get('CertFile') -mqttKeyFile = configPath + mqttConfig.get('KeyFile') - -with Controller.from_port(port = torControllerPort) as controller: - - protocolInfo = stem.connection.get_protocolinfo(controller) - - stem.connection.authenticate_safecookie( - controller, - protocolInfo.cookie_path) - - print("Connected to Tor on port %s" % (torControllerPort)) - - service = controller.create_ephemeral_hidden_service( - sshPort, - detached = True) - - onionAddress = "%s.onion" % (service.service_id) - - print("Created Tor Hidden Service for local port %s at %s" % (sshPort, onionAddress)) - -payload = { - 'clientId': clientID, - 'timestamp': datetime.now().strftime("%d-%b-%Y (%H:%M:%S.%f)"), - 'onionAddress': onionAddress, - 'sshPort': sshPort - } - -client = mqtt.Client() -protocol = "mqtt" - -if mqttRequireCertificate: - client.tls_set( - ca_certs = mqttCaFile, - certfile = mqttCertFile, - keyfile = mqttKeyFile, - cert_reqs=ssl.CERT_REQUIRED) - protocol = "mqtts" - -if mqttBrokerHost.endswith(".onion"): - client.proxy_set(proxy_type=socks.SOCKS5, proxy_addr="localhost", proxy_port=torProxyPort) - client.tls_insecure_set(True) - -client.connect(mqttBrokerHost, mqttBrokerPort, 60) -client.publish(mqttTopic, json.dumps(payload)) -print("Connected to MQTT Broker at %s://%s:%s/%s" % (protocol, mqttBrokerHost, mqttBrokerPort, mqttTopic)) -print("Published payload: " + json.dumps(payload)) - -client.disconnect() -print("Disconnected from MQTT Broker") diff --git a/torch_agent/__init__.py b/torch_agent/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/torch_agent/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/torch_agent/__main__.py b/torch_agent/__main__.py new file mode 100644 index 0000000..b395440 --- /dev/null +++ b/torch_agent/__main__.py @@ -0,0 +1,5 @@ +import sys +from torch_agent import main + +if __name__ == '__main__': + sys.exit(main()) diff --git a/torch_agent/torch_agent.py b/torch_agent/torch_agent.py new file mode 100755 index 0000000..94e2bf6 --- /dev/null +++ b/torch_agent/torch_agent.py @@ -0,0 +1,97 @@ +from stem.control import Controller +import stem.connection +import paho.mqtt.client as mqtt +import ssl +import socks +import socket +import json +import configparser +import argparse +from datetime import datetime +from os import environ + +def main(): + parser = argparse.ArgumentParser(description='Broadcast SSH hidden service hostname via MQTT') + + parser.add_argument('--config-dir', nargs='?', dest='configPath', default='/etc/torch', + help='configuration directory (default: /etc/torch)') + + args = parser.parse_args() + + configPath = args.configPath + + if "TORCH_CONFIG_DIR" in environ: + configPath = environ.get("TORCH_CONFIG_DIR") + + if not configPath.endswith("/"): + configPath = configPath + "/" + + print("Using torch configuration path: " + configPath) + + config = configparser.ConfigParser() + config.read(configPath + "torch.conf") + + torProxyPort = config['tor'].getint('ProxyPort', fallback = 9050) + torControllerPort = config['tor'].getint('ControllerPort', fallback = 9051) + + sshPort = config['ssh'].getint('Port', fallback = 22) + + mqttConfig = config['mqtt'] + mqttBrokerHost = mqttConfig.get('BrokerHost', fallback = "localhost") + mqttBrokerPort = mqttConfig.getint('BrokerPort', fallback = 1883) + clientID = mqttConfig.get('ClientID', fallback = socket.gethostname()) + mqttTopic = mqttConfig.get('Topic', fallback = "torch/%s/onion_url" % (clientID)) + + mqttRequireCertificate = mqttConfig.getboolean( + 'RequireCertificate', + fallback = False) + + mqttCaFile = configPath + mqttConfig.get('CaFile') + mqttCertFile = configPath + mqttConfig.get('CertFile') + mqttKeyFile = configPath + mqttConfig.get('KeyFile') + + with Controller.from_port(port = torControllerPort) as controller: + + protocolInfo = stem.connection.get_protocolinfo(controller) + + stem.connection.authenticate_safecookie( + controller, + protocolInfo.cookie_path) + + print("Connected to Tor on port %s" % (torControllerPort)) + + service = controller.create_ephemeral_hidden_service(sshPort, detached = True) + + onionAddress = "%s.onion" % (service.service_id) + + print("Created Tor Hidden Service for local port %s at %s" % (sshPort, onionAddress)) + + payload = { + 'clientId': clientID, + 'timestamp': datetime.now().strftime("%d-%b-%Y (%H:%M:%S.%f)"), + 'onionAddress': onionAddress, + 'sshPort': sshPort + } + + client = mqtt.Client() + protocol = "mqtt" + + if mqttRequireCertificate: + client.tls_set( + ca_certs = mqttCaFile, + certfile = mqttCertFile, + keyfile = mqttKeyFile, + cert_reqs=ssl.CERT_REQUIRED) + protocol = "mqtts" + + if mqttBrokerHost.endswith(".onion"): + client.proxy_set(proxy_type=socks.SOCKS5, proxy_addr="localhost", proxy_port=torProxyPort) + client.tls_insecure_set(True) + + client.connect(mqttBrokerHost, mqttBrokerPort, 60) + client.publish(mqttTopic, json.dumps(payload)) + print("Connected to MQTT Broker at %s://%s:%s/%s" % (protocol, mqttBrokerHost, mqttBrokerPort, mqttTopic)) + print("Published payload: " + json.dumps(payload)) + + client.disconnect() + print("Disconnected from MQTT Broker")