diff --git a/CHANGELOG.md b/CHANGELOG.md index a34bd63..534016e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [2.1.2] 2019-11-24 +### Added + - Added CircleCI build badge to README. + - Added route key to be defined by user to allow mutliple bots attached to a single StackStorm instance. + - Added version check on start-up to log if newer version of the plugin are available. + +### Changed + - Updated curl example in troubleshooting section. + - Changed all bot commands to be registered dynamically to allow user defined plugin prefix. + +## [2.1.1] 2019-07-19 +### Added + - Added configuration for mattermost, rocketchat, discord, gitter and slack to docker build. + +### Changed + - Improved Discord Chat Adapter formatting. + - Corrected configuration name handling which caused exceptions to be raised. + - Updated documentation to direct readers to readthedocs. + +### Removed + - Removed old readme file. + ## [2.1.0] 2019-07-08 ### Added - Added `deactivate` method to correctly handle errbot reload and stop process. diff --git a/docs/authn.rst b/docs/authn.rst new file mode 100644 index 0000000..62366cd --- /dev/null +++ b/docs/authn.rst @@ -0,0 +1,137 @@ +.. _authentication: + +*************** +Authentication +*************** + +.. contents:: :local: + +.. important:: Do not configure multiple authentication methods at the same time. + +Standalone +========== + +This is the default authentication method where `err-stackstorm` uses its own credentials for all calls to the StackStorm API. All action-aliases issued by chat service users execute the underlying workflows with `err-stackstorm` credentials. + +Any Role Based Access Control policies can only be applied to the bot user which gives limited control. + +Configuration +-------------- + +An empty dictionary in the `standalone` key is all that is required to maintain err-stackstorm's default authentication method. + +.. code-block:: python + + "rbac_auth": { + "standalone": {} + } + +Client-Side +============ + +.. note:: This implementation is specific to err-stackstorm. + +`err-stackstorm` provides a way to associate the chat service user account with a StackStorm username/password or API token. Once a chat user is associated with their StackStorm credentials, any action-alias will be executed using the associated StackStorm credentials. + +This method allows for finer control as StackStorm's Role Based Access Control can be defined per-user. + +Chat users create a new authentication session with `err-stackstorm` by calling `session_start` with a secret word, it can be any thing the user wishes. A `UUID `_ is generated for the session and the chat user is invited to follow a URL to an authentication page hosted by Errbot. + +.. important:: For security reasons, the UUID is used only once and is consumed when the page is accessed. Any subsequent attempts to access the authentication page using the same link will result in an error. + +The login page must be protected by TLS encryption and ideally require an SSL client certificate. The login page should not be exposed directly to the internet, but have a reverse proxy such as nginx placed between it and any external service consumers. + +The user enters their StackStorm credentials via the login page which err-stackstorm will validate against the StackStorm API. If the credentials are valid, the user token or api key will be cached by err-stackstorm and the supplied credentials discarded. + + +Configuration +------------- + +To configure the Client-Side authentication method, one needs to take steps to setup both Nginx and Errbot. Nginx is used to serve static web content for the authentication web page and Errbot serves as the API backend for authentication calls. + + +NGINX +^^^^^^ + +.. note:: This example is provided as a guide only. You are expected to know and understand how to configure nginx for your environment. It is outside of the scope of this documentation to go over nginx's configuration and explain SSL certificates. + +.. important:: This example does not show how to secure access using SSL certificates. It is **highly recommended** to use SSL for production environments. + +To help understand the example below, the following conditions are assumed: + +* the nginx server is running *on the same host* as errbot. +* the host's fully qualified domain name is ``my_host.my_fqdn``. +* nginx listens to standard SSL-enabled port: 443. +* Errbot's webserver listens on the ip address 127.0.0.1 TCP port 3141 without ssl enabled. +* Errbot's plugins directory is /data/errbot/plugins +* `err-stackstorm` is installed in /data/errbot/plugins/nzlosh/err-stackstorm. +* The SSL certificate and private key used by nginx are called errbot.crt and errbot.key. + +.. code-block:: nginx + + server { + listen my_host.my_fqdn:443 ssl; + server_name my_host.my_fqdn; + + ssl on; + + ssl_certificate /etc/ssl/errbot.crt; + ssl_certificate_key /etc/ssl/errbot.key; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES128-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA128:DHE-RSA-AES128-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA128:ECDHE-RSA-AES128-SHA384:ECDHE-RSA-AES128-SHA128:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA384:AES128-GCM-SHA128:AES128-SHA128:AES128-SHA128:AES128-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4; + ssl_prefer_server_ciphers on; + + index index.html index.htm; + + access_log /var/log/nginx/ssl-errbot.access.log combined; + error_log /var/log/nginx/ssl-errbot.error.log; + + add_header Front-End-Https on; + add_header X-Content-Type-Options nosniff; + + location /login/ { + proxy_pass http://127.0.0.1:3141$request_uri; + proxy_read_timeout 90; + proxy_connect_timeout 90; + proxy_redirect off; + + proxy_set_header Host my_host.my_fqdn; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_set_header Connection ''; + chunked_transfer_encoding off; + proxy_buffering off; + proxy_cache off; + proxy_set_header Host my_host.my_fqdn; + } + + location / { + root /data/errbot/plugins/nzlosh/err-stackstorm/html/; + index index.html index.htm; + } + } + +After successfully setting up nginx, the client side authentication url would be ``https://my_host.my_fqdn:443``. + +err-stackstorm configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A url is required to correctly configure client-side authentication for ChatOps. This URL is Errbot's authentication endpoint that you have just set up. + +.. code-block:: python + + "rbac_auth": { + "clientside": { + "url": "https://:/" + } + }, + +Authenticating +^^^^^^^^^^^^^^^ + +Once the client side authentication is setup, you should be able to trigger the authentication process with ``!st2session_start my_secret_word`` which will return a url to complete the login processes. This is how the page looks like: + +.. image:: images/authentication_screen.jpg diff --git a/docs/authz.rst b/docs/authz.rst new file mode 100644 index 0000000..322bc0b --- /dev/null +++ b/docs/authz.rst @@ -0,0 +1,53 @@ +.. _authorisation: + +*************** +Authorisation +*************** + +.. contents:: :local: + + +Errbot Access Control List +========================== + +Errbot comes with native Access Control List support. It can be configured to constrain command execution by grouping ``command``, ``channel`` and ``user``. Glob patterns can be used in each field to provide flexibility in ACL definitions. + +As an example, a StackStorm instance has an automatic package upgrade workflow. Its progress can be viewed by executing the action alias: ``apu stats ``, which is defined as shown below:: + + | action_ref | st2_apu.apu_status | + | formats | [ | + | | { | + | | "representation": [ | + | | "apu status {{role}}" | + | | ], | + | | "display": "apu status " | + | | } | + | | ] | + +The Errbot ACL configuration below allows ``@user1`` to view the status of the upgrade, but *not to start/stop* the upgrade process, which are other action-aliases that are triggered with ``st2 apu ...``) + +.. code-block:: python + + ACL_SQUAD_INFRA = ["@admin1", "@admin2", "@admin3", "@admin4"] + ACL_APU_USERS = ['@user1'] + ACL_EVERYONE = ["*"] + ACCESS_CONTROLS = { + 'whoami': { + 'allowrooms': ['@bot_user'], + 'allowusers': ACL_EVERYONE + }, + 'st2 apu status*':{ + 'allowrooms': ['#channel'], + 'allowusers': ACL_SQUAD_INFRA + ACL_APU_USERS + }, + 'st2 apu*':{ + 'allowrooms': ['#channel'], + 'allowusers': ACL_SQUAD_INFRA + }, + } + +Getting the correct usernames to fill into ``allowusers`` or ``denyusers`` isn't always obvious. Use errbot's ``!whoami`` command to get the correct username for use within ACL definitions. The `nick` value is what should be used in the configuration in the case of Slack. + +.. warning:: UI interface names do not always match with the internal nickname/username. ``!whoami`` is a surefire way of retrieving the correct username. + +On a small scale it's possible to use the ``!whoami`` command to get the correct user account name but for large installations it'd make more sense to use pre-defined patterns. diff --git a/docs/configuration.rst b/docs/configuration.rst index 1cde90e..040e516 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -21,7 +21,8 @@ Here's a sample err-stackstorm configuration: 'auth_url': 'https://your.stackstorm.com/auth/v1', 'api_url': 'https://your.stackstorm.com/api/v1', 'stream_url': 'https://your.stackstorm.com/stream/v1', - + 'route_key': 'errbot', + 'plugin_prefix': 'st2', 'verify_cert': True, 'secrets_store': 'cleartext', 'api_auth': { @@ -52,6 +53,7 @@ Edit the ``//packs/chatops/actions/post_message.yaml`` file and replace `ch route: default: "errbot" +.. note:: See Route Key below for information on customising the route. Authentication --------------- @@ -105,6 +107,16 @@ Cleartext The cleartext store maintains the cache in memory and does not encrypt the contents to disk. It **does not** protect the stored secrets in memory. +Route Key +--------- + +StackStorm ChatOps uses `routes` to indicate where a notification should be sent. By default the StackStorm ChatOps pack uses **chatops** as the route kei to send messages when an action result is posted. It is possible to run more than one errbot instance per StackStorm instance by configuring different route keys. Such a feature would allow running one errbot instance that listens on Slack and another that listens on Discord, where both would expose StackStorm's action-aliases. + +Plugin Prefix +------------- + +Errbot detects commands using a **bot_plugin** prefix, often ``!`` character. Errbot functionality is extended through plugins. Plugins register new commands with Errbot as they are loaded. Err-stackstorm is a plugin and adds a special command for calling StackStorm Action-Aliases. To avoid name collisions between *Errbot Commands* and *StackStorm Action-Aliases*, a **plugin_prefix** is used which is ``st2`` by default. The plugin_prefix can be customised to be any string, but be careful not to use strings that conflict with existing commands. + Locale ------- @@ -112,7 +124,7 @@ Errbot uses the system's locale for handling text. If you're getting unicode err UnicodeEncodeError: 'ascii' codec can't encode character '\xe9' in position 83: ordinal not in range(128) -Make sure the systems locale is configured for unicode encoding. In the example below, the machine has set the English (en) New Zealand (NZ) with utf-8 encoding (.UTF8). +Make sure the systems locale is configured for unicode encoding. In the example below, the machine has been set to English (en) New Zealand (NZ) with utf-8 encoding (.UTF8). .. code-block:: bash @@ -142,15 +154,17 @@ Reference :header: "Option", "Description" :widths: 25, 40 - "auth_url", "StackStorm's authentication url end point. Used to authenticate credentials against StackStorm." - "api_url", "StackStorm's API url end point. Used to execute action aliases received from the chat back-end." - "stream_url", "StackStorm's Stream url end point. Used to received ChatOps notifications." - "verify_cert", "Default is *True*. Verify the SSL certificate is valid when using https end points. Applies to all end points." + "auth_url", "StackStorm's authentication url end point. Used to authenticate credentials against StackStorm." + "api_url", "StackStorm's API url end point. Used to execute action aliases received from the chat back-end." + "stream_url", "StackStorm's Stream url end point. Used to received ChatOps notifications." + "verify_cert", "Default is *True*. Verify the SSL certificate is valid when using https end points. Applies to all end points." + "route_key", "Default is *errbot*. The name of the route to bot will listen for and submit action-alias executions with." + "plugin_prefix", "Default is *st2*. Text used to prefix action-alias commands with to avoid name collisions between StackStorm Action-Aliases and Errbot plugin commands." "api_auth.user.name", "Errbot's username to authenticate with StackStorm." "api_auth.user.password", "Errbot's password to authenticate with StackStorm." "api_auth.token", "Errbot's user token to authenticate with StackStorm. Used instead of a username/password pair." "api_auth.apikey", "Errbot API key to authenticate with StackStorm. Used instead of a username/password pair or user token." - "timer_update", "Unit: seconds. Default is 60. Interval for err-stackstorm to the user token is valid." + "timer_update", "Unit: seconds. Default is 60. Interval to test if err-stackstorm's user token is valid." "rbac_auth.standalone", "Standalone authentication." "rbac_auth.clientside", "Clientside authentication, a chat user will supply StackStorm credentials to err-stackstorm via an authentication page." "rbac_auth.clientside.url", "Url to the authentication web page." diff --git a/docs/getting_started.rst b/docs/getting_started.rst index e473813..5870792 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -9,33 +9,34 @@ Getting Started Overview ========= -`err-stackstorm` is an unofficial community project to bring StackStorm ChatOps to Errbot. Using `err-stackstorm` you will be able to leverage `Errbot `_ as the `ChatOps `_ of your choice for controlling Stackstorm using **conversation-driven development** by using Stackstorm's `Action Aliases `_. +`err-stackstorm` is a community project to bring StackStorm `ChatOps `_ to `Errbot `_. No commercial support is provided by StackStorm. `err-stackstorm` exposes Stackstorm's `Action Aliases `_ to your chat environment, where you and your team can manage aspects of infrastructure and code. +The objectives for err-stackstorm project are: 1. Emulate hubot-stackstorm features. 2. Provide a Python friendly ChatOps solution. 3. Respect StackStorm's enterprise offering. 4. Maintain the same high quality as the StackStorm project. 5. Collaborate with the StackStorm community to evolve ChatOps features. -.. important:: `st2chatops` does not have to be running on your StackStorm instance. This plugin *replaces* `st2chatops`. - Features ======== err-stackstorm communicates directly with the StackStorm API from with an errbot instance. - - List action-alias help dynamically. When StackStorm action-aliases are updated, they are immediately available in the err-stackstorm output. Filtering by pack name and keyword can be used when look for help. + - List action-alias help dynamically. When StackStorm action-aliases are updated, they are immediately available in the err-stackstorm output. Filtering by pack name and keyword can be used when looking for help. - Access-Control Lists based on any combination of chat username, command and room. ACLs are defined in the errbot configuration file. - - Associate StackStorm user credentials with chat usernames. Client-Side authenticate lets err-stackstorm dynamically map chat user accounts with StackStorm authenicated users via an authenticationn web page. + - Associate StackStorm user credentials with chat usernames. Client-Side authenticate lets err-stackstorm dynamically map chat user accounts with StackStorm authenicated users. Credentials are passed via an out of band authentication web page. - Trigger action-aliases directly from the bot. - Support for multiple chat backends, as provided by errbot. + - Customise plugin prefix. + - Customise StackStorm route key to allow more than one bot to be connected to a single StackStorm instance. - Docker image available to get up and running quickly and easily. - Python based using modern 3.6 features. Compatibility ============== -The plugin has been developed and tested against the below software combinations. Because you might be running different Python or Errbot versions, we provide here the combinations to achieve optimal operation: +The plugin has been developed and tested against the below software combinations. Because you might be running different Python or Errbot versions, the below are the optimal combinations: .. csv-table:: Ideal Combination of Versions diff --git a/docs/index.rst b/docs/index.rst index 45539e1..bc9adc8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,7 +8,7 @@ Welcome to err-stackstorm's documentation! ========================================== -err-stackstorm is an unofficial community project to bring StackStorm ChatOps to Errbot. +err-stackstorm is a community project to bring StackStorm ChatOps to Errbot. No commercial support is provided by StackStorm. .. toctree:: :maxdepth: 2 @@ -18,7 +18,8 @@ err-stackstorm is an unofficial community project to bring StackStorm ChatOps to quick_start.rst installation.rst configuration.rst - rbac.rst + authn.rst + authz.rst action_aliases.rst troubleshooting.rst project.rst diff --git a/docs/installation.rst b/docs/installation.rst index e484748..469c0c0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -48,7 +48,8 @@ Paste the sample configuration below in Errbot's ``config.py`` file adjusting th 'auth_url': 'https://your.stackstorm.com/auth/v1', 'api_url': 'https://your.stackstorm.com/api/v1', 'stream_url': 'https://your.stackstorm.com/stream/v1', - + 'route_key': 'errbot', + 'plugin_prefix': 'st2', 'verify_cert': True, 'secrets_store': 'cleartext', 'api_auth': { @@ -62,8 +63,7 @@ Paste the sample configuration below in Errbot's ``config.py`` file adjusting th 'rbac_auth': { 'standalone': {}, }, - 'timer_update': 900, # Unit: second. Interval to check the user token - is still valid. + 'timer_update': 900, # Unit: second. Interval to check the user token is still valid. } diff --git a/docs/mattermost b/docs/mattermost deleted file mode 100644 index 85b8a76..0000000 --- a/docs/mattermost +++ /dev/null @@ -1,167 +0,0 @@ -# mattermost - -## Troubleshooting - -### Errbot virtual environment not activated - -Ran errbot setup guide and got an error using a virtualenv. -``` - errbot --init, the command is not found. -``` - -The virtual environment wasn't activated correctly. - -Errbot doesn't install module dependencies unless explicitly configured to do so. -Enable installation of dependencies when the plugin is installed using the -`!repos install` command. http://errbot.io/en/latest/user_guide/administration.html#dependencies - - -### WebserverConfiguration plugin caused errors: - -`Error activating plugin: WebserverConfiguration failed to start : [Errno 11] Resource temporarily unavailable` - -Removing the plugin resolved the issue. - - -Install script for mattermost - -#!/bin/bash - -echo "Centos 7.6 installed" -yum update -yum install python36 python36-pip -pip3.6 install virtualenv - -mkdir /root/errbot - -python3.6 /usr/local/lib/python3.6/site-packages/virtualenv.py --python `which python3.6` /root/errbot/errbot-ve -source /root/errbot/errbot-ve/bin/activate -pip3.6 install errbot - -mkdir -p /root/errbot/errbot-root/backend -mkdir -p /root/errbot/errbot-root/plugins - -cd /root/errbot/errbot-root/ -errbot --init - -echo 'Install Errbot Mattermost backend: https://github.com/Vaelor/errbot-mattermost-backend' -cd /root/errbot/errbot-root/backend -pip install git+https://github.com/Vaelor/errbot-mattermost-backend.git /root/errbot/errbot-mattermost-backend -cd /root/errbot/errbot-root/backend/errbot-mattermost-backend -pip install -r requirements.txt - -cd /root/errbot/errbot-root/plugins -pip install git+https://github.com/nzlosh/err-stackstorm.git -cd /root/errbot/errbot-root/plugins/err-stackstorm -pip install -r requirements.txt - -cat </root/errbot/errbot-root/config.py - import logging - - # This is a minimal configuration to get you started with the Text mode. - # If you want to connect Errbot to chat services, checkout - # the options in the more complete config-template.py from here: - # https://raw.githubusercontent.com/errbotio/errbot/master/errbot/config-template.py - - BACKEND = 'Mattermost' # Errbot will start in text mode (console only mode) and will answer commands from there. - - BOT_EXTRA_BACKEND_DIR = '/root/errbot/errbot-mattermost-backend' - - BOT_IDENTITY = { - # Required - 'team': 'test', - 'server': 'chat.thischanged.com', - # For the login, either - 'login': 'bot', - 'password': 'thischanged', - # Or, if you have a personal access token - #'token': 'YourPersonalAccessToken', - # Optional - 'insecure': False, # Default = False. Set to true for self signed certificates - 'scheme': 'https', # Default = https - 'port': 443, # Default = 8065 - 'timeout': 30, # Default = 30. If the webserver disconnects idle connections later/earlier change this value - 'cards_hook': 'incomingWebhookId' # Needed for cards/attachments - } - - BOT_DATA_DIR = r'/root/errbot/errbot-root/data' - BOT_EXTRA_PLUGIN_DIR = r'/root/errbot/errbot-root/plugins' - - BOT_LOG_FILE = r'/root/errbot/errbot-root/errbot.log' - BOT_LOG_LEVEL = logging.DEBUG - - BOT_ADMINS = ('@lenba', ) # !! Don't leave that to "@CHANGE_ME" if you connect your errbot to a chat system !! - - STACKSTORM = { - 'auth_url': 'https://192.168.1.17/auth/v1', - 'api_url': 'https://192.168.1.17/api/v1', - 'stream_url': 'https://192.168.1.17/stream/v1', - - 'verify_cert': False, - 'secrets_store': "cleartext", - 'api_auth': { - 'user': { - 'name': 'admin', - 'password': "admin", - } - }, - 'rbac_auth': { - 'standalone': {} - }, - 'timer_update': 900, # Unit: second. Interval for Errbot to refresh to list of available action aliases. - } -EOF - -echo "Configuring errbot unit file for systemd" -cat </etc/systemd/system/errbot.service - - [Unit] - Description=Errbot - After=network.target - - [Service] - User=root - ExecStart=/root/errbot/errbot-ve/bin/errbot --config /root/errbot/errbot-root/config.py - ExecStop=/bin/kill -SIGINT $MAINPID - - [Install] - WantedBy=multi-user.target -EOF - -systemctl daemon-reload -systemctl enable errbot -systemctl start errbot - - -echo "Configure Stackstorm" - -cat < /opt/stackstorm/packs/chatops/rules/notify_errbot.yaml ---- -name: "notify-errbot" -pack: "chatops" -enabled: true -description: "Notification rule to send results of action executions to stream for chatops" -trigger: - type: "core.st2.generic.notifytrigger" -criteria: - trigger.route: - pattern: "errbot" - type: "equals" -action: - ref: chatops.post_result - parameters: - channel: "{{trigger.data.source_channel}}" - user: "{{trigger.data.user}}" - execution_id: "{{trigger.execution_id}}" -EOF - -echo "Updating post_message.yaml to use errbot instead of chatops" -sed -i.bak 's/"chatops"/"errbot"/g' /opt/stackstorm/packs/chatops/actions/post_message.yaml - -cat </issues/new>`_ on the `github repository `_. +If you think you've found a bug or need a new feature `open an issue `_ on the `github repository `_. If you want to contribute to the err-stackstorm project, there are plenty of improvements to be made, contact nzlosh via chat or email to discuss how you can get involved. diff --git a/docs/quick_start.rst b/docs/quick_start.rst index 5732023..f5d1e06 100644 --- a/docs/quick_start.rst +++ b/docs/quick_start.rst @@ -18,7 +18,8 @@ If you are familiar with Errbot and Stackstorm, this guide will get you up and r 'auth_url': 'https://your.stackstorm.com/auth/v1', 'api_url': 'https://your.stackstorm.com/api/v1', 'stream_url': 'https://your.stackstorm.com/stream/v1', - + 'route_key': 'errbot', + 'plugin_prefix': 'st2', 'verify_cert': True, 'secrets_store': 'cleartext', 'api_auth': { @@ -49,3 +50,5 @@ If you are familiar with Errbot and Stackstorm, this guide will get you up and r 7. Sending ``!st2help`` to your bot will list the available Stackstorms's aliases. 8. Aliases can be run like this: ``!st2 run date on 192.168.5.1`` + +.. important:: `st2chatops` does not have to be running on your StackStorm instance. This plugin *replaces* `st2chatops`. diff --git a/docs/rbac.rst b/docs/rbac.rst deleted file mode 100644 index 395d402..0000000 --- a/docs/rbac.rst +++ /dev/null @@ -1,183 +0,0 @@ -.. _rbac: - -***** -RBAC -***** - -.. contents:: :local: - -.. important:: Do not configure multiple RBAC authentication methods at the same time. - -Standalone -========== - -This is the default authentication method where `err-stackstorm` uses its own credentials for all calls to the StackStorm API. All action-aliases issued by chat service users execute the underlying workflows with `err-stackstorm` credentials. - -Any Role Based Access Control policies would only be applied to the bot user which gives only a very coarse control. - -Configuration --------------- - -An empty dictionary in the `standalone` key is all that is required to maintain err-stackstorm's default authentication method. - -.. code-block:: python - - "rbac_auth": { - "standalone": {} - } - -Client-Side -============ - -.. note:: This implementation is specific to err-stackstorm. - -`err-stackstorm` provides a way to associate the chat service user account with a StackStorm username/password or API token. Once a chat user is associated with their StackStorm credentials, any action-alias will be executed using the associated StackStorm credentials. - -This method allows for finer control as Role Based Access Control can be defined per-user. - -This achieved by requesting a new authentication session with `err-stackstorm`. A `UUID `_ is generated for the session and the chat user is invited to follow a URL to an authentication page hosted by Errbot. - -.. important:: For security reasons, the UUID is used only once and is consumed when the page is accessed. Any subsequent attempts to access the authentication page using the same link will result in an error. - -The login page must be protected by TLS encryption and ideally require an SSL client certificate. The login page should not be exposed directly to the internet, but have a reverse proxy such as nginx placed between it and any external service consumers. - -The user enters their StackStorm credentials via the login page which err-stackstorm will validate against the StackStorm API. If the credentials are valid, the user token or api key will be cached by err-stackstorm and the supplied credentials discarded. - - -Configuration -------------- - -To configure the Client-Side RBAC method, one needs to take steps to setup both Nginx and Errbot. Nginx is used to serve static web content for the authentication web page and Errbot functions as the API backend for authentication calls. - - -NGINX -^^^^^^ - -.. note:: This example is provided as a guide and you are expected to know and understand how to configure nginx for your environment. It is outside of the scope of this documentation to go over nginx's configuration and explain SSL certificates. - -.. important:: This example does not show how to secure access using SSL certificates. It is **highly recommended** to use SSL for production environments. - -To help understand the example below, the following is assumed: - -* the nginx server is running *on the same host* as errbot. -* the host's fully qualified domain name is ``my_host.my_fqdn``. -* nginx listens to standard SSL-enabled port: 443. -* Errbot's webserver listens on the ip address 127.0.0.1 TCP port 3141 without ssl enabled. -* Errbot's plugins directory is /data/errbot/plugins -* `err-stackstorm` is installed in /data/errbot/plugins/nzlosh/err-stackstorm. -* The SSL certificate and private key used by nginx are called errbot.crt and errbot.key. - -.. code-block:: nginx - - server { - listen my_host.my_fqdn:443 ssl; - server_name my_host.my_fqdn; - - ssl on; - - ssl_certificate /etc/ssl/errbot.crt; - ssl_certificate_key /etc/ssl/errbot.key; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 5m; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES128-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA128:DHE-RSA-AES128-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA128:ECDHE-RSA-AES128-SHA384:ECDHE-RSA-AES128-SHA128:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA384:AES128-GCM-SHA128:AES128-SHA128:AES128-SHA128:AES128-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4; - ssl_prefer_server_ciphers on; - - index index.html index.htm; - - access_log /var/log/nginx/ssl-errbot.access.log combined; - error_log /var/log/nginx/ssl-errbot.error.log; - - add_header Front-End-Https on; - add_header X-Content-Type-Options nosniff; - - location /login/ { - proxy_pass http://127.0.0.1:3141$request_uri; - proxy_read_timeout 90; - proxy_connect_timeout 90; - proxy_redirect off; - - proxy_set_header Host my_host.my_fqdn; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_set_header Connection ''; - chunked_transfer_encoding off; - proxy_buffering off; - proxy_cache off; - proxy_set_header Host my_host.my_fqdn; - } - - location / { - root /data/errbot/plugins/nzlosh/err-stackstorm/html/; - index index.html index.htm; - } - } - -After successfully setting up nginx, the client side authentication url would be ``https://my_host.my_fqdn:443``. - -err-stackstorm configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A url is required to correctly configure client-side authentication for ChatOps. This URL is Errbot's authentication endpoint that you have just set up. - -.. code-block:: python - - "rbac_auth": { - "clientside": { - "url": "https://:/" - } - }, - -Authenticating -^^^^^^^^^^^^^^^ - -Once the client side authentication is setup, you should be able to trigger the authentication process with ``!st2authenticate my_secret_word`` which will return a url to complete the login processes. This is how the page looks like: - -.. image:: images/authentication_screen.jpg - - -Errbot's ACL -============= - -Errbot comes with native Access Control List support. It can be configured to constrain command execution by grouping ``command``, ``channel`` and ``user``. Glob patterns can be used in each field to provide flexibility in ACL definitions. - -As an example, a StackStorm instance has an automatic package upgrade workflow. Its progress can be viewed by executing the action alias: ``apu stats ``, which is defined as shown below:: - - | action_ref | st2dm_apu.apu_status | - | formats | [ | - | | { | - | | "representation": [ | - | | "apu status {{role}}" | - | | ], | - | | "display": "apu status " | - | | } | - | | ] | - -The Errbot ACL configuration below allows ``@user1`` to view the status of the upgrade, but *not to start/stop* the upgrade process (they are other action aliases that are triggered with st2 apu ...) - -.. code-block:: python - - ACL_SQUAD_INFRA = ["@admin1", "@admin2", "@admin3", "@admin4"] - ACL_APU_USERS = ['@user1'] - ACL_EVERYONE = ["*"] - ACCESS_CONTROLS = { - 'whoami': { - 'allowrooms': ['@bot_user'], - 'allowusers': ACL_EVERYONE - }, - 'st2 apu status*':{ - 'allowrooms': ['#channel'], - 'allowusers': ACL_SQUAD_INFRA + ACL_APU_USERS - }, - 'st2 apu*':{ - 'allowrooms': ['#channel'], - 'allowusers': ACL_SQUAD_INFRA - }, - } - -Getting the correct usernames to fill into ``allowusers`` or ``denyusers`` isn't always obvious. Use errbot's ``!whoami`` command to get the correct us that can be correctly used within ACL definitions. The `nick` value is what should be used in the configuration. - -.. warning:: UI interface names do not always match with the internal nickname/username. ``!whoami`` is a surefire way of retrieving the correct username. - -On a small scale it's possible to use the ``!whoami`` command to get the correct user account name but for large installations it'd make more sense to use pre-defined patterns. diff --git a/lib/config.py b/lib/config.py index 7c42e37..7af8f1e 100644 --- a/lib/config.py +++ b/lib/config.py @@ -23,7 +23,7 @@ class PluginConfiguration(BorgSingleton): def __init__(self): super(BorgSingleton, self).__init__() - def setup(self, bot_conf, plugin_prefix): + def setup(self, bot_conf): if not hasattr(bot_conf, "STACKSTORM"): LOG.critical( "Missing STACKSTORM configuration in config.py. err-stackstorm must be configured" @@ -31,7 +31,6 @@ def setup(self, bot_conf, plugin_prefix): " homepage." ) bot_conf.__setattr__("STACKSTORM", {}) - self.plugin_prefix = plugin_prefix self._configure_prefixes(bot_conf) self._configure_credentials(bot_conf) self._configure_rbac_auth(bot_conf) @@ -40,6 +39,7 @@ def setup(self, bot_conf, plugin_prefix): self.timer_update = bot_conf.STACKSTORM.get("timer_update", 60) self.verify_cert = bot_conf.STACKSTORM.get("verify_cert", True) self.secrets_store = bot_conf.STACKSTORM.get("secrets_store", "cleartext") + self.route_key = bot_conf.STACKSTORM.get("route_key", "errbot") self.client_cert = bot_conf.STACKSTORM.get("client_cert", None) self.client_key = bot_conf.STACKSTORM.get("client_key", None) @@ -61,6 +61,7 @@ def _configure_rbac_auth(self, bot_conf): def _configure_prefixes(self, bot_conf): self.bot_prefix = bot_conf.BOT_PREFIX + self.plugin_prefix = bot_conf.STACKSTORM.get("plugin_prefix", "st2") self.full_prefix = "{}{} ".format(bot_conf.BOT_PREFIX, self.plugin_prefix) def _configure_stackstorm(self, bot_conf): diff --git a/lib/stackstorm_api.py b/lib/stackstorm_api.py index d5a65ea..45405b7 100644 --- a/lib/stackstorm_api.py +++ b/lib/stackstorm_api.py @@ -173,7 +173,7 @@ def listener(callback=None, bot_identity=None): stream = sseclient.SSEClient(stream_url, **stream_kwargs) for event in stream: - if event.event in ["st2.announcement__errbot", "st2.announcement__chatops"]: + if event.event == "st2.announcement__{}".format(self.cfg.route_key): LOG.debug( "*** Errbot announcement event detected! ***\n{}\n{}\n".format( event.dump(), diff --git a/st2.py b/st2.py index a8f0e12..1b3c2bf 100644 --- a/st2.py +++ b/st2.py @@ -2,23 +2,43 @@ import json import logging import threading +import requests from types import SimpleNamespace -from errbot import BotPlugin, re_botcmd, botcmd, arg_botcmd, webhook + +from errbot import ( + BotPlugin, + Command, + re_botcmd, + botcmd, + arg_botcmd, + webhook +) + from lib.config import PluginConfiguration from lib.chat_adapters import ChatAdapterFactory -from lib.errors import SessionConsumedError, SessionExpiredError, \ - SessionInvalidError, SessionExistsError +from lib.errors import ( + SessionConsumedError, + SessionExpiredError, + SessionInvalidError, + SessionExistsError +) from lib.stackstorm_api import StackStormAPI -from lib.authentication_controller import AuthenticationController, BotPluginIdentity -from lib.credentials_adapters import St2ApiKey, St2UserToken, St2UserCredentials -from lib.authentication_handler import AuthHandlerFactory, ClientSideAuthHandler +from lib.authentication_controller import ( + AuthenticationController, + BotPluginIdentity +) +from lib.credentials_adapters import ( + St2ApiKey, + St2UserToken, + St2UserCredentials +) +from lib.authentication_handler import ( + AuthHandlerFactory, + ClientSideAuthHandler +) LOG = logging.getLogger("errbot.plugin.st2") - -# TODO: FIXME: Set the PLUGIN_PREFIX based on configuration from errbot config.py. -# A plugin prefix for stackstorm action aliases to avoid name collisions between -# them and native errbot plugins. Defined here so it's available to errbot's facade decorator. -PLUGIN_PREFIX = r"st2" +ERR_STACKSTORM_VERSION = "2.1.2" class St2(BotPlugin): @@ -32,7 +52,7 @@ def __init__(self, bot, name): # Initialised shared configuraiton with the bot's stackstorm settings. try: self.cfg = PluginConfiguration() - self.cfg.setup(self.bot_config, PLUGIN_PREFIX) + self.cfg.setup(self.bot_config) except Exception as e: LOG.critical( "Errors were encountered processing the STACKSTORM configuration." @@ -54,6 +74,31 @@ def __init__(self, bot, name): self.run_listener = True self.st2events_listener = None + self.dynamic_commands() + self.check_latest_version() + + def check_latest_version(self): + url = "https://raw.githubusercontent.com/nzlosh/err-stackstorm/master/version.json" + response = requests.get(url, timeout=5) + + if response.status_code != 200: + LOG.warning( + "Unable to fetch err-stackstorm version from {}. HTTP code: {}".format( + url, + response.status_code + ) + ) + return True + + latest = response.json().get("version") + if latest is None: + LOG.warning("Failed to read err-stackstorm 'version' from {}.".format(url)) + return True + + if ERR_STACKSTORM_VERSION != latest: + LOG.info("err-stackstorm can be updated to {}.".format(latest)) + else: + LOG.info("err-stackstorm {} is up to date.".format(ERR_STACKSTORM_VERSION)) def authenticate_bot_credentials(self): """ @@ -133,14 +178,12 @@ def deactivate(self): LOG.info("st2stream listener exited.") del self.st2events_listener - @botcmd(admin_only=True) def st2sessionlist(self, msg, args): """ List any established sessions between the chat service and StackStorm API. """ return "Sessions: " + "\n\n".join(self.accessctl.list_sessions()) - @botcmd(admin_only=True) def st2sessiondelete(self, msg, args): """ Delete an established session. @@ -148,7 +191,6 @@ def st2sessiondelete(self, msg, args): if len(args) > 0: self.accessctl.delete_session(args) - @botcmd def st2disconnect(self, msg, args): """ Usage: st2disconnect @@ -156,7 +198,6 @@ def st2disconnect(self, msg, args): """ return "Not implemented yet." - @botcmd def st2authenticate(self, msg, args): """ Usage: st2authenticate @@ -188,7 +229,6 @@ def st2authenticate(self, msg, args): self.accessctl.session_url(session.id(), "/index.html") ) - @re_botcmd(pattern='^{} .*'.format(PLUGIN_PREFIX)) def st2_execute_actionalias(self, msg, match): """ Run an arbitrary stackstorm command. @@ -256,16 +296,12 @@ def remove_bot_prefix(msg): ] ) else: - result = "st2 command '{}' is disabled.".format(msg.body) + result = "The command '{}' is disabled.".format(msg.body) else: result = matched_result.message return result - @arg_botcmd("--pack", dest="pack", type=str) - @arg_botcmd("--filter", dest="filter", type=str) - @arg_botcmd("--limit", dest="limit", type=int) - @arg_botcmd("--offset", dest="offset", type=int) - def st2help(self, msg, pack=None, filter=None, limit=None, offset=None): + def st2help(self, msg, args, pack=None, filter=None, limit=None, offset=None): """ Provide help for StackStorm action aliases. """ @@ -378,3 +414,84 @@ def login_auth(self, request, uuid): LOG.warning(r.message) return json.dumps(vars(r)) + + def dynamic_commands(self): + """ + Register commands. + """ + + def st2help(plugin, msg, pack=None, filter=None, limit=None, offset=None): + return self.st2help(msg, pack, filter, limit, offset) + + def append_args(func, args, kwargs): + wrapper = func.definition + wrapper._err_command_parser.add_argument(*args, **kwargs) + wrapper.__doc__ = wrapper._err_command_parser.format_help() + fmt = wrapper._err_command_parser.format_usage() + wrapper._err_command_syntax = fmt[ + len('usage: ') + len(wrapper._err_command_parser.prog) + 1:-1 + ] + + Help_Command = Command( + st2help, + name="{}help".format(self.cfg.plugin_prefix), + cmd_type=arg_botcmd, + cmd_args=("--pack",), + cmd_kwargs={"dest": "pack", "type": str}, + doc="Provide help for StackStorm action aliases." + ) + append_args(Help_Command, ("--filter",), {"dest": "filter", "type": str}) + append_args(Help_Command, ("--limit",), {"dest": "limit", "type": int}) + append_args(Help_Command, ("--offset",), {"dest": "offset", "type": int}) + + self.create_dynamic_plugin( + name="St2", + doc="StackStorm plugin for authentication and Action Alias execution." + " Use {}{}help for action alias" + " help.".format(self.cfg.bot_prefix, self.cfg.plugin_prefix), + commands=( + Command( + lambda plugin, msg, args: self.st2sessionlist(msg, args), + name="{}session_list".format(self.cfg.plugin_prefix), + cmd_type=botcmd, + cmd_kwargs={"admin_only": True}, + doc="List any established sessions between the chat service and StackStorm API." + ), + Command( + lambda plugin, msg, args: self.st2sessiondelete(msg, args), + name="{}session_cancel".format(self.cfg.plugin_prefix), + cmd_type=botcmd, + cmd_kwargs={"admin_only": True}, + doc="Allow an administrator to cancel a users session." + ), + Command( + lambda plugin, msg, args: self.st2disconnect(msg, args), + name="{}session_end".format(self.cfg.plugin_prefix), + cmd_type=botcmd, + cmd_kwargs={"admin_only": False}, + doc="End a user session. StackStorm credentials are " + "purged when the session is closed." + ), + Command( + lambda plugin, msg, args: self.st2authenticate(msg, args), + name="{}session_start".format(self.cfg.plugin_prefix), + cmd_type=botcmd, + cmd_kwargs={"admin_only": False}, + doc="Usage: {}session_start .\n" + "Authenticate with StackStorm API over an out of bands communication" + " channel. User Token or API Key are stored in a user session managed by" + " err-stackstorm.".format(self.cfg.plugin_prefix) + ), + Command( + lambda plugin, msg, args: self.st2_execute_actionalias(msg, args), + name="{} ".format(self.cfg.plugin_prefix), + cmd_type=re_botcmd, + cmd_kwargs={"pattern": "^{} .*".format(self.cfg.plugin_prefix)}, + doc="Run an arbitrary StackStorm command (action-alias).\n" + "Available commands can be listed using {}{}help".format( + self.cfg.bot_prefix, + self.cfg.plugin_prefix + ) + ), + Help_Command) + ) diff --git a/version.json b/version.json new file mode 100644 index 0000000..824cd49 --- /dev/null +++ b/version.json @@ -0,0 +1,3 @@ +{ + "version": "2.1.2" +}