hatl/hasscontrol

unable to refresh entities

BGOtura opened this issue · 15 comments

Hi,

I have managed to connect to my HA using a long-lived access token.

My configuration in Connect IQ HA settings is:

Scenes:
Group: group.garmin

My config in HA helpers is as below:

image

When I do a "Refresh entities" I get on the watch:

Failed
Unknown error
code -300

It also seems to trigger this on HA´s log:

2023-03-18 11:25:43.732 ERROR (MainThread) [aiohttp.server] Error handling request Traceback (most recent call last): File "/usr/local/lib/python3.10/site-packages/aiohttp/web_protocol.py", line 334, in data_received messages, upgraded, tail = self._request_parser.feed_data(data) File "aiohttp/_http_parser.pyx", line 551, in aiohttp._http_parser.HttpParser.feed_data aiohttp.http_exceptions.BadStatusLine: 400, message="Bad status line 'Invalid method encountered'"

Any ideas?

hatl commented

hi
which HA version are you using?
i ran into the same issue a few days ago.
it disappeared after the update to 2023.3.5

I was using and old version from November 22, but have now updated to the latest 2023.3.5 and face the same issue.
image

I've had the same problem as author (Unknown error -300). I did a little bit of troubleshooting and it turns out it was caused by my self signed certificate even though cert is installed and working in my android (Using Caddy as reverse proxy with self signed cert). When I exposed HA instance to internet (using duckdns with LetsEncrypt certificate) everything started to work.

@hatl is there a way to make self signed cert working? I would prefer using VPN to access my HA instance rather than exposing it to the internet

I can now also confirm that. When I use my self signed certificate, that I imported to my android device the widget refuses to refresh the entities with error code 300.
I use my self signed certs with Nginx Proxy Manager.
When I expose my HA instance to the web with an let's encrypt certificate, it works.

It would be really great if self signed certificates would also work, because I don't want to expose my home assistant to the internet just to use the widget.

hatl commented

I'm using caddy to get the certificates and home assistant is only available in the LAN and the following Caddyfile:

home-assistant.mydomain.com {
    @internal {
        remote_ip 192.168.0.0/24
    }
    handle @internal {
      reverse_proxy 192.168.0.1:8123 {
        header_up X-Forwarded-Host {host}
      }
    }
    respond 403
}
F13 commented

I don't understand this resolution...I get a similar error using an internal-only TLS certificate. Is there any way to allow more general TLS connectivity? The connectivity works totally fine from my Android device.

@hatl : agree with @F13 . Just received my Venu 3 and wanted to your application but I'm getting the same error with a self-signed certificate. The API token works fine. I'm using the buit-in Nginx module.
According to your configuration file, the nginx default one seems pretty much the same:

core-nginx-proxy:/# cat /etc/nginx.conf
daemon off;
error_log stderr;
pid /var/run/nginx.pid;

events {
	worker_connections 1024;
}

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server_tokens off;

    server_names_hash_bucket_size 128;
	
    # intermediate configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    #include /data/cloudflare.conf;
	
    server {
        server_name _;
        listen 80 default_server;
        listen 443 ssl http2 default_server;
        ssl_reject_handshake on;
        return 444;
    }

    server {
        server_name ha.hysteresis.lan;

        # These shouldn't need to be changed
        listen 80;
        return 301 https://$host$request_uri;
    }

    server {
        server_name ha.local.lan;

        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;
        ssl_certificate /ssl/local.lan.crt;
        ssl_certificate_key /ssl/local.lan.key;

        # dhparams file
        ssl_dhparam /data/dhparams.pem;

        listen 443 ssl http2;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

        proxy_buffering off;

        #include /share/nginx_proxy_default*.conf;

        location / {
            proxy_pass http://homeassistant.local.hass.io:8123;
            proxy_set_header Host $http_host;
            proxy_redirect http:// https://;
            proxy_http_version 1.1;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header X-Forwarded-Host $http_host;
        }
    }

    #include /share/nginx_proxy/*.conf;
}

BTW, a couple of comments regarding the logging process:

To login, simply open the widget and trigger any scene. Shortly after, you will see a sign in request on your smartphone. Complete the sign in process on your phone and return to the watch.

I did create a scene and triggered it (using my laptop) after opening the widget (and click on login, but all I got is a black screen.

If you don't have any scenes, you can login by hold the menu button on your watch and logging in from them widget menu.

This is the process I followed and I get a blackscreen.

and FYI, I've added my self-signed certificate to my phone and the application (Companion) works well, as well as with Chrome.

Thanks!

hatl commented

the Garmin API doesn't allow the usage of self-signed certificates - for good reasons (security)

importing them on the phone only makes them available on the phone
they would have to be added on the watch (which is - as far as i know - currently not possible)

so having an "official" SSL certificate is currently a must-have
you can get them either by using the caddy config i provided or by buying a certificate - those have become quite cheap

thanks for the prompt response @hatl !

F13 commented

That is very unfortunate. There's no inherent security risk to allowing the user to trust certificates outside of the "globally trusted" certificate issuers; in fact, for advanced users, it can be just the opposite. In order to get an "official" SSL certificate, I would have to setup DNS resolution for a globally accessible domain that points to my infrastructure.

I understand most people are fine with this, but I disagree with device manufacturers' insistence in locking down SSL trust chains :)

Is there any option in the Garmin API to simply skip certificate validation? Exposing that option to users would allow the user to use whatever certificate they wanted.

F13 commented

Does the watch communicate directly with the services over HTTPS? It doesn't go through your connected mobile (Android/Apple)?

hatl commented

Is there any option in the Garmin API to simply skip certificate validation?

Unfortunately not - the documentation of the corresponding method (makeWebRequest) can be found here: https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html#makeWebRequest-instance_function

I would have to setup DNS resolution for a globally accessible domain that points to my infrastructure

yes.
you could use Duck DNS and Dnsmasq
there are Home Assistant add-ons for both
Duck DNS can be configured to use Let's Encrypt SSL certificates

Does the watch communicate directly with the services over HTTPS?

Its being "tunneled" trhough the bluetooth connection to the phone, but the actual connection is established between the Garmin device and Home Assistant. This is why adding the SSL certificates on the phone doesn't help.

@F13 agree for the security, and yeah, it's unfortunate.
@hatl thanks for the doc.

As a workaround, it could be possible to retrieve a genuine LE certificate and use a local DNS to fake the IP address.

EDIT: someone apparently did it by installing a root certificate on his iPhone: https://forums.garmin.com/developer/connect-iq/f/discussion/291012/makewebrequest-for-internal-networks

F13 commented

My CA cert is fully installed and trusted on my Android already. It doesn't appear that Garmin Connect uses the Android certificates.

I finally managed to find a way using the following steps:

  1. request a certificate from let's encrypt (e.g ha.example.com)
  2. add a A entry on your DNS provider to point to your reverse proxy internal IP
  3. copy the certificate to a server handling the reverse proxy
  4. add this proxy to the trusted_proxies in HA
  5. configure the app to point to your domain
  6. (optional) add a list rebind_domain in case you get a DNS-rebind attack detected with OpenWRT

It's probably possible to bypass the reverse to point to HA directly (Nginx) but the certificate provided by LE doesn't seem to be recognize by Nginx (most probably a format issue)

EDIT: I found a way to add another nginx conf file to handle the request for the new domain following this guide https://community.home-assistant.io/t/using-nginx-ssl-proxy-to-forward-different-domains-to-different-services/347342/4
Now HA handles the request coming from ha.example.com instead of using another server. Last step would be to automate the key copy between the 2 servers but it's pretty easy to deal with.

Anyway, thanks for your app @hatl !