| files/motd | ||
| src | ||
| .gitignore | ||
| dn42-autopeer.py | ||
| LICENSE | ||
| README.md | ||
dn42 Auto-Peering SSH Service (AS4242420263)
A Python-based self-service SSH server that enables dn42 network maintainers to automatically establish and manage peering connections with AS4242420263.
Overview
This project implements a custom SSH server using Paramiko that authenticates users against the dn42 registry. It allows network operators to request peering sessions without manual intervention, storing all peering information in a SQLite database.
Quick Start
Prerequisites
- Debian >= 12
- Python 3
Installation
- Clone this repository
- Clone the dn42 registry
- Install dependencies:
apt install git python3-dnspython python3-packaging python3-paramiko python3-psutil python3-rich - Generate a SSH host key with
ssh-keygen - Set up environment variables (see Configuration section)
- Run the server:
$ python dn42-autopeer.py --server
2025-04-06 22:15:34,391:INFO:[SSHServerBase] Listening thread started
Usage
python dn42-autopeer.py --help
usage: dn42-sshd [-h] (--server | --genconfig | --gaming)
options:
-h, --help show this help message and exit
--server Start the auto peering server
--genconfig Generate the configuration from the peering db
--gaming Start the gaming server
Configuration
Environment variables
These environment variables can be set to configure the service
| Variable | Description | Default |
|---|---|---|
DN42_SSH_HOST_KEY |
Path to SSH host key file | files/ssh-keys/ssh_host_rsa_key |
DN42_SSH_LISTEN_ADDRESS |
Listen address | ::1 |
DN42_SSH_PORT |
SSH port | 4242 |
DN42_SERVER |
Public domain name of the server | nl-ams2.flap42.eu |
DN42_SSH_MOTD_PATH |
Path to custom MOTD file | files/motd/$DN42_SERVER |
DN42_DB_PATH |
Path to SQLite database file | files/db/$DN42_SERVER |
DN42_REGISTRY_DIRECTORY |
Path to dn42 registry clone | files/registry |
DN42_ADMIN_SSH_KEY |
Admin SSH public key (valid for all users) | None |
DN42_ASN |
Your dn42 Autonomous System Number | 4242420263 |
DN42_WG_PUB_KEY |
WireGuard public key for tunnels | rj0SORruOE/hGV... |
DN42_WG_PRIV_KEY |
WireGuard private key used in --genconfig |
**REPLACEME** |
DN42_WG_LINK_LOCAL_PREFIX |
IPv6 link-local prefix for auto-generated addresses | fe80:0263:: |
DN42_WG_LOCAL_ADDRESS |
Default IPv6 link-local address | fe80::263 |
DN42_WG_BASE_PORT |
Base WireGuard port | 52000 |
DN42_RESERVED_NETWORK |
Network where peering is restricted | None |
DN42_PEER_IP_MODE |
IP mode for peer endpoints: ipv4, ipv6, or both |
ipv6 |
DN42_BIRD_CONFIG_DIR |
Place to generate the bird config files in --genconfig |
files/bird |
DN42_WG_CONFIG_DIR |
Place to generate the wg config files in --genconfig |
files/wireguard |
Installation
I deploy this service on Debian using my own ansible role.
This is an example of a systemd service unit (/etc/systemd/system/dn42-sshd.service).
[Unit]
Description=DN42 SSHD Service
After=network.target
[Service]
Type=simple
User=dn42-sshd
Group=dn42-sshd
WorkingDirectory=/home/dn42-sshd/dn42-sshd
ExecStart=python3 dn42-sshd.py --peering
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment="DN42_SSH_HOST_KEY=/home/dn42-sshd/.ssh/id_rsa"
Environment="DN42_SSH_LISTEN_ADDRESS=::"
Environment="DN42_SSH_PORT=4242"
Environment="DN42_DB_PATH=/home/dn42-sshd/peering.db"
Environment="DN42_REGISTRY_DIRECTORY=/home/dn42-sshd/dn42-registry"
Environment="DN42_ADMIN_SSH_KEY=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample... admin@example.com"
Environment="DN42_ASN=4242420263"
Environment="DN42_WG_PUB_KEY=C3Wlu6y+v84FN/vreuTqL6r5wEtGTMXX5rKgHkxDaTI="
Environment="DN42_WG_LINK_LOCAL_PREFIX=fe80:0263::"
Environment="DN42_WG_LOCAL_ADDRESS=fe80::263"
Environment="DN42_WG_BASE_PORT=52000"
Environment="DN42_RESERVED_NETWORK=2001:0bc8:3feb::/48"
Environment="DN42_PEER_IP_MODE=ipv6"
# Security hardening
ProtectSystem=full
PrivateTmp=true
NoNewPrivileges=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=true
RestrictNamespaces=true
[Install]
WantedBy=multi-user.target
It assumes that:
- a local user
dn42-sshdexists - the present git repository is cloned in
/home/dn42-sshd/dn42-sshd - the dn42 registry is cloned in
/home/dn42-sshd/dn42-registry - the SQLite database file is
/home/dn42-sshd/peering.dband is writeable
It can be enabled and started using the systemctl command:
$ systemctl enable --now dn42-sshd
Created symlink /etc/systemd/system/multi-user.target.wants/dn42-sshd.service → /etc/systemd/system/dn42-sshd.service
The logs are readable using the journalctl command:
$ journalctl -n 100 -f -u dn42-sshd.service
...
Apr 06 21:57:24 nl-ams2 systemd[1]: Started dn42-sshd.service - DN42 SSHD Service.
Apr 06 21:57:31 nl-ams2 python3[9780]: 2025-04-06 21:57:31,576:INFO:[SSHServerBase] Listening thread started
Apr 06 22:12:28 nl-ams2 python3[9780]: 2025-04-06 22:12:28,505:INFO:[SSHServerBase] Accepted connection from ('2a01:...', 48396, 0, 0)
Apr 06 22:12:28 nl-ams2 python3[9780]: 2025-04-06 22:12:28,509:INFO:Connected (version 2.0, client OpenSSH_9.9)
Apr 06 22:12:28 nl-ams2 python3[9780]: 2025-04-06 22:12:28,777:INFO:Auth rejected (none).
Apr 06 22:12:28 nl-ams2 python3[9780]: 2025-04-06 22:12:28,877:INFO:[AuthDn42] Authentication successful for hcartiaux
Apr 06 22:12:28 nl-ams2 python3[9780]: 2025-04-06 22:12:28,910:INFO:[AuthDn42] Authentication successful for hcartiaux
Apr 06 22:12:28 nl-ams2 python3[9780]: 2025-04-06 22:12:28,911:INFO:Auth granted (publickey).
Apr 06 22:12:28 nl-ams2 python3[9780]: 2025-04-06 22:12:28,942:INFO:[SSHServerBase] Started thread 139777270654656 for hcartiaux@('2a01:...', 48396, 0, 0)
Apr 06 22:14:51 nl-ams2 python3[9780]: 2025-04-06 22:14:51,924:INFO:[139777270654656][SSHServerShell] User hcartiaux disconnected
Internals
Authentication methods
Anonymous, class SSHServerAuthNone
Accept all connections
dn42 registry, class SSHServerAuthDn42
Accept connections based on the dn42 maintainer objects, using the defined public key(s)
- username: lowercase maintainer name, without the
-MNTsuffix - public keys: supports
ssh-rsaandssh-ed25519keys defined in theauthfield
Specialized SSH servers
Common constructor parameters:
server_interface- can be set toSSHServerAuthNoneorSSHServerAuthDn42host_key_file- path of the ssh host key file
Piping, class SSHServerPipe(server_interface, cmd, host_key_file)
cmd- command to be executed and piped to the user
Shell, class SSHServerShell(server_interface, shell_class, host_key_file)
shell_class- name of theCmdsubclass to be used as shell instances.
Custom shell
The custom shell is implemented by the class ShellDn42(username, ...).
It extends the Cmd class heavily.
Database management
The SQLite database is entirely managed using the class DatabaseManager(db_path). It contains only one table.
CREATE TABLE IF NOT EXISTS peering_links (
id INTEGER PRIMARY KEY CHECK(id BETWEEN 1 AND 65535),
as_num INTEGER UNIQUE NOT NULL,
wg_pub_key TEXT NOT NULL,
wg_endpoint_addr TEXT NOT NULL,
wg_endpoint_port INTEGER NOT NULL CHECK(wg_endpoint_port BETWEEN 1 AND 65535),
user_link_local TEXT
External resources
- Ansible role for deployment
- PythonSSHServerTutorial (Used as a starting point)