diff --git a/data/revpipyload_secure_installation b/data/revpipyload_secure_installation new file mode 100755 index 0000000..c339e8e --- /dev/null +++ b/data/revpipyload_secure_installation @@ -0,0 +1,7 @@ +#!/bin/sh +if [ -d /usr/local/share/revpipyload ] +then + exec "/usr/local/share/revpipyload/secure_installation.py" "$@" +else + exec "/usr/share/revpipyload/secure_installation.py" "$@" +fi diff --git a/revpipyload/secure_installation.py b/revpipyload/secure_installation.py new file mode 100755 index 0000000..1b1e82a --- /dev/null +++ b/revpipyload/secure_installation.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Secure your installation of RevPiPyLoad. + +Exit codes: + 1: Runtime error + 2: Program did no changes on files + 4: No root permissions + 8: Write error to acl files +""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2020 Sven Sager" +__license__ = "GPLv3" + +from configparser import ConfigParser +from os import access, R_OK, system, getuid +from re import match +from sys import stdout, stderr + +from shared.ipaclmanager import IpAclManager + +CONFIG_FILE = "/etc/revpipyload/revpipyload.conf" + +if not access(CONFIG_FILE, R_OK): + raise PermissionError("Can not access {0}".format(CONFIG_FILE)) + +conf = ConfigParser() +conf.read(CONFIG_FILE) + +aclxmlrpc = conf.get("XMLRPC", "aclfile") +if not access(aclxmlrpc, R_OK): + raise PermissionError("Can not access {0}".format(aclxmlrpc)) + +# Load config values +xmlrpc = conf.getboolean("XMLRPC", "xmlrpc", fallback=False) +xmlrpcbindip = conf.get("XMLRPC", "bindip", fallback="127.0.0.1") + +# Prepare variables +xmlrpcacl = IpAclManager(minlevel=0, maxlevel=4) +xmlrpcacl.loadaclfile(aclxmlrpc) +xmlrpc_only_localhost = xmlrpcbindip.find("127.") == 0 or xmlrpcbindip == "" + +# ----- Print summary of actual configuration +stdout.write(""" +This will secure your installation of RevPiPyLoad. + +We found the following configuration files: + RevPiPyLoad: {revpipyload} + XML-RPC ACL: {aclxmlrpc} + +Access with RevPiPyControl is {xmlrpc}activated{source} +""".format( + revpipyload=CONFIG_FILE, + aclxmlrpc=aclxmlrpc, + xmlrpc="" if xmlrpc else "NOT ", + source="" if not xmlrpc + else " from this computer only (localhost)." if xmlrpc_only_localhost + else " from ACL listed remote computers!" +)) + + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# region # REGION: Shared functions +def save_xmlrpcacls(): + """Save new acl's to file.""" + if not xmlrpcacl.writeaclfile(aclname="XML-RPC"): + stderr.write("Error while writing ACL file!\n") + exit(8) + + +def print_xmlrpcacls(): + """Print a list with all acl.""" + if xmlrpcacl.acl: + stdout.write( + "\nThis is the actual ACL file ({0}):".format(aclxmlrpc) + ) + counter = 0 + for acl in xmlrpcacl.acl.split(): + stdout.write("\n" if counter % 2 == 0 else " | ") + ip, level = acl.split(",") + stdout.write("{0:15} - Level: {1:2}".format(ip, level)) + counter += 1 + stdout.write("\n") + else: + stderr.write( + "\nWARNING: NO IP addresses defined in ACL!\n You will " + "not be able to connect with RevPiPyControl at this moment!\n" + ) +# endregion # # # # # + + +try: + if not xmlrpc_only_localhost: + cmd = input( + "\nDo you want to check ACL listed computers? (y/N) " + ).lower() + if cmd == "y": + print_xmlrpcacls() + + if getuid() != 0: + stderr.write( + "\nYou need root permissions to change values (sudo).\n" + ) + exit(4) + + cmd = input( + "\nDo you want to allow connections from remote hosts? (y/N) " + ).lower() + if cmd == "y": + conf.set("XMLRPC", "xmlrpc", "1") + conf.set("XMLRPC", "bindip", "*") + + cmd = input( + "Reset the ACL file to allow all private networks? (y/N) " + ).lower() + if cmd == "y": + xmlrpcacl.acl = "127.*.*.*,4 " \ + "169.254.*.*,4 " \ + "10.*.*.*,4 " \ + "172.16.*.*,4 172.17.*.*,4 172.18.*.*,4 172.19.*.*,4 " \ + "172.20.*.*,4 172.21.*.*,4 172.22.*.*,4 172.23.*.*,4 " \ + "172.24.*.*,4 172.25.*.*,4 172.26.*.*,4 172.27.*.*,4 " \ + "172.28.*.*,4 172.29.*.*,4 172.30.*.*,4 172.31.*.*,4 " \ + "192.168.*.*,4" + save_xmlrpcacls() + + else: + cmd = input( + "Reset the ACL file by enter individual ip addresses to " + "grant access? (y/N) " + ).lower() + if cmd == "y": + lst_ip = [] + while True: + cmd = input( + "Enter single IPv4 address | " + "Press RETURN to complete: " + ) + if not cmd: + xmlrpcacl.acl = " ".join(lst_ip) + save_xmlrpcacls() + break + elif match(r"([\d*]{1,3}\.){3}[\d*]{1,3}", cmd): + lst_ip.append("{0},4".format(cmd)) + else: + stderr.write("Wrong format (0.0.0.0)\n") + + else: + cmd = input( + "Do you want to allow connections from localhost ONLY? (y/N) " + ).lower() + if cmd == "y": + conf.set("XMLRPC", "xmlrpc", "1") + conf.set("XMLRPC", "bindip", "127.0.0.1") + + cmd = input( + "Reset the ACL file to allow localhost connections only? (y/N) " + ).lower() + if cmd == "y": + xmlrpcacl.acl = "127.*.*.*,4 " + save_xmlrpcacls() + + else: + cmd = input( + "\nWARNING: This will disable the possibility to connect with " + "RevPiPyControl!\n Are you sure? (y/N) " + ).lower() + if cmd == "y": + conf.set("XMLRPC", "xmlrpc", "0") + conf.set("XMLRPC", "bindip", "127.0.0.1") + xmlrpcacl.acl = "" + save_xmlrpcacls() + else: + stdout.write("\nWe did no changes!\n") + exit(2) + + # Write configuration + with open(CONFIG_FILE, "w") as fh: + conf.write(fh) + + print_xmlrpcacls() + +except KeyboardInterrupt: + stdout.write("\n\nWe did no changes!\n") + exit(2) + +try: + cmd = input("\nDo you want to apply the new settings now? (Y/n) ").lower() + if cmd in ("", "y"): + system("/etc/init.d/revpipyload reload") + else: + stderr.write( + "\nYou have to activate the new settings for RevPiPyLoad!\n" + " sudo /etc/init.d/revpipyload reload\n" + ) +except KeyboardInterrupt: + pass diff --git a/setup.py b/setup.py index 41bda15..fd16409 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,10 @@ setup( name="revpipyload", version="0.9.1", - scripts=["data/revpipyload"], + scripts=[ + "data/revpipyload", + "data/revpipyload_secure_installation", + ], install_requires=["revpimodio2 >= 2.5.0"], python_requires=">=3.2",