Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add config option to use non-default manhole password and keys #10643

Merged
merged 14 commits into from
Sep 6, 2021
1 change: 1 addition & 0 deletions changelog.d/10643.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add config option to use non-default manhole password and keys.
29 changes: 25 additions & 4 deletions docs/manhole.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Note that this will give administrative access to synapse to **all users** with
shell access to the server. It should therefore **not** be enabled in
environments where untrusted users have shell access.

***
## Configuring the manhole

To enable it, first uncomment the `manhole` listener configuration in
`homeserver.yaml`. The configuration is slightly different if you're using docker.
Expand Down Expand Up @@ -52,16 +52,37 @@ listeners:
type: manhole
```

#### Accessing synapse manhole
### Security settings

The following config options are available:

- `username`- The username for the manhole (defaults to `matrix`)
- `password` - The password for the manhole (defaults to `rabbithole`)
- `ssh_priv_key` - The path to a private SSH key (defaults to a hardcoded value)
- `ssh_pub_key` - The path to a public SSH key (defaults to a hardcoded value)

For example:

```yaml
manhole_settings:
username: manhole
password: mypassword
ssh_priv_key: "/home/synapse/manhole_keys/id_rsa"
ssh_pub_key: "/home/synapse/manhole_keys/id_rsa.pub"
```


## Accessing synapse manhole

Then restart synapse, and point an ssh client at port 9000 on localhost, using
the username `matrix`:
the username and password configured in `homeserver.yaml` - with the default
configuration, this would be:

```bash
ssh -p9000 matrix@localhost
```

The password is `rabbithole`.
Then enter the password when prompted.
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

This gives a Python REPL in which `hs` gives access to the
`synapse.server.HomeServer` object - which in turn gives access to many other
Expand Down
7 changes: 7 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ listeners:
# bind_addresses: ['::1', '127.0.0.1']
# type: manhole

# Connection settings for the manhole
manhole_settings:
# username: matrix
# password: rabbithole
# ssh_priv_key: CONFDIR/id_rsa
# ssh_pub_key: CONFDIR/id_rsa.pub

# Forward extremities can build up in a room due to networking delays between
# homeservers. Once this happens in a large room, calculation of the state of
# that room can become quite expensive. To mitigate this, once the number of
Expand Down
9 changes: 7 additions & 2 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,12 @@ def listen_metrics(bind_addresses, port):
start_http_server(port, addr=host, registry=RegistryProxy)


def listen_manhole(bind_addresses: Iterable[str], port: int, manhole_globals: dict):
def listen_manhole(
bind_addresses: Iterable[str],
port: int,
manhole_settings: dict,
manhole_globals: dict,
):
# twisted.conch.manhole 21.1.0 uses "int_from_bytes", which produces a confusing
# warning. It's fixed by https://github.com/twisted/twisted/pull/1522), so
# suppress the warning for now.
Expand All @@ -244,7 +249,7 @@ def listen_manhole(bind_addresses: Iterable[str], port: int, manhole_globals: di
listen_tcp(
bind_addresses,
port,
manhole(username="matrix", password="rabbithole", globals=manhole_globals),
manhole(settings=manhole_settings, globals=manhole_globals),
)


Expand Down
5 changes: 4 additions & 1 deletion synapse/app/generic_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,10 @@ def start_listening(self):
self._listen_http(listener)
elif listener.type == "manhole":
_base.listen_manhole(
listener.bind_addresses, listener.port, manhole_globals={"hs": self}
listener.bind_addresses,
listener.port,
manhole_settings=self.config.server.manhole_settings,
manhole_globals={"hs": self},
)
elif listener.type == "metrics":
if not self.config.enable_metrics:
Expand Down
5 changes: 4 additions & 1 deletion synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,10 @@ def start_listening(self):
)
elif listener.type == "manhole":
_base.listen_manhole(
listener.bind_addresses, listener.port, manhole_globals={"hs": self}
listener.bind_addresses,
listener.port,
manhole_settings=self.config.server.manhole_settings,
manhole_globals={"hs": self},
)
elif listener.type == "replication":
services = listen_tcp(
Expand Down
42 changes: 42 additions & 0 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import yaml
from netaddr import AddrFormatError, IPNetwork, IPSet

from twisted.conch.ssh.keys import Key

from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.util.module_loader import load_module
from synapse.util.stringutils import parse_and_validate_server_name
Expand Down Expand Up @@ -649,6 +651,39 @@ class LimitRemoteRoomsConfig:
)
)

manhole_settings = config.get("manhole_settings") or {}
manhole_username = manhole_settings.get("username") or "matrix"
manhole_password = manhole_settings.get("password") or "rabbithole"
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
manhole_priv_key_file = manhole_settings.get("ssh_priv_key") or None
manhole_pub_key_file = manhole_settings.get("ssh_pub_key") or None
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

manhole_priv_key = None
if manhole_priv_key_file is not None:
try:
manhole_priv_key = Key.fromFile(manhole_priv_key_file)
except Exception as e:
raise ConfigError(
"Failed to open manhole private key file %s: %s"
% (manhole_priv_key_file, e)
)
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

manhole_pub_key = None
if manhole_pub_key_file is not None:
try:
manhole_pub_key = Key.fromFile(manhole_pub_key_file)
except Exception as e:
raise ConfigError(
"Failed to open manhole public key file %s: %s"
% (manhole_pub_key_file, e)
)
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

self.manhole_settings = {
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
"username": manhole_username,
"password": manhole_password,
"priv_key": manhole_priv_key,
"pub_key": manhole_pub_key,
}

metrics_port = config.get("metrics_port")
if metrics_port:
logger.warning(METRICS_PORT_WARNING)
Expand Down Expand Up @@ -1056,6 +1091,13 @@ def generate_config_section(
# bind_addresses: ['::1', '127.0.0.1']
# type: manhole

# Connection settings for the manhole
manhole_settings:
# username: matrix
# password: rabbithole
# ssh_priv_key: CONFDIR/id_rsa
# ssh_pub_key: CONFDIR/id_rsa.pub
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
# Forward extremities can build up in a room due to networking delays between
# homeservers. Once this happens in a large room, calculation of the state of
# that room can become quite expensive. To mitigate this, once the number of
Expand Down
11 changes: 8 additions & 3 deletions synapse/util/manhole.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
-----END RSA PRIVATE KEY-----"""


def manhole(username, password, globals):
def manhole(settings, globals):
"""Starts a ssh listener with password authentication using
the given username and password. Clients connecting to the ssh
listener will find themselves in a colored python shell with
Expand All @@ -75,6 +75,11 @@ def manhole(username, password, globals):
Returns:
twisted.internet.protocol.Factory: A factory to pass to ``listenTCP``
"""
username = settings.get("username")
password = settings.get("password")
priv_key = settings.get("priv_key") or Key.fromString(PRIVATE_KEY)
pub_key = settings.get("pub_key") or Key.fromString(PUBLIC_KEY)

if not isinstance(password, bytes):
password = password.encode("ascii")

Expand All @@ -86,8 +91,8 @@ def manhole(username, password, globals):
)

factory = manhole_ssh.ConchFactory(portal.Portal(rlm, [checker]))
factory.publicKeys[b"ssh-rsa"] = Key.fromString(PUBLIC_KEY)
factory.privateKeys[b"ssh-rsa"] = Key.fromString(PRIVATE_KEY)
factory.privateKeys[b"ssh-rsa"] = priv_key
factory.publicKeys[b"ssh-rsa"] = pub_key

return factory

Expand Down