diff options
| author | Simeon Simeonov | 2023-07-07 10:04:04 +0200 |
|---|---|---|
| committer | Simeon Simeonov | 2023-07-07 10:04:04 +0200 |
| commit | c4cc9c90b3546e72efbaeb9cb632b9d98288bac1 (patch) | |
| tree | c3ca05b349f719ecc1afd2ebb7016373f030d7c1 | |
| parent | e62d7c5ea3bc014123cf6f78e5345c588cd4f068 (diff) | |
Add basic https support and format the code using black -S
| -rw-r--r-- | README.md | 20 | ||||
| -rw-r--r-- | src/ngus/__main__.py | 103 |
2 files changed, 95 insertions, 28 deletions
| @@ -31,8 +31,8 @@ To my amazement I wasn't able to find any free software matching that criteria. | |||
| 31 | The main purpose of *ngus* is to accept file uploads as POST requests from an | 31 | The main purpose of *ngus* is to accept file uploads as POST requests from an |
| 32 | HTTP client. | 32 | HTTP client. |
| 33 | 33 | ||
| 34 | Currently *ngus* does not provide encryption (HTTPS) support. Many different | 34 | Currently *ngus* provides only simple encryption (HTTPS) support. Many |
| 35 | tools can be employed to serve as an HTTPS proxy. | 35 | different tools can be employed to serve as an HTTPS proxy. |
| 36 | 36 | ||
| 37 | See the examples bellow for more details! | 37 | See the examples bellow for more details! |
| 38 | 38 | ||
| @@ -49,7 +49,9 @@ See the examples bellow for more details! | |||
| 49 | ### Gentoo | 49 | ### Gentoo |
| 50 | 50 | ||
| 51 | ```bash | 51 | ```bash |
| 52 | layman -a sgs | 52 | # add sgs' custom repository using app-eselect/eselect-repository |
| 53 | eselect repository add sgs | ||
| 54 | |||
| 53 | emerge www-servers/ngus | 55 | emerge www-servers/ngus |
| 54 | ``` | 56 | ``` |
| 55 | 57 | ||
| @@ -107,6 +109,16 @@ The default form input field name is *ufile*. That can be changed by using the | |||
| 107 | curl --basic -u uname -F "uploadfile=@myfile.zip" http://158.39.125.240:8080 | 109 | curl --basic -u uname -F "uploadfile=@myfile.zip" http://158.39.125.240:8080 |
| 108 | ``` | 110 | ``` |
| 109 | 111 | ||
| 112 | Simple encryption (HTTPS) support | ||
| 113 | |||
| 114 | ```bash | ||
| 115 | # creating a self-signed key / certificate pair valid for 356 days | ||
| 116 | openssl req -sha256 -newkey rsa:4096 -nodes -x509 -extensions v3_ca -subj "/C=NO/O=MyOrg/CN=uploads.myhostname.net" -days 365 -keyout uploads.myhostname.net.key.pem -out uploads.myhostname.net.cert.pem | ||
| 117 | python -m ngus -H 0.0.0.0 -p 8080 -b "uname:foo" -k uploads.myhostname.net.key.pem -c uploads.myhostname.net.cert.pem -u /home/s/uploads | ||
| 118 | # since the certificate is self-signed, it can serve as a CA to itself in this example | ||
| 119 | curl --basic -u uname -F "ufile=@myfile.zip" --cacert uploads.myhostname.net.cert.pem https://uploads.myhostname.net:8080 | ||
| 120 | ``` | ||
| 121 | |||
| 110 | 122 | ||
| 111 | ### Using *nginx* as a proxy | 123 | ### Using *nginx* as a proxy |
| 112 | 124 | ||
| @@ -138,7 +150,7 @@ described in the examples above. | |||
| 138 | 150 | ||
| 139 | location / { | 151 | location / { |
| 140 | # point at the ngus instance running on a private network address | 152 | # point at the ngus instance running on a private network address |
| 141 | proxy_pass http://192.168.1.240:8080/; | 153 | proxy_pass http://158.39.125.240:8080/; |
| 142 | } | 154 | } |
| 143 | 155 | ||
| 144 | } | 156 | } |
diff --git a/src/ngus/__main__.py b/src/ngus/__main__.py index efa2c4a..a1f3992 100644 --- a/src/ngus/__main__.py +++ b/src/ngus/__main__.py | |||
| @@ -18,6 +18,7 @@ import argparse | |||
| 18 | import base64 | 18 | import base64 |
| 19 | import logging | 19 | import logging |
| 20 | import pathlib | 20 | import pathlib |
| 21 | import ssl | ||
| 21 | import sys | 22 | import sys |
| 22 | 23 | ||
| 23 | import ngus | 24 | import ngus |
| @@ -31,75 +32,129 @@ def eprint(*arg, **kwargs): | |||
| 31 | def main(inargs=None): | 32 | def main(inargs=None): |
| 32 | """main entry point""" | 33 | """main entry point""" |
| 33 | parser = argparse.ArgumentParser( | 34 | parser = argparse.ArgumentParser( |
| 34 | description='The following options are available') | 35 | description='The following options are available' |
| 36 | ) | ||
| 35 | parser.add_argument( | 37 | parser.add_argument( |
| 36 | '-b', '--basic-auth', | 38 | '-b', |
| 39 | '--basic-auth', | ||
| 37 | metavar='<username:password>', | 40 | metavar='<username:password>', |
| 38 | type=str, | 41 | type=str, |
| 39 | dest='basic_auth', | 42 | dest='basic_auth', |
| 40 | default='', | 43 | default='', |
| 41 | help=('Require Basic auth from the client in order to POST a file ' | 44 | help=( |
| 42 | '(default: No basic auth required)')) | 45 | 'Require Basic auth from the client in order to POST a file ' |
| 46 | '(default: No basic auth required)' | ||
| 47 | ), | ||
| 48 | ) | ||
| 43 | parser.add_argument( | 49 | parser.add_argument( |
| 44 | '-H', '--hostname', | 50 | '-H', |
| 51 | '--hostname', | ||
| 45 | metavar='<hostname>', | 52 | metavar='<hostname>', |
| 46 | type=str, | 53 | type=str, |
| 47 | dest='hostname', | 54 | dest='hostname', |
| 48 | default='127.0.0.1', | 55 | default='127.0.0.1', |
| 49 | help='ngus server IP / hostname (default: 127.0.0.1)') | 56 | help='ngus server IP / hostname (default: 127.0.0.1)', |
| 57 | ) | ||
| 50 | parser.add_argument( | 58 | parser.add_argument( |
| 51 | '-i', '--input-name', | 59 | '-c', |
| 60 | '--certfile', | ||
| 61 | metavar='<certfile>', | ||
| 62 | type=pathlib.Path, | ||
| 63 | dest='certfile', | ||
| 64 | default=None, | ||
| 65 | help=( | ||
| 66 | 'Certfile (PEM) for TLS/SSL connections ' | ||
| 67 | '(default: SSL/TLS disabled)' | ||
| 68 | ), | ||
| 69 | ) | ||
| 70 | parser.add_argument( | ||
| 71 | '-i', | ||
| 72 | '--input-name', | ||
| 52 | metavar='<name>', | 73 | metavar='<name>', |
| 53 | type=str, | 74 | type=str, |
| 54 | dest='input_name', | 75 | dest='input_name', |
| 55 | default='ufile', | 76 | default='ufile', |
| 56 | help='The name of the form input field (default: "ufile")') | 77 | help='The name of the form input field (default: "ufile")', |
| 78 | ) | ||
| 79 | parser.add_argument( | ||
| 80 | '-k', | ||
| 81 | '--keyfile', | ||
| 82 | metavar='<keyfile>', | ||
| 83 | type=pathlib.Path, | ||
| 84 | dest='keyfile', | ||
| 85 | default=None, | ||
| 86 | help='Keyfile for TLS/SSL connections (default: SSL/TLS disabled)', | ||
| 87 | ) | ||
| 57 | parser.add_argument( | 88 | parser.add_argument( |
| 58 | '-p', '--port', | 89 | '-p', |
| 90 | '--port', | ||
| 59 | metavar='<port>', | 91 | metavar='<port>', |
| 60 | type=int, | 92 | type=int, |
| 61 | dest='port', | 93 | dest='port', |
| 62 | default=8080, | 94 | default=8080, |
| 63 | help='ngus server port (default: 8080)') | 95 | help='ngus server port (default: 8080)', |
| 96 | ) | ||
| 64 | parser.add_argument( | 97 | parser.add_argument( |
| 65 | '-u', '--upload-dir', | 98 | '-u', |
| 99 | '--upload-dir', | ||
| 66 | metavar='<dir>', | 100 | metavar='<dir>', |
| 67 | type=pathlib.Path, | 101 | type=pathlib.Path, |
| 68 | dest='upload_dir', | 102 | dest='upload_dir', |
| 69 | default=pathlib.Path.cwd(), | 103 | default=pathlib.Path.cwd(), |
| 70 | help='ngus server upload dir (default: CWD)') | 104 | help='ngus server upload dir (default: CWD)', |
| 105 | ) | ||
| 71 | parser.add_argument( | 106 | parser.add_argument( |
| 72 | '-U', '--upload-page', | 107 | '-U', |
| 108 | '--upload-page', | ||
| 73 | metavar='<filename>', | 109 | metavar='<filename>', |
| 74 | type=argparse.FileType('rb'), | 110 | type=argparse.FileType('rb'), |
| 75 | dest='upload_page', | 111 | dest='upload_page', |
| 76 | default=None, | 112 | default=None, |
| 77 | help='Alternative upload page (form) to display (default: None)') | 113 | help='Alternative upload page (form) to display (default: None)', |
| 114 | ) | ||
| 78 | parser.add_argument( | 115 | parser.add_argument( |
| 79 | '-v', '--version', | 116 | '-v', |
| 117 | '--version', | ||
| 80 | action='version', | 118 | action='version', |
| 81 | version=f'%(prog)s {ngus.__version__}', | 119 | version=f'%(prog)s {ngus.__version__}', |
| 82 | help='Display program-version and exit') | 120 | help='Display program-version and exit', |
| 121 | ) | ||
| 83 | args = parser.parse_args(inargs) | 122 | args = parser.parse_args(inargs) |
| 84 | try: | 123 | try: |
| 85 | logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', | 124 | logging.basicConfig( |
| 86 | level=logging.DEBUG) | 125 | format='%(asctime)s - %(levelname)s - %(message)s', |
| 126 | level=logging.DEBUG, | ||
| 127 | ) | ||
| 87 | logger = logging.getLogger('ngus') | 128 | logger = logging.getLogger('ngus') |
| 88 | logger.info('ngus starting') | 129 | logger.info('ngus starting') |
| 89 | server_params = { | 130 | server_params = { |
| 90 | 'input_name': args.input_name, | 131 | 'input_name': args.input_name, |
| 91 | 'upload_dir': args.upload_dir | 132 | 'upload_dir': args.upload_dir, |
| 92 | } | 133 | } |
| 93 | if args.upload_page is not None: | 134 | if args.upload_page is not None: |
| 94 | server_params['upload_page'] = args.upload_page.read() | 135 | server_params['upload_page'] = args.upload_page.read() |
| 95 | if args.basic_auth and len(args.basic_auth.split(':')) == 2: | 136 | if args.basic_auth and len(args.basic_auth.split(':')) == 2: |
| 96 | server_params['basic_auth'] = base64.b64encode( | 137 | server_params['basic_auth'] = base64.b64encode( |
| 97 | args.basic_auth.strip().encode('utf-8')).decode() | 138 | args.basic_auth.strip().encode('utf-8') |
| 139 | ).decode() | ||
| 98 | logger.info('Requiring basic-auth') | 140 | logger.info('Requiring basic-auth') |
| 99 | ngus_server = ngus.NgusHTTPServer((args.hostname, args.port), | 141 | ngus_server = ngus.NgusHTTPServer( |
| 100 | ngus.NgusBaseHTTPRequestHandler, | 142 | (args.hostname, args.port), |
| 101 | **server_params) | 143 | ngus.NgusBaseHTTPRequestHandler, |
| 102 | logger.info(f'Listening on {args.hostname}:{args.port}') | 144 | **server_params, |
| 145 | ) | ||
| 146 | if args.certfile and args.keyfile: | ||
| 147 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | ||
| 148 | context.load_cert_chain( | ||
| 149 | certfile=args.certfile, keyfile=args.keyfile | ||
| 150 | ) | ||
| 151 | ngus_server.socket = context.wrap_socket( | ||
| 152 | ngus_server.socket, server_side=True | ||
| 153 | ) | ||
| 154 | logger.info( | ||
| 155 | f'Listening on {args.hostname}:{args.port}' | ||
| 156 | f'{" (SSL/TLS)" if args.certfile and args.keyfile else ""}' | ||
| 157 | ) | ||
| 103 | logger.info('Done!') | 158 | logger.info('Done!') |
| 104 | ngus_server.serve_forever() | 159 | ngus_server.serve_forever() |
| 105 | except KeyboardInterrupt: | 160 | except KeyboardInterrupt: |
