Skip to content

Commit

Permalink
Merge branch 'main' into feat/save_chat_to_incident_timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
sylviamclaughlin authored Jan 26, 2024
2 parents 4c5260e + 3216631 commit 8bded6a
Show file tree
Hide file tree
Showing 19 changed files with 795 additions and 155 deletions.
2 changes: 1 addition & 1 deletion app/commands/incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def submit(ack, view, say, body, client, logger):
say(text=text, channel=channel_id)

# Reminder to brief up
text = ":alphabet-yellow-question: Is this a `cybersecurity incident` (secret/data leak, account compromise, attack)? Please initiate the briefing process for CCCS and TBS OCIO Cyber."
text = ":alphabet-yellow-question: Is this a `cybersecurity incident` (secret/data leak, account compromise, attack)? Please initiate the briefing process for CCCS and TBS OCIO Cyber. This just means we send a summary of the incident (or initial findings and updates if incident is ongoing) to [email protected] and CC [email protected], and [email protected]! CCCS will reach out with a case number, and any questions if they need more information."
say(text=text, channel=channel_id)

# Reminder to stop planned testing
Expand Down
18 changes: 18 additions & 0 deletions app/commands/locales/secret.en-US.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
en-US:
locale_button: "Français"
locale_value: "en-US"
locale_short: "en"
submit: "Submit"
close: "Close"
title: "Share a new secret"
label: "Secret"
placeholder: "Enter your secret here"
ttl: "Time to live"
days: "days"
day: "day"
hours: "hours"
hour: "hour"
minutes: "minutes"
minute: "minute"
error: "There was an error creating your secret"
link_available: "Your secret is available at the following link:"
18 changes: 18 additions & 0 deletions app/commands/locales/secret.fr-FR.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
fr-FR:
locale_button: "English"
locale_value: "fr-FR"
locale_short: "fr"
submit: "Soumettre"
close: "Fermer"
title: "Partager secret"
label: "Secret"
placeholder: "Entrez votre secret ici"
ttl: "Durée de vie"
days: "jours"
day: "jour"
hours: "heures"
hour: "heure"
minutes: "minutes"
minute: "minute"
error: "Il y avait une erreur en créant votre secret"
link_available: "Votre secret est disponible au lien suivant:"
163 changes: 163 additions & 0 deletions app/commands/secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import i18n
import requests
import time
from commands.utils import get_user_locale

i18n.load_path.append("./commands/locales/")

i18n.set("locale", "en-US")
i18n.set("fallback", "en-US")


def secret_command(client, ack, command, body):
ack()
if "user" in body:
user_id = body["user"]["id"]
else:
user_id = body["user_id"]
locale = get_user_locale(user_id, client)
i18n.set("locale", locale)
view = generate_secret_command_modal_view(command, user_id, locale)
client.views_open(trigger_id=body["trigger_id"], view=view)


def secret_view_handler(ack, client, view, logger):
ack()
locale = view["blocks"][0]["elements"][0]["value"]
i18n.set("locale", locale)
secret = view["state"]["values"]["secret_input"]["secret_submission"]["value"]
ttl = view["state"]["values"]["product"]["secret_ttl"]["selected_option"]["value"]

# Encrypted message API
endpoint = "https://encrypted-message.cdssandbox.xyz/encrypt"
json = {"body": secret, "ttl": int(ttl) + int(time.time())}
response = requests.post(
endpoint, json=json, timeout=10, headers={"Content-Type": "application/json"}
)

try:
id = response.json()["id"]
url = f"https://encrypted-message.cdssandbox.xyz/{i18n.t('secret.locale_short')}/view/{id}"
client.chat_postEphemeral(
channel=view["private_metadata"],
user=view["private_metadata"],
text=f"{i18n.t('secret.link_available')} {url}",
)
except Exception as e:
logger.error(e)
client.chat_postEphemeral(
channel=view["private_metadata"],
user=view["private_metadata"],
text=i18n.t("secret.error"),
)
return


def handle_change_locale_button(ack, client, body):
ack()
if "user" in body:
user_id = body["user"]["id"]
else:
user_id = body["user_id"]
locale = body["actions"][0]["value"]
if locale == "en-US":
locale = "fr-FR"
else:
locale = "en-US"
i18n.set("locale", locale)
command = {
"text": body["view"]["state"]["values"]["secret_input"]["secret_submission"][
"value"
]
}
if command["text"] is None:
command["text"] = ""
view = generate_secret_command_modal_view(command, user_id, locale)
client.views_update(view_id=body["view"]["id"], view=view)


def generate_secret_command_modal_view(command, user_id, locale="en-US"):
ttl_options = [
{"name": "7 " + i18n.t("secret.days"), "value": "604800"},
{"name": "3 " + i18n.t("secret.days"), "value": "259200"},
{"name": "1 " + i18n.t("secret.day"), "value": "86400"},
{"name": "12 " + i18n.t("secret.hours"), "value": "43200"},
{"name": "4 " + i18n.t("secret.hours"), "value": "14400"},
{"name": "1 " + i18n.t("secret.hour"), "value": "3600"},
{"name": "30 " + i18n.t("secret.minutes"), "value": "1800"},
{"name": "5 " + i18n.t("secret.minutes"), "value": "300"},
]

options = [
{
"text": {"type": "plain_text", "text": i["name"]},
"value": i["value"],
}
for i in ttl_options
]
return {
"type": "modal",
"private_metadata": user_id,
"callback_id": "secret_view",
"title": {
"type": "plain_text",
"text": i18n.t("secret.title"),
},
"submit": {
"type": "plain_text",
"text": i18n.t("secret.submit"),
},
"blocks": [
{
"type": "actions",
"block_id": "locale",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": i18n.t("secret.locale_button"),
"emoji": True,
},
"value": locale,
"action_id": "secret_change_locale",
}
],
},
{
"type": "input",
"block_id": "secret_input",
"label": {
"type": "plain_text",
"text": i18n.t("secret.label"),
},
"element": {
"type": "plain_text_input",
"action_id": "secret_submission",
"initial_value": command["text"],
"placeholder": {
"type": "plain_text",
"text": i18n.t("secret.placeholder"),
},
},
},
{
"block_id": "product",
"type": "input",
"element": {
"type": "static_select",
"placeholder": {
"type": "plain_text",
"text": i18n.t("secret.ttl"),
},
"options": options,
"action_id": "secret_ttl",
},
"label": {
"type": "plain_text",
"text": i18n.t("secret.ttl"),
"emoji": True,
},
},
],
}
12 changes: 12 additions & 0 deletions app/integrations/google_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,15 @@ def update_spreadsheet_close_incident(channel_name):
).execute()
return True
return False


def healthcheck():
"""Check if the bot can interact with Google Drive."""
healthy = False
try:
metadata = list_metadata(INCIDENT_TEMPLATE)
healthy = "id" in metadata
logging.info(f"Google Drive healthcheck result: {metadata}")
except Exception as error:
logging.error(f"Google Drive healthcheck failed: {error}")
return healthy
13 changes: 13 additions & 0 deletions app/integrations/maxmind.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import geoip2.database
from geoip2.errors import AddressNotFoundError

Expand All @@ -16,3 +17,15 @@ def geolocate(ip):
return "IP address not found"
except ValueError:
return "Invalid IP address"


def healthcheck():
"""Check if the bot can interact with Maxmind."""
healthy = False
try:
result = geolocate("8.8.8.8")
healthy = isinstance(result, tuple)
logging.info(f"Maxmind healthcheck result: {result}")
except Exception as error:
logging.error(f"Maxmind healthcheck failed: {error}")
return healthy
16 changes: 16 additions & 0 deletions app/integrations/opsgenie.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ def create_alert(description):
return "Could not issue alert to Opsgenie!"


def healthcheck():
"""Check if the bot can interact with the Opsgenie API."""
healthy = False
try:
content = api_get_request(
"https://api.opsgenie.com/v1/services",
{"name": "GenieKey", "token": OPSGENIE_KEY},
)
result = json.loads(content)
healthy = "data" in result
logging.info(f"OpsGenie healthcheck result: {result}")
except Exception as error:
logging.error(f"OpsGenie healthcheck failed: {error}")
return healthy


def api_get_request(url, auth):
req = Request(url)
req.add_header("Authorization", f"{auth['name']} {auth['token']}")
Expand Down
17 changes: 17 additions & 0 deletions app/jobs/scheduled_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import schedule
import logging

from integrations import google_drive, maxmind, opsgenie

logging.basicConfig(level=logging.INFO)


Expand All @@ -17,12 +19,27 @@ def init(bot):

schedule.every(10).seconds.do(revoke_aws_sso_access, client=bot.client)
schedule.every(5).minutes.do(scheduler_heartbeat)
schedule.every(5).minutes.do(integration_healthchecks)


def scheduler_heartbeat():
logging.info("Scheduler is running at %s", time.ctime())


def integration_healthchecks():
logging.info("Running integration healthchecks ...")
healthchecks = {
"google_drive": google_drive.healthcheck,
"maxmind": maxmind.healthcheck,
"opsgenie": opsgenie.healthcheck,
}
for key, healthcheck in healthchecks.items():
if not healthcheck():
logging.error(f"Integration {key} is unhealthy 💀")
else:
logging.info(f"Integration {key} is healthy 🌈")


def run_continuously(interval=1):
"""Continuously run, while executing pending jobs at each
elapsed time interval.
Expand Down
7 changes: 6 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_bolt import App
from dotenv import load_dotenv
from commands import atip, aws, incident, sre, role
from commands import atip, aws, incident, secret, sre, role
from commands.helpers import incident_helper, webhook_helper
from server import bot_middleware, server

Expand Down Expand Up @@ -60,6 +60,11 @@ def main(bot):
bot.action("archive_channel")(incident_helper.archive_channel_action)
bot.view("view_save_incident_roles")(incident_helper.save_incident_roles)

# Register Secret command
bot.command(f"/{PREFIX}secret")(secret.secret_command)
bot.action("secret_change_locale")(secret.handle_change_locale_button)
bot.view("secret_view")(secret.secret_view_handler)

# Register SRE events
bot.command(f"/{PREFIX}sre")(sre.sre_command)

Expand Down
8 changes: 4 additions & 4 deletions app/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
arrow==1.3.0
Authlib==1.3.0
Jinja2==3.1.2
awscli==1.32.15
Jinja2==3.1.3
awscli==1.32.19
aws-sns-message-validator==0.0.5
boto3==1.34.15
boto3==1.34.19
fastapi==0.109.0
geoip2==4.8.0
google-api-python-client==2.113.0
google-auth-httplib2==0.2.0
google-auth-oauthlib==0.8.0
httpx==0.26.0
itsdangerous==2.1.2
Jinja2==3.1.2
Jinja2==3.1.3
PyJWT==2.8.0
PyYAML!=6.0.0,!=5.4.0,!=5.4.1
python-dotenv==0.21.1
Expand Down
Loading

0 comments on commit 8bded6a

Please sign in to comment.