# rest_cherrypy

A script to start the CherryPy WSGI server

This is run by `salt-api` and started in a multiprocess.

## A REST API for Salt

Note

This module is Experimental on Windows platforms and supports limited configurations:

- doesn't support PAM authentication (i.e. external_auth: auto)

- doesn't support SSL (i.e. disable_ssl: True)

depends:

  - CherryPy Python module.

    Note: there is a known SSL traceback for CherryPy versions 3.2.5 through 3.7.x. Please use version 3.2.3 or the latest 10.x version instead.

optdepends:

  - ws4py Python module for websockets support.

client_libraries:

  - Java: https://github.com/SUSE/salt-netapi-client

  - Python: https://github.com/saltstack/pepper

setup:

  All steps below are performed on the machine running the Salt Master daemon. Configuration goes into the Master configuration file.

  1. Install `salt-api`. (This step varies between OS and Linux distros. Some package systems have a split package, others include salt-api in the main Salt package. Ensure the `salt-api --version` output matches the `salt --version` output.)

  2. Install CherryPy. (Read the version caveat in the section above.)

  3. Optional: generate self-signed SSL certificates.

     Using a secure HTTPS connection is strongly recommended since Salt eauth authentication credentials will be sent over the wire.

     1. Install the PyOpenSSL package.

     2. Generate a self-signed certificate using the `create_self_signed_cert()` execution function.

            salt-call --local tls.create_self_signed_cert

  4. Edit the master config to create at least one external auth user or group following the full external auth instructions.

  5. Edit the master config with the following production-ready example to enable the `rest_cherrypy` module. (Adjust cert paths as needed, or disable SSL (not recommended!).)

         rest_cherrypy:
           port: 8000
           ssl_crt: /etc/pki/tls/certs/localhost.crt
           ssl_key: /etc/pki/tls/certs/localhost.key

  6. Restart the `salt-master` daemon.

  7. Start the `salt-api` daemon.

configuration:

  All available configuration options are detailed below. These settings configure the CherryPy HTTP server and do not apply when using an external server such as Apache or Nginx.

  port

    Required

    The port for the webserver to listen on.

  host`0.0.0.0`

    The socket interface for the HTTP server to listen on.

  debug`False`

    Starts the web server in development mode. It will reload itself when the underlying code is changed and will output more debugging info.

  log_access_file

    Path to a file to write HTTP access logs.

    Added in version 2016.11.0.

  log_error_file

    Path to a file to write HTTP error logs.

    Added in version 2016.11.0.

  ssl_crt

    The path to a SSL certificate. (See below)

  ssl_key

    The path to the private key for your SSL certificate. (See below)

  ssl_chain

    (Optional when using PyOpenSSL) the certificate chain to pass to `Context.load_verify_locations`.

  disable_ssl

    A flag to disable SSL. Warning: your Salt authentication credentials will be sent in the clear!

  webhook_disable_authFalse

    The `Webhook` URL requires authentication by default but external services cannot always be configured to send authentication. See the Webhook documentation for suggestions on securing this interface.

  webhook_url/hook

    Configure the URL endpoint for the `Webhook` entry point.

  thread_pool`100`

    The number of worker threads to start up in the pool.

  socket_queue_size`30`

    Specify the maximum number of HTTP connections to queue.

  expire_responsesTrue

    Whether to check for and kill HTTP responses that have exceeded the default timeout.

    Deprecated since version 2016.11.9,2017.7.3,2018.3.0: The "expire_responses" configuration setting, which corresponds to the `timeout_monitor` setting in CherryPy, is no longer supported in CherryPy versions >= 12.0.0.

  max_request_body_size`1048576`

    Maximum size for the HTTP request body.

  collect_statsFalse

    Collect and report statistics about the CherryPy server

    Reports are available via the `Stats` URL.

  stats_disable_authFalse

    Do not require authentication to access the `/stats` endpoint.

    Added in version 2018.3.0.

  static

    A filesystem path to static HTML/JavaScript/CSS/image assets.

  static_path`/static`

    The URL prefix to use when serving static assets out of the directory specified in the `static` setting.

  enable_sessions`True`

    Enable or disable all endpoints that rely on session cookies. This can be useful to enforce only header-based authentication.

    Added in version 2017.7.0.

  app`index.html`

    A filesystem path to an HTML file that will be served as a static file. This is useful for bootstrapping a single-page JavaScript app.

    Warning! If you set this option to a custom web application, anything that uses cookie-based authentication is vulnerable to XSRF attacks. Send the custom `X-Auth-Token` header instead and consider disabling the `enable_sessions` setting.

    Changed in version 2017.7.0: Add a proof-of-concept JavaScript single-page app.

  app_path`/app`

    The URL prefix to use for serving the HTML file specified in the `app` setting. This should be a simple name containing no slashes.

    Any path information after the specified path is ignored; this is useful for apps that utilize the HTML5 history API.

  root_prefix`/`

    A URL path to the main entry point for the application. This is useful for serving multiple applications from the same URL.

### Authentication

Authentication is performed by passing a session token with each request. Tokens are generated via the `Login` URL.

The token may be sent in one of two ways: as a custom header or as a session cookie. The latter is far more convenient for clients that support cookies.

- Include a custom header named X-Auth-Token.

  For example, using curl:

      curl -sSk https://localhost:8000/login \
          -H 'Accept: application/x-yaml' \
          -d username=saltdev \
          -d password=saltdev \
          -d eauth=pam

  Copy the `token` value from the output and include it in subsequent requests:

      curl -sSk https://localhost:8000 \
          -H 'Accept: application/x-yaml' \
          -H 'X-Auth-Token: 697adbdc8fe971d09ae4c2a3add7248859c87079'\
          -d client=local \
          -d tgt='*' \
          -d fun=test.ping

- Sent via a cookie. This option is a convenience for HTTP clients that automatically handle cookie support (such as browsers).

  For example, using curl:

      # Write the cookie file:
      curl -sSk https://localhost:8000/login \
            -c ~/cookies.txt \
            -H 'Accept: application/x-yaml' \
            -d username=saltdev \
            -d password=saltdev \
            -d eauth=auto

      # Read the cookie file:
      curl -sSk https://localhost:8000 \
            -b ~/cookies.txt \
            -H 'Accept: application/x-yaml' \
            -d client=local \
            -d tgt='*' \
            -d fun=test.ping

  Another example using the requests library in Python:

      >>> import requests
      >>> session = requests.Session()
      >>> session.post('http://localhost:8000/login', json={
          'username': 'saltdev',
          'password': 'saltdev',
          'eauth': 'auto',
      })
      <Response [200]>
      >>> resp = session.post('http://localhost:8000', json=[{
          'client': 'local',
          'tgt': '*',
          'fun': 'test.arg',
          'arg': ['foo', 'bar'],
          'kwarg': {'baz': 'Baz!'},
      }])
      >>> resp.json()
      {u'return': [{
          ...snip...
      }]}

See also

You can bypass the session handling via the `Run` URL.

### Usage

This interface directly exposes Salt's Python API. Everything possible at the CLI is possible through the Python API. Commands are executed on the Salt Master.

The root URL (`/`) is RPC-like in that it accepts instructions in the request body for what Salt functions to execute, and the response contains the result of those function calls.

For example:

    % curl -sSi https://localhost:8000         -H 'Content-type: application/json'         -d '[{
            "client": "local",
            "tgt": "*",
            "fun": "test.ping"
        }]'
    HTTP/1.1 200 OK
    Content-Type: application/json
    [...snip...]

    {"return": [{"jerry": true}]}

The request body must be an array of commands. Use this workflow to build a command:

1. Choose a client interface.

2. Choose a function.

3. Fill out the remaining parameters needed for the chosen client.

The `client` field is a reference to the main Python classes used in Salt's Python API. Read the full Client APIs documentation, but in short:

- "local" uses `LocalClient` which sends commands to Minions. Equivalent to the `salt` CLI command.

- "runner" uses `RunnerClient` which invokes runner modules on the Master. Equivalent to the `salt-run` CLI command.

- "wheel" uses `WheelClient` which invokes wheel modules on the Master. Wheel modules do not have a direct CLI equivalent but they typically manage Master-side resources such as state files, pillar files, the Salt config files, and the `key wheel module` exposes similar functionality as the `salt-key` CLI command.

Most clients have variants like synchronous or asynchronous execution as well as others like batch execution. See the full list of client interfaces.

Each client requires different arguments and sometimes has different syntax. For example, `LocalClient` requires the `tgt` argument because it forwards the command to Minions and the other client interfaces do not. `LocalClient` also takes `arg` (array) and `kwarg` (dictionary) arguments because these values are sent to the Minions and used to execute the requested function there. `RunnerClient` and `WheelClient` are executed directly on the Master and thus do not need or accept those arguments.

Read the method signatures in the client documentation linked above, but hopefully an example will help illustrate the concept. This example causes Salt to execute two functions -- the `test.arg execution function` using `LocalClient` and the `test.arg runner function` using `RunnerClient`; note the different structure for each command. The results for both are combined and returned as one response.

    % curl -b ~/cookies.txt -sSi localhost:8000         -H 'Content-type: application/json'         -d '
    [
        {
            "client": "local",
            "tgt": "*",
            "fun": "test.arg",
            "arg": ["positional arg one", "positional arg two"],
            "kwarg": {
                "keyword arg one": "Hello from a minion",
                "keyword arg two": "Hello again from a minion"
            }
        },
        {
            "client": "runner",
            "fun": "test.arg",
            "keyword arg one": "Hello from a master",
            "keyword arg two": "Runners do not support positional args"
        }
    ]
    '
    HTTP/1.1 200 OK
    [...snip...]
    {
      "return": [
        {
          "jerry": {
            "args": [
              "positional arg one",
              "positional arg two"
            ],
            "kwargs": {
              "keyword arg one": "Hello from a minion",
              "keyword arg two": "Hello again from a minion",
              [...snip...]
            }
          },
          [...snip; other minion returns here...]
        },
        {
          "args": [],
          "kwargs": {
            "keyword arg two": "Runners do not support positional args",
            "keyword arg one": "Hello from a master"
          }
        }
      ]
    }

One more example, this time with more commonly used functions:

    curl -b /tmp/cookies.txt -sSi localhost:8000         -H 'Content-type: application/json'         -d '
    [
        {
            "client": "local",
            "tgt": "*",
            "fun": "state.sls",
            "kwarg": {
                "mods": "apache",
                "pillar": {
                    "lookup": {
                        "wwwdir": "/srv/httpd/htdocs"
                    }
                }
            }
        },
        {
            "client": "runner",
            "fun": "cloud.create",
            "provider": "my-ec2-provider",
            "instances": "my-centos-6",
            "image": "ami-1624987f",
            "delvol_on_destroy", true
        }
    ]
    '
    HTTP/1.1 200 OK
    [...snip...]
    {
      "return": [
        {
          "jerry": {
            "pkg_|-install_apache_|-httpd_|-installed": {
                [...snip full state return here...]
            }
          }
          [...snip other minion returns here...]
        },
        {
            [...snip full salt-cloud output here...]
        }
      ]
    }

### Content negotiation

This REST interface is flexible in what data formats it will accept as well as what formats it will return (e.g., JSON, YAML, urlencoded).

- Specify the format of data in the request body by including the Content-Type header.

- Specify the desired data format for the response body with the Accept header.

We recommend the JSON format for most HTTP requests. urlencoded data is simple and cannot express complex data structures -- and that is often required for some Salt commands, such as starting a state run that uses Pillar data. Salt's CLI tool can reformat strings passed in at the CLI into complex data structures, and that behavior also works via salt-api, but that can be brittle and since salt-api can accept JSON it is best just to send JSON.

Here is an example of sending urlencoded data:

    curl -sSik https://localhost:8000 \
        -b ~/cookies.txt \
        -d client=runner \
        -d fun='jobs.lookup_jid' \
        -d jid='20150129182456704682'

urlencoded data caveats

- Only a single command may be sent per HTTP request.

- Repeating the `arg` parameter multiple times will cause those parameters to be combined into a single list.

  Note, some popular frameworks and languages (notably jQuery, PHP, and Ruby on Rails) will automatically append empty brackets onto repeated query string parameters. E.g., `?foo[]=fooone&foo[]=footwo`. This is not supported; send `?foo=fooone&foo=footwo` instead, or send JSON or YAML.

A note about `curl`

The `-d` flag to curl does not automatically urlencode data which can affect passwords and other data that contains characters that must be encoded. Use the `--data-urlencode` flag instead. E.g.:

    curl -ksi http://localhost:8000/login \
    -H "Accept: application/json" \
    -d username='myapiuser' \
    --data-urlencode password='1234+' \
    -d eauth='pam'

## Performance Expectations and Recommended Usage

This module provides a thin wrapper around Salt's Python API. Executing a Salt command via rest_cherrypy is directly analogous to executing a Salt command via Salt's CLI (which also uses the Python API) -- they share the same semantics, performance characteristics, and 98% of the same code. As a rule-of-thumb: if you wouldn't do it at the CLI don't do it via this API.

### Long-Running HTTP Connections

The CherryPy server is a production-ready, threading HTTP server written in Python. Because it makes use of a thread pool to process HTTP requests it is not ideally suited to maintaining large numbers of concurrent, synchronous connections. On moderate hardware with default settings it should top-out at around 30 to 50 concurrent connections.

That number of long-running, synchronous Salt processes is also not ideal. Like at the CLI, each Salt command run will start a process that instantiates its own `LocalClient`, which instantiates its own listener to the Salt event bus, and sends out its own periodic `saltutil.find_job` queries to determine if a Minion is still running the command. Not exactly a lightweight operation.

### Timeouts

In addition to the above resource overhead for long-running connections, there are the usual HTTP timeout semantics for the CherryPy server, any HTTP client being used, as well as any hardware in between such as proxies, gateways, or load balancers. rest_cherrypy can be configured not to time-out long responses via the `expire_responses` setting, and both `LocalClient` and `RunnerClient` have their own timeout parameters that may be passed as top-level keywords:

    curl -b /tmp/cookies.txt -sSi localhost:8000         -H 'Content-type: application/json'         -d '
    [
        {
            "client": "local",
            "tgt": "*",
            "fun": "test.sleep",
            "kwarg": {"length": 30},
            "timeout": 60
        },
        {
            "client": "runner",
            "fun": "test.sleep",
            "kwarg": {"s_time": 30},
            "timeout": 60
        }
    ]
    '

### Best Practices

Given the performance overhead and HTTP timeouts for long-running operations described above, the most effective and most scalable way to use both Salt and salt-api is to run commands asynchronously using the `local_async`, `runner_async`, and `wheel_async` clients.

Running asynchronous jobs results in being able to process 3x more commands per second for `LocalClient` and 17x more commands per second for `RunnerClient`, in addition to much less network traffic and memory requirements. Job returns can be fetched from Salt's job cache via the `/jobs/<jid>` endpoint, or they can be collected into a data store using Salt's Returner system.

The `/events` endpoint is specifically designed to handle long-running HTTP connections and it exposes Salt's event bus which includes job returns. Watching this endpoint first, then executing asynchronous Salt commands second, is the most lightweight and scalable way to use `rest_cherrypy` while still receiving job returns in real-time. But this requires clients that can properly handle the inherent asynchronicity of that workflow.

### Performance Tuning

The `thread_pool` and `socket_queue_size` settings can be used to increase the capacity of rest_cherrypy to handle incoming requests. Keep an eye on RAM usage as well as available file handles while testing changes to these settings. As salt-api is a thin wrapper around Salt's Python API, also keep an eye on the performance of Salt when testing.

### Future Plans

Now that Salt uses the Tornado concurrency library internally, we plan to improve performance in the API by taking advantage of existing processes and event listeners and to use lightweight coroutines to facilitate more simultaneous HTTP connections and better support for synchronous operations. That effort can be tracked in issue 26505, but until that issue is closed rest_cherrypy will remain the officially recommended REST API.

## Deployment

The `rest_cherrypy` netapi module is a standard Python WSGI app. It can be deployed one of two ways.

### salt-api using the CherryPy server

The default configuration is to run this module using salt-api to start the Python-based CherryPy server. This server is lightweight, multi-threaded, encrypted with SSL, and should be considered production-ready. See the section above for performance expectations.

### Using a WSGI-compliant web server

This module may be deployed on any WSGI-compliant server such as Apache with mod_wsgi or Nginx with FastCGI, to name just two (there are many).

Note, external WSGI servers handle URLs, paths, and SSL certs directly. The `rest_cherrypy` configuration options are ignored and the `salt-api` daemon does not need to be running at all. Remember Salt authentication credentials are sent in the clear unless SSL is being enforced!

An example Apache virtual host configuration:

    <VirtualHost *:80>
        ServerName example.com
        ServerAlias *.example.com

        ServerAdmin webmaster@example.com

        LogLevel warn
        ErrorLog /var/www/example.com/logs/error.log
        CustomLog /var/www/example.com/logs/access.log combined

        DocumentRoot /var/www/example.com/htdocs

        WSGIScriptAlias / /path/to/salt/netapi/rest_cherrypy/wsgi.py
    </VirtualHost>

# REST URI Reference

## `/`

class salt.netapi.rest_cherrypy.app.LowDataAdapter

  The primary entry point to Salt's REST API

  GET()

    An explanation of the API with links of where to go next

    GET /
      Request Headers:

        - Accept -- the desired response format.

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -i localhost:8000

        GET / HTTP/1.1
        Host: localhost:8000
        Accept: application/json

    Example response:

        HTTP/1.1 200 OK
        Content-Type: application/json

  POST(**kwargs)

    Send one or more Salt commands in the request body

    POST /
      Request Headers:

        - X-Auth-Token -- a session token from `Login`.

        - Accept -- the desired response format.

        - Content-Type -- the format of the request body.

      Response Headers:

        - Content-Type -- the format of the response body; depends on the Accept request header.

      Status Codes:

        - 200 OK -- success

        - 400 Bad Request -- bad or malformed request

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

      lowstate data describing Salt commands must be sent in the request body.

    Example request:

        curl -sSik https://localhost:8000 \
            -b ~/cookies.txt \
            -H "Accept: application/x-yaml" \
            -H "Content-type: application/json" \
            -d '[{"client": "local", "tgt": "*", "fun": "test.ping"}]'

        POST / HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml
        X-Auth-Token: d40d1e1e
        Content-Type: application/json

        [{"client": "local", "tgt": "*", "fun": "test.ping"}]

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 200
        Allow: GET, HEAD, POST
        Content-Type: application/x-yaml

        return:
        - ms-0: true
          ms-1: true
          ms-2: true
          ms-3: true
          ms-4: true

## `/login`

class salt.netapi.rest_cherrypy.app.Login(*args, **kwargs)

  Log in to receive a session token

  Authentication information.

  GET()

    Present the login interface

    GET /login

      An explanation of how to log in.

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -i localhost:8000/login

        GET /login HTTP/1.1
        Host: localhost:8000
        Accept: text/html

    Example response:

        HTTP/1.1 200 OK
        Content-Type: text/html

  POST(**kwargs)

    Authenticate against Salt's eauth system

    POST /login
      Request Headers:

        - X-Auth-Token -- a session token from `Login`.

        - Accept -- the desired response format.

        - Content-Type -- the format of the request body.

      Form Parameters:

        - eauth -- the eauth backend configured for the user

        - username -- username

        - password -- password

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -si localhost:8000/login \
            -c ~/cookies.txt \
            -H "Accept: application/json" \
            -H "Content-type: application/json" \
            -d '{
                "username": "saltuser",
                "password": "saltuser",
                "eauth": "auto"
            }'

        POST / HTTP/1.1
        Host: localhost:8000
        Content-Length: 42
        Content-Type: application/json
        Accept: application/json

        {"username": "saltuser", "password": "saltuser", "eauth": "auto"}

    Example response:

        HTTP/1.1 200 OK
        Content-Type: application/json
        Content-Length: 206
        X-Auth-Token: 6d1b722e
        Set-Cookie: session_id=6d1b722e; expires=Sat, 17 Nov 2012 03:23:52 GMT; Path=/

        {"return": {
            "token": "6d1b722e",
            "start": 1363805943.776223,
            "expire": 1363849143.776224,
            "user": "saltuser",
            "eauth": "pam",
            "perms": [
                "grains.*",
                "status.*",
                "sys.*",
                "test.*"
            ]
        }}

## `/logout`

class salt.netapi.rest_cherrypy.app.Logout

  Class to remove or invalidate sessions

  POST()

    Destroy the currently active session and expire the session cookie

## `/minions`

class salt.netapi.rest_cherrypy.app.Minions

  Convenience URLs for working with minions

  GET(mid=None)

    A convenience URL for getting lists of minions or getting minion details

    GET /minions/(mid)
      Request Headers:

        - X-Auth-Token -- a session token from `Login`.

        - Accept -- the desired response format.

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -i localhost:8000/minions/ms-3

        GET /minions/ms-3 HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 129005
        Content-Type: application/x-yaml

        return:
        - ms-3:
            grains.items:
                ...

  POST(**kwargs)

    Start an execution command and immediately return the job id

    POST /minions
      Request Headers:

        - X-Auth-Token -- a session token from `Login`.

        - Accept -- the desired response format.

        - Content-Type -- the format of the request body.

      Response Headers:

        - Content-Type -- the format of the response body; depends on the Accept request header.

      Status Codes:

        - 200 OK -- success

        - 400 Bad Request -- bad or malformed request

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

      Lowstate data describing Salt commands must be sent in the request body. The `client` option will be set to `local_async()`.

    Example request:

        curl -sSi localhost:8000/minions \
            -b ~/cookies.txt \
            -H "Accept: application/x-yaml" \
            -d '[{"tgt": "*", "fun": "status.diskusage"}]'

        POST /minions HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml
        Content-Type: application/x-www-form-urlencoded

        tgt=*&fun=status.diskusage

    Example response:

        HTTP/1.1 202 Accepted
        Content-Length: 86
        Content-Type: application/x-yaml

        return:
        - jid: '20130603122505459265'
          minions: [ms-4, ms-3, ms-2, ms-1, ms-0]
        _links:
          jobs:
            - href: /jobs/20130603122505459265

## `/jobs`

class salt.netapi.rest_cherrypy.app.Jobs
  GET(jid=None, timeout='')

    A convenience URL for getting lists of previously run jobs or getting the return from a single job

    GET /jobs/(jid)

      List jobs or show a single job from the job cache.

      Request Headers:

        - X-Auth-Token -- a session token from `Login`.

        - Accept -- the desired response format.

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -i localhost:8000/jobs

        GET /jobs HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 165
        Content-Type: application/x-yaml

        return:
        - '20121130104633606931':
            Arguments:
            - '3'
            Function: test.fib
            Start Time: 2012, Nov 30 10:46:33.606931
            Target: jerry
            Target-type: glob

    Example request:

        curl -i localhost:8000/jobs/20121130104633606931

        GET /jobs/20121130104633606931 HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 73
        Content-Type: application/x-yaml

        info:
        - Arguments:
            - '3'
            Function: test.fib
            Minions:
            - jerry
            Start Time: 2012, Nov 30 10:46:33.606931
            Target: '*'
            Target-type: glob
            User: saltdev
            jid: '20121130104633606931'
        return:
        - jerry:
            - - 0
            - 1
            - 1
            - 2
            - 6.9141387939453125e-06

## `/run`

class salt.netapi.rest_cherrypy.app.Run

  Run commands bypassing the normal session handling.

  salt-api does not enforce authorization, Salt's eauth system does that. Local/Runner/WheelClient all accept `username`/`password`/`eauth` or `token` kwargs that are then checked by the eauth system. The session mechanism in `rest_cherrypy` simply pairs a session with a Salt eauth token and then passes the `token` kwarg in automatically.

  If you already have a Salt eauth token, perhaps generated by the `mk_token` function in the Auth Runner module, then there is no reason to use sessions.

  This endpoint accepts either a `username`, `password`, `eauth` trio, or a `token` kwarg and does not make use of sessions at all.

  POST(**kwargs)

    Run commands bypassing the normal session handling. Otherwise, this URL is identical to the `root URL (/)`.

    POST /run

      An array of lowstate data describing Salt commands must be sent in the request body.

      Status Codes:

        - 200 OK -- success

        - 400 Bad Request -- bad or malformed request

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -sS localhost:8000/run \
            -H 'Accept: application/x-yaml' \
            -H 'Content-type: application/json' \
            -d '[{
                "client": "local",
                "tgt": "*",
                "fun": "test.ping",
                "username": "saltdev",
                "password": "saltdev",
                "eauth": "auto"
            }]'

    Or using a Salt Eauth token:

        curl -sS localhost:8000/run \
            -H 'Accept: application/x-yaml' \
            -H 'Content-type: application/json' \
            -d '[{
                "client": "local",
                "tgt": "*",
                "fun": "test.ping",
                "token": "<salt eauth token here>"
            }]'

        POST /run HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml
        Content-Length: 75
        Content-Type: application/json

        [{"client": "local", "tgt": "*", "fun": "test.ping", "username": "saltdev", "password": "saltdev", "eauth": "auto"}]

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 73
        Content-Type: application/x-yaml

        return:
        - ms-0: true
          ms-1: true
          ms-2: true
          ms-3: true
          ms-4: true

    The /run endpoint can also be used to issue commands using the salt-ssh subsystem. When using salt-ssh, eauth credentials must also be supplied, and are subject to eauth access-control lists.

    All SSH client requests are synchronous.

    Example SSH client request:

        curl -sS localhost:8000/run \
            -H 'Accept: application/x-yaml' \
            -d client='ssh' \
            -d tgt='*' \
            -d username='saltdev' \
            -d password='saltdev' \
            -d eauth='auto' \
            -d fun='test.ping'

        POST /run HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml
        Content-Length: 75
        Content-Type: application/x-www-form-urlencoded

    Example SSH response:

        return:
        - silver:
            _stamp: '2020-09-08T23:04:28.912609'
            fun: test.ping
            fun_args: []
            id: silver
            jid: '20200908230427905565'
            retcode: 0
            return: true

## `/events`

class salt.netapi.rest_cherrypy.app.Events

  Expose the Salt event bus

  The event bus on the Salt master exposes a large variety of things, notably when executions are started on the master and also when minions ultimately return their results. This URL provides a real-time window into a running Salt infrastructure.

  See also

  Events & Reactor

  GET(token=None, salt_token=None)

    An HTTP stream of the Salt master event bus

    This stream is formatted per the Server Sent Events (SSE) spec. Each event is formatted as JSON.

    GET /events
      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

      Query Parameters:

        - token -- optional parameter containing the token ordinarily supplied via the X-Auth-Token header in order to allow cross-domain requests in browsers that do not include CORS support in the EventSource API. E.g., `curl -NsS localhost:8000/events?token=308650d`

        - salt_token -- optional parameter containing a raw Salt eauth token (not to be confused with the token returned from the /login URL). E.g., `curl -NsS localhost:8000/events?salt_token=30742765`

    Example request:

        curl -NsS localhost:8000/events

        GET /events HTTP/1.1
        Host: localhost:8000

    Example response:

    Note, the `tag` field is not part of the spec. SSE compliant clients should ignore unknown fields. This addition allows non-compliant clients to only watch for certain tags without having to deserialze the JSON object each time.

        HTTP/1.1 200 OK
        Connection: keep-alive
        Cache-Control: no-cache
        Content-Type: text/event-stream;charset=utf-8

        retry: 400

        tag: salt/job/20130802115730568475/new
        data: {'tag': 'salt/job/20130802115730568475/new', 'data': {'minions': ['ms-4', 'ms-3', 'ms-2', 'ms-1', 'ms-0']}}

        tag: salt/job/20130802115730568475/ret/jerry
        data: {'tag': 'salt/job/20130802115730568475/ret/jerry', 'data': {'jid': '20130802115730568475', 'return': True, 'retcode': 0, 'success': True, 'cmd': '_return', 'fun': 'test.ping', 'id': 'ms-1'}}

    The event stream can be easily consumed via JavaScript:

        var source = new EventSource('/events');
        source.onopen = function() { console.info('Listening ...') };
        source.onerror = function(err) { console.error(err) };
        source.onmessage = function(message) {
            var saltEvent = JSON.parse(message.data);
            console.log(saltEvent.tag, saltEvent.data);
        };

    Note, the SSE stream is fast and completely asynchronous and Salt is very fast. If a job is created using a regular POST request, it is possible that the job return will be available on the SSE stream before the response for the POST request arrives. It is important to take that asynchronicity into account when designing an application. Below are some general guidelines.

    - Subscribe to the SSE stream _before_ creating any events.

    - Process SSE events directly as they arrive and don't wait for any other process to "complete" first (like an ajax request).

    - Keep a buffer of events if the event stream must be used for synchronous lookups.

    - Be cautious in writing Salt's event stream directly to the DOM. It is very busy and can quickly overwhelm the memory allocated to a browser tab.

    A full, working proof-of-concept JavaScript application is available adjacent to this file. It can be viewed by pointing a browser at the `/app` endpoint in a running `rest_cherrypy` instance.

    Or using CORS:

        var source = new EventSource('/events?token=ecd589e4e01912cf3c4035afad73426dbb8dba75', {withCredentials: true});

    It is also possible to consume the stream via the shell.

    Records are separated by blank lines; the `data:` and `tag:` prefixes will need to be removed manually before attempting to unserialize the JSON.

    curl's `-N` flag turns off input buffering which is required to process the stream incrementally.

    Here is a basic example of printing each event as it comes in:

        curl -NsS localhost:8000/events |\
                while IFS= read -r line ; do
                    echo $line
                done

    Here is an example of using awk to filter events based on tag:

        curl -NsS localhost:8000/events |\
                awk '
                    BEGIN { RS=""; FS="\\n" }
                    $1 ~ /^tag: salt\/job\/[0-9]+\/new$/ { print $0 }
                '
        tag: salt/job/20140112010149808995/new
        data: {"tag": "salt/job/20140112010149808995/new", "data": {"tgt_type": "glob", "jid": "20140112010149808995", "tgt": "jerry", "_stamp": "2014-01-12_01:01:49.809617", "user": "shouse", "arg": [], "fun": "test.ping", "minions": ["jerry"]}}
        tag: 20140112010149808995
        data: {"tag": "20140112010149808995", "data": {"fun_args": [], "jid": "20140112010149808995", "return": true, "retcode": 0, "success": true, "cmd": "_return", "_stamp": "2014-01-12_01:01:49.819316", "fun": "test.ping", "id": "jerry"}}

## `/hook`

class salt.netapi.rest_cherrypy.app.Webhook

  A generic web hook entry point that fires an event on Salt's event bus

  External services can POST data to this URL to trigger an event in Salt. For example, Amazon SNS, Jenkins-CI or Travis-CI, or GitHub web hooks.

  Note

  Be mindful of security

  Salt's Reactor can run any code. A Reactor SLS that responds to a hook event is responsible for validating that the event came from a trusted source and contains valid data.

  This is a generic interface and securing it is up to you!

  This URL requires authentication however not all external services can be configured to authenticate. For this reason authentication can be selectively disabled for this URL. Follow best practices -- always use SSL, pass a secret key, configure the firewall to only allow traffic from a known source, etc.

  The event data is taken from the request body. The Content-Type header is respected for the payload.

  The event tag is prefixed with `salt/netapi/hook` and the URL path is appended to the end. For example, a `POST` request sent to `/hook/mycompany/myapp/mydata` will produce a Salt event with the tag `salt/netapi/hook/mycompany/myapp/mydata`.

  The following is an example `.travis.yml` file to send notifications to Salt of successful test runs:

      language: python
      script: python -m unittest tests
      after_success:
          - |
              curl -sSk https://saltapi-url.example.com:8000/hook/travis/build/success                         -d branch="${TRAVIS_BRANCH}"                         -d commit="${TRAVIS_COMMIT}"

  See also

  Events & Reactor, reactor

  POST(*args, **kwargs)

    Fire an event in Salt with a custom event tag and data

    POST /hook
      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

        - 413 Request Entity Too Large -- request body is too large

    Example request:

        curl -sS localhost:8000/hook \
            -H 'Content-type: application/json' \
            -d '{"foo": "Foo!", "bar": "Bar!"}'

        POST /hook HTTP/1.1
        Host: localhost:8000
        Content-Length: 16
        Content-Type: application/json

        {"foo": "Foo!", "bar": "Bar!"}

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 14
        Content-Type: application/json

        {"success": true}

    As a practical example, an internal continuous-integration build server could send an HTTP POST request to the URL `https://localhost:8000/hook/mycompany/build/success` which contains the result of a build and the SHA of the version that was built as JSON. That would then produce the following event in Salt that could be used to kick off a deployment via Salt's Reactor:

        Event fired at Fri Feb 14 17:40:11 2014
        *************************
        Tag: salt/netapi/hook/mycompany/build/success
        Data:
        {'_stamp': '2014-02-14_17:40:11.440996',
            'headers': {
                'X-My-Secret-Key': 'F0fAgoQjIT@W',
                'Content-Length': '37',
                'Content-Type': 'application/json',
                'Host': 'localhost:8000',
                'Remote-Addr': '127.0.0.1'},
            'post': {'revision': 'aa22a3c4b2e7', 'result': True}}

    Salt's Reactor could listen for the event:

        reactor:
          - 'salt/netapi/hook/mycompany/build/*':
            - /srv/reactor/react_ci_builds.sls

    And finally deploy the new build:

        {% set secret_key = data.get('headers', {}).get('X-My-Secret-Key') %}
        {% set build = data.get('post', {}) %}

        {% if secret_key == 'F0fAgoQjIT@W' and build.result == True %}
        deploy_my_app:
          cmd.state.sls:
            - tgt: 'application*'
            - arg:
              - myapp.deploy
            - kwarg:
                pillar:
                  revision: {{ revision }}
        {% endif %}

## `/keys`

class salt.netapi.rest_cherrypy.app.Keys

  Convenience URLs for working with minion keys

  Added in version 2014.7.0.

  These URLs wrap the functionality provided by the `key wheel module` functions.

  GET(mid=None)

    Show the list of minion keys or detail on a specific key

    Added in version 2014.7.0.

    GET /keys/(mid)

      List all keys or show a specific key

      Request Headers:

        - X-Auth-Token -- a session token from `Login`.

        - Accept -- the desired response format.

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -i localhost:8000/keys

        GET /keys HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 165
        Content-Type: application/x-yaml

        return:
          local:
          - master.pem
          - master.pub
          minions:
          - jerry
          minions_pre: []
          minions_rejected: []

    Example request:

        curl -i localhost:8000/keys/jerry

        GET /keys/jerry HTTP/1.1
        Host: localhost:8000
        Accept: application/x-yaml

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 73
        Content-Type: application/x-yaml

        return:
          minions:
            jerry: 51:93:b3:d0:9f:3a:6d:e5:28:67:c2:4b:27:d6:cd:2b

  POST(**kwargs)

    Easily generate keys for a minion and auto-accept the new key

    Accepts all the same parameters as the `key.gen_accept`.

    Note

    A note about `curl` Avoid using the `-i` flag or HTTP headers will be written and produce an invalid tar file.

    Example partial kickstart script to bootstrap a new minion:

        %post
        mkdir -p /etc/salt/pki/minion
        curl -sSk https://localhost:8000/keys \
                -d mid=jerry \
                -d username=kickstart \
                -d password=kickstart \
                -d eauth=pam \
            | tar -C /etc/salt/pki/minion -xf -

        mkdir -p /etc/salt/minion.d
        printf 'master: 10.0.0.5\nid: jerry' > /etc/salt/minion.d/id.conf
        %end

    POST /keys

      Generate a public and private key and return both as a tarball

      Authentication credentials must be passed in the request.

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available

    Example request:

        curl -sSk https://localhost:8000/keys \
                -d mid=jerry \
                -d username=kickstart \
                -d password=kickstart \
                -d eauth=pam \
                -o jerry-salt-keys.tar

        POST /keys HTTP/1.1
        Host: localhost:8000

    Example response:

        HTTP/1.1 200 OK
        Content-Length: 10240
        Content-Disposition: attachment; filename="saltkeys-jerry.tar"
        Content-Type: application/x-tar

        jerry.pub0000644000000000000000000000070300000000000010730 0ustar  00000000000000

## `/ws`

class salt.netapi.rest_cherrypy.app.WebsocketEndpoint

  Open a WebSocket connection to Salt's event bus

  The event bus on the Salt master exposes a large variety of things, notably when executions are started on the master and also when minions ultimately return their results. This URL provides a real-time window into a running Salt infrastructure. Uses websocket as the transport mechanism.

  See also

  Events & Reactor

  GET(token=None, **kwargs)

    Return a websocket connection of Salt's event stream

    GET /ws/(token)
    Query format_events:

      The event stream will undergo server-side formatting if the `format_events` URL parameter is included in the request. This can be useful to avoid formatting on the client-side:

          curl -NsS <...snip...> localhost:8000/ws?format_events

    Reqheader X-Auth-Token:

      an authentication token from `Login`.

    Status 101:

      switching to the websockets protocol

    Status 401:

      authentication required

    Status 406:

      requested Content-Type not available

    Example request:

        curl -NsSk \
            -H 'X-Auth-Token: ffedf49d' \
            -H 'Host: localhost:8000' \
            -H 'Connection: Upgrade' \
            -H 'Upgrade: websocket' \
            -H 'Origin: https://localhost:8000' \
            -H 'Sec-WebSocket-Version: 13' \
            -H 'Sec-WebSocket-Key: '"$(echo -n $RANDOM | base64)" \
            localhost:8000/ws

        GET /ws HTTP/1.1
        Connection: Upgrade
        Upgrade: websocket
        Host: localhost:8000
        Origin: https://localhost:8000
        Sec-WebSocket-Version: 13
        Sec-WebSocket-Key: s65VsgHigh7v/Jcf4nXHnA==
        X-Auth-Token: ffedf49d

    Example response:

        HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: mWZjBV9FCglzn1rIKJAxrTFlnJE=
        Sec-WebSocket-Version: 13

    An authentication token may optionally be passed as part of the URL for browsers that cannot be configured to send the authentication header or cookie:

        curl -NsS <...snip...> localhost:8000/ws/ffedf49d

    The event stream can be easily consumed via JavaScript:

        // Note, you must be authenticated!
        var source = new Websocket('ws://localhost:8000/ws/d0ce6c1a');
        source.onerror = function(e) { console.debug('error!', e); };
        source.onmessage = function(e) { console.debug(e.data); };

        source.send('websocket client ready')

        source.close();

    Or via Python, using the Python module websocket-client for example.

        # Note, you must be authenticated!

        from websocket import create_connection

        ws = create_connection('ws://localhost:8000/ws/d0ce6c1a')
        ws.send('websocket client ready')

        # Look at https://pypi.python.org/pypi/websocket-client/ for more
        # examples.
        while listening_to_events:
            print ws.recv()

        ws.close()

    Above examples show how to establish a websocket connection to Salt and activating real time updates from Salt's event stream by signaling `websocket client ready`.

## `/stats`

class salt.netapi.rest_cherrypy.app.Stats

  Expose statistics on the running CherryPy server

  GET()

    Return a dump of statistics collected from the CherryPy server

    GET /stats
      Request Headers:

        - X-Auth-Token -- a session token from `Login`.

        - Accept -- the desired response format.

      Response Headers:

        - Content-Type -- the format of the response body; depends on the Accept request header.

      Status Codes:

        - 200 OK -- success

        - 401 Unauthorized -- authentication required

        - 406 Not Acceptable -- requested Content-Type not available