supervisor icon indicating copy to clipboard operation
supervisor copied to clipboard

SUPERVISOR_TOKEN for addon websocket connection isn't working

Open dneprojects opened this issue 2 years ago • 7 comments

Describe the issue you are experiencing

I have an addon written in Python code, which opens a websocket connection to HA in order to call a service. In the addon I get the token by the command 'os.getenv("SUPERVISOR_TOKEN")'. With that token I connect: 'websockets.connect("ws://supervisor/core/websocket",extra_headers={"Authorization": f"Bearer {bearer_token}"}' . In the next strep, the server sends a message '{"type":"auth_required","ha_version":"2024.4.3"}', my addon responds with '{"type": "auth", "access_token": "788dba363765f...184e6f8c21acc2415dc43"}' with the SUPERVISOR_TOKEN. The server replys '{"type":"auth_invalid","message":"Invalid access"}' and closes the connection.

I think, the procedure is OK, but the token is wrong. There are more strange things:

  1. If I run exactly that code in an addon which has been generated internally from the local folder /addons/<slug_name>, it runs fine.
  2. I have two Raspis, a Raspi 4B and a Raspi 5. On the 4B the code runs, if I pick the SUPERVISOR_TOKEN from the terminal addon with a 'printenv SUPERVISOR_TOKEN' and use that instead of the environment variable got inside of the addon. On the Pi 5 this workaround does not help.

It seems as if the internal handling of the tokens is out of order...

What type of installation are you running?

Home Assistant OS

Which operating system are you running on?

Home Assistant Operating System

Steps to reproduce the issue

  1. In the addon:
    'supervisor_token = os.getenv("SUPERVISOR_TOKEN")'
  2. 'websockets.connect("ws://supervisor/core/websocket",extra_headers={"Authorization": f"Bearer {supervisor_token }"}'
  3. 'self.websck.send(json.dumps({"type": "auth", "access_token": supervisor_token})) ...

Anything in the Supervisor logs that might be useful for us?

2024-04-19 12:35:25.626 INFO (MainThread) [supervisor.api.proxy] Home Assistant WebSocket API request initialize
2024-04-19 12:35:25.630 WARNING (MainThread) [supervisor.api.proxy] Unauthorized WebSocket access!

System Health information

This is taken from my addon logs: 2024-04-19 12:38:52 DEBUG websockets.client: = connection is CONNECTING 2024-04-19 12:38:52 DEBUG websockets.client: > GET /core/websocket HTTP/1.1 2024-04-19 12:38:52 DEBUG websockets.client: > Host: supervisor 2024-04-19 12:38:52 DEBUG websockets.client: > Upgrade: websocket 2024-04-19 12:38:52 DEBUG websockets.client: > Connection: Upgrade 2024-04-19 12:38:52 DEBUG websockets.client: > Sec-WebSocket-Key: 6mWZuuqIfqnVlzrBMRem3Q== 2024-04-19 12:38:52 DEBUG websockets.client: > Sec-WebSocket-Version: 13 2024-04-19 12:38:52 DEBUG websockets.client: > Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits 2024-04-19 12:38:52 DEBUG websockets.client: > Authorization: Bearer 80b41982adf8b6d08d983120a4445d11bec099e5e6f0d3c1e4789d6808eea627daf952ad03789680e1fa24faf77abcadf62191922a3bff5a 2024-04-19 12:38:52 DEBUG websockets.client: > User-Agent: Python/3.11 websockets/12.0 2024-04-19 12:38:52 DEBUG websockets.client: < HTTP/1.1 101 Switching Protocols 2024-04-19 12:38:52 DEBUG websockets.client: < Upgrade: websocket 2024-04-19 12:38:52 DEBUG websockets.client: < Connection: upgrade 2024-04-19 12:38:52 DEBUG websockets.client: < Sec-WebSocket-Accept: +F5BKQQ50hTwu+2j0ImYx4iKQ+M= 2024-04-19 12:38:52 DEBUG websockets.client: < Sec-WebSocket-Extensions: permessage-deflate 2024-04-19 12:38:52 DEBUG websockets.client: < Date: Fri, 19 Apr 2024 10:38:52 GMT 2024-04-19 12:38:52 DEBUG websockets.client: < Server: Python/3.12 aiohttp/3.9.3 2024-04-19 12:38:52 DEBUG websockets.client: = connection is OPEN 2024-04-19 12:38:52 DEBUG websockets.client: < TEXT '{"type":"auth_required","ha_version":"2024.4.3"}' [48 bytes] 2024-04-19 12:38:52 DEBUG websockets.client: > TEXT '{"type": "auth", "access_token": "80b41982adf8b...abcadf62191922a3bff5a"}' [148 bytes] 2024-04-19 12:38:52 DEBUG websockets.client: < TEXT '{"type":"auth_invalid","message":"Invalid access"}' [50 bytes] 2024-04-19 12:38:52 DEBUG websockets.client: < CLOSE 1000 (OK) [2 bytes] 2024-04-19 12:38:52 DEBUG websockets.client: = connection is CLOSING 2024-04-19 12:38:52 DEBUG websockets.client: > CLOSE 1000 (OK) [2 bytes] 2024-04-19 12:38:52 DEBUG websockets.client: = connection is CLOSED

Supervisor diagnostics

config_entry-hassio-393b944bf81b49e9dbad4d65c5ee4e8f.json

Additional information

HA Core 2024.4.3, HA Supervisor 2024.04.0, HAOS 12.2

dneprojects avatar Apr 19 '24 10:04 dneprojects

I found the reason for my "strange things no 2": On the Pi5 I used the "Terminal and SSH" addon, on the Pi4 the "Advanced SSH and Web Terminal" addon. I switched to the latter on my Pi5 and got the working token typing 'printenv SUPERVISOR_TOKEN'.

However, the behavior is weird. Under which circumstance supervisor tokens are generated?

dneprojects avatar Apr 19 '24 11:04 dneprojects

I can't seem to replicate your error, how exactly have you implemented the method for websocket connections?

SilverBrother avatar Apr 23 '24 08:04 SilverBrother

Hi Erik

Thanks for looking at the issue.

I initialize my handler with: self.auth_token: str | None = os.getenv("SUPERVISOR_TOKEN") self._uri = "ws://supervisor/core/websocket"

Later I open the websocket with self.websck = await websockets.connect( self._uri, extra_headers={ "Authorization": f"Bearer {self.auth_token}", "Content-Type": "application/json", }, open_timeout=1, )

The next step is resp = await self.websck.recv()

if json.loads(resp)["type"] == "auth_required": try: msg = WEBSOCK_MSG.auth_msg msg["access_token"] = self.auth_token await self.websck.send(json.dumps(msg)) resp = await self.websck.recv() self.logger.info( f"Websocket connecting to {self._uri}, response: {resp}" ) if json.loads(resp)["type"] == "auth_invalid": self.logger.error(f"Websocket authentification failed: {json. loads(resp)['message']}") await self.close_websocket() return False except Exception as err_msg: self.logger.error(f"Websocket authentification failed: {err_msg}") await self.close_websocket() self.token_ok = False return False

If this step fails (never, if I run it als a locally built addon, always, if installed from ghcr.io), I pick the SUPERVISOR_TOKEN from file, which I got from the 'Advanced SSH & Web Terminal'-addon with print_env SUPERVISOR_TOKEN > def_token.def. This one works fine. It seems to me as if the token generated for my addon isn't the one against the authentification procedure checks.

Do you need more details?

Here my config.yaml:

name: "Smart Hub" description: "Habitron Smart Hub as Home Assistant Add-on" version: 1.1.9 slug: "smart_hub" arch:

  • aarch64 ports: 7777/udp: 7777 startup: services init: false full_access: false hassio_api: true host_network: true map: [addon_config:rw] devices: ["dev/serial0"] uart: true devicetree: true ingress: true panel_title: Habitron Smart Hub panel_icon: hbt:habitron-logo panel_admin: true image: "ghcr.io/dneprojects/smart_hub/{arch}"

Thanks for helping me! If you have an idea, which I can check, please don't hesitate to contact me.

Best regards, Dietmar

Am Di., 23. Apr. 2024 um 10:32 Uhr schrieb Erik @.***>:

I can't seem to replicate your error, how exactly have you implemented the method for websocket connections?

— Reply to this email directly, view it on GitHub https://github.com/home-assistant/supervisor/issues/5028#issuecomment-2071727802, or unsubscribe https://github.com/notifications/unsubscribe-auth/A5VKTZXHCZ7OP73IHO7KYFLY6YMAHAVCNFSM6AAAAABGO6FLGGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANZRG4ZDOOBQGI . You are receiving this because you authored the thread.Message ID: @.***>

dneprojects avatar Apr 23 '24 10:04 dneprojects

I forgot to include the definition of the auth_msg: auth_msg = {"type": "auth", "access_token": ""}

Thanks, Dietmar

Am Di., 23. Apr. 2024 um 12:09 Uhr schrieb dne.projects < @.***>:

Hi Erik

Thanks for looking at the issue.

I initialize my handler with: self.auth_token: str | None = os.getenv("SUPERVISOR_TOKEN") self._uri = "ws://supervisor/core/websocket"

Later I open the websocket with self.websck = await websockets.connect( self._uri, extra_headers={ "Authorization": f"Bearer {self.auth_token}", "Content-Type": "application/json", }, open_timeout=1, )

The next step is resp = await self.websck.recv()

if json.loads(resp)["type"] == "auth_required": try: msg = WEBSOCK_MSG.auth_msg msg["access_token"] = self.auth_token await self.websck.send(json.dumps(msg)) resp = await self.websck.recv() self.logger.info( f"Websocket connecting to {self._uri}, response: {resp}" ) if json.loads(resp)["type"] == "auth_invalid": self.logger.error(f"Websocket authentification failed: {json. loads(resp)['message']}") await self.close_websocket() return False except Exception as err_msg: self.logger.error(f"Websocket authentification failed: {err_msg}") await self.close_websocket() self.token_ok = False return False

If this step fails (never, if I run it als a locally built addon, always, if installed from ghcr.io), I pick the SUPERVISOR_TOKEN from file, which I got from the 'Advanced SSH & Web Terminal'-addon with print_env SUPERVISOR_TOKEN > def_token.def. This one works fine. It seems to me as if the token generated for my addon isn't the one against the authentification procedure checks.

Do you need more details?

Here my config.yaml:

name: "Smart Hub" description: "Habitron Smart Hub as Home Assistant Add-on" version: 1.1.9 slug: "smart_hub" arch:

  • aarch64 ports: 7777/udp: 7777 startup: services init: false full_access: false hassio_api: true host_network: true map: [addon_config:rw] devices: ["dev/serial0"] uart: true devicetree: true ingress: true panel_title: Habitron Smart Hub panel_icon: hbt:habitron-logo panel_admin: true image: "ghcr.io/dneprojects/smart_hub/{arch} http://ghcr.io/dneprojects/smart_hub/%7Barch%7D"

Thanks for helping me! If you have an idea, which I can check, please don't hesitate to contact me.

Best regards, Dietmar

Am Di., 23. Apr. 2024 um 10:32 Uhr schrieb Erik @.***

:

I can't seem to replicate your error, how exactly have you implemented the method for websocket connections?

— Reply to this email directly, view it on GitHub https://github.com/home-assistant/supervisor/issues/5028#issuecomment-2071727802, or unsubscribe https://github.com/notifications/unsubscribe-auth/A5VKTZXHCZ7OP73IHO7KYFLY6YMAHAVCNFSM6AAAAABGO6FLGGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANZRG4ZDOOBQGI . You are receiving this because you authored the thread.Message ID: @.***>

dneprojects avatar Apr 23 '24 10:04 dneprojects

I'm seeing this too:

config.yaml:

name: blah
version: 0.0.12
slug: blah
description: blah
arch:
- amd64
url: blah
map:
- config:rw
init: false
homeassistant_api: true

Dockerfile:

ARG BUILD_FROM
FROM $BUILD_FROM
WORKDIR /opt/addon/
COPY run.sh ./
CMD ["./run.sh"]

run.sh:

echo "Token: $SUPERVISOR_TOKEN"

Log:

System: Home Assistant OS 12.2  (amd64 / qemux86-64)
 Home Assistant Core: 2024.4.4
 Home Assistant Supervisor: 2024.04.4
-----------------------------------------------------------
 Please, share the above information when looking for help
 or support in, e.g., GitHub, forums or the Discord chat.
-----------------------------------------------------------
s6-rc: info: service base-addon-banner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service base-addon-log-level: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service base-addon-log-level successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
Token: 

grahamj avatar May 05 '24 21:05 grahamj

I've spent the better part of today trying to get this working. 31 tries in and still no dice. I've tried every relevant config.yaml option, older base images, rebooting, you name it. No matter what I do I never see this env var in my container.

I have the ssh addon and I can echo the token there so other addons seem to get it, I just can't figure out why I don't. The main difference is mine is local in /addons but not sure why that would matter. It shows up in the UI, updating version in config.yaml and looking for updates in the UI shows it, it installs and runs fine, everything seems normal. But no token.

Interestingly, when I grab the token from the ssh addon and use it in my code to connect to websockets I get an auth_invalid response. Had to resort to using a long-lived as a workaround for now.

/sad panda

grahamj avatar May 06 '24 00:05 grahamj

Hi

First, you need to add the line

hassio_api: true

to your config.yaml.

I'm not sure whether this is needed in your case. In order to get access to the token inside the container, the first line in the shell script should be

#!/usr/bin/with-contenv sh

I hope this helps... Best wishes, Dietmar

grahamj @.***> schrieb am Mo., 6. Mai 2024, 02:10:

I've spent the better part of today trying to get this working. 31 tries in and still no dice. I've tried every relevant config.yaml option, older base images, rebooting, you name it. No matter what I do I never see this env var in my container.

I have the ssh addon and I can echo the token there so other addons seem to get it, I just can't figure out why I don't. The main difference is mine is local in /addons but not sure why that would matter. It shows up in the UI, updating version in config.yaml and looking for updates in the UI shows it, it installs and runs fine, everything seems normal. But no token.

Interestingly, when I grab the token from the ssh addon and use it in my code to connect to websockets I get an auth_invalid response. Had to resort to using a long-lived as a workaround for now.

/sad panda

— Reply to this email directly, view it on GitHub https://github.com/home-assistant/supervisor/issues/5028#issuecomment-2095010054, or unsubscribe https://github.com/notifications/unsubscribe-auth/A5VKTZTLIJNRAXYLE4EG5ALZA3DALAVCNFSM6AAAAABGO6FLGGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOJVGAYTAMBVGQ . You are receiving this because you authored the thread.Message ID: @.***>

dneprojects avatar May 06 '24 08:05 dneprojects

Hi First, you need to add the line hassio_api: true to your config.yaml. I'm not sure whether this is needed in your case. In order to get access to the token inside the container, the first line in the shell script should be #!/usr/bin/with-contenv sh I hope this helps... Best wishes, Dietmar

OMG the shebang worked, thank you so much! I had it in there at one point before but something else must have been wrong.

Wow the docs really need to make more clear how critical that is!

grahamj avatar May 08 '24 03:05 grahamj

You are right, the documentation is really bad!

I faced another problem after I solved that one you had. The code seems to be very sensitive to timings:

self.websck = await websockets.connect(
    self._uri,
    extra_headers={"Authorization": f"Bearer {auth_token}"},
    open_timeout=4,
)

await asyncio.sleep(1)  # needed, otherwise the following auth fails
resp = await self.websck.recv()

if json.loads(resp)["type"] == "auth_required":
  await self.websck.send(json.dumps({"type": "auth", "access_token": auth_token}))

I don't understand, why, but if I used the supervisor_token from the "Advanced SSH & Web terminal"-addon, the code worked without the delay and also with a timeout of 1s. The timeout value was also dependent on the type of Raspi I was using: on a Pi4 I succeeded with 2s, on a Pi5 I needed 3s.

This is why I feel, the code must be buggy!

dneprojects avatar May 08 '24 09:05 dneprojects

yeah you should not need to sleep for sure. My Python is pretty rusty (my addon is actually a shim to write automations in nodejs!) but I can tell you that in Node I don't have any issues connecting to websockets 🤷‍♂️

grahamj avatar May 08 '24 12:05 grahamj

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Jun 07 '24 13:06 github-actions[bot]