~fabrixxm/remotami

5548bff5f695efda998c2ff9d508c01bd58951e5 — fabrixxm 1 year, 8 months ago
First commit
A  => .gitignore +1 -0
@@ 1,1 @@
__pycache__

A  => client/README.md +21 -0
@@ 1,21 @@
## requirements

- python3-requests
- python3-sshpubkeys
- x11vnc
- ssh
- vnagre (or another vnc client)

## run as "server"

`./remotamicli.py`

Will print user id and password

## connect with "client"

`./remotamicli.py -u <userid>`

Will launch vinagre and connect to server.
Password is managed by the vnc client.


A  => client/remotamicli.py +102 -0
@@ 1,102 @@
#!/usr/bin/env python3
import subprocess
import sys
import argparse
import re
from pathlib import Path
from random import randrange
import requests
from sshpubkeys import SSHKey

SERVER = "kirgroup.net"
VNCVIEWER = "vinagre vnc://{host}:{port}"

PRIV_KEY_FILE = Path.home() / ".ssh" / "remotami"
KEY_FILE = Path.home() / ".ssh" / "remotami.pub"

http = f"http://{SERVER}:5000"


def get_userid(key):
    keyhash = key.hash_md5().split(":")[-5:]
    code = [int(n, 16) for n in keyhash]
    return " ".join([str(x) for x in code])


def register():
    if not PRIV_KEY_FILE.exists():
        cmd = ["ssh-keygen", "-N", "", "-f", str(PRIV_KEY_FILE)]
        subprocess.run(args=cmd)

    with open(KEY_FILE, "r") as f:
        pubkey = SSHKey(f.read())

    r = requests.post(f"{http}/register", data={'pubkey': pubkey.keydata})
    if not r.ok:
        print(f"Unable to register: {r.status_code}")
        sys.exit(-1)

    return pubkey


def server():
    pubkey = register()
    userid = get_userid(pubkey)
    password = hex(randrange(10000, 1000000)).replace("0x", "").upper()

    r = requests.get(f"{http}/port", params={'u': userid.replace(" ", "")})
    if not r.ok:
        print(f"Unable to request port: {r.status_code}")
        sys.exit(-1)
    remoteport = int(r.text)
    if remoteport < 5900 or remoteport >= 6000:
        print("Got invalid port from server")
        sys.exit(-1)

    print("-"*79)
    print("userid   :, userid)
    print("password :", password)
    print("-"*79)

    with open("/tmp/remotamis.sh", "w") as f:
        f.write(f"ssh -N -R $2:localhost:5900 -i '{PRIV_KEY_FILE}'  vnc@$1 &\n")
        f.write("PID=$!\n")
        f.write("x11vnc -localhost -quiet -display :0 -passwd $3 2>/dev/null 1>/dev/null \n")
        f.write("kill $PID\n")

    subprocess.run(args=['bash', '-e', '/tmp/remotamis.sh',
                         SERVER, str(remoteport), password])


def client(userid):
    register()

    r = requests.get(f"{http}/remoteport", params={'u': userid})
    if not r.ok:
        print(f"Unable to request port: {r.status_code}")
        sys.exit(-1)
    remoteport = int(r.text)
    if remoteport < 5900 or remoteport >= 6000:
        print("Got invalid port from server")
        sys.exit(-1)

    with open("/tmp/remotamic.sh", "w") as f:
        f.write(f"ssh -N -L 5899:localhost:$2 -i '{PRIV_KEY_FILE}'  vnc@$1 &\n")
        f.write("PID=$!\n")
        f.write("sleep 3\n")
        f.write(VNCVIEWER.format(host="localhost", port="5899") + "\n")
        f.write("kill $PID\n")

    subprocess.run(args=['bash', '-e', '/tmp/remotamic.sh',
                         SERVER, str(remoteport)])


parser = argparse.ArgumentParser(description='Remote access from everywhere')
parser.add_argument("-u, --id", metavar="ID", required=False, dest="id")
args = parser.parse_args()

if args.id is None:
    server()
else:
    userid = re.sub(r"[^0-9]+", "", args.id)
    client(userid)

A  => server/README.md +32 -0
@@ 1,32 @@
# Remotami server

## requirements

- python3
- python3-flask
- python3-requests
- python3-sshpubkeys

## setup:
- create user "vnc" with `/usr/sbin/nologin` as shell
- start server as user "vnc"

## api

`/register` - **POST**

Add public key into server's authorized keys.
Require public key as string in `pubkey` form field.
Return nothing.

`/port` - **GET**

Assign port on server to user id.
Require user id as get parameter `u`
Return port number.

`/remoteport` - **GET**

Get assigned port on server for user id.
Require user id as get parameter `u`
Return port number.

A  => server/localrun.sh +4 -0
@@ 1,4 @@
#!/bin/bash
export FLASK_ENV=development
export FLASK_APP=remotamid.py
flask run

A  => server/remotamid.py +65 -0
@@ 1,65 @@
#!/usr/bin/python3

from pathlib import Path
from flask import Flask
from flask import request, make_response, abort
from sshpubkeys import SSHKey

app = Flask("lesanaweb")
authkeyfile = Path.home() / ".ssh" / "authorized_keys"
portsdir = Path.home() / "remotami"
portsdir.mkdir(mode=0o700, parents=True, exist_ok=True)


def is_key_authorized(key):
    if authkeyfile.exists():
        with open(authkeyfile, "r") as f:
            return key.keydata in [k.strip() for k in f.readlines()]
    return False


def authorize_key(key):
    authkeyfile.touch(mode=0o600, exist_ok=True)
    with open(authkeyfile, "a") as f:
        f.write(key.keydata + "\n")


@app.route('/register', methods=['POST'])
def register():
    pubkey = request.form.get('pubkey', None)
    if pubkey is None:
        abort(400)

    pubkey = SSHKey(pubkey, strict=True)
    pubkey.parse()  # raise an error on invalid key

    if is_key_authorized(pubkey):
        return make_response("200", 200)
    authorize_key(pubkey)
    return make_response("201", 201)


@app.route('/port', methods=['GET'])
def port():
    uid = request.args.get('u', None)
    if uid is None or uid == "":
        abort(400)
    portfile = portsdir / (uid + ".port")
    if portfile.exists():
        port = portfile.read_text()
        portfile.touch(exist_ok=True)
    else:
        n = len(list(portsdir.glob("*.port")))
        port = str(5900 + n)
        if port >= 6000:
            # this is just to put a limit
            # port files should be deleted once in a while
            abort(503)
        portfile.write_text(port)
    return make_response(port)



@app.route('/', methods=['GET'])
def index():
    return "remotami"

A  => server/requirements.txt +3 -0
@@ 1,3 @@
flask
sshpubkeys
requests