Merge pull request #7756 from wizrds/feat/secure-ws-conn

Support SSL in WebSocket connection
This commit is contained in:
Matthias 2022-11-22 19:18:16 +01:00 committed by GitHub
commit 7785c91c5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 2 deletions

View File

@ -21,6 +21,7 @@ Enable subscribing to an instance by adding the `external_message_consumer` sect
"name": "default", // This can be any name you'd like, default is "default" "name": "default", // This can be any name you'd like, default is "default"
"host": "127.0.0.1", // The host from your producer's api_server config "host": "127.0.0.1", // The host from your producer's api_server config
"port": 8080, // The port from your producer's api_server config "port": 8080, // The port from your producer's api_server config
"secure": false, // Use a secure websockets connection, default false
"ws_token": "sercet_Ws_t0ken" // The ws_token from your producer's api_server config "ws_token": "sercet_Ws_t0ken" // The ws_token from your producer's api_server config
} }
], ],
@ -42,6 +43,7 @@ Enable subscribing to an instance by adding the `external_message_consumer` sect
| `producers.name` | **Required.** Name of this producer. This name must be used in calls to `get_producer_pairs()` and `get_producer_df()` if more than one producer is used.<br> **Datatype:** string | `producers.name` | **Required.** Name of this producer. This name must be used in calls to `get_producer_pairs()` and `get_producer_df()` if more than one producer is used.<br> **Datatype:** string
| `producers.host` | **Required.** The hostname or IP address from your producer.<br> **Datatype:** string | `producers.host` | **Required.** The hostname or IP address from your producer.<br> **Datatype:** string
| `producers.port` | **Required.** The port matching the above host.<br> **Datatype:** string | `producers.port` | **Required.** The port matching the above host.<br> **Datatype:** string
| `producers.secure` | **Optional.** Use ssl in websockets connection. Default False.<br> **Datatype:** string
| `producers.ws_token` | **Required.** `ws_token` as configured on the producer.<br> **Datatype:** string | `producers.ws_token` | **Required.** `ws_token` as configured on the producer.<br> **Datatype:** string
| | **Optional settings** | | **Optional settings**
| `wait_timeout` | Timeout until we ping again if no message is received. <br>*Defaults to `300`.*<br> **Datatype:** Integer - in seconds. | `wait_timeout` | Timeout until we ping again if no message is received. <br>*Defaults to `300`.*<br> **Datatype:** Integer - in seconds.

View File

@ -389,6 +389,44 @@ Now anytime those types of RPC messages are sent in the bot, you will receive th
} }
``` ```
#### Reverse Proxy setup
When using [Nginx](https://nginx.org/en/docs/), the following configuration is required for WebSockets to work (Note this configuration is incomplete, it's missing some information and can not be used as is):
Please make sure to replace `<freqtrade_listen_ip>` (and the subsequent port) with the IP and Port matching your configuration/setup.
```
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
#...
server {
#...
location / {
proxy_http_version 1.1;
proxy_pass http://<freqtrade_listen_ip>:8080;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
}
}
}
```
To properly configure your reverse proxy (securely), please consult it's documentation for proxying websockets.
- **Traefik**: Traefik supports websockets out of the box, see the [documentation](https://doc.traefik.io/traefik/)
- **Caddy**: Caddy v2 supports websockets out of the box, see the [documentation](https://caddyserver.com/docs/v2-upgrade#proxy)
!!! Tip "SSL certificates"
You can use tools like certbot to setup ssl certificates to access your bot's UI through encrypted connection by using any fo the above reverse proxies.
While this will protect your data in transit, we do not recommend to run the freqtrade API outside of your private network (VPN, SSH tunnel).
### OpenAPI interface ### OpenAPI interface
To enable the builtin openAPI interface (Swagger UI), specify `"enable_openapi": true` in the api_server configuration. To enable the builtin openAPI interface (Swagger UI), specify `"enable_openapi": true` in the api_server configuration.

View File

@ -512,6 +512,7 @@ CONF_SCHEMA = {
'minimum': 0, 'minimum': 0,
'maximum': 65535 'maximum': 65535
}, },
'secure': {'type': 'boolean', 'default': False},
'ws_token': {'type': 'string'}, 'ws_token': {'type': 'string'},
}, },
'required': ['name', 'host', 'ws_token'] 'required': ['name', 'host', 'ws_token']

View File

@ -31,6 +31,7 @@ class Producer(TypedDict):
name: str name: str
host: str host: str
port: int port: int
secure: bool
ws_token: str ws_token: str
@ -180,7 +181,8 @@ class ExternalMessageConsumer:
host, port = producer['host'], producer['port'] host, port = producer['host'], producer['port']
token = producer['ws_token'] token = producer['ws_token']
name = producer['name'] name = producer['name']
ws_url = f"ws://{host}:{port}/api/v1/message/ws?token={token}" scheme = 'wss' if producer.get('secure', False) else 'ws'
ws_url = f"{scheme}://{host}:{port}/api/v1/message/ws?token={token}"
# This will raise InvalidURI if the url is bad # This will raise InvalidURI if the url is bad
async with websockets.connect( async with websockets.connect(

View File

@ -199,6 +199,7 @@ async def create_client(
host, host,
port, port,
token, token,
scheme='ws',
name='default', name='default',
protocol=ClientProtocol(), protocol=ClientProtocol(),
sleep_time=10, sleep_time=10,
@ -211,13 +212,14 @@ async def create_client(
:param host: The host :param host: The host
:param port: The port :param port: The port
:param token: The websocket auth token :param token: The websocket auth token
:param scheme: `ws` for most connections, `wss` for ssl
:param name: The name of the producer :param name: The name of the producer
:param **kwargs: Any extra kwargs passed to websockets.connect :param **kwargs: Any extra kwargs passed to websockets.connect
""" """
while 1: while 1:
try: try:
websocket_url = f"ws://{host}:{port}/api/v1/message/ws?token={token}" websocket_url = f"{scheme}://{host}:{port}/api/v1/message/ws?token={token}"
logger.info(f"Attempting to connect to {name} @ {host}:{port}") logger.info(f"Attempting to connect to {name} @ {host}:{port}")
async with websockets.connect(websocket_url, **kwargs) as ws: async with websockets.connect(websocket_url, **kwargs) as ws:
@ -304,6 +306,7 @@ async def _main(args):
producer['host'], producer['host'],
producer['port'], producer['port'],
producer['ws_token'], producer['ws_token'],
'wss' if producer.get('secure', False) else 'ws',
producer['name'], producer['name'],
sleep_time=sleep_time, sleep_time=sleep_time,
ping_timeout=ping_timeout, ping_timeout=ping_timeout,