File manager - Edit - /opt/saltstack/salt/lib/python3.10/site-packages/salt/netapi/rest_cherrypy/__pycache__/app.cpython-310.pyc
Back
o ;j:� � @ s~ d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl m Z mZ ddlm Z mZ ddlmZ ddlZddlZddlZddlZddlZddlZddlZddlZddlZddlZddlZe�e�Zzddlm Z W n e!y� dZ e�"d� Y n e#y� dZ e�"d� Y nw zd d l$m%Z% d dl&m'Z' dZ(W n e#y� e)d e*fddi�Z'dZ(Y nw dd� Z+dd� Z,dd� Z-dd� Z.dd� Z/dd� Z0dej1j2j3fdej4ej1j5j6dd�ffZ7dd � Z8d!d"� Z9d#d$� Z:d%d&� Z;e:d'd(� �Z<e:d)d*� �Z=e:d+d,� �Z>d-d.� Z?d/d0� Z@d1e+fd2e,fgde0fd3e/fd.e?fgd0e@fd"e9fd4e.fgd5�ZAeA�B� D ]"\ZCZDeEeD�D ]\ZFZGeG\ZHZIeJej&eHejKeCeId6eF d7�� �q5�q-G d8d9� d9�ZLG d:d;� d;eL�ZMG d<d=� d=eL�ZNG d>d?� d?eL�ZOG d@dA� dAeL�ZPG dBdC� dCeL�ZQG dDdE� dEeL�ZRG dFdG� dGeL�ZSG dHdI� dI�ZTG dJdK� dK�ZUG dLdM� dM�ZVG dNdO� dO�ZWG dPdQ� dQ�ZXG dRdS� dS�ZYdTdU� ZZdS )VamR A REST API for Salt =================== .. py:currentmodule:: salt.netapi.rest_cherrypy.app .. 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 <https://github.com/cherrypy/cherrypy/issues/1298>`_. 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 :py:func:`~salt.modules.tls.create_self_signed_cert` execution function. .. code-block:: bash 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 :ref:`full external auth instructions <acl-eauth>`. 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!).) .. code-block:: yaml 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. .. versionadded:: 2016.11.0 log_error_file Path to a file to write HTTP error logs. .. versionadded:: 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_auth : False The :py:class:`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 :py:class:`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_responses : True Whether to check for and kill HTTP responses that have exceeded the default timeout. .. deprecated:: 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_stats : False Collect and report statistics about the CherryPy server Reports are available via the :py:class:`Stats` URL. stats_disable_auth : False Do not require authentication to access the ``/stats`` endpoint. .. versionadded:: 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. .. versionadded:: 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. .. versionchanged:: 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. .. _rest_cherrypy-auth: Authentication -------------- Authentication is performed by passing a session token with each request. Tokens are generated via the :py:class:`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 :mailheader:`X-Auth-Token`. For example, using curl: .. code-block:: bash 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: .. code-block:: bash 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: .. code-block:: bash # 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 :program:`requests` library in Python: .. code-block:: 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... }]} .. seealso:: You can bypass the session handling via the :py:class:`Run` URL. Usage ----- This interface directly exposes Salt's :ref:`Python API <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: .. code-block:: text % 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 :ref:`Client APIs <client-apis>` documentation, but in short: * "local" uses :py:class:`LocalClient <salt.client.LocalClient>` which sends commands to Minions. Equivalent to the ``salt`` CLI command. * "runner" uses :py:class:`RunnerClient <salt.runner.RunnerClient>` which invokes runner modules on the Master. Equivalent to the ``salt-run`` CLI command. * "wheel" uses :py:class:`WheelClient <salt.wheel.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 :py:mod:`key wheel module <salt.wheel.key>` 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 :ref:`full list of client interfaces <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 :py:func:`test.arg execution function <salt.modules.test.arg>` using ``LocalClient`` and the :py:func:`test.arg runner function <salt.runners.test.arg>` using ``RunnerClient``; note the different structure for each command. The results for both are combined and returned as one response. .. code-block:: text % 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: .. code-block:: text 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 :mailheader:`Content-Type` header. * Specify the desired data format for the response body with the :mailheader:`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: .. code-block:: bash curl -sSik https://localhost:8000 \ -b ~/cookies.txt \ -d client=runner \ -d fun='jobs.lookup_jid' \ -d jid='20150129182456704682' .. admonition:: 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.: .. code-block:: bash 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 :ref:`Salt's Python API <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 :py:class:`LocalClient <salt.client.LocalClient>` and :py:class:`RunnerClient <salt.runner.RunnerClient>` have their own timeout parameters that may be passed as top-level keywords: .. code-block:: bash 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 :ref:`Returner system <returners>`. 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. .. __: https://github.com/saltstack/salt/issues/26505 .. |req_token| replace:: a session token from :py:class:`~Login`. .. |req_accept| replace:: the desired response format. .. |req_ct| replace:: the format of the request body. .. |res_ct| replace:: the format of the response body; depends on the :mailheader:`Accept` request header. .. |200| replace:: success .. |400| replace:: bad or malformed request .. |401| replace:: authentication required .. |406| replace:: requested Content-Type not available � N)�Iterator�Mapping)�Pipe�Process)� parse_qsl)�cpstatszjImport of cherrypy.cpstats failed. Possible upstream bug: https://github.com/cherrypy/cherrypy/issues/1444z"Import of cherrypy.cpstats failed.� )�event_processor)� websocketsTr �SynchronizingWebsocketFc C s� t jd } t j}| �dd�| �dd�f}dt jd vrdS |j�|�r%dS |j�d�d kr/dS z t jj� d �}W n t j yC Y dS w |d krJdS t �| �dd���)z� Bypass the normal handler and serve HTML for all URLs The ``app_path`` setting must be non-empty and the request must ask for ``text/html`` in the ``Accept`` header. �apiopts�app_pathz/app�static_path�/static�appNZAcceptz*/*z text/html)�cherrypy�config�request�get� path_info� startswith�headers�lib�cptools�accept� HTTPErrorZInternalRedirect)r r � url_blacklistZ wants_html� r �Q/opt/saltstack/salt/lib/python3.10/site-packages/salt/netapi/rest_cherrypy/app.py�html_override_tool� s&