Python websocket on interactive exec

Hello!

I trying execute a command in a instance by LXD API /1.0/instances/{name}/exec in interactive mode, and getting two sockets - bi-direction and control as described in the documentation:

In interactive mode, a single bi-directional websocket is used for stdin and stdout/stderr.

An additional "control" socket is always added on top which can be used for out of band communication with LXD.
This allows sending signals and window sizing information through.

Below it my Python code. Copy source code to new a file and save it. Then replace value of instance_name to your an instance name and run the code:

import os
from requests import Session, Response
import requests_unixsocket
from urllib import parse
from shlex import shlex


unix_socket = '/var/lib/lxd/unix.socket'
instance_name = 'pgsql'


# ------------------------------------ Certs default paths ---------------
lxc_conf_path = os.path.expanduser('~/.config/lxc/')
cert = os.path.join(lxc_conf_path, 'client.crt')
key = os.path.join(lxc_conf_path, 'client.key')
certs = (cert, key,)
print('Certs:', certs)
# -------------------------------------------------------------------------

endpoint = requests_unixsocket.DEFAULT_SCHEME + parse.quote(unix_socket, safe='')
session = requests_unixsocket.Session()

json={
    "command": tuple(shlex('pwd')),
    "environment": None,
    "wait-for-websocket": True,
    "interactive": True,
    "user": None,
    "group": None,
    "cwd": None,
}

response = session.post(f'{endpoint}/1.0/instances/{instance_name}/exec', json=json)

print('URI:', response.request.method, response.request.url)
if isinstance(response.request.body, bytes):
    print('BODY:', response.request.body.decode('utf-8'))

metadata = response.json()['metadata']
operation_id = metadata['id']
fds = metadata['metadata']['fds']['0']
fds_ctrl = metadata['metadata']['fds']['control']

print(f'{operation_id=}')
print(f'{fds=}')
print(f'{fds_ctrl=}')

uri = f'wss://127.0.0.1:8443/1.0/operations/{operation_id}/websocket?secret={fds}'
uri_ctrl = f'wss://127.0.0.1:8443/1.0/operations/{operation_id}/websocket?secret={fds_ctrl}'
print(uri)
print(uri_ctrl)

Output:

Certs: ('/home/dv/.config/lxc/client.crt', '/home/dv/.config/lxc/client.key')
URI: POST http+unix://%2Fvar%2Flib%2Flxd%2Funix.socket/1.0/instances/pgsql/exec
BODY: {"command": ["pwd"], "environment": null, "wait-for-websocket": true, "interactive": true, "user": null, "group": null, "cwd": null}
operation_id='f53f3e3e-9ddf-479c-907b-11c8ed25b50a'
fds='ea29b9358cb351ad6c58a25e22544e77f763c1052734c9797e32b8e28417b018'
fds_ctrl='4d682de4ef151ab30ceca788009ff46efd563a96efdf93769b3915a843ebc4b0'
wss://127.0.0.1:8443/1.0/operations/f53f3e3e-9ddf-479c-907b-11c8ed25b50a/websocket?secret=ea29b9358cb351ad6c58a25e22544e77f763c1052734c9797e32b8e28417b018
wss://127.0.0.1:8443/1.0/operations/f53f3e3e-9ddf-479c-907b-11c8ed25b50a/websocket?secret=4d682de4ef151ab30ceca788009ff46efd563a96efdf93769b3915a843ebc4b0

I got two URL (URI) which are shown in the console output above to connect websokets by documentation /1.0/operations/{id}/websocket or need to use untrusted API link? /1.0/operations/{id}/websocket?public

What to do next? How to access a socket and get stdout, stderr, stdin from running a command?

I tried to get to access with websockets but server reject all connection by timeout and I don’t get anything.

Next example for websokets, for addition to the python code above:

import asyncio
# from websockets.sync.client import ClientConnection, connect, unix_connect
import websockets
import ssl


ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_default_certs()
ssl_context.load_cert_chain(certfile=certs[0], keyfile=certs[1])
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE


async def message():
    async with websockets.connect(uri=uri, ssl=ssl_context) as socket:
        # msg = input("Write your message to the server: ")
        await socket.send('msg')
        print(await socket.recv())

asyncio.get_event_loop().run_until_complete(message())

Before running a code above, I switched LXD to mode listening on 127.0.0.1:8443:

lxc config set core.https_address 127.0.0.1:8443

And I get an error after running the code:

websockets.exceptions.ConnectionClosedError: no close frame received or sent

I do not know what to do next…

It turned out that I had an issue with access via certificates (Trouble auntentification in LXD and SSL/TLS keys) to LXD, so I’m closing the question