This repository has been archived by the owner on Dec 2, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c62e8cc
Showing
7 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pem | ||
test/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Copyright (c) 2024- Germano Rizzo <oss AT germanorizzo DOT it> | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# fileserver v0.1.0 | ||
|
||
This project aids in setting up a tunnel that serves a single file on an encrypted connection, allowing to source it from your system and downloading it on another system you don't have "direct" access to, because under a firewall or such reasons. | ||
|
||
It employs a "jump server" to which it will reverse tunnel a local port via SSH. | ||
|
||
It has been tested under Linux, both for the source and destination system; it will be adapted to Windows and MacOS. | ||
|
||
## Prerequisites | ||
|
||
- A "jump server" that you can access via SSH from the source system; | ||
- A port on it, accessible by "the world"; | ||
- SSH on the jump server must be configured to allow remote tunnels (see below); | ||
- `python` v3 on the source system; | ||
- `curl` on the destination system. | ||
|
||
## Usage | ||
|
||
- You configure and run the script `filetunnel.sh` with the file to transfer: | ||
```bash | ||
./fileserver.sh myFile.binary | ||
``` | ||
- It will output a `curl` command to use on the destination system to download the file. | ||
|
||
Behind the scenes, the script opens a web server using python, on a random local port; then reverse tunnels it on the jump server, making it available remotely. | ||
|
||
The `curl` script, when executed on the destination system, will connect to the port and download the file, assigning the correct filename to it. | ||
|
||
## Setup | ||
|
||
### The jump server | ||
|
||
This is a "normal" server such as a VPS, that you can access via SSH from the source system. | ||
|
||
A port (to configure inside the script) must be accessible from outside, at least from the destination system. | ||
|
||
On ssh, (reverse) tunneling must be enabled. Ensure that you have this setting in `/etc/ssh/sshd_config`: | ||
|
||
``` | ||
AllowTcpForwarding yes | ||
``` | ||
|
||
We'll also need to access the remote-forwarded port from outside. So, set: | ||
|
||
```python | ||
GatewayPorts clientspecified # or 'yes' | ||
``` | ||
|
||
**WARNING!** This setting allows the forwarded port (*any* forwarded port, even for other uses) to be globally accessible. Carefully consider the security implications of this. | ||
|
||
### The source system | ||
|
||
Download `filetunnel.sh` from the release page. | ||
|
||
Open it, and configure the variables in the first section. You'll need: | ||
|
||
- `SSH_SERVER`: address to contact the jump server from the source system, using ssh, in form `user@host`. | ||
- `FILE_SERVER`: IP or DNS name to contact the jump server from the destination system. | ||
- `PORT`: port on the jump server for the tunnel, accessible from the destination server. | ||
|
||
If you want HTTPS, see the next section. | ||
|
||
### Setup https | ||
|
||
First, generate the certificates using: | ||
```bash | ||
openssl req -newkey rsa:4096 -nodes -keyout key.pem -x509 -days 365 -out cert.pem | ||
``` | ||
This will generate a `cert.pem` and a (secret) `key.pem` files. | ||
|
||
Then configure `fileserver.sh` to use HTTPS, by setting the relevant variables: | ||
|
||
- `DO_HTTPS`: set to `1`. | ||
- `CERT_FILE`, `KEY_FILE`: paths to the `cert.pem` and `key.pem` files generated by `openssl`. | ||
|
||
## Security | ||
|
||
- There is an inherent risk in doing reverse tunneling. It's a good idea to reserve the jump server to this use; | ||
- The connection between the source system and the jump server is protected by `ssh`; | ||
- The connection between the jump server and the destination system is protected by (optional) HTTPS, using a user-provided certificate; | ||
- Also, the generated URL is random; | ||
- Once transferred the file, it's good measure to terminate the script to avoid continued exposure. | ||
|
||
## Troubleshooting | ||
|
||
### When stopping the script w/ `ctrl-c`, a python error is shown | ||
``` | ||
Traceback (most recent call last): | ||
File "<string>", line 18, in <module> | ||
File "/usr/lib/python3.12/socketserver.py", line 235, in serve_forever | ||
ready = selector.select(poll_interval) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
File "/usr/lib/python3.12/selectors.py", line 415, in select | ||
fd_event_list = self._selector.poll(timeout) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
KeyboardInterrupt | ||
``` | ||
This is ok, for now just ignore it. It's on the todo not to show it. | ||
|
||
## To do | ||
|
||
- Adapt and fully test under MacOS, Windows | ||
- Optional "one shot" mode: when the file is downloaded, the server exists; | ||
- Optional compression; | ||
- When stopping the script, the python part emits an ugly-looking error, even if it's totally intended. | ||
|
||
## Build and contribute | ||
|
||
In the `src` folder there are three script (one bash, two python) to manually "compile" into the target script. Make a copy of the bash script, and then follow the comments that contain `[BUILD]`. | ||
|
||
If you have any good idea, please feel free to hack on it! The code should be fairly simple to understand and change, and doesn't have many dependencies. | ||
|
||
## License | ||
|
||
``` | ||
filetunnel v0.1.0 https://github.com/proofrock/filetunnel | ||
Copyright (c) 2024- Germano Rizzo <oss AT germanorizzo DOT it> | ||
See LICENSE file (MIT License) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
#!/bin/bash | ||
|
||
# filetunnel v0.1.0 https://github.com/proofrock/filetunnel | ||
# Copyright (c) 2024- Germano Rizzo <oss AT germanorizzo DOT it> | ||
# See LICENSE file (MIT License) | ||
|
||
##################### | ||
# Edit this section # | ||
##################### | ||
|
||
# How to contact the jump server from the "file server side". user@host | ||
SSH_SERVER=user@123.123.123.123 | ||
# How to contact the jump server from the "client side". | ||
FILE_SERVER=123.123.123.123 | ||
# Port on the jump server to tunnel the HTTP server on | ||
PORT=7017 | ||
|
||
# Setup HTTPS | ||
DO_HTTPS=0 | ||
CERT_FILE=./cert.pem | ||
KEY_FILE=./key.pem | ||
|
||
################### | ||
# Not from now on # | ||
################### | ||
|
||
RND=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24) | ||
|
||
FILESERVERSCRIPT=`cat <<PYDOC | ||
import os, sys, ssl | ||
from http.server import HTTPServer, SimpleHTTPRequestHandler | ||
# CLI parameters (internal) | ||
file_path = sys.argv[1] # File to serve | ||
url_path = sys.argv[2] # Random string for URL path | ||
http_port = int(sys.argv[3]) # Port for the server | ||
do_ssl = sys.argv[4] == "1" # Do ssl? | ||
if do_ssl: | ||
cert_file = sys.argv[5] | ||
key_file = sys.argv[6] | ||
# Size | ||
file_size = os.stat(file_path).st_size | ||
# Create a custom request handler | ||
class SingleFileRequestHandler(SimpleHTTPRequestHandler): | ||
def do_GET(self): | ||
if self.path == f'/{url_path}': | ||
self.send_response(200) | ||
self.send_header('Content-Type', 'binary/octet-stream') | ||
self.send_header('Content-Length', str(file_size)) | ||
self.send_header('Content-Disposition', f'attachment; filename="{os.path.basename(file_path)}"') | ||
self.end_headers() | ||
with open(file_path, 'rb') as file: | ||
self.wfile.write(file.read()) | ||
# elif self.path == '/exit': | ||
# self.wfile.write(b'bye') | ||
# self.send_response(200) | ||
# exit(0) | ||
else: | ||
self.send_error(404) | ||
# Set up the HTTPS server | ||
server_address = ('', http_port) | ||
httpd = HTTPServer(server_address, SingleFileRequestHandler) | ||
if do_ssl: | ||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | ||
context.load_cert_chain(cert_file, key_file) | ||
httpd.socket = context.wrap_socket(httpd.socket, server_side=True) | ||
httpd.serve_forever() | ||
PYDOC` | ||
FREEPORTSCRIPT=`cat <<PYDOC | ||
import socket | ||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | ||
s.bind(('', 0)) | ||
addr, port = s.getsockname() | ||
print(port) | ||
PYDOC` | ||
LOCAL_PORT=$(python -c "$FREEPORTSCRIPT") | ||
function handle_interrupt { | ||
kill -TERM "$PID1" "$PID2" 2>/dev/null | ||
wait "$PID1" "$PID2" 2>/dev/null | ||
# [TODO] Python dies with ugly-looking "logs" | ||
echo "Bye bye!" | ||
exit 0 | ||
} | ||
trap handle_interrupt SIGINT | ||
ssh $SSH_SERVER -N -R:$PORT:localhost:$LOCAL_PORT & | ||
PID1=$! | ||
{ python -c "$FILESERVERSCRIPT" "$1" "$RND" "$LOCAL_PORT" "$DO_HTTPS" "$CERT_FILE" "$KEY_FILE"; kill $PID1; } & # At exit, kills the ssh session | ||
PID2=$! | ||
PROTO="http" | ||
if [ "$DO_HTTPS" -eq "1" ]; then | ||
PROTO="https" | ||
ADD_FLAG="k" | ||
fi | ||
echo "All set up, using local port $LOCAL_PORT. Now use the following command:" | ||
echo | ||
echo "curl -OJf$ADD_FLAG $PROTO://$FILE_SERVER:$PORT/$RND" | ||
echo | ||
echo "Then you can close this process with ctrl-c." | ||
wait "$PID1" "$PID2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import os, sys, ssl | ||
from http.server import HTTPServer, SimpleHTTPRequestHandler | ||
|
||
# CLI parameters (internal) | ||
|
||
file_path = sys.argv[1] # File to serve | ||
url_path = sys.argv[2] # Random string for URL path | ||
http_port = int(sys.argv[3]) # Port for the server | ||
do_ssl = sys.argv[4] == "1" # Do ssl? | ||
if do_ssl: | ||
cert_file = sys.argv[5] | ||
key_file = sys.argv[6] | ||
|
||
# Size | ||
file_size = os.stat(file_path).st_size | ||
|
||
# Create a custom request handler | ||
class SingleFileRequestHandler(SimpleHTTPRequestHandler): | ||
def do_GET(self): | ||
if self.path == f'/{url_path}': | ||
self.send_response(200) | ||
self.send_header('Content-Type', 'binary/octet-stream') | ||
self.send_header('Content-Length', str(file_size)) | ||
self.send_header('Content-Disposition', f'attachment; filename="{os.path.basename(file_path)}"') | ||
self.end_headers() | ||
with open(file_path, 'rb') as file: | ||
self.wfile.write(file.read()) | ||
# elif self.path == '/exit': | ||
# self.wfile.write(b'bye') | ||
# self.send_response(200) | ||
# exit(0) | ||
else: | ||
self.send_error(404) | ||
|
||
# Set up the HTTPS server | ||
server_address = ('', http_port) | ||
httpd = HTTPServer(server_address, SingleFileRequestHandler) | ||
|
||
if do_ssl: | ||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | ||
context.load_cert_chain(cert_file, key_file) | ||
httpd.socket = context.wrap_socket(httpd.socket, server_side=True) | ||
|
||
httpd.serve_forever() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#!/bin/bash | ||
|
||
# filetunnel v0.1.0 https://github.com/proofrock/filetunnel | ||
# Copyright (c) 2024- Germano Rizzo <oss AT germanorizzo DOT it> | ||
# See LICENSE file (MIT License) | ||
|
||
##################### | ||
# Edit this section # | ||
##################### | ||
|
||
# How to contact the jump server from the "file server side". user@host | ||
SSH_SERVER=user@123.123.123.123 | ||
# How to contact the jump server from the "client side". | ||
FILE_SERVER=123.123.123.123 | ||
# Port on the jump server to tunnel the HTTP server on | ||
PORT=7017 | ||
|
||
# Setup HTTPS | ||
DO_HTTPS=0 | ||
CERT_FILE=./cert.pem | ||
KEY_FILE=./key.pem | ||
|
||
################### | ||
# Not from now on # | ||
################### | ||
|
||
RND=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24) | ||
|
||
# [BUILD] Put the contents of 'fileserver.py' in this section | ||
FREEPORTSCRIPT=`cat <<PYDOC | ||
# Put 'fileserver.py' here | ||
PYDOC` | ||
# [BUILD] Put the contents of 'freeport.py' in this section | ||
FILESERVERSCRIPT=`cat <<PYDOC | ||
# Put 'freeport.py' here | ||
PYDOC` | ||
LOCAL_PORT=$(python -c "$FREEPORTSCRIPT") | ||
function handle_interrupt { | ||
kill -TERM "$PID1" "$PID2" 2>/dev/null | ||
wait "$PID1" "$PID2" 2>/dev/null | ||
# [TODO] Python dies with ugly-looking "logs" | ||
echo "Bye bye!" | ||
exit 0 | ||
} | ||
trap handle_interrupt SIGINT | ||
ssh $SSH_SERVER -N -R:$PORT:localhost:$LOCAL_PORT & | ||
PID1=$! | ||
{ python -c "$FILESERVERSCRIPT" "$1" "$RND" "$LOCAL_PORT" "$DO_HTTPS" "$CERT_FILE" "$KEY_FILE"; kill $PID1; } & # At exit, kills the ssh session | ||
PID2=$! | ||
PROTO="http" | ||
if [ "$DO_HTTPS" -eq "1" ]; then | ||
PROTO="https" | ||
ADD_FLAG="k" | ||
fi | ||
echo "All set up, using local port $LOCAL_PORT. Now use the following command:" | ||
echo | ||
echo "curl -OJf$ADD_FLAG $PROTO://$FILE_SERVER:$PORT/$RND" | ||
echo | ||
echo "Then you can close this process with ctrl-c." | ||
wait "$PID1" "$PID2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Returns a free port | ||
|
||
import socket | ||
|
||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | ||
s.bind(('', 0)) | ||
addr, port = s.getsockname() | ||
print(port) |