-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace websocat on target machine with Python script
We want to minimize the requirements on the target machines. websocat is a great tool, but not packaged anywhere, and downloading straight from GitHub isn't appropriate for production. Thus replace this with a simple Python connector which uses the builtin asyncio module, and the external websockets one. To avoid having to install the latter, build a pyz application bundle. We can then also test/CI this before shipping. The connector only supports basic auth for now. Eventually it will need to do TLS client cert auth, but we'll get to that when we have the necessary test harness. Stop putting the connector into the webconsoleserver image. Eventually it will be sent to the target machine through an Ansible script. Thus for testing, send it to the container as a volume instead, which from the point of the connector amounts to the same thing. Fixes #41
- Loading branch information
1 parent
2a62a1e
commit dc60123
Showing
7 changed files
with
114 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
[flake8] | ||
max-line-length = 120 | ||
exclude = tmp/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
3scale/certs | ||
__pycache__ | ||
*.pyz | ||
3scale/certs | ||
tmp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
# Test container image that represents a remote managed server which connects | ||
# to consoledot. This illustrates the minimum dependencies. | ||
FROM debian:bookworm | ||
|
||
RUN apt-get update && \ | ||
apt-get install -y cockpit-bridge cockpit-system curl procps && \ | ||
apt-get install -y cockpit-bridge cockpit-system ca-certificates python3 && \ | ||
apt-get clean | ||
|
||
RUN curl -L -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/download/v1.10.0/websocat.x86_64-unknown-linux-musl && \ | ||
chmod a+x /usr/local/bin/websocat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import asyncio | ||
import asyncio.subprocess | ||
import base64 | ||
import logging | ||
import ssl | ||
|
||
import websockets | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
BRIDGE = 'cockpit-bridge' | ||
|
||
|
||
def parse_args(): | ||
parser = argparse.ArgumentParser(description='Connect cockpit-bridge to a websocket URL') | ||
parser.add_argument('-d', '--debug', action='store_true', help='Enable debug logging') | ||
parser.add_argument('--extra-ca-cert', help='Additional CA certificate') | ||
parser.add_argument('-k', '--insecure', action='store_true', | ||
help='Accept invalid certificates and hostnames while connecting to TLS') | ||
parser.add_argument('--basic-auth', metavar="USER:PASSWORD", | ||
help='Authenticate with user/password (for testing)') | ||
parser.add_argument('url', help='Connect to this ws:// or wss:// URL') | ||
return parser.parse_args() | ||
|
||
|
||
async def ws2bridge(ws, bridge_input): | ||
try: | ||
async for message in ws: | ||
bridge_input.write(message) | ||
logger.debug('ws -> bridge: %s', message) | ||
await bridge_input.drain() | ||
except websockets.exceptions.ConnectionClosedError as e: | ||
logger.debug('ws2bridge: websocket connection got closed: %s', e) | ||
return | ||
|
||
|
||
async def bridge2ws(bridge_output, ws): | ||
while True: | ||
message = await bridge_output.read(4096) | ||
if not message: | ||
break | ||
logger.debug('bridge -> ws: %s', message) | ||
await ws.send(message) | ||
|
||
|
||
async def bridge(args): | ||
headers = {} | ||
ssl_context = ssl.create_default_context() | ||
|
||
if args.basic_auth: | ||
headers['Authorization'] = 'Basic ' + base64.b64encode(args.basic_auth.encode()).decode() | ||
|
||
if args.extra_ca_cert: | ||
ssl_context.load_verify_locations(args.extra_ca_cert) | ||
|
||
if args.insecure: | ||
ssl_context.check_hostname = False | ||
ssl_context.verify_mode = ssl.CERT_NONE | ||
|
||
async with websockets.connect(args.url, extra_headers=headers, ssl=ssl_context) as websocket: | ||
p_bridge = await asyncio.create_subprocess_exec( | ||
BRIDGE, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE) | ||
logger.debug('Started %s: pid %i', BRIDGE, p_bridge.pid) | ||
|
||
ws2bridge_task = asyncio.create_task(ws2bridge(websocket, p_bridge.stdin)) | ||
bridge2ws_task = asyncio.create_task(bridge2ws(p_bridge.stdout, websocket)) | ||
_done, pending = await asyncio.wait([ws2bridge_task, bridge2ws_task], | ||
return_when=asyncio.FIRST_COMPLETED) | ||
for task in pending: | ||
task.cancel() | ||
|
||
|
||
def main(): | ||
args = parse_args() | ||
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) | ||
asyncio.run(bridge(args)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters