FreeNas API v2: Authenticate with Generated Token

monkgamer

Cadet
Joined
Jun 8, 2020
Messages
1
Anybody know how to use the Generated Token to Authenticate on FreeNas v2 API? I could not find a way to supply a Token with the SwaggerUI so I was unable to test any of those APIs using those. The token looks like an OAuth2 token: Eg: `j9clUTu9UUq1VEDdJD0UnfqA7WvuMb9ZfsBMaCXubkD9ekly8Inan6jwAAlCvXzY` which is normally used with a `Bearer` keyword, but i keep running into 401 unauthorized.

As an example, I am trying to access `http://<freenashostip>/api/v2.0/user`. If I use root/password with Basic auth, it works. And looks like Basic Auth only supports root user. So I am trying to use a limited time root's Token for automation around it.

I am sure I am doing something wrong. Hope somebody here can point me in the right direction. I am using FreeNas for the first time and apologies for not being familiar with the tradition of this community.
 

kevinjj

Dabbler
Joined
May 4, 2020
Messages
14
Hello,
I am encountering the same error as you.
Like you, I don't know if this is a bug or a bad use on my part.
 

JCL

Dabbler
Joined
Nov 18, 2015
Messages
14
Hi,
I am also struggling with api v2.0. I found a way to do different actions with curl command. Then to answer your question about authentification, you can do the following :

export key=your_api_key

(exemple from monkgamer user)

export key=j9clUTu9UUq1VEDdJD0UnfqA7WvuMb9ZfsBMaCXubkD9ekly8Inan6jwAAlCvXzY

Then use following command :

curl -s -X GET "http://<freenashostip>//api/v2.0/user" -H "Authorization: Bearer $key"

JC
 

Jfs

Dabbler
Joined
Feb 23, 2023
Messages
23
Thanks, this is the best example so far of how to use the API. Now to figure out how to make it work from a Python script so I can actually *do* something using the API. What a pain!
 

MisterE2002

Patron
Joined
Sep 5, 2015
Messages
211
Hi, not a Python expert but cobbled this together. Maybe contains something useful.
It starts the cifs/nfs services.

called from shell
Code:
echo "Enable services..."
python3 "${bin}/truenas_services.py" --action "start"



truenas_services.py
Code:
import argparse
import sys
import requests
import json
import yaml
import os
import logging
import logging.config
from pathlib import Path
from requests.auth import HTTPBasicAuth

failure = False


script_folder = os.path.dirname(os.path.realpath(__file__))
log_config_path = os.path.abspath(os.path.join(script_folder, '..', 'bin_var', 'logging.yaml'))
config_path = os.path.abspath(os.path.join(script_folder, '..', 'bin_var', 'config_truenas_services.json'))


config = yaml.safe_load(open(log_config_path, 'r'))
logging.config.dictConfig(config)
logger = logging.getLogger()

with open(config_path) as json_data_file:
  config = json.load(json_data_file)





# http://<host>/api/docs/
def service_action(host, service, api_key, action):
  global failure
  api_call = 'http://{}/api/v2.0/service/{}'.format(host, action)
  headers = {"Content-Type":"application/json", "Authorization": "Bearer " + api_key}
  json_string = r'{"service": "' + service + r'","service-control": {"ha_propagate": false}}'
 
  try:
    resp = requests.post(api_call, json=json.loads(json_string), headers=headers)

    if resp.status_code != 200:
      logger.error('Action "{}" on host "{}" for service "{}" failed with error: {}'.format(action, host, service, resp.status_code))
      failure = True
    else:
      logger.info('Action "{}" on host "{}" for service "{}" successful'.format(action, host, service))
  except Exception as e:
    logger.error('Action "{}" on host "{}" for service "{}" failed with error: {}'.format(action, host, service, e))
    failure = True
  
  


parser = argparse.ArgumentParser()
parser.add_argument('-a', '--action', required=True, type=str)
args = parser.parse_args(sys.argv[1:])
action = args.action


for nas in config:
  host = config[nas]["host"]
  api_key = config[nas]["api_key"]
  skip = config[nas]["skip"]
  if skip:
    logger.info('Ignoring actions for host "{}"'.format(host))
  else:
    service_action(host, 'cifs', api_key, action)
    service_action(host, 'nfs', api_key, action)

if failure:
  raise Exception('Some or all commands failed')



config_truenas_services.json

Code:
{

    "nas":{
        "host":"nas",
        "api_key":"<key>",
        "skip": false
    },
    "nas2":{
        "host":"nas2",
        "api_key":"<key2>",
        "skip": false
    }
}



logging.yaml. Filename is full path. If i remember correctly this was really necessary.
Code:
version: 1
disable_existing_loggers: False
formatters:
    console_formatter:
        format: "%(levelname)s - %(message)s"
       
    file_formatter:
        format: "[%(asctime)s] [%(levelname)s] [%(funcName)s] - %(message)s"
       
filters: []
handlers:
    console:
        class: logging.StreamHandler
        level: INFO
        formatter: console_formatter
        stream: ext://sys.stdout

    file_handler:
        class: logging.FileHandler
        level: DEBUG
        formatter: file_formatter
        filename: "/home/test/bin_var/logging.log"
       
#    session_handler:
#        class: logging.FileHandler
#        level: DEBUG
#        formatter: file_formatter
#        filename: "session.log"
#        mode: 'w'

root:
    level: DEBUG
    handlers: [console, file_handler]
    #handlers: [console, file_handler, session_handler]
 
Last edited:

MisterE2002

Patron
Joined
Sep 5, 2015
Messages
211
Question for the community. I am testing this with SCALE.
It now fails to start NFS. It only starts SMB.

However the result value is "200".
If i start the service using the GUI it also fails.

Code:
[EINVAL] At least one configured and available NFS export is required in order to start the NFS service. Check the NFS share configuration and availability of any paths currently being exported.


It should not return 200 for failing calls?

Update

SCALE API seems changed? "ha_propagate" does not exist it seems and it seems we can sent a boolean.
Anyhow, this seems to reliable work. Returns a non-zero code if fails.

Code:
json_string = r'{"service": "' + service + '"' + ',' + r'"service-control": {"silent": false}' + '}'
 
Last edited:

MisterE2002

Patron
Joined
Sep 5, 2015
Messages
211
Another example. I need to start/stop my container.
Also improved the readability of the JSON in python

Code:
def container_scale(host, api_key, container_name, replica_count):
  global failure
  api_call = 'http://{}/api/v2.0/chart/release/scale'.format(host)
  headers = {"Content-Type":"application/json", "Authorization": "Bearer " + api_key}
  json_struct = {
    "release_name": container_name,
    "scale_options": {
      "replica_count": replica_count
    }
  }
  logger.debug(json_struct)

  try:
    resp = requests.post(api_call, json=json_struct, headers=headers)
    logger.info(resp)

    if resp.status_code != 200:
      logger.error('Scaling to "{}" on host "{}" for container "{}" failed with error: {}'.format(replica_count, host, container_name, resp.status_code))
      failure = True
    else:
      logger.info('Scaling to "{}" on host "{}" for container "{}" successful'.format(replica_count, host, container_name))
  except Exception as e:
    logger.error('Scaling to "{}" on host "{}" for container "{}" failed with error: {}'.format(replica_count, host, container_name, e))
    failure = True  
 
Top