From 8389b356f4f17625f504347e18266742049bbc94 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Sun, 26 Feb 2017 16:23:54 -0500 Subject: [PATCH 01/18] Added Docker support Signed-off-by: Yanlin Wang --- Dockerfile | 13 +++++++++++++ README.md | 36 ++++++++++++++++++++++++++++++++++++ docker_entrypoint.sh | 29 +++++++++++++++++++++++++++++ main.py | 12 +++++++----- securitybot/sql.py | 9 ++++++++- util/db_up.py | 10 ++++++---- 6 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 Dockerfile create mode 100755 docker_entrypoint.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..87abcce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:2.7 + +COPY . /securitybot + +env PYTHONPATH $PYTHONPATH:/securitybot + +RUN apt-get update && \ + apt-get install -y mysql-client && \ + pip install -r /securitybot/requirements.txt + +WORKDIR /securitybot + +ENTRYPOINT ["/securitybot/docker_entrypoint.sh"] diff --git a/README.md b/README.md index 440aa64..530f5db 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,42 @@ If the following were all generated successfully, Securitybot should be up and r To test it, message the bot user it's assigned to and say `hi`. To test the process of dealing with an alert, message `test` to test the bot. +#### Environment variable +To prevent having to modify the scripts, you can set the following environment variables to configure the bot: + +|Variable|Setting| +|--------|-------| +|SLACK_API_TOKEN|Slack API Token| +|REPORTING_CHANNEL|Slack Channel ID| +|DUO_INTEGRATION_KEY|Duo Integration Key| +|DUO_SECRET_KEY|Duo Secret Key| +|DUO_ENDPOINT|Duo Endpoint| +|DB_HOST|MySQL Hostname| +|DB_USER|MySQL Username| +|DB_PASS|MySQL Password| +|DB_NAME|MySQL Database name| + +#### Docker + +Dockerfile is included to generate a Docker Image to run the bot. Entrypoint script will wait for database startup and initialize database if it does not already exist. Entrypoint takes one of two arguments: + +* bot: Starts the main bot +* fronend: starts the frontend and API server + +Run configuration will be based on the environment variables above. + +Example: + +Bot: +``` +docker run -e DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password -e SLACK_API_TOKEN= -e DUO_INTEGRATION_KEY= -e DUO_SECRET_KEY= -e DUO_ENDPOINT= quay.io/patl/securitybot bot +``` + +Frontend: +``` +docker run -p 8888:8888 -e DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password quay.io/patl/securitybot frontend +``` + ## Architecture Securitybot was designed to be as modular as possible. This means that it's possible to easily swap out chat systems, 2FA providers, and alerting data sources. diff --git a/docker_entrypoint.sh b/docker_entrypoint.sh new file mode 100755 index 0000000..b622d6b --- /dev/null +++ b/docker_entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + + +end=$((SECONDS + 120)) + +# Wait max of 2 minutes for database to come up +until mysql --user $DB_USER --password=$DB_PASS --host=$DB_HOST -e"quit" || [ $SECONDS -ge $end ]; do echo "Waiting for DB"; sleep 2; done; + +# Check if database is initialized +if ! mysql --user $DB_USER --password=$DB_PASS --host=$DB_HOST -e"use $DB_NAME"; then + echo "Creating database" + mysql --user $DB_USER --password=$DB_PASS --host=$DB_HOST -e"create database $DB_NAME" + echo "Populating database" + python /securitybot/util/db_up.py +fi + + +if [ "$1" = 'bot' ]; then + echo "Starting bot" + shift + exec python /securitybot/main.py "$@" +elif [ "$1" = 'frontend' ]; then + echo "Starting frontend" + shift + exec python /securitybot/frontend/securitybot_frontend.py "$@" +fi + +exec "$@" diff --git a/main.py b/main.py index 9d81a72..b67cf2e 100644 --- a/main.py +++ b/main.py @@ -6,14 +6,16 @@ from securitybot.tasker.sql_tasker import SQLTasker from securitybot.auth.duo import DuoAuth from securitybot.sql import init_sql +from os import getenv + import duo_client CONFIG = {} -SLACK_KEY = 'slack_api_token' -DUO_INTEGRATION = 'duo_integration_key' -DUO_SECRET = 'duo_secret_key' -DUO_ENDPOINT = 'duo_endpoint' -REPORTING_CHANNEL = 'some_slack_channel_id' +SLACK_KEY = getenv('SLACK_API_TOKEN', 'slack_api_token') +DUO_INTEGRATION = getenv('DUO_INTEGRATION_KEY', 'duo_integration_key') +DUO_SECRET = getenv('DUO_SECRET_KEY', 'duo_secret_key') +DUO_ENDPOINT = getenv('DUO_ENDPOINT', 'duo_endpoint') +REPORTING_CHANNEL = getenv('REPORTING_CHANNEL', 'some_slack_channel_id') ICON_URL = 'https://dl.dropboxusercontent.com/s/t01pwfrqzbz3gzu/securitybot.png' def init(): diff --git a/securitybot/sql.py b/securitybot/sql.py index 43ebd7d..f5434ec 100644 --- a/securitybot/sql.py +++ b/securitybot/sql.py @@ -4,8 +4,15 @@ import MySQLdb import logging +from os import getenv from typing import Any, Sequence +DB_HOST = getenv('DB_HOST', 'localhost') +DB_USER = getenv('DB_USER', 'root') +DB_PASS = getenv('DB_PASS', '') +DB_NAME = getenv('DB_NAME', 'securitybot') + + class SQLEngine(object): # Whether the singleton has been instantiated _host = None # type: str @@ -90,4 +97,4 @@ class SQLEngineException(Exception): def init_sql(): # type: () -> None '''Initializes SQL.''' - SQLEngine('localhost', 'root', '', 'securitybot') + SQLEngine(DB_HOST, DB_USER, DB_PASS, DB_NAME) diff --git a/util/db_up.py b/util/db_up.py index fd1fdd1..901105d 100644 --- a/util/db_up.py +++ b/util/db_up.py @@ -1,16 +1,18 @@ #!/usr/bin/env python import MySQLdb import sys +from os import getenv # DB CONFIG GOES HERE -host = 'localhost' -user = 'root' -passwd= '' +host = getenv('DB_HOST', 'localhost') +user = getenv('DB_USER', 'root') +passwd=getenv('DB_PASS', '') +dbname=getenv('DB_NAME', 'securitybot') db = MySQLdb.connect(host=host, user=user, passwd=passwd, - db='securitybot') + db=dbname) cur = db.cursor() From 7212b540ecfa61734751a57f2022cce6a4e15db6 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Mon, 27 Feb 2017 14:31:26 -0500 Subject: [PATCH 02/18] Fixed Dockerfile and documentation Signed-off-by: Yanlin Wang --- Dockerfile | 2 ++ README.md | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 87abcce..d340819 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ FROM python:2.7 COPY . /securitybot +USER securitybot + env PYTHONPATH $PYTHONPATH:/securitybot RUN apt-get update && \ diff --git a/README.md b/README.md index 530f5db..d89d4cb 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ To prevent having to modify the scripts, you can set the following environment v Dockerfile is included to generate a Docker Image to run the bot. Entrypoint script will wait for database startup and initialize database if it does not already exist. Entrypoint takes one of two arguments: * bot: Starts the main bot -* fronend: starts the frontend and API server +* frontend: starts the frontend and API server Run configuration will be based on the environment variables above. @@ -61,12 +61,14 @@ Example: Bot: ``` -docker run -e DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password -e SLACK_API_TOKEN= -e DUO_INTEGRATION_KEY= -e DUO_SECRET_KEY= -e DUO_ENDPOINT= quay.io/patl/securitybot bot +docker build --tag securitybot +docker run -e DB_NAME=securitybot DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password -e SLACK_API_TOKEN= -e DUO_INTEGRATION_KEY= -e DUO_SECRET_KEY= -e DUO_ENDPOINT= securitybot bot ``` Frontend: ``` -docker run -p 8888:8888 -e DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password quay.io/patl/securitybot frontend +docker build --tag securitybot +docker run -p 8888:8888 -e DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password securitybot frontend ``` ## Architecture From ab4bbc67716dc14398fb6255ccf0872e3a4cf3cb Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Wed, 1 Mar 2017 20:52:44 -0500 Subject: [PATCH 03/18] Fixed Dockerfile and added docker build test to Travis Signed-off-by: Yanlin Wang --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index d340819..92518fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,14 @@ FROM python:2.7 COPY . /securitybot -USER securitybot - env PYTHONPATH $PYTHONPATH:/securitybot RUN apt-get update && \ apt-get install -y mysql-client && \ - pip install -r /securitybot/requirements.txt + pip install -r /securitybot/requirements.txt && \ + useradd -N -s '/bin/false' -e '' securitybot + +USER securitybot WORKDIR /securitybot From 1556e179dc943eee545b247d1bb189d98a1822d1 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Wed, 1 Mar 2017 21:04:41 -0500 Subject: [PATCH 04/18] Added Docker tests to travis Signed-off-by: Yanlin Wang --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f733e25..6bd6b92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,9 @@ language: python python: - "2.7" sudo: false + install: pip install -r requirements.txt script: + - docker build . - flake8 --ignore F401 securitybot/ - PYTHONPATH=$(pwd) py.test -v tests/ From ee579a6e0dc28724417fd8c2ecc9ef0024ca24e8 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Wed, 1 Mar 2017 21:53:50 -0500 Subject: [PATCH 05/18] Added docker-compose Signed-off-by: Yanlin Wang --- README.md | 2 ++ docker-compose.yml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 docker-compose.yml diff --git a/README.md b/README.md index d89d4cb..60c7f35 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ docker build --tag securitybot docker run -p 8888:8888 -e DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password securitybot frontend ``` +A docker-compose file is provided for ease of use. docker-compose up will start database, bot and frontend. Frontend and API server will be available on port 8888. Database content will be stored in persistent volume `mysql-securitybot`. Slack-related, Duo related and database password environment variables must be set for docker-compose stack to run properly. + ## Architecture Securitybot was designed to be as modular as possible. This means that it's possible to easily swap out chat systems, 2FA providers, and alerting data sources. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4851dea --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +version: '2' +services: + db: + image: mysql + environment: + - MYSQL_ROOT_PASSWORD=${DB_PASS} + #volumes: + # - mysql-securitybot:/var/lib/mysql + ports: + - "3306" + bot: + build: . + image: securitybot + command: bot + environment: + - SLACK_API_TOKEN=${SLACK_API_TOKEN} + - REPORTING_CHANNEL=${REPORTING_CHANNEL} + - DUO_INTEGRATION_KEY=${DUO_INTEGRATION_KEY} + - DUO_SECRET_KEY=${DUO_SECRET_KEY} + - DUO_ENDPOINT=${DUO_ENDPOINT} + - DB_HOST=db + - DB_USER=root + - DB_PASS=${DB_PASS} + - DB_NAME=securitybot + frontend: + build: . + image: securitybot + command: frontend + ports: + - "8888:8888" + environment: + - DB_HOST=db + - DB_USER=root + - DB_PASS=${DB_PASS} + - DB_NAME=securitybot +#volumes: +# mysql-securitybot: From 5f13b108b6a9dcac87e48f108441059d1f6e49d7 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Wed, 1 Mar 2017 21:55:16 -0500 Subject: [PATCH 06/18] Fixed travis.yml for Docker tests Fixed business hours issue for test_auto_escalate Workaround for flake8 F401 error on type hints fixed .travis.yml for Docker tests Signed-off-by: Yanlin Wang --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6bd6b92..a5db62d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ python: - "2.7" sudo: false +services: + - docker + install: pip install -r requirements.txt script: - docker build . From 3ea43dd5c92dd0235bda8195f730606a1329c162 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Mon, 13 Mar 2017 21:22:38 -0400 Subject: [PATCH 07/18] Added volume to docker-compose Signed-off-by: Yanlin Wang --- docker-compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4851dea..bbd4216 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ -version: '2' +version: '3' services: db: image: mysql environment: - MYSQL_ROOT_PASSWORD=${DB_PASS} - #volumes: - # - mysql-securitybot:/var/lib/mysql + volumes: + - mysql-securitybot:/var/lib/mysql ports: - "3306" bot: @@ -33,5 +33,5 @@ services: - DB_USER=root - DB_PASS=${DB_PASS} - DB_NAME=securitybot -#volumes: -# mysql-securitybot: +volumes: + mysql-securitybot: From 344b1f7813b71aa6668ad727fa7995bdee709219 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Mon, 13 Mar 2017 21:25:06 -0400 Subject: [PATCH 08/18] Typo Signed-off-by: Yanlin Wang --- Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 92518fe..a4096de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,12 @@ FROM python:2.7 COPY . /securitybot -env PYTHONPATH $PYTHONPATH:/securitybot +ENV PYTHONPATH $PYTHONPATH:/securitybot -RUN apt-get update && \ - apt-get install -y mysql-client && \ - pip install -r /securitybot/requirements.txt && \ - useradd -N -s '/bin/false' -e '' securitybot +RUN apt-get update +RUN apt-get install -y mysql-client +RUN pip install -r /securitybot/requirements.txt +RUN useradd -N -s '/bin/false' -e '' securitybot USER securitybot From b6b94e41ead9ecb335251ab50c625b605892c4a1 Mon Sep 17 00:00:00 2001 From: Patrick Lecuyer Date: Mon, 13 Mar 2017 21:25:11 -0400 Subject: [PATCH 09/18] Fixed env vars in doc Signed-off-by: Yanlin Wang --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60c7f35..da2b00c 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Example: Bot: ``` docker build --tag securitybot -docker run -e DB_NAME=securitybot DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password -e SLACK_API_TOKEN= -e DUO_INTEGRATION_KEY= -e DUO_SECRET_KEY= -e DUO_ENDPOINT= securitybot bot +docker run -e DB_NAME=securitybot -e DB_USER=root -e DB_HOST=127.0.0.1 -e DB_PASS=password -e SLACK_API_TOKEN= -e REPORTING_CHANNEL=security-notifications -e DUO_INTEGRATION_KEY= -e DUO_SECRET_KEY= -e DUO_ENDPOINT= securitybot bot ``` Frontend: From 82326cf7246d56362cc0613cdba97947f45684e3 Mon Sep 17 00:00:00 2001 From: Yanlin Wang Date: Fri, 20 Apr 2018 18:44:11 -0700 Subject: [PATCH 10/18] add noauth as default auth --- docker-compose.yml | 2 +- main.py | 22 +++++++------- securitybot/auth/default.py | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 securitybot/auth/default.py diff --git a/docker-compose.yml b/docker-compose.yml index bbd4216..009901c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: - DB_NAME=securitybot frontend: build: . - image: securitybot + image: centrifybot command: frontend ports: - "8888:8888" diff --git a/main.py b/main.py index b67cf2e..c0f390d 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from securitybot.bot import SecurityBot from securitybot.chat.slack import Slack from securitybot.tasker.sql_tasker import SQLTasker -from securitybot.auth.duo import DuoAuth +from securitybot.auth.default import DefaultAuth from securitybot.sql import init_sql from os import getenv @@ -16,7 +16,7 @@ DUO_SECRET = getenv('DUO_SECRET_KEY', 'duo_secret_key') DUO_ENDPOINT = getenv('DUO_ENDPOINT', 'duo_endpoint') REPORTING_CHANNEL = getenv('REPORTING_CHANNEL', 'some_slack_channel_id') -ICON_URL = 'https://dl.dropboxusercontent.com/s/t01pwfrqzbz3gzu/securitybot.png' +ICON_URL = 'https://s3-us-west-2.amazonaws.com/slack-files2/avatars/2016-12-07/113269613041_dfc5b7cb41ffd17b494d_48.png' def init(): # Setup logging @@ -30,17 +30,17 @@ def main(): init_sql() # Create components needed for Securitybot - duo_api = duo_client.Auth( - ikey=DUO_INTEGRATION, - skey=DUO_SECRET, - host=DUO_ENDPOINT - ) - duo_builder = lambda name: DuoAuth(duo_api, name) - - chat = Slack('securitybot', SLACK_KEY, ICON_URL) + # duo_api = duo_client.Auth( + # ikey=DUO_INTEGRATION, + # skey=DUO_SECRET, + # host=DUO_ENDPOINT + # ) + # duo_builder = lambda name: DuoAuth(duo_api, name) + default_builder = lambda name: DefaultAuth(name) + chat = Slack('YanlinBot', SLACK_KEY, ICON_URL) tasker = SQLTasker() - sb = SecurityBot(chat, tasker, duo_builder, REPORTING_CHANNEL, 'config/bot.yaml') + sb = SecurityBot(chat, tasker, default_builder, REPORTING_CHANNEL, 'config/bot.yaml') sb.run() if __name__ == '__main__': diff --git a/securitybot/auth/default.py b/securitybot/auth/default.py new file mode 100644 index 0000000..4aee799 --- /dev/null +++ b/securitybot/auth/default.py @@ -0,0 +1,58 @@ +''' +Default Authentication. +''' +__author__ = 'Yanlin Wang' +__email__ = 'yanlin.wang@centrify.com' + +import logging +from datetime import datetime +from urllib import urlencode +from securitybot.auth.auth import Auth, AUTH_STATES, AUTH_TIME + +from typing import Any + +class DefaultAuth(Auth): + def __init__(self, username): + # type: (Any, str) -> None + ''' + Args: + duo_api (duo_client.Auth): An Auth API client from Duo. + username (str): The username of the person authorized through + this object. + ''' + self.username = username + self.txid = None # type: str + self.auth_time = datetime.min + self.state = AUTH_STATES.NONE + + def can_auth(self): + # type: () -> bool + # Use Duo preauth to look for a device with Push + # TODO: This won't work for anyone who's set to auto-allow, but + # I don't believe we have anyone like that... + logging.debug('Checking auth capabilities for {}'.format(self.username)) + + return True + + def auth(self, reason=None): + # type: (str) -> None + pushinfo = 'from=Securitybot' + if reason: + pushinfo += '&' + pushinfo += urlencode({'reason': reason}) + + self.txid = 'txid' + self.state = AUTH_STATES.AUTHORIZED + + def _recently_authed(self): + # type: () -> bool + return (datetime.now() - self.auth_time) < AUTH_TIME + + def auth_status(self): + # type: () -> int + return AUTH_STATES.AUTHORIZED + + def reset(self): + # type: () -> None + self.txid = None + self.state = AUTH_STATES.NONE From 3bef017a6e654dbc050a7ecf3d0c2dd716c6c7ae Mon Sep 17 00:00:00 2001 From: Yanlin Wang Date: Wed, 25 Apr 2018 22:51:17 -0700 Subject: [PATCH 11/18] add attachments --- config/messages.yaml | 35 ++++++++++++++----- docker-compose.yml | 6 ++-- frontend/securitybot_api.py | 10 ++++-- frontend/securitybot_frontend.py | 32 ++++++++++++++--- frontend/static/js/main.js | 3 ++ frontend/templates/index.html | 7 ++++ .../securitybot_alerts/bin/send_bot_alerts.py | 2 +- securitybot/bot.py | 22 ++++++++---- securitybot/chat/slack.py | 12 ++++--- securitybot/commands.py | 7 ++-- securitybot/sql.py | 1 + securitybot/tasker/sql_tasker.py | 5 +-- securitybot/tasker/tasker.py | 3 +- securitybot/user.py | 22 ++++++++---- securitybot/util.py | 17 ++++++--- util/db_up.py | 1 + 16 files changed, 137 insertions(+), 48 deletions(-) diff --git a/config/messages.yaml b/config/messages.yaml index 09eb5f8..8f0586e 100644 --- a/config/messages.yaml +++ b/config/messages.yaml @@ -20,17 +20,27 @@ bye: > # Message sent when alerting a user that a security action occured # Takes alert description and alert reason as formatting parameters 0 and 1 rsp alert: > - There's an alert, `{0}`, that's associated with your username. - Here's some more information on the alert: + { + "color": "danger", + "text" : "Following alert is associated with you" + } - {1} # Message sent when asking for user response on whether they're responsible action_prompt: > - Did you do this? - Respond with either "yes" or "no" followed by an explanation in one message. + { + "title": "Did you do this?", + "color": "#3AA3E3", + "fields": [ + { + "value": "Respond with either 'yes' or 'no' followed by an explanation in one message.", + "short": false + } + ] + } + #Respond with either "yes" or "no" followed by an explanation in one message. # Message sent if the user is not responsible escalated: > @@ -73,11 +83,18 @@ no_response: > # Message sent to report a user didn't do something. report: > - `{username}` reports they didn't do `{title}` (`{description}`): + {{ + "color": "danger", + "text" : "`{username}` reports `{username}` didn't do following `{title}`", + "fields": [ + {{ + "title": "Comment", + "value": "{comment}", + "short": false + }} + ] + }} - {comment} - - URL: {url} # Sent when a message is unrecognized bad_command: > diff --git a/docker-compose.yml b/docker-compose.yml index 009901c..31b1a1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,8 @@ services: image: mysql environment: - MYSQL_ROOT_PASSWORD=${DB_PASS} - volumes: - - mysql-securitybot:/var/lib/mysql +# volumes: +# - mysql-securitybot:/var/lib/mysql ports: - "3306" bot: @@ -24,7 +24,7 @@ services: - DB_NAME=securitybot frontend: build: . - image: centrifybot + image: securitybot command: frontend ports: - "8888:8888" diff --git a/frontend/securitybot_api.py b/frontend/securitybot_api.py index f484227..224e5c2 100644 --- a/frontend/securitybot_api.py +++ b/frontend/securitybot_api.py @@ -296,7 +296,7 @@ def blacklist(**kwargs): return response # Custom alert creation -def create_alert(ldap, title, description, reason): +def create_alert(ldap, title, description, reason, attachments=None): # type: (str, str, str, str) -> Dict[str, Any] ''' Creates a new alert. @@ -307,10 +307,16 @@ def create_alert(ldap, title, description, reason): reason: The reason for creating the alert Content: Empty. + :param user: + :param alert_time: + :param location: + :param target: + :param source: + :param risk: ''' response = build_response() try: - create_new_alert(title, ldap, description, reason) + create_new_alert(title, ldap, description, reason, url='N/A', key=None, attachments=attachments) except SQLEngineException: response['error'] = 'Invalid parameters' return response diff --git a/frontend/securitybot_frontend.py b/frontend/securitybot_frontend.py index 692f865..ec253e3 100644 --- a/frontend/securitybot_frontend.py +++ b/frontend/securitybot_frontend.py @@ -13,6 +13,7 @@ import tornado.ioloop import tornado.netutil import tornado.web +import json # Securitybot includes import securitybot_api as api @@ -73,18 +74,38 @@ class NewAlertHandler(tornado.web.RequestHandler): def post(self): response = api.build_response() args = {} - for name in ['title', 'ldap', 'description', 'reason']: + for name in ['title', 'ldap', 'description', 'reason', 'attachments']: args[name] = self.get_argument(name, default=None) if args[name] is None: response['error'] += 'ERROR: {} must be specified!\n'.format(name) if all(v is not None for v in args.values()): - self.write(api.create_alert(args['ldap'], - args['title'], - args['description'], - args['reason'])) + self.write(api.create_alert(args['ldap'], args['title'], args['description'], args['reason'], + args['attachments'])) else: self.write(response) +''' +prompt -> description +actions -> reason +''' +class NewJSONAlertHandler(tornado.web.RequestHandler): + def post(self): + response = api.build_response() + data = json.loads(self.request.body) + args = {} + for name in ['title', 'user', 'prompt', 'actions', 'attachments']: + args[name] = data.get(name, None) + if args[name] is None: + response['error'] += 'ERROR: {} must be specified!\n'.format(name) + if all(v is not None for v in args.values()): + self.write(api.create_alert(args['user'], args['title'], args['prompt'], args['actions'], + args['attachments'])) + else: + self.write(response) + + + + class IndexHandler(tornado.web.RequestHandler): def get(self): self.render() @@ -106,6 +127,7 @@ def __init__(self, port): (r'/api/ignored', IgnoredHandler), (r'/api/blacklist', BlacklistHandler), (r'/api/create', NewAlertHandler), + (r'/api/new', NewJSONAlertHandler), ], xsrf_cookie=True, static_path=static_path, diff --git a/frontend/static/js/main.js b/frontend/static/js/main.js index 04712c9..0511310 100644 --- a/frontend/static/js/main.js +++ b/frontend/static/js/main.js @@ -202,6 +202,8 @@ function submitCustom(form) { data["ldap"] = form.customLdap.value; data["description"] = form.customDescription.value; data["reason"] = form.customReason.value; + data["attachments"] = form.customAttachments.value; + // Check for empty title or ldap let hasError = false; @@ -218,6 +220,7 @@ function submitCustom(form) { form.customLdap.parentElement.parentElement.classList.remove("has-error"); } + if (hasError) { presentAlert("alert-danger", "Please fill in the fields highlighted in red."); } else { diff --git a/frontend/templates/index.html b/frontend/templates/index.html index 720d775..155ac87 100644 --- a/frontend/templates/index.html +++ b/frontend/templates/index.html @@ -343,6 +343,13 @@

Send custom alert

+ +
+ +
+ +
+
diff --git a/plugins/splunk/apps/securitybot_alerts/bin/send_bot_alerts.py b/plugins/splunk/apps/securitybot_alerts/bin/send_bot_alerts.py index 943648e..af598f0 100755 --- a/plugins/splunk/apps/securitybot_alerts/bin/send_bot_alerts.py +++ b/plugins/splunk/apps/securitybot_alerts/bin/send_bot_alerts.py @@ -28,7 +28,7 @@ def create_securitybot_task(search_name, hash, username, description, reason, ur '''.format(rows, hash)) # Insert that into the database as a new alert - create_new_alert(search_name, username, description, reason, url, hash) + create_new_alert(search_name, username, description, reason, url, user=hash) class CollisionException(Exception): pass diff --git a/securitybot/bot.py b/securitybot/bot.py index 4f3ed90..28fbd61 100644 --- a/securitybot/bot.py +++ b/securitybot/bot.py @@ -13,6 +13,7 @@ import shlex import yaml import string +import json import securitybot.commands as bot_commands from securitybot.blacklist.sql_blacklist import SQLBlacklist @@ -325,12 +326,21 @@ def alert_user(self, user, task): task (Task): A task to alert on. ''' # Format the reason to be indented - reason = '\n'.join(['>' + s for s in task.reason.split('\n')]) - - message = self.messages['alert'].format(task.description, reason) - message += '\n' - message += self.messages['action_prompt'] - self.chat.message_user(user, message) + #reason = '\n'.join(['>' + s for s in task.reason.split('\n')]) + + #message = self.messages['alert'].format(task.description, reason) + #message += '\n' + #message += self.messages['action_prompt'] + message =''; + message_attachement = self.messages['alert'] + message_attachement += ',\n' + message_attachement += task.attachments + message_attachement += ',\n' + message_attachement += self.messages['action_prompt'] + message_attachement = '[ \n' + message_attachement + '\n ]' + mjson = json.loads(message_attachement) + + self.chat.message_user(user, message, mjson) # User creation and lookup methods diff --git a/securitybot/chat/slack.py b/securitybot/chat/slack.py index 6d9510c..f790ecf 100644 --- a/securitybot/chat/slack.py +++ b/securitybot/chat/slack.py @@ -6,7 +6,6 @@ import logging from slackclient import SlackClient -import json from securitybot.user import User from securitybot.chat.chat import Chat, ChatException @@ -103,7 +102,8 @@ def get_messages(self): messages = [e for e in events if e['type'] == 'message'] return [m for m in messages if 'user' in m and m['channel'].startswith('D')] - def send_message(self, channel, message): + + def send_message(self, channel, message, msg_attachments=[]): # type: (Any, str) -> None ''' Sends some message to a desired channel. @@ -114,12 +114,14 @@ def send_message(self, channel, message): text=message, username=self._username, as_user=False, - icon_url=self._icon_url) + icon_url=self._icon_url, + attachments=msg_attachments) + - def message_user(self, user, message): + def message_user(self, user, message,msg_attachments=[]): # type: (User, str) -> None ''' Sends some message to a desired user, using a User object and a string message. ''' channel = self._api_call('im.open', user=user['id'])['channel']['id'] - self.send_message(channel, message) + self.send_message(channel, message, msg_attachments) diff --git a/securitybot/commands.py b/securitybot/commands.py index 099f052..c914f00 100644 --- a/securitybot/commands.py +++ b/securitybot/commands.py @@ -7,8 +7,7 @@ if the command doesn't have success/failure messages. ''' import re - -from datetime import timedelta +from datetime import datetime, timedelta import securitybot.ignored_alerts as ignored_alerts from securitybot.util import create_new_alert @@ -94,6 +93,8 @@ def ignore(bot, user, args): def test(bot, user, args): '''Creates a new test alert in Maniphest for a user.''' - create_new_alert('testing_alert', user['name'], 'Testing alert', 'Testing Securitybot') + create_new_alert('testing_alert', user['name'], 'Testing alert', 'Testing Securitybot', user=user['name'], + alert_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), location='Santa Clara', + target='127.0.0.1', source='127.0.0.1', risk='High') return True diff --git a/securitybot/sql.py b/securitybot/sql.py index f5434ec..16c4f7c 100644 --- a/securitybot/sql.py +++ b/securitybot/sql.py @@ -78,6 +78,7 @@ def execute(query, params=None): SQLEngine._conn.commit() except (AttributeError, MySQLdb.OperationalError): # Recover from lost connection + logging.exception('Got exception on sql exec') logging.warn('Recovering from lost MySQL connection.') SQLEngine._create_engine(SQLEngine._host, SQLEngine._user, diff --git a/securitybot/tasker/sql_tasker.py b/securitybot/tasker/sql_tasker.py index e5db55f..3af1b74 100644 --- a/securitybot/tasker/sql_tasker.py +++ b/securitybot/tasker/sql_tasker.py @@ -14,6 +14,7 @@ reason, description, url, + attachments, performed, comment, authenticated, @@ -67,14 +68,14 @@ def get_pending_tasks(self): class SQLTask(Task): def __init__(self, hsh, title, username, reason, description, url, - performed, comment, authenticated, status): + attachments, performed, comment, authenticated, status): # type: (str, str, str, str, str, str, bool, str, bool, int) -> None ''' Args: hsh (str): SHA256 primary key hash. ''' super(SQLTask, self).__init__(title, username, reason, description, url, - performed, comment, authenticated, status) + attachments, performed, comment, authenticated, status) self.hash = hsh def _set_status(self, status): diff --git a/securitybot/tasker/tasker.py b/securitybot/tasker/tasker.py index 4167489..f74d56a 100644 --- a/securitybot/tasker/tasker.py +++ b/securitybot/tasker/tasker.py @@ -50,7 +50,7 @@ def get_pending_tasks(self): class Task(object): __metaclass__ = ABCMeta - def __init__(self, title, username, reason, description, url, performed, comment, + def __init__(self, title, username, reason, description, url, attachments, performed, comment, authenticated, status): # type: (str, str, str, str, str, bool, str, bool, int) -> None ''' @@ -75,6 +75,7 @@ def __init__(self, title, username, reason, description, url, performed, comment self.reason = reason self.description = description self.url = url + self.attachments = attachments self.performed = performed self.comment = comment self.authenticated = authenticated diff --git a/securitybot/user.py b/securitybot/user.py index f7e47cf..8db050b 100644 --- a/securitybot/user.py +++ b/securitybot/user.py @@ -6,6 +6,7 @@ __email__ = 'abertsch@dropbox.com' import logging +import json import pytz from datetime import datetime, timedelta import securitybot.ignored_alerts as ignored_alerts @@ -242,14 +243,23 @@ def _act_on_not_performed(self): comment = self._last_message.text else: comment = 'No comment provided.' - comment = '\n'.join('> ' + s for s in comment.split('\n')) + # comment = '\n'.join('> ' + s for s in comment.split('\n')) + msg ='' + msg_att = self.parent.messages['report'].format(username=self['name'], + title=self.pending_task.title, + comment=comment) + msg_att += ',\n' + msg_att += self.pending_task.attachments + msg_att += ',\n' + msg_att += self.pending_task.reason + msg_att = '[ \n' + msg_att + '\n ]' + + #print(msg_att) + msg_att_json = json.loads(msg_att) self.parent.chat.send_message( self.parent.reporting_channel, - self.parent.messages['report'].format(username=self['name'], - title=self.pending_task.title, - description=self.pending_task.description, - comment=comment, - url=self.pending_task.url)) + msg, + msg_att_json) # Exit actions diff --git a/securitybot/util.py b/securitybot/util.py index 33ccca3..084e13b 100644 --- a/securitybot/util.py +++ b/securitybot/util.py @@ -69,27 +69,34 @@ def get_expiration_time(start, time): end = next_day + delta return end -def create_new_alert(title, ldap, description, reason, url='N/A', key=None): +def create_new_alert(title, ldap, description, reason, url='N/A', key=None, attachments=None): # type: (str, str, str, str, str, str) -> None ''' Creates a new alert in the SQL DB with an optionally random hash. + :param key: + :param alert_time: + :param location: + :param target: + :param source: + :param risk: ''' + # Generate random key if none provided if key is None: key = binascii.hexlify(os.urandom(32)) # Insert that into the database as a new alert SQLEngine.execute(''' - INSERT INTO alerts (hash, ldap, title, description, reason, url, event_time) - VALUES (UNHEX(%s), %s, %s, %s, %s, %s, NOW()) + INSERT INTO alerts (hash, ldap, title, description, reason, url, event_time, attachments) + VALUES (UNHEX(%s), %s, %s, %s, %s, %s, NOW(), %s) ''', - (key, ldap, title, description, reason, url)) + (key, ldap, title, description, reason, url, attachments)) SQLEngine.execute(''' INSERT INTO user_responses (hash, comment, performed, authenticated) VALUES (UNHEX(%s), '', false, false) ''', - (key,)) + (key,)) SQLEngine.execute('INSERT INTO alert_status (hash, status) VALUES (UNHEX(%s), 0)', (key,)) diff --git a/util/db_up.py b/util/db_up.py index 901105d..9e3fd3e 100644 --- a/util/db_up.py +++ b/util/db_up.py @@ -59,6 +59,7 @@ reason TEXT NOT NULL, url VARCHAR(511) NOT NULL, event_time DATETIME NOT NULL, + attachments TEXT, PRIMARY KEY ( hash ) ) ''' From 991b6e65c43dbcd8c84a6508fdcfe4e004aff8ab Mon Sep 17 00:00:00 2001 From: Yanlin Wang Date: Thu, 26 Apr 2018 15:06:24 -0700 Subject: [PATCH 12/18] env driven --- main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index c0f390d..99c9ccd 100644 --- a/main.py +++ b/main.py @@ -16,7 +16,8 @@ DUO_SECRET = getenv('DUO_SECRET_KEY', 'duo_secret_key') DUO_ENDPOINT = getenv('DUO_ENDPOINT', 'duo_endpoint') REPORTING_CHANNEL = getenv('REPORTING_CHANNEL', 'some_slack_channel_id') -ICON_URL = 'https://s3-us-west-2.amazonaws.com/slack-files2/avatars/2016-12-07/113269613041_dfc5b7cb41ffd17b494d_48.png' +BOT_NAME = getenv('BOT_NAME', 'SecurityBot') +BOT_ICON_URL = getenv('BOT_ICON_URL', 'https://colerisetemp2.files.wordpress.com/2009/11/dock-icon-flat.png') def init(): # Setup logging @@ -37,7 +38,7 @@ def main(): # ) # duo_builder = lambda name: DuoAuth(duo_api, name) default_builder = lambda name: DefaultAuth(name) - chat = Slack('YanlinBot', SLACK_KEY, ICON_URL) + chat = Slack(BOT_NAME, SLACK_KEY, BOT_ICON_URL) tasker = SQLTasker() sb = SecurityBot(chat, tasker, default_builder, REPORTING_CHANNEL, 'config/bot.yaml') From e5baf36549dcab408ab35f9ba71e15ec9e59ecc8 Mon Sep 17 00:00:00 2001 From: Yanlin Wang Date: Thu, 21 Jun 2018 16:29:24 -0700 Subject: [PATCH 13/18] add more stuff --- config/commands.yaml | 9 ++++++ config/messages.yaml | 10 ++++++ securitybot/analytics/__init__.py | 0 securitybot/analytics/analytics.py | 51 ++++++++++++++++++++++++++++++ securitybot/commands.py | 20 +++++++++++- 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 securitybot/analytics/__init__.py create mode 100644 securitybot/analytics/analytics.py diff --git a/config/commands.yaml b/config/commands.yaml index 5df825d..ef01d13 100644 --- a/config/commands.yaml +++ b/config/commands.yaml @@ -57,4 +57,13 @@ test: # Generates a test alert for a user success_msg: 'Sending you a testing alert. Be patient...' failure_msg: 'I was unable to generate a testing alert. Sorry. :sadpanda:' +insightlet: # Generates insightlet by name + info: Generates insightlet by name + usage: + - "'insightlet (widget name)'" + fn: insightlet + success_msg: 'Done!' + failure_msg: 'I can not generate insightlet for you. Sorry. :sadpanda:' + + ... diff --git a/config/messages.yaml b/config/messages.yaml index 8f0586e..f86a7a7 100644 --- a/config/messages.yaml +++ b/config/messages.yaml @@ -118,4 +118,14 @@ ignore_time: > ignore_no_time: > You must provide a non-zero amount of time. + +insightlet: > + [ + {{ + "title": "{title}", + "title_link": "{url}", + "image_url": "{insightlet}", + "color": "#3AA3E3" + }} + ] ... diff --git a/securitybot/analytics/__init__.py b/securitybot/analytics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/securitybot/analytics/analytics.py b/securitybot/analytics/analytics.py new file mode 100644 index 0000000..8d77410 --- /dev/null +++ b/securitybot/analytics/analytics.py @@ -0,0 +1,51 @@ +from urllib2 import Request, urlopen, quote +import json +import logging + +BASE_URL = 'https://data-platform-pod0-dev.centrify.io' +TOKEN = "" +DASHBOARD_URL = BASE_URL + '/analytics/ui/#/dashboards' + +def list_insightlets(): + + url = BASE_URL + "/analytics/services/v1.0/preference/dashboards/library" + widgets = [] + req = Request(url) + req.add_header("authorization", "Bearer " + TOKEN) + resp = urlopen(req).read() + jsons = json.loads(resp) + jsons = jsons['parcels'] + for i in range(len(jsons)): + widgets.append(jsons[i]['name']) + return widgets + +def get_insightlet(name): + # type: () -> str + url = BASE_URL + "/report?token={token}&uri=&uri=/analytics/ui/%23/widgets?parcel_name={name}&embedded=true&type=screenshot&height=600&width=800&delay=1000" + return url.format(token=TOKEN, name=name) + #img_name = "/tmp/" + name + ".jpg" + #urllib.request.urlretrieve(url, img_name) + +def get_insightlet_id(id): + # type: () -> Tuple[str, str] + i = int(id) + widgets = list_insightlets() + name = quote(widgets[i]) + url = BASE_URL + "/report?token={token}&uri=/analytics/ui/%23/widgets?parcel_name={name}%26embedded=true&type=screenshot&height=600&width=800&delay=2500" + return (widgets[i], url.format(token=TOKEN, name=name)) + + +def list_insightlet_message(): + # type: () -> str + widgets = list_insightlets() + md = '' + for i in range(len(widgets)): + md += str(i) + ". " + widgets[i] + '\n' + return "```" + md + "```\n" + +def main(): + print list_insightlet_message() + + +if __name__ == '__main__': + main() diff --git a/securitybot/commands.py b/securitybot/commands.py index c914f00..4beb36f 100644 --- a/securitybot/commands.py +++ b/securitybot/commands.py @@ -7,10 +7,12 @@ if the command doesn't have success/failure messages. ''' import re +import logging from datetime import datetime, timedelta - +import json import securitybot.ignored_alerts as ignored_alerts from securitybot.util import create_new_alert +from securitybot.analytics.analytics import list_insightlet_message, get_insightlet_id, DASHBOARD_URL def hi(bot, user, args): '''Says hello to a user.''' @@ -98,3 +100,19 @@ def test(bot, user, args): target='127.0.0.1', source='127.0.0.1', risk='High') return True + +def insightlet(bot, user, args): + if len(args)<1: + # list the insights + msg = list_insightlet_message() + msg += "Please find the insightlet id you want to run\n" + msg += "```insightlet id```" + bot.chat.message_user(user, msg) + return True + if len(args) == 1: + name, insightlet_url = get_insightlet_id(args[0]) + logging.info ("insight_url = " + insightlet_url) + msg_att = bot.messages['insightlet'].format(title=name, url=DASHBOARD_URL, insightlet=insightlet_url) + msg_att_json = json.loads(msg_att) + bot.chat.message_user(user, '', msg_att_json) + return True From ad237ce58645a126838dcd7bd6ed01a960515208 Mon Sep 17 00:00:00 2001 From: Mengying Fan Date: Thu, 5 Jul 2018 11:17:09 -0700 Subject: [PATCH 14/18] modify to handle with slow response --- securitybot/bot.py | 6 ++++-- securitybot/user.py | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/securitybot/bot.py b/securitybot/bot.py index 28fbd61..5714c8e 100644 --- a/securitybot/bot.py +++ b/securitybot/bot.py @@ -44,7 +44,7 @@ def clean_input(text): # smart quote, as happens with auto-formatting. text = (text.replace(u'\u2018', '\'') .replace(u'\u2019', '\'') - .replace(u'\u201c','"') + .replace(u'\u201c', '"') .replace(u'\u201d', '"')) # Undo autoformatting of dashes text = (text.replace(u'\u2013', '--') @@ -331,13 +331,14 @@ def alert_user(self, user, task): #message = self.messages['alert'].format(task.description, reason) #message += '\n' #message += self.messages['action_prompt'] - message =''; + message ='' message_attachement = self.messages['alert'] message_attachement += ',\n' message_attachement += task.attachments message_attachement += ',\n' message_attachement += self.messages['action_prompt'] message_attachement = '[ \n' + message_attachement + '\n ]' + #logging.info(message_attachement) mjson = json.loads(message_attachement) self.chat.message_user(user, message, mjson) @@ -356,6 +357,7 @@ def _populate_users(self): user = User(member, self.auth_builder(member['name']), self) self.users[member['id']] = user self.users_by_name[member['name']] = user + #logging.info(member['name']) logging.info('Gathered info on {} users.'.format(len(self.users))) def user_lookup(self, id): diff --git a/securitybot/user.py b/securitybot/user.py index 8db050b..8819a81 100644 --- a/securitybot/user.py +++ b/securitybot/user.py @@ -18,6 +18,7 @@ from typing import Any, Dict, List ESCALATION_TIME = timedelta(hours=2) +SLOW_RESPONSE_TIME = timedelta(minutes=1) BACKOFF_TIME = timedelta(hours=21) class User(object): @@ -54,6 +55,8 @@ def __init__(self, user, auth, parent): # Task auto-escalation time self._escalation_time = datetime.max.replace(tzinfo=pytz.utc) + self._start = datetime.now(tz=pytz.utc) + # Build state hierarchy states = ['need_task', 'action_performed_check', @@ -66,7 +69,8 @@ def __init__(self, user, auth, parent): { 'source': 'need_task', 'dest': 'action_performed_check', - 'condition': self._has_tasks + 'condition': self._has_tasks, + 'action': self._start_timer, }, # Finish task if user says action was performed and recently authorized { @@ -198,7 +202,8 @@ def _did_not_perform_action(self): def _slow_response_time(self): # type: () -> bool '''Returns true if the user has taken a long time to respond.''' - return datetime.now(tz=pytz.utc) > self._escalation_time + now = datetime.now(tz=pytz.utc) + return now - self._start > SLOW_RESPONSE_TIME def _allows_authorization(self): # type: () -> bool @@ -217,6 +222,9 @@ def _auth_completed(self): # State actions + def _start_timer(self): + self._start = datetime.now(tz=pytz.utc) + def _auto_escalate(self): # type: () -> None '''Marks the current task as needing verification and moves on.''' @@ -227,6 +235,7 @@ def _auto_escalate(self): self.pending_task.set_verifying() self._escalation_time = datetime.max.replace(tzinfo=pytz.utc) self.send_message('no_response') + self._act_on_not_performed() def _act_on_not_performed(self): # type: () -> None @@ -250,11 +259,13 @@ def _act_on_not_performed(self): comment=comment) msg_att += ',\n' msg_att += self.pending_task.attachments + # msg_att += '"' + self.pending_task.attachments +'"' msg_att += ',\n' msg_att += self.pending_task.reason + # msg_att += '"' + self.pending_task.reason + '"' msg_att = '[ \n' + msg_att + '\n ]' - #print(msg_att) + logging.info(msg_att) msg_att_json = json.loads(msg_att) self.parent.chat.send_message( self.parent.reporting_channel, From 437418f38d1971ee2ab7dfd85849942cd30d608f Mon Sep 17 00:00:00 2001 From: Mengying Fan Date: Thu, 5 Jul 2018 14:44:18 -0700 Subject: [PATCH 15/18] make SLOW_RESPONSE_TIME configurable. --- securitybot/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/securitybot/user.py b/securitybot/user.py index 8819a81..818b064 100644 --- a/securitybot/user.py +++ b/securitybot/user.py @@ -14,11 +14,12 @@ from securitybot.auth.auth import AUTH_STATES from securitybot.state_machine import StateMachine from securitybot.util import tuple_builder, get_expiration_time +from os import getenv from typing import Any, Dict, List ESCALATION_TIME = timedelta(hours=2) -SLOW_RESPONSE_TIME = timedelta(minutes=1) +SLOW_RESPONSE_TIME = timedelta(minutes=int(getenv("SLOW_RESPONSE_TIME", '1'))) BACKOFF_TIME = timedelta(hours=21) class User(object): From eb996857c85d88c11ed9d0a766697acb09e79f3c Mon Sep 17 00:00:00 2001 From: Mengying Fan Date: Mon, 9 Jul 2018 15:46:12 -0700 Subject: [PATCH 16/18] docker file modification --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 31b1a1d..df8877a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: db: - image: mysql + image: mysql:5.7.22 environment: - MYSQL_ROOT_PASSWORD=${DB_PASS} # volumes: @@ -22,6 +22,7 @@ services: - DB_USER=root - DB_PASS=${DB_PASS} - DB_NAME=securitybot + - SLOW_RESPONSE_TIME=${SLOW_RESPONSE_TIME} frontend: build: . image: securitybot From 0f3a301c159059a8814851fc76d8300b9593edfd Mon Sep 17 00:00:00 2001 From: Mengying Fan Date: Tue, 17 Jul 2018 17:20:35 -0700 Subject: [PATCH 17/18] make analytics token to be environment variable --- docker-compose.yml | 1 + securitybot/analytics/analytics.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index df8877a..a9d76a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,7 @@ services: - DB_PASS=${DB_PASS} - DB_NAME=securitybot - SLOW_RESPONSE_TIME=${SLOW_RESPONSE_TIME} + - ANALYTICS_TOKEN=${ANALYTICS_TOKEN} frontend: build: . image: securitybot diff --git a/securitybot/analytics/analytics.py b/securitybot/analytics/analytics.py index 8d77410..0b90b62 100644 --- a/securitybot/analytics/analytics.py +++ b/securitybot/analytics/analytics.py @@ -1,9 +1,9 @@ from urllib2 import Request, urlopen, quote import json -import logging +from os import getenv BASE_URL = 'https://data-platform-pod0-dev.centrify.io' -TOKEN = "" +TOKEN = getenv('ANALYTICS_TOKEN') DASHBOARD_URL = BASE_URL + '/analytics/ui/#/dashboards' def list_insightlets(): From 79c0ef1e1e5686b82739ce09cd8c44972d4edd88 Mon Sep 17 00:00:00 2001 From: Weizhi Li Date: Tue, 17 Jul 2018 17:43:01 -0700 Subject: [PATCH 18/18] update to use widget lib summary api --- securitybot/analytics/analytics.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/securitybot/analytics/analytics.py b/securitybot/analytics/analytics.py index 0b90b62..09afca9 100644 --- a/securitybot/analytics/analytics.py +++ b/securitybot/analytics/analytics.py @@ -8,15 +8,14 @@ def list_insightlets(): - url = BASE_URL + "/analytics/services/v1.0/preference/dashboards/library" + url = BASE_URL + "/analytics/services/v1.0/preference/dashboards/library/widgets/summaries" widgets = [] req = Request(url) req.add_header("authorization", "Bearer " + TOKEN) resp = urlopen(req).read() jsons = json.loads(resp) - jsons = jsons['parcels'] for i in range(len(jsons)): - widgets.append(jsons[i]['name']) + widgets.append(jsons[i]['parcel_name']) return widgets def get_insightlet(name):