Fangtooth API authentication changed?

Not sure if this is a bug in Fangtooth BETA, a change to the way it handles permissions for API keys, or something else, but I’m having trouble figuring it out. As in my last few topics, I’m working on getting my deploy-freenas script updated to use the new Websocket API, since I understand the REST API will be deprecated in 25.04. I have, I think, gotten the code to a decently-working state, and it works with 24.10 and 24.04. But when I test it against 25.04-BETA1, it seems to fail to authenticate. The code in question:

with Client(CONNECT_URI) as c:
    try:
        c.call("auth.login_with_api_key", API_KEY)
    except Exception as e:
        print(f"Failed to authenticate: {e}")
        exit(1)
    # Import the certificate
    args = {"name": cert_name, "certificate": full_chain, "privatekey": priv_key, "create_type": "CERTIFICATE_CREATE_IMPORTED"}
    cert = c.call("certificate.create", args, job=True)
    print("Certificate " + cert_name + " imported.\n")

The output under 24.10:

dan@tn-client:~$ .local/share/pipx/venvs/truenas-api-client/bin/python ./deploy_freenas.py
Certificate letsencrypt-2025-03-08-155147 imported.

The output under 25.04:

dan@tn-client:~$ .local/share/pipx/venvs/truenas-api-client/bin/python ./deploy_freenas.py -c deploy_config_2504
Traceback (most recent call last):
  File "/home/dan/./deploy_freenas.py", line 102, in <module>
    with Client(CONNECT_URI) as c:
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/truenas_api_client/__init__.py", line 118, in __exit__
    return self.__client.__exit__(exc_type, exc_val, exc_tb)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dan/./deploy_freenas.py", line 110, in <module>
    cert = c.call("certificate.create", args, job=True)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/truenas_api_client/legacy.py", line 386, in call
    self._jobs_subscribe()
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/truenas_api_client/legacy.py", line 378, in _jobs_subscribe
    self.subscribe('core.get_jobs', self._jobs_callback, sync=True)
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/truenas_api_client/legacy.py", line 459, in subscribe
    raise ValueError(payload['error'])
ValueError: {'error': 13, 'errname': 'EACCES', 'type': None, 'reason': 'Not authorized', 'trace': None, 'extra': None}

I get the same output under 25.04 using an API key for root or for truenas_admin. The system Audit gives the following information:

Credentials:
  Credentials: API_KEY
  Credentials Data:
    Username: root
    API Key:
      ID: 2
      Name: deploy_freenas2
Error: User not known to the underlying authentication module

I’m a little stumped here–do I need to do something to enable the API key once I create it?

Edit: under 24.10, if I give it a bogus API key, I get the same output. Which tells me two things:

  • The auth.login_with_api_key method doesn’t raise an error for an invalid API key - seems like a bug. It does return False, though, so still easy enough to test for it.
  • …and having determined that, it’s definitely the case that authentication using the API key is failing in Fangtooth.

I see in the docs that API keys in Fangtooth can’t be used via HTTP, and will be revoked if that’s done. I’m not accessing the API via http://; I’m using ws://, but I’m now seeing notifications in the UI that both keys have been revoked.

But that does seem to be the issue–changing the connection protocol to wss:// allows authentication to succeed, and from there the rest of the script runs without issues.

But that presents a bit of a chicken-and-the-egg problem. The API client (currently) has no option to ignore SSL validation errors. There’s been a PR outstanding since December to add this feature, but it has yet to be merged.

So, to use my script on Fangtooth, you need to be able to connect via WSS using a trusted, valid certificate. So:

  • You must have already imported a trusted certificate by some other means (likely through the UI)
  • That cert must be currently valid (i.e., not expired)
  • Whatever address you’re using to connect (i.e., connect_host in my script) must be named in that cert
    • This likely means local DNS entries to tie the FQDN to the IP address of your NAS.
1 Like

The API client has the ability to ignore cert errors. The midclt tool currently lacks it.

That’s not how I’d read that issue, but of course I could be misreading. So using with Client(uri=args.uri, verify_ssl=false) as c: would connect without verifying the cert?

with Client("wss://example.internal/api/current", verify_ssl=False) as c:
    # Authenticate using some pre-existing API key
    resp = c.call("auth.login_ex", {
        "mechanism": "API_KEY_PLAIN",
        "username": API_USERNAME,
        "api_key": API_KEY
    })

Will ignore errors related to self-signed certificate and connect to server. I gave sample auth in the Fangtooth discussion thread.

You can see us doing that in some of our internal CI assets: middleware/src/middlewared/middlewared/test/integration/utils/client.py at b4875cc8a734542a95f82dd51a0e643e229f0a4a · truenas/middleware · GitHub

1 Like

Which is the correct path? All the API docs I’ve seen say it’s /websocket, but the client README on GitHub (and your post) use /api/current. Which is it? Or are they equivalent?

And the verify_ssl parameter doesn’t seem to be having the expected effect. When I use this code with verify_ssl set to False:

args = PROTOCOL + "://" + CONNECT_HOST + "/websocket"
if VERIFY_SSL==False:
    args += ", verify_ssl=False"
logger.info(args)
with Client(args) as c:
    result=c.call("auth.login_with_api_key", API_KEY)

I get this output:

dan@tn-client:~$ .local/share/pipx/venvs/truenas-api-client/bin/python ./deploy_freenas.py -c deploy_config_2504
2025-03-09 18:35:20,817 - root - INFO - wss://nas2504.lan.2v6.in/websocket, verify_ssl=False
Traceback (most recent call last):
  File "/home/dan/./deploy_freenas.py", line 117, in <module>
    with Client(args) as c:
         ^^^^^^^^^^^^
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/truenas_api_client/__init__.py", line 108, in __init__
    self.__client = client_class(uri, reserved_ports, private_methods, py_exceptions, log_py_exceptions,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/truenas_api_client/__init__.py", line 446, in __init__
    self._ws.connect()
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/truenas_api_client/__init__.py", line 170, in connect
    self.socket = connect(self.url, sockopt, proxy_info(), None)[0]
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/websocket/_http.py", line 151, in connect
    sock = _ssl_socket(sock, options.sslopt, hostname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/websocket/_http.py", line 311, in _ssl_socket
    sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dan/.local/share/pipx/venvs/truenas-api-client/lib/python3.12/site-packages/websocket/_http.py", line 281, in _wrap_sni_socket
    return context.wrap_socket(
           ^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/ssl.py", line 455, in wrap_socket
    return self.sslsocket_class._create(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/ssl.py", line 1042, in _create
    self.do_handshake()
  File "/usr/lib/python3.12/ssl.py", line 1320, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1000)

I expect I’m doing something wrong in passing those arguments, but not sure what it is.

api/current starting in 24.10.