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
optdepends
  • ws4py Python module for websockets support.

client_libraries
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.

host0.0.0.0

The socket interface for the HTTP server to listen on.

debugFalse

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.

New in version 2016.11.0.

log_error_file

Path to a file to write HTTP error logs.

New 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_pool100

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

socket_queue_size30

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_size1048576

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.

New 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_sessionsTrue

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

New in version 2017.7.0.

appindex.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'

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 [email protected]

    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>

© 2021 SaltStack.
Licensed under the Apache License, Version 2.0.
https://docs.saltproject.io/en/latest/ref/netapi/all/salt.netapi.rest_cherrypy.html