Serving a Paracosm to other machines safely
A Paracosm serves one machine by default: PARACOSM_EXTERNAL_HOST=127.0.0.1 puts that address in every URL the
world hands out, so no other machine can log in. quickstart.md section 12 has the steps to open
it to a LAN or the internet. This chapter is about doing that safely: what crosses the network in clear, the two
ways to add TLS, and which ports to open and which never to.
Untested. Everything below is taken from the OpenSimulator 0.9.3.0 source and its example configs, and none of it has been run: this house has only ever served itself. Try it on a spare machine first.
What travels, and how
| traffic | port | protocol | encrypted by default |
|---|---|---|---|
login (XML-RPC at /), get_grid_info, the capabilities (inventory, textures, meshes, uploads, the event queue), map tiles, script HTTP-in (llRequestURL), the profile service |
PARACOSM_PORT TCP (9000) |
HTTP | no |
| the region circuit: movement, chat, object updates, sound triggers | PARACOSM_PORT UDP (9000, the region's InternalPort) |
LLUDP | no, and it has no TLS option |
| OpenSim's REST console | PARACOSM_CONSOLE_PORT TCP (9001) |
HTTP | no |
| WorldBridge | 9002 TCP, 127.0.0.1 only | HTTP + bearer token | no |
| the viewer's LEAP helper bridge (on the viewer's machine) | 9004 TCP, 127.0.0.1 only | HTTP + bearer token | no |
What that means:
- The login. The viewer sends the account name and either the password or its unsalted MD5 (
$1$+ hex;LLLoginServiceaccepts both and checksmd5(md5(password) + ":" + salt)). Either one is enough to log in as that avatar, so on an untrusted network the login is the thing to protect first. - After the login the viewer talks to the addresses the login response gives it: the region's seed capability
is
http://<ExternalHostName>:<http port>/CAPS/...(LLLoginResponse:destination.ServerURI+ the caps path) and the region's UDP endpoint. Those do not go through anything you put in front of the login URI. - The simulator's HTTP port listens on every interface (
BaseHttpServerbindsIPAddress.Any; OpenSim does not narrow it), even whilePARACOSM_EXTERNAL_HOSTis 127.0.0.1. The REST console's port does too. - The profile service answers JSON-RPC on the simulator port with no credentials (config.md, Offline messages and profiles): anyone who reaches the port can read and change local profiles and read the email in a user's preferences. This is how OpenSimulator serves profiles, not a Paracosm addition, but it is on by default here.
Option 1: OpenSim's own HTTPS listener
OpenSim can serve its main HTTP server over TLS itself. The keys, all in [Network] of OpenSim.ini
(OpenSim.ini.example documents them; NetworkServersInfo.loadFromConfiguration reads them):
| key | default | meaning |
|---|---|---|
http_listener_ssl |
false | serve the main server over HTTPS |
http_listener_sslport |
9001 | the HTTPS port. The default collides with Paracosm's REST console port (9001): pick another |
http_listener_cn |
localhost |
the certificate's host name. It must equal the region's ExternalHostName (PARACOSM_EXTERNAL_HOST), or region creation fails with "main http cert CN doesn't match region External IP" |
http_listener_cert_path |
empty | the certificate, PKCS#12 (.p12) |
http_listener_cert_pass |
empty | its password |
What changes (RegionApplicationBase, OpenSimBase.CreateRegion): the HTTPS server becomes the main server, the
region's ServerURI, and so its capabilities, become https://<ExternalHostName>:<sslport>/, and the plain
server on http_listener_port keeps running beside it. The example config warns: "the main unsecure port will
still open for some services". The FreeSWITCH voice account URL, for one, is always built as http://.
Getting a certificate ([Startup], read in OpenSimBase.Initialize):
EnableCertConverter = truewithPemCertPublicKey(thefullchain.pem),PemCertPrivateKey(theprivkey.pem),outputCertNameandoutputCertPasswordconverts a Let's Encrypt (or other CA) PEM pair intobin/SSL/ssl/<outputCertName>.p12at every start. Pointhttp_listener_cert_pathat that file.EnableSelfsignedCertSupport = truewithCertFileName,CertHostName,CertHostIp,CertPassword(andCertRenewOnStartup) makes a self-signed certificate. The example config says some viewers accept only certificates signed by an official CA, and a self-signed one needs the viewer's debug settingNoVerifySSLCertset true.
Then point the login at the HTTPS port: [Const] BaseURL = https://${Const|BaseHostname} and PublicPort = the
SSL port (both build the login URI that get_grid_info returns), and give viewers https://<host>:<sslport>/ as
the login URI. In Paracosm these are overlay keys: put them in config/paracosm.ini (the PublicPort line there
reads ${PARACOSM_PORT} today) and run make_config.py --force. The HTTPS port must be opened as well as the plain
one.
Option 2: a reverse proxy in front of the login
A TLS reverse proxy (nginx, Caddy) on the same machine can serve https://world.example.org/ and forward to
http://127.0.0.1:9000. This protects what goes through it: the login and get_grid_info. It does not protect the
capabilities or the UDP circuit, because the login response sends the viewer straight to
http://<ExternalHostName>:9000/CAPS/... and to UDP 9000. Port 9000 (TCP and UDP) must still be open to the
viewers. Use it when the password exchange is what you want off the wire and the rest can stay in clear.
For the viewer to log in through the proxy, both the URI the person types and the one get_grid_info returns must
be the proxy's: set [GridInfoService] login = https://world.example.org/ in
config/StandaloneCommon-overlay.ini (upstream's value is ${Const|BaseURL}:${Const|PublicPort}/). The login
service rate-limits by client address ([LoginService] DOSMaxRequestsInTimeFrame, 5 per 10 s by default); behind
a proxy every login comes from 127.0.0.1, so set DOSAllowXForwardedForHeader = true there, and only there, and
have the proxy set X-Forwarded-For.
Caddy (it obtains and renews the certificate itself when the name resolves to this machine):
world.example.org {
reverse_proxy 127.0.0.1:9000
}
nginx:
server {
listen 443 ssl;
server_name world.example.org;
ssl_certificate /etc/letsencrypt/live/world.example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/world.example.org/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 64m;
}
}
Do not proxy the whole simulator port to the internet on 443 and close 9000: the viewer would still be told to use 9000 for everything after the login.
WorldBridge and the REST console
- WorldBridge stays on loopback. It binds 127.0.0.1 and forces any other
BindAddressback to it (a warning in the log). It has no TLS. Its token is full control of the world. The rule in this handbook is to keep the AI and its MCP server on the server machine and never tunnel, proxy or port-forward 9002. - If an AI must run elsewhere anyway, the only shape to consider is an encrypted, authenticated channel that ends
on the server's loopback: an SSH local forward (
ssh -L 9002:127.0.0.1:9002 you@server, so the SSH login is the gate), or a TLS proxy on the server that forwards to 127.0.0.1:9002 and admits only known client addresses or client certificates. The bearer token is still required on every request. Give that client a role token (WORLDBRIDGE_OBSERVER_TOKENorWORLDBRIDGE_BUILDER_TOKEN), never the gaia token. WorldBridge will log the proxy's address, not the client's. Untested. - The REST console is plain HTTP on all interfaces, and credentials cross in clear. Leave it off
(
PARACOSM_CONSOLE_USERempty) on an exposed server, or firewall its port and reach it through an SSH forward. Do not setconsole_port = 0: OpenSim then serves the console on the main simulator port (OpenSim.cs).
The firewall
| port | open? |
|---|---|
PARACOSM_PORT TCP (9000) |
yes, to the viewers that should reach the world |
PARACOSM_PORT UDP (9000) |
yes, the same |
| the HTTPS port (option 1) or 443 (option 2) | yes, if you use one |
PARACOSM_CONSOLE_PORT TCP (9001) |
never |
| WorldBridge TCP (9002) | never (it is loopback-bound anyway) |
| the viewer's LEAP bridge TCP (9004) | never (loopback-bound, on the viewer's machine) |
Windows (an elevated PowerShell):
New-NetFirewallRule -DisplayName "Paracosm TCP" -Direction Inbound -Protocol TCP -LocalPort 9000 -Action Allow
New-NetFirewallRule -DisplayName "Paracosm UDP" -Direction Inbound -Protocol UDP -LocalPort 9000 -Action Allow
Linux with ufw:
ufw allow 9000/tcp
ufw allow 9000/udp
ufw deny 9001/tcp
Behind a router, forward TCP and UDP 9000 (and the HTTPS port) to the server, and set PARACOSM_EXTERNAL_HOST to
the public name. A viewer on the server's own LAN then reaches the world by its public name only if the router
loops traffic back to itself (NAT loopback); the profile service does not depend on that, because Paracosm's
ProfileServiceURL is loopback.
Checklist for an exposed world
- The estate owner's and every account's password changed from anything in a template.
PARACOSM_CONSOLE_USERempty, or 9001 firewalled and reached over SSH.- WorldBridge untouched (loopback), the AI on the server machine.
- TLS for the login at least (option 1 or 2).
- Decide about the profile service's open JSON-RPC (config.md): keep it, or turn full profiles off.
[Permissions] region_manager_is_god = true(the overlay): make estate managers only of people you trust.- Backups running (backup.md).