Simple REST like API

Return value

Generally API calls return a JSON object with at least a status key.

If API call is successful (but it doesn’t imply that all underlying actions have been successful too):

{ "status": "success" }

On top level error, reply is at least:

{ "status": "error", "errors": [ERROR1, ...] }

API call may add more keys described in “Reply” part.

An API session is automatically closed after 1 hour of inactivity.

Important

Under same API major version M, all API calls from M.m are available in M.m+1.

Session

Login

Request:

POST /api/v0/login
{
   "username": USERNAME,
   "password": PASSWORD
}

Reply:

{
   "csrf-token": CSRF token for POST HTTP request,
   "csrf-token-http-header": HTTP request header name for CSRF token,
   "api": ["v0", "v0.1", "v0.2"] list of supported API version (new in v0.1)
}

If api key is missing, caller must assume only v0 is available.

Note

Notes for the beginners

  • Use Content-type: application/json for POST requests (but it doesn’t hurt if used for all type of requests)

  • Cookies are used to track the session and they may be updated on each API call since the login phase; they must be saved and re-used for subsequent calls.

    On a successful login, FortiPoc returns cookies similar to fortipoc-csrftoken-<UID> and fortipoc-sessionid-<UID>.

POST

  • You must add the CSRF in HTTP header using csrf-token-http-header key value for the header key and csrf-token key value for the value.

  • You must speficy a valid Referer HTTP header with your current host FQDN or IP as value as seen in your API URL (ex: https://10.0.0.1/ for https://10.0.0.1/api/v0/poc/launch).

Logout

Request:

GET /api/v0/logout

Alive

Check session is still alive.

Request:

GET /api/v0/alive

Status

Get FortiPoC information

Request:

GET /api/v0/status

Reply:

{
  "kvm": "official", # is it "official" (Intel) or "experimental" (AMD) kvm support
  "fpuuid": "e8ed488f6b5ce2b0efce10aaed7a23df", # FortiPoC compact SN
  "version": "1.7.4-dhcp4", # FortiPoC version
  "hostname": "fortipoc", # FortiPoC hostname
  "softkvm": false, # software emulation enabled or not
  "nested": true, # nested virtualization enabled or not
  "g_resources": true, # resource disk mounted or not
  "username": "admin" # current user logged in
}

PoC

List

Request:

GET /api/v0/poc/list

Reply:

{
    "pocs": {
      "POC.PK": {
        "pk": POC.PK,
        "name": POC.NAME,
        "running": true|false
      },...
    }
}

Launch

Request:

POST /api/v0/poc/launch
{
   "poc": POC.PK,
   "power-on": true(default)|false
}

When power-on is true (default if not specified), normal launch with devices power on if they are configured to be powered on on launch. If false no device is powered on.

Reply:

{
   "task": TASK.PK
}

Eject

Request:

GET /api/v0/poc/eject

Reply:

{
   "task": TASK.PK
}

Import (API >= v0.1)

The import uses multipart/form-data content type.

Request:

POST /api/v0.1/poc/import

Files are passed with fpocs form field, you can import multiple files.

Set overwrite form field to 1 if import must overwrite existing PoC with same name.

Reply:

{

   "status": "success",
   "objects": {
       "FILENAME" (or "INDEX"): {
          "status": "success" | "error",
          "pk": PK if sucessfully imported,
          "name": POC name,
          "errors": ["reason", ...]
          }, ...
   }
}

Delete (API >= v0.1)

Request:

POST /api/v0.1/poc/delete
{
   "pocs": [POC.PK,...]
}

Reply:

{
   "pocs": {
      POC.PK: {
        "status": "success" | "error"
        "errors": ["REASON", ....]
      }
   }
}

Running Poc

List devices (API < v0.2)

Request:

GET /api/v0/poc/current/devices/list

Reply:

{
   "devices": {
      "DEVICE.PK": {
         "name" : DEVICE.NAME,
         "state" : device normalized state (as in GUI),
         "pk" : DEVICE.PK,
         "reason" : libvirt domain state, empty for lxc
         "license": license information if any
      }, ...
   }
}

List devices (API == v0.2)

You can list all devices or specific devices using the primary key or the device name. You can specify multiple device in filter using comma.

Request:

GET /api/v0.2/poc/current/devices/list[?filter=<NAME or PK>,...]

Reply:

{
   "devices": {
      "DEVICE.PK": {
         "name" : DEVICE.NAME,
         "state" : device normalized state (as in GUI),
         "pk" : DEVICE.PK,
         "reason" : libvirt domain state, empty for lxc
         "license": license information if any
      }, ...
   }
}

List devices (API >= v0.3)

You can list all devices or specific devices using the primary key or the device name or a part of the device name. You can specify multiple device in filter using comma.

Request:

GET /api/v0.3/poc/current/devices/list[?filter=<NAME or PK>,...]

Reply:

{
   "devices": {
      "DEVICE.PK": {
         "name" : DEVICE.NAME,
         "state" : device normalized state (as in GUI),
         "pk" : DEVICE.PK,
         "reason" : libvirt domain state, empty for lxc
         "license": license information if any
      }, ...
   }
}

Actions devices

An action can be performed on a list of devices, each action is running in its own task.

Request:

POST /api/v0/poc/current/devices/ACTION
{
   "devices": [DEVICE.PK, ...]
}

ACTION can be:

  • poweron: to power on devices

  • shutdown: to cleanly shutdown devices

  • poweroff: to power off devices

    Warning

    Device’s disks may be corrupted

  • rebuild: to rebuild device’s disk as after PoC launch (device stays powered off)

Reply:

{
   "devices": {
       "DEVICE.PK": {
         "pk": DEVICE.PK,
         "status": "error"|"task",
         "error": error if "status" is "error",
         "task": TASK.PK
       }, ...
   }
}

Warning

If too many devices are processed in one call, you may reach the Web Server timeout.

Saving configuration (API >= 0.5)

To backup devices configuration, each save is running in its own task.

Request:

POST /api/v0/poc/current/devices/backup
{
   "devices": { (source key) "DEVICE.PK" | DEVICE.NAME :
                {
                   "filename": FILENAME,
                   "apply": "replace"|"yes"|"no"(default)
                }, ...}
}
  • filename: mandatory if apply is yes or no

  • apply:

    • replace: replace exiting configuration, keep same filename

    • yes: device definition updated to use the new backup

    • no: just backup configuration

Reply:

{
   "devices": {
       "DEVICE.PK": {
         "pk": DEVICE.PK,
         "name": DEVICE.NAME,
         "status": "error"|"task",
         "error": error if "status" is "error",
         "task": TASK.PK
       },
       "(source key)": {
         "pk_or_name": (source key),
         "status": "error",
         "error": error message
       }, ...
   }
}

Tasks

List

Request:

POST /api/v0/task/list
{
   "complete": false(default)|true
}

Return the list of tasks that have not yet been completed (complete is missing or false) or all task (complete is true).

Reply:

{
   "tasks": [TASK.PK, ...]
}

Status

Request:

POST /api/v0/task/status
{
   "tasks": [TASK.PK, ...]
}

Reply:

{
   "tasks": {
      "TASK.PK": {
        "pk": TASK.PK,
        "status": "data" or "error",
        "data": { if status is "data"
          "name" : name of the task,
          "feedback_pk" : PK of the feedback log (use to fill the modal window in GUI),
          "log_pk" : PK of the full log,
          "returncode" : see details,
          "signal" : null or signal value if task process aborted on signal,
          "taskmgr_id" : see details,
          "date_start" : UTC date of task start,
          "date_end" : UTC date of task end (on complete or error),
          "error": the task manager error is any,
          "progress_error": internal error if unable to retrieve task progression
        }
        "error": internal error if status is "error"
      }, ...
   }
}

Details:

  • returncode: is null until task process has returned, contains the task return code or:

    • -3: task process aborted on signal (see signal value)

    • or taskmgr_id error value

  • taskmgr_id: id of the task in the task manager, can be:

    • -1: task manager has not yet received the task

    • -2: task manager was unable to run task

    • -4: task manager fails to accomplish action

Kill

Request:

POST /api/v0/task/kill
{
   "tasks": [TASK.PK, ...],
   "timeout": 0(default) or timeout value in seconds
}

Reply, see Status except process_error is replaced by kill_error.

Logs

List

Request:

POST /api/v0/log/list
{
   "poc": POC.PK
}

If poc is not specified use the current running PoC if any.

Reply:

{
  "poc": POC.PK,
  "logs": {
       "LOG.PK": {
           "pk": LOG.PK,
           "name": LOG.NAME,
           "create_at": log UTC creation date
       }, ...
  }
}

Get

Return available log content.

Request:

GET /api/v0/log/[last|LOG.PK](/OFFSET)

Reply:

LOG content

You can access log of a running task, in this case, only available part or the log is returned.

You can retrieve “missing” part of the log by specifying OFFSET the log offset you want to start receiving content. You can get only the new lines between calls.

FortiPoC adds in the HTTP response a X-FortiPoC-Size header with the size of the log (it’s not the size of the HTTP content) so you can use this value as OFFSET on next call to complete the log.

Receiving an empty log doesn’t mean the task is complete, it either means the OFFSET is invalid (past the end of the file) or no new content is available yet.

Shell script example

You can source this shell script in your console. It installs 4 functions based on curl and jq that handles the cookies and the CSRF:

  1. fp_login HOST USERNAME [PASSWORD]:

    fp_login 192.168.1.1 admin
    

    or:

    fp_login 192.168.1.1 admin your_password
    
  2. fp_alive HOST:

    fp_alive 192.168.1.1
    
  3. fp_get HOST PATH:

    fp_get 192.168.1.1 /api/v0/status
    
  4. fp_post HOST PATH [DATA]:

    fp_post 192.168.1.1 /api/v0/task/list
    

    or:

    fp_post 192.168.1.1 /api/v0/task/list '{"complete": true}'
    
  5. fp_post_raw HOST PATH [CURL_OPTIONS]*:

    fp_post 192.168.1.1 /api/v0.1/poc/import --form fpocs=file1.fpoc
    

    or:

    fp_post 192.168.1.1 /api/v0.1/poc/import --form fpocs=file1.fpoc --form fpocs=file2.fpoc --form overwrite=1