remctl 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- remctl-0.1.0/CHANGELOG.md +15 -0
- remctl-0.1.0/LICENSE +21 -0
- remctl-0.1.0/PKG-INFO +172 -0
- remctl-0.1.0/README.md +137 -0
- remctl-0.1.0/docs/SECURITY.md +146 -0
- remctl-0.1.0/py.typed +0 -0
- remctl-0.1.0/pyproject.toml +74 -0
- remctl-0.1.0/src/remctl/__init__.py +1 -0
- remctl-0.1.0/src/remctl/__main__.py +4 -0
- remctl-0.1.0/src/remctl/audit.py +50 -0
- remctl-0.1.0/src/remctl/cli.py +265 -0
- remctl-0.1.0/src/remctl/config.py +84 -0
- remctl-0.1.0/src/remctl/crypto.py +148 -0
- remctl-0.1.0/src/remctl/ctl/__init__.py +0 -0
- remctl-0.1.0/src/remctl/ctl/client.py +134 -0
- remctl-0.1.0/src/remctl/ctl/commands.py +234 -0
- remctl-0.1.0/src/remctl/ctl/pairing.py +163 -0
- remctl-0.1.0/src/remctl/discovery.py +122 -0
- remctl-0.1.0/src/remctl/node/__init__.py +0 -0
- remctl-0.1.0/src/remctl/node/daemon.py +232 -0
- remctl-0.1.0/src/remctl/node/handlers.py +198 -0
- remctl-0.1.0/src/remctl/node/service_linux.py +64 -0
- remctl-0.1.0/src/remctl/node/service_windows.py +77 -0
- remctl-0.1.0/src/remctl/platform/__init__.py +27 -0
- remctl-0.1.0/src/remctl/platform/base.py +45 -0
- remctl-0.1.0/src/remctl/platform/linux.py +98 -0
- remctl-0.1.0/src/remctl/platform/macos.py +35 -0
- remctl-0.1.0/src/remctl/platform/windows.py +85 -0
- remctl-0.1.0/src/remctl/protocol.py +96 -0
- remctl-0.1.0/tests/__init__.py +0 -0
- remctl-0.1.0/tests/test_cli.py +42 -0
- remctl-0.1.0/tests/test_crypto.py +141 -0
- remctl-0.1.0/tests/test_handlers_mocked.py +123 -0
- remctl-0.1.0/tests/test_protocol.py +93 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
- Initial release
|
|
6
|
+
- Core crypto: Ed25519 identity keys, X25519 ECDH handshake, AES-256-GCM transport encryption
|
|
7
|
+
- Wire protocol: pydantic-based message schemas, length-prefixed framing, replay protection
|
|
8
|
+
- LAN discovery: UDP broadcast beacon + peer table
|
|
9
|
+
- Node daemon: async TCP server, command dispatch, audit logging, rate limiting
|
|
10
|
+
- CLI client: all commands (lock, shutdown, restart, sleep, logout, closefocused,
|
|
11
|
+
exec, screenshot, push, pull, status, cancel)
|
|
12
|
+
- Two-way pairing: both node and controller must explicitly trust each other
|
|
13
|
+
- Permission tiers: observe, standard, full
|
|
14
|
+
- Platform support: Windows (first-class), Linux (second-class), macOS (stub)
|
|
15
|
+
- Service installation: Windows service (pywin32), Linux systemd unit
|
remctl-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 remctl contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
remctl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: remctl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Secure LAN-based remote PC control and administration
|
|
5
|
+
Author: remctl contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: administration,lan,remote-control
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: System Administrators
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: System :: Networking
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: cryptography>=42
|
|
21
|
+
Requires-Dist: mss>=9.0
|
|
22
|
+
Requires-Dist: psutil>=5.9
|
|
23
|
+
Requires-Dist: pydantic>=2
|
|
24
|
+
Requires-Dist: pywin32>=306; sys_platform == 'win32'
|
|
25
|
+
Requires-Dist: rich>=13
|
|
26
|
+
Requires-Dist: typer>=0.12
|
|
27
|
+
Requires-Dist: zeroconf>=0.132
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-mock>=3; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.3; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# remctl
|
|
37
|
+
|
|
38
|
+
**Secure, LAN-based remote PC control and administration.**
|
|
39
|
+
|
|
40
|
+
`remctl` lets you control machines on your local network from the command line —
|
|
41
|
+
lock, shutdown, execute shell commands with streaming output, transfer files,
|
|
42
|
+
take screenshots, and check system status. All communication is authenticated
|
|
43
|
+
using Ed25519 keys and encrypted with AES-256-GCM.
|
|
44
|
+
|
|
45
|
+
> ⚠️ **Intended for your own trusted LAN devices only.** `remctl` is not designed
|
|
46
|
+
> for Internet-facing use. Do not expose it via port forwarding or NAT.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install remctl
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### From source
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# TODO: user adds PyPI API token via env var / twine config before publishing
|
|
58
|
+
git clone <repo-url>
|
|
59
|
+
cd remctl
|
|
60
|
+
pip install -e ".[dev]"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start: Pairing Walkthrough
|
|
64
|
+
|
|
65
|
+
`remctl` requires two-way trust: both the node and the controller must explicitly
|
|
66
|
+
authorize each other.
|
|
67
|
+
|
|
68
|
+
### On the target machine (the node)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Show the node's fingerprint
|
|
72
|
+
remctl node pair --show
|
|
73
|
+
|
|
74
|
+
# Start the agent (background via service is recommended)
|
|
75
|
+
remctl node start
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### On the controlling machine
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Discover nodes on the LAN
|
|
82
|
+
remctl ctl discover
|
|
83
|
+
|
|
84
|
+
# Pair with a node (verify the fingerprint before confirming)
|
|
85
|
+
remctl ctl pair <node-ip>
|
|
86
|
+
|
|
87
|
+
# Show your own fingerprint for the node to trust you
|
|
88
|
+
remctl ctl pair-show
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Back on the node (trust the controller)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Trust the controller's public key (copy from the controller's output)
|
|
95
|
+
remctl node trust-controller /path/to/controller_identity.pub
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Send commands
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
remctl ctl <target> status
|
|
102
|
+
remctl ctl <target> lock
|
|
103
|
+
remctl ctl <target> exec "whoami"
|
|
104
|
+
remctl ctl <target> screenshot -o screenshot.png
|
|
105
|
+
remctl ctl <target> push ./file.txt /remote/path/file.txt
|
|
106
|
+
remctl ctl <target> pull /remote/log.log ./log.log
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Command Reference
|
|
110
|
+
|
|
111
|
+
| Command | Description |
|
|
112
|
+
|---------|-------------|
|
|
113
|
+
| `remctl node start` | Start the node agent in foreground |
|
|
114
|
+
| `remctl node install-service` | Install as OS service |
|
|
115
|
+
| `remctl ctl discover` | Discover nodes on LAN |
|
|
116
|
+
| `remctl ctl pair <target>` | Pair with a node |
|
|
117
|
+
| `remctl ctl <target> lock` | Lock the remote workstation |
|
|
118
|
+
| `remctl ctl <target> shutdown --delay 60` | Shutdown with optional delay |
|
|
119
|
+
| `remctl ctl <target> restart` | Restart |
|
|
120
|
+
| `remctl ctl <target> sleep` | Sleep/Suspend |
|
|
121
|
+
| `remctl ctl <target> logout` | Log out current user |
|
|
122
|
+
| `remctl ctl <target> closefocused` | Close active window |
|
|
123
|
+
| `remctl ctl <target> exec <command>` | Execute shell command (streamed) |
|
|
124
|
+
| `remctl ctl <target> status` | Show system status (CPU/mem/disk/uptime) |
|
|
125
|
+
| `remctl ctl <target> screenshot -o out.png` | Capture screenshot |
|
|
126
|
+
| `remctl ctl <target> push <local> <remote>` | Push file to node |
|
|
127
|
+
| `remctl ctl <target> pull <remote> <local>` | Pull file from node |
|
|
128
|
+
| `remctl ctl <target> cancel` | Cancel pending shutdown/restart |
|
|
129
|
+
|
|
130
|
+
## Security Model (Summary)
|
|
131
|
+
|
|
132
|
+
- **Identity:** Ed25519 keypairs generated on first run.
|
|
133
|
+
- **Authentication:** Two-way explicit pairing — both sides must trust each other.
|
|
134
|
+
- **Encryption:** X25519 ECDH key exchange → AES-256-GCM, with forward secrecy.
|
|
135
|
+
- **Replay protection:** Per-message sequence numbers + 30-second timestamp window.
|
|
136
|
+
- **Permission tiers:** `observe` (status/screenshot), `standard` (commands +
|
|
137
|
+
files), `full` (includes `exec`). Default: `standard`.
|
|
138
|
+
- **Audit log:** All commands logged unredacted at `~/.remctl/audit.log`.
|
|
139
|
+
|
|
140
|
+
See [docs/SECURITY.md](docs/SECURITY.md) for the full threat model.
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
All config files live in `~/.remctl/`:
|
|
145
|
+
|
|
146
|
+
| File | Purpose |
|
|
147
|
+
|------|---------|
|
|
148
|
+
| `identity.key` | Private key (keep secure) |
|
|
149
|
+
| `identity.pub` | Public key |
|
|
150
|
+
| `known_nodes.json` | Paired nodes (ctl side) |
|
|
151
|
+
| `known_controllers.json` | Trusted controllers (node side) |
|
|
152
|
+
| `node_config.json` | Node settings (bind address, port, tiers) |
|
|
153
|
+
| `audit.log` | Command audit log |
|
|
154
|
+
|
|
155
|
+
## Platform Support
|
|
156
|
+
|
|
157
|
+
| Platform | Status |
|
|
158
|
+
|----------|--------|
|
|
159
|
+
| Windows | First-class |
|
|
160
|
+
| Linux | Second-class (best-effort) |
|
|
161
|
+
| macOS | Not yet implemented |
|
|
162
|
+
|
|
163
|
+
## Publishing to PyPI
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
python -m build
|
|
167
|
+
twine upload dist/*
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
> The user must configure `~/.pypirc` or set `TWINE_USERNAME`/`TWINE_PASSWORD`
|
|
171
|
+
> environment variables before running `twine upload`. No credentials are
|
|
172
|
+
> included in this repository.
|
remctl-0.1.0/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# remctl
|
|
2
|
+
|
|
3
|
+
**Secure, LAN-based remote PC control and administration.**
|
|
4
|
+
|
|
5
|
+
`remctl` lets you control machines on your local network from the command line —
|
|
6
|
+
lock, shutdown, execute shell commands with streaming output, transfer files,
|
|
7
|
+
take screenshots, and check system status. All communication is authenticated
|
|
8
|
+
using Ed25519 keys and encrypted with AES-256-GCM.
|
|
9
|
+
|
|
10
|
+
> ⚠️ **Intended for your own trusted LAN devices only.** `remctl` is not designed
|
|
11
|
+
> for Internet-facing use. Do not expose it via port forwarding or NAT.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install remctl
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### From source
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# TODO: user adds PyPI API token via env var / twine config before publishing
|
|
23
|
+
git clone <repo-url>
|
|
24
|
+
cd remctl
|
|
25
|
+
pip install -e ".[dev]"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start: Pairing Walkthrough
|
|
29
|
+
|
|
30
|
+
`remctl` requires two-way trust: both the node and the controller must explicitly
|
|
31
|
+
authorize each other.
|
|
32
|
+
|
|
33
|
+
### On the target machine (the node)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Show the node's fingerprint
|
|
37
|
+
remctl node pair --show
|
|
38
|
+
|
|
39
|
+
# Start the agent (background via service is recommended)
|
|
40
|
+
remctl node start
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### On the controlling machine
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Discover nodes on the LAN
|
|
47
|
+
remctl ctl discover
|
|
48
|
+
|
|
49
|
+
# Pair with a node (verify the fingerprint before confirming)
|
|
50
|
+
remctl ctl pair <node-ip>
|
|
51
|
+
|
|
52
|
+
# Show your own fingerprint for the node to trust you
|
|
53
|
+
remctl ctl pair-show
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Back on the node (trust the controller)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Trust the controller's public key (copy from the controller's output)
|
|
60
|
+
remctl node trust-controller /path/to/controller_identity.pub
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Send commands
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
remctl ctl <target> status
|
|
67
|
+
remctl ctl <target> lock
|
|
68
|
+
remctl ctl <target> exec "whoami"
|
|
69
|
+
remctl ctl <target> screenshot -o screenshot.png
|
|
70
|
+
remctl ctl <target> push ./file.txt /remote/path/file.txt
|
|
71
|
+
remctl ctl <target> pull /remote/log.log ./log.log
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Command Reference
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
|---------|-------------|
|
|
78
|
+
| `remctl node start` | Start the node agent in foreground |
|
|
79
|
+
| `remctl node install-service` | Install as OS service |
|
|
80
|
+
| `remctl ctl discover` | Discover nodes on LAN |
|
|
81
|
+
| `remctl ctl pair <target>` | Pair with a node |
|
|
82
|
+
| `remctl ctl <target> lock` | Lock the remote workstation |
|
|
83
|
+
| `remctl ctl <target> shutdown --delay 60` | Shutdown with optional delay |
|
|
84
|
+
| `remctl ctl <target> restart` | Restart |
|
|
85
|
+
| `remctl ctl <target> sleep` | Sleep/Suspend |
|
|
86
|
+
| `remctl ctl <target> logout` | Log out current user |
|
|
87
|
+
| `remctl ctl <target> closefocused` | Close active window |
|
|
88
|
+
| `remctl ctl <target> exec <command>` | Execute shell command (streamed) |
|
|
89
|
+
| `remctl ctl <target> status` | Show system status (CPU/mem/disk/uptime) |
|
|
90
|
+
| `remctl ctl <target> screenshot -o out.png` | Capture screenshot |
|
|
91
|
+
| `remctl ctl <target> push <local> <remote>` | Push file to node |
|
|
92
|
+
| `remctl ctl <target> pull <remote> <local>` | Pull file from node |
|
|
93
|
+
| `remctl ctl <target> cancel` | Cancel pending shutdown/restart |
|
|
94
|
+
|
|
95
|
+
## Security Model (Summary)
|
|
96
|
+
|
|
97
|
+
- **Identity:** Ed25519 keypairs generated on first run.
|
|
98
|
+
- **Authentication:** Two-way explicit pairing — both sides must trust each other.
|
|
99
|
+
- **Encryption:** X25519 ECDH key exchange → AES-256-GCM, with forward secrecy.
|
|
100
|
+
- **Replay protection:** Per-message sequence numbers + 30-second timestamp window.
|
|
101
|
+
- **Permission tiers:** `observe` (status/screenshot), `standard` (commands +
|
|
102
|
+
files), `full` (includes `exec`). Default: `standard`.
|
|
103
|
+
- **Audit log:** All commands logged unredacted at `~/.remctl/audit.log`.
|
|
104
|
+
|
|
105
|
+
See [docs/SECURITY.md](docs/SECURITY.md) for the full threat model.
|
|
106
|
+
|
|
107
|
+
## Configuration
|
|
108
|
+
|
|
109
|
+
All config files live in `~/.remctl/`:
|
|
110
|
+
|
|
111
|
+
| File | Purpose |
|
|
112
|
+
|------|---------|
|
|
113
|
+
| `identity.key` | Private key (keep secure) |
|
|
114
|
+
| `identity.pub` | Public key |
|
|
115
|
+
| `known_nodes.json` | Paired nodes (ctl side) |
|
|
116
|
+
| `known_controllers.json` | Trusted controllers (node side) |
|
|
117
|
+
| `node_config.json` | Node settings (bind address, port, tiers) |
|
|
118
|
+
| `audit.log` | Command audit log |
|
|
119
|
+
|
|
120
|
+
## Platform Support
|
|
121
|
+
|
|
122
|
+
| Platform | Status |
|
|
123
|
+
|----------|--------|
|
|
124
|
+
| Windows | First-class |
|
|
125
|
+
| Linux | Second-class (best-effort) |
|
|
126
|
+
| macOS | Not yet implemented |
|
|
127
|
+
|
|
128
|
+
## Publishing to PyPI
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python -m build
|
|
132
|
+
twine upload dist/*
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
> The user must configure `~/.pypirc` or set `TWINE_USERNAME`/`TWINE_PASSWORD`
|
|
136
|
+
> environment variables before running `twine upload`. No credentials are
|
|
137
|
+
> included in this repository.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Security Model
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`remctl` implements a defense-in-depth security model for LAN-based remote PC
|
|
6
|
+
administration. Every communication channel is authenticated, encrypted, and
|
|
7
|
+
protected against replay attacks.
|
|
8
|
+
|
|
9
|
+
## Identity
|
|
10
|
+
|
|
11
|
+
- Each `remctl` instance (both node and controller) generates an Ed25519 keypair
|
|
12
|
+
on first run, stored in `~/.remctl/identity.key` (private key) and
|
|
13
|
+
`~/.remctl/identity.pub` (public key).
|
|
14
|
+
- A node or controller's **identity** is the SHA-256 fingerprint of its Ed25519
|
|
15
|
+
public key, displayed as a colon-separated hex string (SSH-fingerprint style).
|
|
16
|
+
- Private keys are stored with restrictive permissions (`chmod 600` on Linux,
|
|
17
|
+
equivalent ACL restriction on Windows).
|
|
18
|
+
|
|
19
|
+
## Pairing (Trust-On-First-Use)
|
|
20
|
+
|
|
21
|
+
- **Two-way trust is required.** Both the node and the controller must explicitly
|
|
22
|
+
trust each other. There is no auto-trust mechanism.
|
|
23
|
+
- When a controller pairs with a node (`remctl ctl pair <target>`):
|
|
24
|
+
1. The controller opens a direct TCP connection to the node.
|
|
25
|
+
2. The node sends its public key over this direct connection (never over
|
|
26
|
+
broadcast).
|
|
27
|
+
3. The controller computes the fingerprint and displays it for visual
|
|
28
|
+
confirmation (mimicking SSH's `known_hosts` behavior).
|
|
29
|
+
4. Only after the user types `yes` is the node's public key saved.
|
|
30
|
+
- For the node to trust the controller, the user must run
|
|
31
|
+
`remctl node trust-controller <pubkey-file>` on the node, adding the
|
|
32
|
+
controller's public key to the allowlist.
|
|
33
|
+
|
|
34
|
+
## Wire Protocol Security
|
|
35
|
+
|
|
36
|
+
### Session Key Exchange (Forward Secrecy)
|
|
37
|
+
|
|
38
|
+
1. Each connection performs an X25519 ECDH handshake.
|
|
39
|
+
2. Both sides generate ephemeral X25519 keypairs per connection.
|
|
40
|
+
3. Each handshake message is signed with the long-term Ed25519 identity key.
|
|
41
|
+
4. The shared secret is derived via HKDF-SHA256, incorporating:
|
|
42
|
+
- The X25519 ECDH shared secret
|
|
43
|
+
- The peer's long-term public key
|
|
44
|
+
5. This provides **forward secrecy**: compromising the long-term keys does not
|
|
45
|
+
compromise past session keys.
|
|
46
|
+
|
|
47
|
+
### Transport Encryption
|
|
48
|
+
|
|
49
|
+
- All post-handshake traffic is encrypted with **AES-256-GCM** (authenticated
|
|
50
|
+
encryption).
|
|
51
|
+
- Frame format: `[4-byte big-endian length][12-byte nonce][ciphertext]`
|
|
52
|
+
- Each frame uses a random nonce (never reused).
|
|
53
|
+
|
|
54
|
+
### Replay Protection
|
|
55
|
+
|
|
56
|
+
- Every command message includes a **monotonically increasing sequence number**
|
|
57
|
+
and a **timestamp**.
|
|
58
|
+
- The receiver rejects:
|
|
59
|
+
- Messages with a timestamp more than **30 seconds** from the current time.
|
|
60
|
+
- Messages with a sequence number that has already been processed.
|
|
61
|
+
- Sequence numbers are tracked per-connection.
|
|
62
|
+
|
|
63
|
+
### Network Binding
|
|
64
|
+
|
|
65
|
+
- By default, the node daemon binds to `0.0.0.0` (all interfaces). This is
|
|
66
|
+
suitable for trusted LAN environments.
|
|
67
|
+
- Users can restrict binding to a specific interface via the `--bind` option
|
|
68
|
+
or `node_config.json`.
|
|
69
|
+
- **Port forwarding or WAN exposure is explicitly out of scope.** `remctl` is
|
|
70
|
+
not designed or tested for Internet-facing use, and the documentation does
|
|
71
|
+
not include instructions for exposing it beyond the LAN.
|
|
72
|
+
|
|
73
|
+
## Authorization
|
|
74
|
+
|
|
75
|
+
### Controller Allowlist
|
|
76
|
+
|
|
77
|
+
- The node maintains an allowlist of trusted controller public keys in
|
|
78
|
+
`~/.remctl/known_controllers.json`.
|
|
79
|
+
- Any connection whose identity is not in this list is rejected and logged.
|
|
80
|
+
|
|
81
|
+
### Permission Tiers
|
|
82
|
+
|
|
83
|
+
Each trusted controller is assigned a permission tier (configured in
|
|
84
|
+
`~/.remctl/node_config.json`):
|
|
85
|
+
|
|
86
|
+
| Tier | Permissions |
|
|
87
|
+
|------------|---------------------------------------------------|
|
|
88
|
+
| `observe` | `status`, `screenshot` only |
|
|
89
|
+
| `standard` | All simple commands + file push/pull, no `exec` |
|
|
90
|
+
| `full` | Everything, including arbitrary shell `exec` |
|
|
91
|
+
|
|
92
|
+
- The default tier for newly-paired controllers is **`standard`**, not `full`.
|
|
93
|
+
- Upgrading to `full` requires explicit configuration by the node operator.
|
|
94
|
+
|
|
95
|
+
## Rate Limiting / Lockout
|
|
96
|
+
|
|
97
|
+
- Failed handshake/auth attempts are tracked per source IP address.
|
|
98
|
+
- After **5 failures within 60 seconds**, the source IP is temporarily banned
|
|
99
|
+
for **5 minutes**.
|
|
100
|
+
- Rate limiting is in-memory only (not persistent across restarts).
|
|
101
|
+
|
|
102
|
+
## Audit Logging
|
|
103
|
+
|
|
104
|
+
- Every received command is recorded in `~/.remctl/audit.log` as a JSON line.
|
|
105
|
+
- Each log entry includes: timestamp, source fingerprint, command name,
|
|
106
|
+
arguments (unredacted), outcome (accepted/rejected/error), and exit code
|
|
107
|
+
where applicable.
|
|
108
|
+
- Log rotation is size-based (default: 10MB per file, keep 5 backups) using
|
|
109
|
+
`RotatingFileHandler`.
|
|
110
|
+
|
|
111
|
+
## Threat Model
|
|
112
|
+
|
|
113
|
+
### What `remctl` protects against:
|
|
114
|
+
|
|
115
|
+
| Threat | Mitigation |
|
|
116
|
+
|--------|-----------|
|
|
117
|
+
| Unauthenticated LAN actor sending commands | Ed25519 identity verification; controller allowlist |
|
|
118
|
+
| Passive eavesdropping on the wire | AES-256-GCM encryption of all traffic |
|
|
119
|
+
| Active man-in-the-middle | X25519 ECDH signed with long-term identity keys |
|
|
120
|
+
| Replay of captured commands | Per-message sequence numbers + timestamp window |
|
|
121
|
+
| Brute-force connection attempts | Per-IP rate limiting with temporary ban |
|
|
122
|
+
| Unauthorized `exec` on a partially trusted controller | Permission tiers (default `standard`) |
|
|
123
|
+
| Controller key compromise (past sessions) | Forward secrecy via ephemeral X25519 keys |
|
|
124
|
+
|
|
125
|
+
### What `remctl` does NOT protect against:
|
|
126
|
+
|
|
127
|
+
| Non-goal | Reason |
|
|
128
|
+
|----------|--------|
|
|
129
|
+
| A fully compromised endpoint | Any machine where the node daemon runs with root/admin access can be controlled; if an attacker has root/Administrator, all bets are off |
|
|
130
|
+
| Physical access to the machine | Physical access enables bypassing all software security measures |
|
|
131
|
+
| A compromised controller machine | If a trusted controller machine is compromised, the attacker inherits that controller's permissions |
|
|
132
|
+
| WAN/Internet threats | `remctl` is explicitly designed for LAN use only |
|
|
133
|
+
| DoS attacks | While rate limiting mitigates casual DoS, a determined LAN attacker can still flood the network |
|
|
134
|
+
| Malicious `exec` payloads | `exec` runs arbitrary shell commands; the node operator should only grant `full` tier to highly trusted controllers |
|
|
135
|
+
|
|
136
|
+
## Security Recommendations for Users
|
|
137
|
+
|
|
138
|
+
1. **Keep your identity keys safe.** The `~/.remctl/identity.key` file grants
|
|
139
|
+
full access to all nodes that trust it. Back it up securely, or generate a
|
|
140
|
+
new keypair if compromised.
|
|
141
|
+
2. **Use the `observe` tier** for controllers that only need to check status.
|
|
142
|
+
3. **Grant `full` tier sparingly** — only to controllers that genuinely need
|
|
143
|
+
shell access.
|
|
144
|
+
4. **Review the audit log** periodically at `~/.remctl/audit.log`.
|
|
145
|
+
5. **Bind to a specific interface** if your machine has multiple network
|
|
146
|
+
interfaces and you only want `remctl` accessible on one of them.
|
remctl-0.1.0/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "remctl"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Secure LAN-based remote PC control and administration"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "remctl contributors"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["remote-control", "lan", "administration"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: System Administrators",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: System :: Networking",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"typer>=0.12",
|
|
31
|
+
"pydantic>=2",
|
|
32
|
+
"cryptography>=42",
|
|
33
|
+
"zeroconf>=0.132",
|
|
34
|
+
"psutil>=5.9",
|
|
35
|
+
"mss>=9.0",
|
|
36
|
+
"rich>=13",
|
|
37
|
+
"pywin32>=306; sys_platform == 'win32'",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=7",
|
|
43
|
+
"pytest-asyncio>=0.23",
|
|
44
|
+
"pytest-mock>=3",
|
|
45
|
+
"ruff>=0.3",
|
|
46
|
+
"mypy>=1.8",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
remctl = "remctl.cli:app"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/remctl"]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
asyncio_mode = "auto"
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff]
|
|
60
|
+
target-version = "py310"
|
|
61
|
+
line-length = 120
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint]
|
|
64
|
+
select = ["E", "F", "I", "N", "W", "UP"]
|
|
65
|
+
|
|
66
|
+
[tool.mypy]
|
|
67
|
+
python_version = "3.11"
|
|
68
|
+
ignore_missing_imports = true
|
|
69
|
+
warn_unused_configs = true
|
|
70
|
+
warn_redundant_casts = true
|
|
71
|
+
warn_unused_ignores = true
|
|
72
|
+
disallow_any_generics = false
|
|
73
|
+
disallow_subclassing_any = false
|
|
74
|
+
exclude = ["tests/"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from logging.handlers import RotatingFileHandler
|
|
6
|
+
|
|
7
|
+
from remctl.crypto import KEY_DIR
|
|
8
|
+
|
|
9
|
+
AUDIT_LOG = KEY_DIR / "audit.log"
|
|
10
|
+
|
|
11
|
+
_logger: logging.Logger | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_logger(max_bytes: int = 10 * 1024 * 1024, backup_count: int = 5) -> logging.Logger:
|
|
15
|
+
global _logger
|
|
16
|
+
if _logger is not None:
|
|
17
|
+
return _logger
|
|
18
|
+
KEY_DIR.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
19
|
+
_logger = logging.getLogger("remctl.audit")
|
|
20
|
+
_logger.setLevel(logging.INFO)
|
|
21
|
+
handler = RotatingFileHandler(
|
|
22
|
+
str(AUDIT_LOG),
|
|
23
|
+
maxBytes=max_bytes,
|
|
24
|
+
backupCount=backup_count,
|
|
25
|
+
encoding="utf-8",
|
|
26
|
+
)
|
|
27
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
28
|
+
_logger.addHandler(handler)
|
|
29
|
+
_logger.propagate = False
|
|
30
|
+
return _logger
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def log_command(
|
|
34
|
+
source_fingerprint: str,
|
|
35
|
+
command: str,
|
|
36
|
+
args: dict,
|
|
37
|
+
outcome: str,
|
|
38
|
+
detail: str | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
logger = get_logger()
|
|
41
|
+
record = {
|
|
42
|
+
"timestamp": __import__("time").time(),
|
|
43
|
+
"source": source_fingerprint,
|
|
44
|
+
"command": command,
|
|
45
|
+
"args": args,
|
|
46
|
+
"outcome": outcome,
|
|
47
|
+
}
|
|
48
|
+
if detail:
|
|
49
|
+
record["detail"] = detail
|
|
50
|
+
logger.info(json.dumps(record, default=str))
|