From e8b6047aa2f27a11ec2f51347406ec086386f99e Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Thu, 12 Sep 2024 14:52:05 +0200 Subject: [PATCH 1/8] refactor: move analyze_url workflow in seperate folder and add workflow specific documentation --- workflows/analyze_url/README.md | 46 ++++++++++++++++++ workflows/analyze_url/__init__.py | 0 workflows/{ => analyze_url}/analyze_url.py | 0 workflows/template/README.md | 27 +++++++++++ workflows/template/__init__.py | 0 workflows/virus_total_workflow.py | 55 ++++++++++++++++++++++ 6 files changed, 128 insertions(+) create mode 100644 workflows/analyze_url/README.md create mode 100644 workflows/analyze_url/__init__.py rename workflows/{ => analyze_url}/analyze_url.py (100%) create mode 100644 workflows/template/README.md create mode 100644 workflows/template/__init__.py create mode 100644 workflows/virus_total_workflow.py diff --git a/workflows/analyze_url/README.md b/workflows/analyze_url/README.md new file mode 100644 index 0000000..e5fc713 --- /dev/null +++ b/workflows/analyze_url/README.md @@ -0,0 +1,46 @@ +# Analyze URL Workflow + +The workflow uses VirusTotal to analyze a URL. + +## Required Secrets + +To use this workflow, the following secret is required. To set it up, please follow the respective guide on the linked documentation page. + +- [VirusTotal](https://docs.admyral.dev/integrations/virus_total/virus_total) + +> [!IMPORTANT] \ +> The workflow expects the following secret name: \ +> VirusTotal: virus_total + +## Set Up Workflow + +Use the `admyral` CLI to push the workflow: + +```bash +poetry run admyral workflow push analyze_url -f workflows/analyze_url/analyze_url.py --activate +``` + +## Expected Payload + +The workflow expects the following payload: + +```json +{ + "url": "your_url_to_analyze" +} +``` + +## Trigger Workflow + +Use the Admyral UI: + +1. Open the workflow, by clicking on the **>** icon in the Workflow Overview +2. Click on **Run** +3. Input the expected payload +4. Click on **Run Workflow** + +Use the `admyral` CLI: + +```bash +poetry run admyral workflow trigger analyze_url -p '{"url": "your_url_to_analyze"}' +``` diff --git a/workflows/analyze_url/__init__.py b/workflows/analyze_url/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/analyze_url.py b/workflows/analyze_url/analyze_url.py similarity index 100% rename from workflows/analyze_url.py rename to workflows/analyze_url/analyze_url.py diff --git a/workflows/template/README.md b/workflows/template/README.md new file mode 100644 index 0000000..47cbf30 --- /dev/null +++ b/workflows/template/README.md @@ -0,0 +1,27 @@ +# + +## Required Secrets + +## Set Up Workflow + +Use the `admyral` CLI to push the workflow: + +```bash +poetry run admyral workflow push <> -f workflows/ --activate +``` + +## Expected Payload + +The workflow expects the following payload: + +```json +{} +``` + +## Trigger Workflow + +You can run the workflow from the Admyral UI or using the `admyral` CLI: + +```bash +poetry run admyral workflow trigger <> -p '{"": ""}' +``` diff --git a/workflows/template/__init__.py b/workflows/template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/virus_total_workflow.py b/workflows/virus_total_workflow.py new file mode 100644 index 0000000..4530ffa --- /dev/null +++ b/workflows/virus_total_workflow.py @@ -0,0 +1,55 @@ +from admyral.workflow import workflow, Webhook +from admyral.typings import JsonValue +from admyral.actions import ( + virus_total_analyze_url, + send_slack_message, + openai_chat_completion, +) + + +@workflow(description="Analyze an URL using VirusTotal", triggers=[Webhook()]) +def virus_total_workflow(payload: dict[str, JsonValue]): + # get the analyis result from VirusTotal + analysis_result = virus_total_analyze_url( + url=payload["url"], secrets={"VIRUS_TOTAL_SECRET": "virus_total"} + ) + + # ask the AI to analyze the result + ai_summary = openai_chat_completion( + model="gpt-4o", + prompt=f"You are an expert security analyst. You received the subsequent url analyis result form VirusTotal. \ + Can you briefly state which website it is about and summarize \ + in a short and precise manner if the website is safe to visit? \ + Here is the alert:\n{analysis_result}", + secrets={"OPENAI_SECRET": "openai_secret"}, + ) + + # send the summary to a Slack channel + send_slack_message( + channel_id="C06QP0KV1L2", + text=f"INFORMATION: The URL {payload['url']} has been analyzed by VirusTotal.", + blocks=[ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"*Information: URL {payload['url']} analysis by VirusTotal.*", + }, + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Please note that the following information is generated by an AI model and should be verified by a human expert.", + }, + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"AI VirusTotal Result Summary:\n{ai_summary}", + }, + }, + ], + secrets={"SLACK_SECRET": "slack_secret"}, + ) From 2595fb708604546820ecb57bcdb3647c3499f29b Mon Sep 17 00:00:00 2001 From: Leon Schmid <43738459+leon-schmid@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:55:30 +0200 Subject: [PATCH 2/8] refactor: try out Alerts --- workflows/analyze_url/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflows/analyze_url/README.md b/workflows/analyze_url/README.md index e5fc713..15c146a 100644 --- a/workflows/analyze_url/README.md +++ b/workflows/analyze_url/README.md @@ -8,8 +8,8 @@ To use this workflow, the following secret is required. To set it up, please fol - [VirusTotal](https://docs.admyral.dev/integrations/virus_total/virus_total) -> [!IMPORTANT] \ -> The workflow expects the following secret name: \ +> [!IMPORTANT] +> The workflow expects the following secret name: > VirusTotal: virus_total ## Set Up Workflow From 79701cdeeddbe7d91e351d689c3c3c4811f07356 Mon Sep 17 00:00:00 2001 From: Leon Schmid <43738459+leon-schmid@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:56:18 +0200 Subject: [PATCH 3/8] refactor: Readme --- workflows/analyze_url/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflows/analyze_url/README.md b/workflows/analyze_url/README.md index 15c146a..4135ea6 100644 --- a/workflows/analyze_url/README.md +++ b/workflows/analyze_url/README.md @@ -9,7 +9,7 @@ To use this workflow, the following secret is required. To set it up, please fol - [VirusTotal](https://docs.admyral.dev/integrations/virus_total/virus_total) > [!IMPORTANT] -> The workflow expects the following secret name: +> The workflow expects the following secret name: \ > VirusTotal: virus_total ## Set Up Workflow From 715f80f4e15d9b9703e3b6124a70799f8309cd06 Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Thu, 12 Sep 2024 15:28:42 +0200 Subject: [PATCH 4/8] refactor: add github_audit_logs_owner_changes workflow from main repository and add documentation --- workflows/analyze_url/README.md | 4 +- .../github_audit_logs_owner_changes/README.md | 55 ++++++++++++ .../__init__.py | 0 .../github_audit_logs_owner_changes.py | 85 +++++++++++++++++++ workflows/template/README.md | 7 ++ workflows/virus_total_workflow.py | 55 ------------ 6 files changed, 149 insertions(+), 57 deletions(-) create mode 100644 workflows/github_audit_logs_owner_changes/README.md create mode 100644 workflows/github_audit_logs_owner_changes/__init__.py create mode 100644 workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py delete mode 100644 workflows/virus_total_workflow.py diff --git a/workflows/analyze_url/README.md b/workflows/analyze_url/README.md index 4135ea6..5daddf9 100644 --- a/workflows/analyze_url/README.md +++ b/workflows/analyze_url/README.md @@ -30,7 +30,7 @@ The workflow expects the following payload: } ``` -## Trigger Workflow +## Run Workflow Use the Admyral UI: @@ -39,7 +39,7 @@ Use the Admyral UI: 3. Input the expected payload 4. Click on **Run Workflow** -Use the `admyral` CLI: +Use the `admyral` CLI to trigger the workflow: ```bash poetry run admyral workflow trigger analyze_url -p '{"url": "your_url_to_analyze"}' diff --git a/workflows/github_audit_logs_owner_changes/README.md b/workflows/github_audit_logs_owner_changes/README.md new file mode 100644 index 0000000..2c91e22 --- /dev/null +++ b/workflows/github_audit_logs_owner_changes/README.md @@ -0,0 +1,55 @@ +# GitHub Audit Logs Owner Changes Workflow + +This workflow analyzes the GitHub Audit logs for a specified enterprise for the last hour to check if there were any owner changes within that enterprise. In case there were changes, a notification via Slack is being sent. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [GitHub Enterprise](https://docs.admyral.dev/integrations/github/github) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow expects the following secret names: \ +> Slack: slack_secret +> GitHub Enterprise: github_enterprise_secret + +## Set Up Workflow + +1. Open the `github_audit_logs_owner_changes.py` file. +2. Adjust the `enterprise` and `email` with your enterprise slug and the email of the person to be notified of the respective events via slack + +Use the `admyral` CLI to push the custom actions: + +```bash +poetry run admyral action push get_time_range_of_last_full_hour -a workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py +``` + +```bash +poetry run admyral action push build_info_message_owner_changes -a workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py +``` + +Use the `admyral` CLI to push the workflow: + +```bash +poetry run admyral workflow push github_audit_logs_owner_changes -f workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow, by clicking on the **>** icon in the Workflow Overview +2. Click on **Run** +3. Click on **Run Workflow** + +You can run the workflow from the Admyral UI or using the `admyral` CLI: + +```bash +poetry run admyral workflow trigger github_audit_logs_owner_changes +``` diff --git a/workflows/github_audit_logs_owner_changes/__init__.py b/workflows/github_audit_logs_owner_changes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py b/workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py new file mode 100644 index 0000000..833d5ca --- /dev/null +++ b/workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py @@ -0,0 +1,85 @@ +from typing import Annotated +from datetime import datetime, timedelta, UTC + +from admyral.workflow import workflow, Schedule +from admyral.typings import JsonValue +from admyral.action import action, ArgumentMetadata +from admyral.actions import ( + search_github_enterprise_audit_logs, + batched_send_slack_message_to_user_by_email, +) + + +@action( + display_name="Calculate Time Range for Last Full Hour", + display_namespace="Utilities", + description="Calculate the time range for the last full hour", +) +def get_time_range_of_last_full_hour() -> tuple[str, str]: + end_time = datetime.now(UTC).replace(minute=0, second=0, microsecond=0) + start_time = (end_time - timedelta(hours=1)).isoformat() + "Z" + return (start_time, end_time.isoformat() + "Z") + + +@action( + display_name="Build Info message", + display_namespace="GitHub", + description="Builds a message for the slack notification", +) +def build_info_message_owner_changes( + logs: Annotated[ + list[dict[str, JsonValue]], + ArgumentMetadata( + display_name="Logs", + description="The logs to build the message from", + ), + ], + email: Annotated[ + str, + ArgumentMetadata( + display_name="Email", + description="The email to send the message to", + ), + ], +) -> list[tuple[str, str | None, JsonValue]]: + messages = [] + for log in logs: + timestamp = datetime.fromtimestamp(int(log["created_at"]) / 1000).strftime( + "%Y-%m-%d %H:%M:%S" + ) + if log["action"] == "org.update_member": + messages.append( + ( + email, + f"Owner change detected in enterprise {log['business']} at {timestamp} by {log['actor']}:\nChanged Permission for {log['user']}: {log['old_permission']} -> {log['permission']}\n", + None, + ) + ) + return messages + + +@workflow( + description="Alert on GitHub Orga Owner Changes", + triggers=[Schedule(cron="0 * * * *")], +) +def github_audit_logs_owner_changes(payload: dict[str, JsonValue]): + start_and_end_time = get_time_range_of_last_full_hour() + + logs = search_github_enterprise_audit_logs( + enterprise="admyral", # TODO: set your enterprise slug here + filter="action:org.update_member", + start_time=start_and_end_time[0], + end_time=start_and_end_time[1], + secrets={"GITHUB_ENTERPRISE_SECRET": "github_enterprise_secret"}, + ) + + if logs: + messages = build_info_message_owner_changes( + logs=logs, + email="daniel@admyral.dev", # TODO: set your Slack email here + ) + + batched_send_slack_message_to_user_by_email( + messages=messages, + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/template/README.md b/workflows/template/README.md index 47cbf30..f994b90 100644 --- a/workflows/template/README.md +++ b/workflows/template/README.md @@ -2,6 +2,13 @@ ## Required Secrets +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- + +> [!IMPORTANT] +> The workflow expects the following secret name: \ + ## Set Up Workflow Use the `admyral` CLI to push the workflow: diff --git a/workflows/virus_total_workflow.py b/workflows/virus_total_workflow.py deleted file mode 100644 index 4530ffa..0000000 --- a/workflows/virus_total_workflow.py +++ /dev/null @@ -1,55 +0,0 @@ -from admyral.workflow import workflow, Webhook -from admyral.typings import JsonValue -from admyral.actions import ( - virus_total_analyze_url, - send_slack_message, - openai_chat_completion, -) - - -@workflow(description="Analyze an URL using VirusTotal", triggers=[Webhook()]) -def virus_total_workflow(payload: dict[str, JsonValue]): - # get the analyis result from VirusTotal - analysis_result = virus_total_analyze_url( - url=payload["url"], secrets={"VIRUS_TOTAL_SECRET": "virus_total"} - ) - - # ask the AI to analyze the result - ai_summary = openai_chat_completion( - model="gpt-4o", - prompt=f"You are an expert security analyst. You received the subsequent url analyis result form VirusTotal. \ - Can you briefly state which website it is about and summarize \ - in a short and precise manner if the website is safe to visit? \ - Here is the alert:\n{analysis_result}", - secrets={"OPENAI_SECRET": "openai_secret"}, - ) - - # send the summary to a Slack channel - send_slack_message( - channel_id="C06QP0KV1L2", - text=f"INFORMATION: The URL {payload['url']} has been analyzed by VirusTotal.", - blocks=[ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": f"*Information: URL {payload['url']} analysis by VirusTotal.*", - }, - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Please note that the following information is generated by an AI model and should be verified by a human expert.", - }, - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": f"AI VirusTotal Result Summary:\n{ai_summary}", - }, - }, - ], - secrets={"SLACK_SECRET": "slack_secret"}, - ) From 68e0a8cd2bfe512f47c5735a99d81638db65b59f Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Thu, 12 Sep 2024 16:49:09 +0200 Subject: [PATCH 5/8] refactor: update documentation for workflows --- workflows/analyze_url/README.md | 16 ++--- .../github_audit_logs_owner_changes/README.md | 55 ----------------- .../README.md | 60 +++++++++++++++++++ .../__init__.py | 0 .../github_audit_logs_owner_changes.py | 2 +- workflows/template/README.md | 40 ++++++++++--- 6 files changed, 101 insertions(+), 72 deletions(-) delete mode 100644 workflows/github_audit_logs_owner_changes/README.md create mode 100644 workflows/monitor_github_org_owner_changes/README.md rename workflows/{github_audit_logs_owner_changes => monitor_github_org_owner_changes}/__init__.py (100%) rename workflows/{github_audit_logs_owner_changes => monitor_github_org_owner_changes}/github_audit_logs_owner_changes.py (97%) diff --git a/workflows/analyze_url/README.md b/workflows/analyze_url/README.md index 5daddf9..750fc38 100644 --- a/workflows/analyze_url/README.md +++ b/workflows/analyze_url/README.md @@ -9,12 +9,14 @@ To use this workflow, the following secret is required. To set it up, please fol - [VirusTotal](https://docs.admyral.dev/integrations/virus_total/virus_total) > [!IMPORTANT] -> The workflow expects the following secret name: \ -> VirusTotal: virus_total +> The workflow currently expects the following secret names: \ +> **VirusTotal**: `virus_total` \ +> If your secret has a different name, please adjust the secret mapping in the workflow function accordingly \ +> e.g `secrets = {"VIRUS_TOTAL_SECRET": "your_secret_name"}` ## Set Up Workflow -Use the `admyral` CLI to push the workflow: +Use the CLI to push the workflow: ```bash poetry run admyral workflow push analyze_url -f workflows/analyze_url/analyze_url.py --activate @@ -22,7 +24,7 @@ poetry run admyral workflow push analyze_url -f workflows/analyze_url/analyze_ur ## Expected Payload -The workflow expects the following payload: +The workflow expects the following payload schema: ```json { @@ -34,12 +36,12 @@ The workflow expects the following payload: Use the Admyral UI: -1. Open the workflow, by clicking on the **>** icon in the Workflow Overview +1. Open the workflow in the workflow No-Code editor 2. Click on **Run** -3. Input the expected payload +3. Input the payload following the expeted schema 4. Click on **Run Workflow** -Use the `admyral` CLI to trigger the workflow: +Use the CLI to trigger the workflow: ```bash poetry run admyral workflow trigger analyze_url -p '{"url": "your_url_to_analyze"}' diff --git a/workflows/github_audit_logs_owner_changes/README.md b/workflows/github_audit_logs_owner_changes/README.md deleted file mode 100644 index 2c91e22..0000000 --- a/workflows/github_audit_logs_owner_changes/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# GitHub Audit Logs Owner Changes Workflow - -This workflow analyzes the GitHub Audit logs for a specified enterprise for the last hour to check if there were any owner changes within that enterprise. In case there were changes, a notification via Slack is being sent. - -## Required Secrets - -To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. - -- [GitHub Enterprise](https://docs.admyral.dev/integrations/github/github) -- [Slack](https://docs.admyral.dev/integrations/slack/slack) - -> [!IMPORTANT] -> The workflow expects the following secret names: \ -> Slack: slack_secret -> GitHub Enterprise: github_enterprise_secret - -## Set Up Workflow - -1. Open the `github_audit_logs_owner_changes.py` file. -2. Adjust the `enterprise` and `email` with your enterprise slug and the email of the person to be notified of the respective events via slack - -Use the `admyral` CLI to push the custom actions: - -```bash -poetry run admyral action push get_time_range_of_last_full_hour -a workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py -``` - -```bash -poetry run admyral action push build_info_message_owner_changes -a workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py -``` - -Use the `admyral` CLI to push the workflow: - -```bash -poetry run admyral workflow push github_audit_logs_owner_changes -f workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py --activate -``` - -## Expected Payload - -> [!IMPORTANT] -> The workflow doesn't expect any payload. - -## Run Workflow - -Use the Admyral UI: - -1. Open the workflow, by clicking on the **>** icon in the Workflow Overview -2. Click on **Run** -3. Click on **Run Workflow** - -You can run the workflow from the Admyral UI or using the `admyral` CLI: - -```bash -poetry run admyral workflow trigger github_audit_logs_owner_changes -``` diff --git a/workflows/monitor_github_org_owner_changes/README.md b/workflows/monitor_github_org_owner_changes/README.md new file mode 100644 index 0000000..27ccd4b --- /dev/null +++ b/workflows/monitor_github_org_owner_changes/README.md @@ -0,0 +1,60 @@ +# Monitor GitHub Org Owner Changes + +This workflow analyzes the GitHub Audit logs for a specified enterprise. +It is scheduled to run at every full hour and analyze the previous hour. +In case there were changes, a notification via Slack is being sent. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [GitHub Enterprise](https://docs.admyral.dev/integrations/github/github) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Slack**: `slack_secret` \ +> **GitHub Enterprise**: `github_enterprise_secret` +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"GITHUB_ENTERPRISE_SECRET": "your_secret_name"}` \ +> and for **Slack** respectively + +## Set Up Workflow + +1. Open the `monitor_github_org_owner_changes.py` file +2. Adjust the `enterprise` and `email` with your enterprise slug and the email of the person to be notified of the respective events via slack + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push get_time_range_of_last_full_hour -a workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py +``` + +```bash +poetry run admyral action push build_info_message_owner_changes -a workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py +``` + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push monitor_github_org_owner_changes -f workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger monitor_github_org_owner_changes +``` diff --git a/workflows/github_audit_logs_owner_changes/__init__.py b/workflows/monitor_github_org_owner_changes/__init__.py similarity index 100% rename from workflows/github_audit_logs_owner_changes/__init__.py rename to workflows/monitor_github_org_owner_changes/__init__.py diff --git a/workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py b/workflows/monitor_github_org_owner_changes/github_audit_logs_owner_changes.py similarity index 97% rename from workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py rename to workflows/monitor_github_org_owner_changes/github_audit_logs_owner_changes.py index 833d5ca..13395a2 100644 --- a/workflows/github_audit_logs_owner_changes/github_audit_logs_owner_changes.py +++ b/workflows/monitor_github_org_owner_changes/github_audit_logs_owner_changes.py @@ -62,7 +62,7 @@ def build_info_message_owner_changes( description="Alert on GitHub Orga Owner Changes", triggers=[Schedule(cron="0 * * * *")], ) -def github_audit_logs_owner_changes(payload: dict[str, JsonValue]): +def monitor_github_org_owner_changes(payload: dict[str, JsonValue]): start_and_end_time = get_time_range_of_last_full_hour() logs = search_github_enterprise_audit_logs( diff --git a/workflows/template/README.md b/workflows/template/README.md index f994b90..6517be2 100644 --- a/workflows/template/README.md +++ b/workflows/template/README.md @@ -1,4 +1,4 @@ -# +# Monitor GitHub Org Owner Changes ## Required Secrets @@ -7,28 +7,50 @@ To use this workflow, the following secrets are required. To set them up, please - > [!IMPORTANT] -> The workflow expects the following secret name: \ +> The workflow currently expects the following secret names: \ +> +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"<>": "your_secret_name"}` \ ## Set Up Workflow -Use the `admyral` CLI to push the workflow: +1. + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push +``` + +Use the CLI to push the workflow: ```bash -poetry run admyral workflow push <> -f workflows/ --activate +poetry run admyral workflow push --activate ``` ## Expected Payload -The workflow expects the following payload: +The workflow expects the following payload schema: ```json -{} +{ + "": "" +} ``` -## Trigger Workflow +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** -You can run the workflow from the Admyral UI or using the `admyral` CLI: +Use the CLI to trigger the workflow: ```bash -poetry run admyral workflow trigger <> -p '{"": ""}' +poetry run admyral workflow trigger ``` From 6393b7ad6d2060651762d7aa191821b302f82be9 Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Thu, 12 Sep 2024 21:06:50 +0200 Subject: [PATCH 6/8] refactor: move remaining example workflows from the main repo to this repo, add doc for every workflow and minor adjustments --- workflows/analyze_url/README.md | 6 +- .../jira_notification_user_created/README.md | 50 +++++ .../__init__.py | 0 .../jira_notification_user_created.py | 25 +++ workflows/list_okta_admins/README.md | 45 +++++ workflows/list_okta_admins/__init__.py | 0 .../list_okta_admins/list_okta_admins.py | 18 ++ .../README.md | 2 +- ...py => monitor_github_org_owner_changes.py} | 0 .../okta_password_policy_monitoring/README.md | 62 +++++++ .../__init__.py | 0 .../okta_password_policy_monitoring.py | 122 ++++++++++++ workflows/okta_use_it_or_lose_it/README.md | 60 ++++++ workflows/okta_use_it_or_lose_it/__init__.py | 0 .../okta_use_it_or_lose_it.py | 150 +++++++++++++++ .../README.md | 59 ++++++ .../__init__.py | 0 .../one_password_user_added_to_vault.py | 92 +++++++++ workflows/panther_alert_handling/README.md | 65 +++++++ workflows/panther_alert_handling/__init__.py | 0 .../panther_alert_handling.py | 116 ++++++++++++ .../panther_slack_interactivity/README.md | 55 ++++++ .../panther_slack_interactivity/__init__.py | 0 .../panther_slack_interactivity.py | 71 +++++++ workflows/retool_access_review/README.md | 54 ++++++ workflows/retool_access_review/__init__.py | 0 .../retool_access_review.py | 175 ++++++++++++++++++ workflows/retool_use_it_or_loose_it/README.md | 55 ++++++ .../retool_use_it_or_loose_it/__init__.py | 0 .../retool_use_it_or_loose_it.py | 102 ++++++++++ workflows/slack_interactivity/README.md | 66 +++++++ workflows/slack_interactivity/__init__.py | 0 .../slack_interactivity.py | 43 +++++ workflows/template/README.md | 4 +- workflows/vulnerability_sla_breach/README.md | 51 +++++ .../vulnerability_sla_breach/__init__.py | 0 .../vulnerability_sla_breach.py | 112 +++++++++++ 37 files changed, 1654 insertions(+), 6 deletions(-) create mode 100644 workflows/jira_notification_user_created/README.md create mode 100644 workflows/jira_notification_user_created/__init__.py create mode 100644 workflows/jira_notification_user_created/jira_notification_user_created.py create mode 100644 workflows/list_okta_admins/README.md create mode 100644 workflows/list_okta_admins/__init__.py create mode 100644 workflows/list_okta_admins/list_okta_admins.py rename workflows/monitor_github_org_owner_changes/{github_audit_logs_owner_changes.py => monitor_github_org_owner_changes.py} (100%) create mode 100644 workflows/okta_password_policy_monitoring/README.md create mode 100644 workflows/okta_password_policy_monitoring/__init__.py create mode 100644 workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py create mode 100644 workflows/okta_use_it_or_lose_it/README.md create mode 100644 workflows/okta_use_it_or_lose_it/__init__.py create mode 100644 workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py create mode 100644 workflows/one_password_user_added_to_vault/README.md create mode 100644 workflows/one_password_user_added_to_vault/__init__.py create mode 100644 workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py create mode 100644 workflows/panther_alert_handling/README.md create mode 100644 workflows/panther_alert_handling/__init__.py create mode 100644 workflows/panther_alert_handling/panther_alert_handling.py create mode 100644 workflows/panther_slack_interactivity/README.md create mode 100644 workflows/panther_slack_interactivity/__init__.py create mode 100644 workflows/panther_slack_interactivity/panther_slack_interactivity.py create mode 100644 workflows/retool_access_review/README.md create mode 100644 workflows/retool_access_review/__init__.py create mode 100644 workflows/retool_access_review/retool_access_review.py create mode 100644 workflows/retool_use_it_or_loose_it/README.md create mode 100644 workflows/retool_use_it_or_loose_it/__init__.py create mode 100644 workflows/retool_use_it_or_loose_it/retool_use_it_or_loose_it.py create mode 100644 workflows/slack_interactivity/README.md create mode 100644 workflows/slack_interactivity/__init__.py create mode 100644 workflows/slack_interactivity/slack_interactivity.py create mode 100644 workflows/vulnerability_sla_breach/README.md create mode 100644 workflows/vulnerability_sla_breach/__init__.py create mode 100644 workflows/vulnerability_sla_breach/vulnerability_sla_breach.py diff --git a/workflows/analyze_url/README.md b/workflows/analyze_url/README.md index 750fc38..38beb9e 100644 --- a/workflows/analyze_url/README.md +++ b/workflows/analyze_url/README.md @@ -1,4 +1,4 @@ -# Analyze URL Workflow +# Analyze URL Using VirusTotal The workflow uses VirusTotal to analyze a URL. @@ -9,7 +9,7 @@ To use this workflow, the following secret is required. To set it up, please fol - [VirusTotal](https://docs.admyral.dev/integrations/virus_total/virus_total) > [!IMPORTANT] -> The workflow currently expects the following secret names: \ +> The workflow currently expects the following secret name: \ > **VirusTotal**: `virus_total` \ > If your secret has a different name, please adjust the secret mapping in the workflow function accordingly \ > e.g `secrets = {"VIRUS_TOTAL_SECRET": "your_secret_name"}` @@ -41,7 +41,7 @@ Use the Admyral UI: 3. Input the payload following the expeted schema 4. Click on **Run Workflow** -Use the CLI to trigger the workflow: +Or use the CLI to trigger the workflow: ```bash poetry run admyral workflow trigger analyze_url -p '{"url": "your_url_to_analyze"}' diff --git a/workflows/jira_notification_user_created/README.md b/workflows/jira_notification_user_created/README.md new file mode 100644 index 0000000..552bd5d --- /dev/null +++ b/workflows/jira_notification_user_created/README.md @@ -0,0 +1,50 @@ +# Jira Notify User Creation + +This workflow monitors Jira for newly created user accounts and sends a Slack notification with relevant details. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Jira](https://docs.admyral.dev/integrations/jira/jira) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Slack**: `slack_secret` \ +> **Jira**: `jira_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"JIRA_SECRET": "your_secret_name"}` \ +> and for **Slack** respectively + +## Set Up Workflow + +1. Open the `jira_notification_user_created.py` file +2. Adjust the `email` with the email of the person to receive the slack notification + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push jira_notification_user_created workflows/jira_notification_user_created/jira_notification_user_created.py --activate +``` + +## Expected Payload + +The workflow expects the following payload schema: + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger jira_notification_user_created +``` diff --git a/workflows/jira_notification_user_created/__init__.py b/workflows/jira_notification_user_created/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/jira_notification_user_created/jira_notification_user_created.py b/workflows/jira_notification_user_created/jira_notification_user_created.py new file mode 100644 index 0000000..f7d057d --- /dev/null +++ b/workflows/jira_notification_user_created/jira_notification_user_created.py @@ -0,0 +1,25 @@ +from admyral.workflow import workflow, Webhook, Schedule +from admyral.typings import JsonValue +from admyral.actions import get_jira_audit_records, send_slack_message_to_user_by_email + + +@workflow( + description="Monitors Jira for newly created user accounts and sends a Slack notification with relevant details. " + "This workflow automatically retrieves audit records for user creation events and notifies the specified recipient " + "via Slack with the user ID and creation timestamp.", + triggers=[Webhook(), Schedule(interval_days=1)], +) +def jira_notification_permission_change(payload: dict[str, JsonValue]): + # jira get audit records for newly created users + records = get_jira_audit_records( + filter=["User", "created"], + start_date="2024-08-01T00:00:00", + secrets={"JIRA_SECRET": "jira_secret"}, + ) + + # notify via Slack about changes + send_slack_message_to_user_by_email( + email="daniel@admyral.dev", # TODO: set your Slack email here + text=f"*A new user was created*\n\nUser ID: {records[0]['objectItem']['id']}\nCreated on: {records[0]['created']}", + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/list_okta_admins/README.md b/workflows/list_okta_admins/README.md new file mode 100644 index 0000000..6a7493b --- /dev/null +++ b/workflows/list_okta_admins/README.md @@ -0,0 +1,45 @@ +# Okta List Admins + +This workflow retrieves all admin users. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Okta](https://docs.admyral.dev/integrations/okta/okta) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Okta**: `okta_secret` +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"OKTA_SECRET": "your_secret_name"}` \ + +## Set Up Workflow + +1. Open the `list_okta_admins.py` file +2. Adjust the search query for user type of interest + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push list_okta_admins -f workflows/list_okta_admins/list_okta_admins.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger list_okta_admins +``` diff --git a/workflows/list_okta_admins/__init__.py b/workflows/list_okta_admins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/list_okta_admins/list_okta_admins.py b/workflows/list_okta_admins/list_okta_admins.py new file mode 100644 index 0000000..f48c157 --- /dev/null +++ b/workflows/list_okta_admins/list_okta_admins.py @@ -0,0 +1,18 @@ +from admyral.workflow import workflow +from admyral.typings import JsonValue +from admyral.actions import okta_search_users, okta_get_all_user_types + + +@workflow( + description="Retrieves all user types from Okta and lists the corresponding admin users.", +) +def list_okta_admins(payload: dict[str, JsonValue]): + # Step 1: Get all user types + user_types = okta_get_all_user_types(secrets={"OKTA_SECRET": "okta_secret"}) + + # Step 2: Return admin user type + # TODO: Adjust the search query to match the wished user type + okta_search_users( + search=f"type.id eq \"{user_types[0]['id']}\"", + secrets={"OKTA_SECRET": "okta_secret"}, + ) diff --git a/workflows/monitor_github_org_owner_changes/README.md b/workflows/monitor_github_org_owner_changes/README.md index 27ccd4b..9051d41 100644 --- a/workflows/monitor_github_org_owner_changes/README.md +++ b/workflows/monitor_github_org_owner_changes/README.md @@ -53,7 +53,7 @@ Use the Admyral UI: 2. Click on **Run** 3. Click on **Run Workflow** -Use the CLI to trigger the workflow: +Or use the CLI to trigger the workflow: ```bash poetry run admyral workflow trigger monitor_github_org_owner_changes diff --git a/workflows/monitor_github_org_owner_changes/github_audit_logs_owner_changes.py b/workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py similarity index 100% rename from workflows/monitor_github_org_owner_changes/github_audit_logs_owner_changes.py rename to workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py diff --git a/workflows/okta_password_policy_monitoring/README.md b/workflows/okta_password_policy_monitoring/README.md new file mode 100644 index 0000000..5b27cb1 --- /dev/null +++ b/workflows/okta_password_policy_monitoring/README.md @@ -0,0 +1,62 @@ +# Monitor Okta Password Policy Changes + +This workflow monitors changes to the password policies in Okta and sends notifications via Slack with relevant details. The workflow runs at every full hour and checks for updates made during the previous hour. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Okta](https://docs.admyral.dev/integrations/okta/okta) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Okta**: `okta_secret` \ +> **Slack**: `slack_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly. \ +> e.g. `secrets = {"OKTA_SECRET": "your_secret_name"}` and similarly for Slack. + +## Set Up Workflow + +1. Open the `okta_password_policy_monitoring.py` file +2. Adjust the email address in the `send_slack_message_to_user_by_email` action with the email of the Slack user to receive notifications + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push get_time_range_of_last_full_hour -a workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py +``` + +```bash +poetry run admyral action push get_okta_password_policy_update_logs -a workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py +``` + +```bash +poetry run admyral action push format_okta_policy_update_message -a workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py +``` + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push okta_password_policy_monitoring -f workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger okta_password_policy_monitoring + +``` diff --git a/workflows/okta_password_policy_monitoring/__init__.py b/workflows/okta_password_policy_monitoring/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py b/workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py new file mode 100644 index 0000000..6c8a8ff --- /dev/null +++ b/workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py @@ -0,0 +1,122 @@ +from typing import Annotated +from datetime import datetime, timedelta, UTC +import json + +from admyral.workflow import workflow, Schedule +from admyral.typings import JsonValue +from admyral.action import action, ArgumentMetadata +from admyral.actions import get_okta_logs, send_slack_message_to_user_by_email + + +@action( + display_name="Calculate Time Range for Last Full Hour", + display_namespace="Utilities", + description="Calculate the time range for the last full hour", +) +def get_time_range_of_last_full_hour() -> tuple[str, str]: + end_time = datetime.now(UTC).replace(minute=0, second=0, microsecond=0) + + start_time = (end_time - timedelta(hours=1)).isoformat() + "Z" + + return (start_time, end_time.isoformat() + "Z") + + +@action( + display_name="Get Okta Password Policy Update Logs", + display_namespace="Okta", + description="Retrieve Okta password policy update logs for a specified time range", + secrets_placeholders=["OKTA_SECRET"], +) +def get_okta_password_policy_update_logs( + start_time: Annotated[ + str, + ArgumentMetadata( + display_name="Start Time", + description="The start time for the logs to retrieve in ISO 8601 format", + ), + ], + end_time: Annotated[ + str, + ArgumentMetadata( + display_name="End Time", + description="The end time for the logs to retrieve in ISO 8601 format", + ), + ], +) -> list[dict[str, JsonValue]]: + logs = get_okta_logs( + query="policy.lifecycle.update Password", + start_time=start_time, + end_time=end_time, + ) + + return [ + log + for log in logs + if log.get("target", [{}])[0].get("detailEntry", {}).get("policyType") + == "Password" + ] + + +@action( + display_name="Format Okta Policy Update Message", + display_namespace="Okta", + description="Format Okta policy update logs into a readable message", +) +def format_okta_policy_update_message( + logs: Annotated[ + list[dict[str, JsonValue]], + ArgumentMetadata( + display_name="Okta Logs", + description="List of Okta policy update logs", + ), + ], +) -> str: + message = f"Attention: {len(logs)} Okta password policy update(s) detected in the last hour.\n\n" + for log in logs: + message += f"Event ID: {log.get('transaction', {}).get('id')}\n" + message += f"Timestamp: {log.get('published')}\n" + message += f"Actor: {log.get('actor', {}).get('displayName')} ({log.get('actor', {}).get('alternateId')})\n" + + debug_data = log.get("debugContext", {}).get("debugData", {}) + + new_policy = json.loads( + debug_data.get("newPolicyExtensiblePropertiesJson", "{}") + ) + old_policy = json.loads( + debug_data.get("oldPolicyExtensiblePropertiesJson", "{}") + ) + + message += "Changes:\n" + + for key in new_policy: + new_value = str(new_policy.get(key)) + old_value = str(old_policy.get(key)) + + if new_value.lower() != old_value.lower(): + message += f"- {key}: {old_value} -> {new_value}\n" + + message += "---\n" + return message + + +@workflow( + description="Monitor Okta password policy changes and notify via Slack", + triggers=[Schedule(cron="0 * * * *")], +) +def okta_password_policy_monitoring(payload: dict[str, JsonValue]): + time_range = get_time_range_of_last_full_hour() + + logs = get_okta_password_policy_update_logs( + start_time=time_range[0], + end_time=time_range[1], + secrets={"OKTA_SECRET": "okta_secret"}, + ) + + if logs: + message = format_okta_policy_update_message(logs=logs) + + send_slack_message_to_user_by_email( + email="daniel@admyral.dev", + text=message, + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/okta_use_it_or_lose_it/README.md b/workflows/okta_use_it_or_lose_it/README.md new file mode 100644 index 0000000..fadce8d --- /dev/null +++ b/workflows/okta_use_it_or_lose_it/README.md @@ -0,0 +1,60 @@ +# Okta User Inactivity Check Workflow + +This workflow checks for inactive Okta users who have not logged in for a specified period (default: 90 days) and sends a Slack message to that user, asking if they still need access to Okta. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Okta](https://docs.admyral.dev/integrations/okta/okta) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Okta**: `okta_secret` \ +> **Slack**: `slack_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly. \ +> e.g. `secrets = {"OKTA_SECRET": "your_secret_name"}` and similarly for Slack. + +## Set Up Workflow + +There are no adjustments required for the workflow to work, however you can optionally: + +1. Add a search filter to the `search` parameter +2. Adjust, how many users should be checked for inactivity with the `limit` parameter +3. Adjust the threshold, determining if a user should be counted as inactive by changing the value of `inactivity_threshold` + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push filter_inactive_okta_users -a workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py +``` + +```bash +poetry run admyral action push build_okta_inactivity_messages -a workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py +``` + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push okta_use_it_or_lose_it -f workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger okta_use_it_or_lose_it +``` diff --git a/workflows/okta_use_it_or_lose_it/__init__.py b/workflows/okta_use_it_or_lose_it/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py b/workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py new file mode 100644 index 0000000..adef585 --- /dev/null +++ b/workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py @@ -0,0 +1,150 @@ +from typing import Annotated +import json +from datetime import datetime, timedelta + +from admyral.workflow import workflow, Schedule +from admyral.typings import JsonValue +from admyral.action import action, ArgumentMetadata +from admyral.actions import ( + batched_send_slack_message_to_user_by_email, + okta_search_users, +) + + +@action( + display_name="Filter Inactive Okta Users", + display_namespace="Okta", + description="Filter Okta users who haven't logged in for a specified number of days", +) +def filter_inactive_okta_users( + users: Annotated[ + list[dict[str, JsonValue]], + ArgumentMetadata( + display_name="Okta Users", + description="List of Okta users to filter", + ), + ], + inactivity_threshold: Annotated[ + int, + ArgumentMetadata( + display_name="Inactivity Threshold", + description="Number of days of inactivity to filter by", + ), + ], +) -> list[dict[str, JsonValue]]: + inactive_users = [] + threshold_date = datetime.now() - timedelta(days=inactivity_threshold) + + for user in users: + last_login = user.get("lastLogin") + if last_login: + last_login_date = datetime.fromisoformat(last_login.rstrip("Z")) + if last_login_date < threshold_date: + inactive_users.append(user) + else: + # If lastLogin is None, the user has never logged in + inactive_users.append(user) + + return inactive_users + + +@action( + display_name="Build Okta Inactivity Messages", + display_namespace="Okta", + description="Build Slack messages for inactive Okta users", +) +def build_okta_inactivity_messages( + inactive_users: Annotated[ + list[dict[str, JsonValue]], + ArgumentMetadata( + display_name="Inactive Users", + description="List of inactive Okta users", + ), + ], +) -> list[tuple[str, str | None, JsonValue]]: + messages = [] + for user in inactive_users: + email = user["profile"]["email"] + first_name = user["profile"]["firstName"] + messages.append( + ( + email, + None, + [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Hello {first_name}, you haven't logged into Okta for over 90 days. " + "Please confirm if you still need access.", + }, + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "action_id": "okta_use_it_or_lose_it-yes", + "value": json.dumps( + { + "user": email, + "workflow": "okta_use_it_or_lose_it", + "response": "yes", + } + ), + "text": { + "type": "plain_text", + "text": "Yes, I need access", + }, + }, + { + "type": "button", + "action_id": "okta_use_it_or_lose_it-no", + "value": json.dumps( + { + "user": email, + "workflow": "okta_use_it_or_lose_it", + "response": "no", + } + ), + "text": { + "type": "plain_text", + "text": "No, I no longer need access", + }, + }, + ], + }, + ], + ) + ) + return [message for message in messages if not message[0].endswith("@admyral.dev")] + + +@workflow( + description="Check Okta user inactivity and ask if access is still required", + triggers=[Schedule(interval_days=1)], +) +def okta_use_it_or_lose_it(payload: dict[str, JsonValue]): + # Search for all Okta users + all_users = okta_search_users( + search=None, # No filter, get all users + limit=1000, # Adjust as needed + secrets={"OKTA_SECRET": "okta_secret"}, + ) + + # Filter inactive users (90 days threshold) + inactive_users = filter_inactive_okta_users( + users=all_users, + inactivity_threshold=90, + ) + + # Build messages for inactive users + messages = build_okta_inactivity_messages( + inactive_users=inactive_users, + ) + + # Send Slack messages to inactive users + batched_send_slack_message_to_user_by_email( + messages=messages, + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/one_password_user_added_to_vault/README.md b/workflows/one_password_user_added_to_vault/README.md new file mode 100644 index 0000000..34a7fe6 --- /dev/null +++ b/workflows/one_password_user_added_to_vault/README.md @@ -0,0 +1,59 @@ +# 1Password User Added to Vault + +This workflow monitors 1Password vault events, filtering audit logs to find users who were added to specific vaults. It then sends a Slack notification to the user managing the vault, summarizing the relevant activity. It is scheduled to run at every full hour, checking the previous hour. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [1Password](https://docs.admyral.dev/integrations/1password/1password) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **1Password**: `1password_secret` \ +> **Slack**: `slack_secret`\ +> If your secrets have a different name, please adjust the secret \ +> mappings in the workflow function accordingly \ +> e.g `secrets = {"SLACK_SECRET": "your_secret_name"}` and for 1password respectively. \ + +## Set Up Workflow + +1. Open the `one_password_user_added_to_vault.py` file +2. Adjust the `user_email` with the email of the person who should be notified +3. Adjust the `vault_id` with your Vault ID for which the audit events should be filtered. + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push get_time_range_of_last_full_hour -a workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py +``` + +```bash +poetry run admyral action push filter_by_vault_and_build_slack_message -a workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py +``` + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push one_password_user_added_to_vault -f workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger one_password_user_added_to_vault +``` diff --git a/workflows/one_password_user_added_to_vault/__init__.py b/workflows/one_password_user_added_to_vault/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py b/workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py new file mode 100644 index 0000000..d602ad0 --- /dev/null +++ b/workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py @@ -0,0 +1,92 @@ +from typing import Annotated +from datetime import datetime, timedelta, UTC + + +from admyral.workflow import workflow, Schedule +from admyral.typings import JsonValue +from admyral.actions import ( + list_1password_audit_events, + batched_send_slack_message_to_user_by_email, +) +from admyral.action import ArgumentMetadata, action + + +@action( + display_name="Calculate Time Range for Last Full Hour", + display_namespace="Utilities", + description="Calculate the time range for the last full hour", +) +def get_time_range_of_last_full_hour() -> tuple[str, str]: + end_time = datetime.now(UTC).replace(minute=0, second=0, microsecond=0) + start_time = (end_time - timedelta(hours=1)).isoformat() + "Z" + return (start_time, end_time.isoformat() + "Z") + + +@action( + display_name="Filter by 1Password Vault and Build Slack Message", + display_namespace="1Password", + description="Filter audit events by vault and build a Slack message.", +) +def filter_by_vault_and_build_slack_message( + user_email: Annotated[ + str, + ArgumentMetadata( + display_name="User Email", + description="The email of the user who should receive the Slack messages.", + ), + ], + vault_id: Annotated[ + str, + ArgumentMetadata( + display_name="Vault ID", + description="The vault ID to filter audit events by. The vault ID can be found " + "in the 1Password web app in the URL of the vault.", + ), + ], + audit_events: Annotated[ + list[dict[str, JsonValue]], + ArgumentMetadata( + display_name="Audit Events", + description="The list of audit events to filter.", + ), + ], +) -> list[JsonValue]: + messages = [] + for audit_event in audit_events: + if audit_event["object_uuid"] == vault_id: + messages.append( + ( + user_email, + f"User {audit_event['actor_details']['name']} ({audit_event['actor_details']['email']}) " + f"added user {audit_event['aux_details']['name']} ({audit_event["aux_details"]["email"]}) " + f"to vault {vault_id}.", + None, + ) + ) + return messages + + +@workflow( + description="Retrieves all user types from Okta and lists the corresponding admin users.", + triggers=[Schedule(cron="0 * * * *")], +) +def one_password_user_added_to_vault(payload: dict[str, JsonValue]): + start_and_end_time = get_time_range_of_last_full_hour() + + events = list_1password_audit_events( + action_type_filter="grant", + object_type_filter="uva", + start_time=start_and_end_time[0], + end_time=start_and_end_time[1], + secrets={"1PASSWORD_SECRET": "1password_secret"}, + ) + + messages = filter_by_vault_and_build_slack_message( + user_email="daniel@admyral.dev", # TODO: set your email here + vault_id="ut22fmh7v55235s6t5gjd3t4cy", # TODO: set your vault ID here + audit_events=events, + ) + + batched_send_slack_message_to_user_by_email( + messages=messages, secrets={"SLACK_SECRET": "slack_secret"} + ) diff --git a/workflows/panther_alert_handling/README.md b/workflows/panther_alert_handling/README.md new file mode 100644 index 0000000..ecfe852 --- /dev/null +++ b/workflows/panther_alert_handling/README.md @@ -0,0 +1,65 @@ +# Panther Alert Handling + +This workflow handles alerts from Panther, specifically focusing on AWS ALB alerts for a high volume of 4xx errors. It uses AI to generate summaries and recommendations, creates a Jira issue for tracking, and notifies the team via Slack. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Slack](https://docs.admyral.dev/integrations/slack/slack) +- [Jira](https://docs.admyral.dev/integrations/jira/jira) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Slack**: `slack_secret` \ +> **Jira**: `jira_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"SLACK_SECRET": "your_secret_name"}` and for Jira respectively. + +## Set Up Workflow + +1. Open the `panther_alert_handling.py` file +2. Set the Slack channel ID in `channel_id` where the notifications should be sent + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push panther_alert_handling -f workflows/panther_alert_handling/panther_alert_handling.py --activate +``` + +## Expected Payload + +The workflow expects the following payload schema: + +```json +{ + "alert": { + "id": "AWS.ALB.HighVol400s", + "title": "your_alert_title", + "event_details": { + "domain_name": "your_domain" + }, + "account": "your_account", + "mitre_attack": { + "tactic": "your_mitre_attack_tactic", + "technique": "your_mitre_attack_technique" + }, + "severity": "your_alert_priority" + } +} +``` + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger panther_alert_handling -p '{"alert": {"id": "AWS.ALB.HighVol400s", "title": "your_alert_title", "event_details": {"domain_name": "your_domain"}, "account": "your_account", "mitre_attack": {"tactic": "your_mitre_attack_tactic", "technique": "your_mitre_attack_technique"}, "severity": "your_alert_priority"}}' + +``` diff --git a/workflows/panther_alert_handling/__init__.py b/workflows/panther_alert_handling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/panther_alert_handling/panther_alert_handling.py b/workflows/panther_alert_handling/panther_alert_handling.py new file mode 100644 index 0000000..4656a42 --- /dev/null +++ b/workflows/panther_alert_handling/panther_alert_handling.py @@ -0,0 +1,116 @@ +from admyral.workflow import workflow, Webhook +from admyral.typings import JsonValue +from admyral.actions import send_slack_message, create_jira_issue, ai_action + + +@workflow( + description="This workflow handles alerts from Panther.", + triggers=[Webhook()], +) +def panther_alert_handling(payload: dict[str, JsonValue]): + if payload["alert"]["id"] == "AWS.ALB.HighVol400s": + alert_summary = ai_action( + model="gpt-4o", + prompt=f"You are an expert security analyst. You received the subsequent alert. Can you briefly summarize " + "it in a short and precise manner? Can you also provide a recommendation regarding investigation steps? " + f"Here is the alert:\n{payload['alert']}", + ) + + jira_issue = create_jira_issue( + summary=f"[{payload["alert"]["id"]}] {payload["alert"]["title"]}", + project_id="10001", + issue_type="Bug", + description={ + "content": [ + { + "content": [ + { + "text": f"AI Alert Summary:\n{alert_summary}", + "type": "text", + } + ], + "type": "paragraph", + }, + { + "content": [ + { + "text": f"Alert: {payload['alert']}", + "type": "text", + } + ], + "type": "paragraph", + }, + ], + "type": "doc", + "version": 1, + }, + labels=[ + payload["alert"]["mitre_attack"]["tactic"], + payload["alert"]["mitre_attack"]["technique"], + ], + priority=payload["alert"]["severity"], + secrets={"JIRA_SECRET": "jira_secret"}, + ) + + send_slack_message( + channel_id="TODO(user): set correct channel ID here", + text=f"ACTION REQUIRED: High volume of web port 4xx errors to {payload['alert']['event_details']['domain_name']} in account {payload['alert']['account']}", + blocks=[ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"*ACTION REQUIRED: High volume of web port 4xx errors to {payload['alert']['event_details']['domain_name']} in account {payload['alert']['account']}*", + }, + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Please confirm whether the following alert is suspicious:", + }, + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"AI Alert Summary:\n{alert_summary}", + }, + }, + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"```\n{payload['alert']}\n```"}, + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Jira Ticket: https://christesting123.atlassian.net/browse/{jira_issue['key']}", + }, + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "action_id": "panther_alert_handling-suspicious", + "value": jira_issue["id"], + "text": { + "type": "plain_text", + "text": "Suspicious alert", + }, + }, + { + "type": "button", + "action_id": "panther_alert_handling-non-suspicious", + "value": jira_issue["id"], + "text": { + "type": "plain_text", + "text": "Non-suspicious alert", + }, + }, + ], + }, + ], + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/panther_slack_interactivity/README.md b/workflows/panther_slack_interactivity/README.md new file mode 100644 index 0000000..7d6d68a --- /dev/null +++ b/workflows/panther_slack_interactivity/README.md @@ -0,0 +1,55 @@ +# Panther Handle Slack Interactivity + +This workflow manages Slack interactivity responses, specifically handling actions related to Panther alerts by updating Jira issues. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Jira](https://docs.admyral.dev/integrations/jira/jira) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Jira**: `jira_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"JIRA_SECRET": "your_secret_name"}`. + +## Set Up Workflow + +There are no adjustments required for the workflow to work + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push panther_slack_interactivity -f workflows/panther_slack_interactivity/panther_slack_interactivity.py --activate + +``` + +## Expected Payload + +The workflow expects the following payload schema: + +```json +{ + "actions": [ + { + "action_id": "your_action_id", // one of {panther_alert_handling-suspicious, panther_alert_handling-non-suspicious} + "value": "your_jira_issue_id_or_key" + } + ] +} +``` + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger panther_slack_interactivity -p '{"actions": [{"action_id": "your_action_id", "value": "your_jira_issue_id_or_key"}]}' +``` diff --git a/workflows/panther_slack_interactivity/__init__.py b/workflows/panther_slack_interactivity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/panther_slack_interactivity/panther_slack_interactivity.py b/workflows/panther_slack_interactivity/panther_slack_interactivity.py new file mode 100644 index 0000000..2b9cab6 --- /dev/null +++ b/workflows/panther_slack_interactivity/panther_slack_interactivity.py @@ -0,0 +1,71 @@ +from admyral.workflow import workflow, Webhook +from admyral.typings import JsonValue +from admyral.actions import ( + update_jira_issue_status, + comment_jira_issue_status, +) + + +""" +Setup + +1. Create a Slack app (https://api.slack.com/apps) +2. Go to "Interactivity & Shortcuts" and enable interactivity +3. In the Request URL field enter the URL of the Admyral Webhook trigger + +""" + + +@workflow( + description="This workflow handles Slack interactivity responses.", + triggers=[Webhook()], +) +def slack_interactivity(payload: dict[str, JsonValue]): + # Workflow: Panther Alert Handling + if payload["actions"][0]["action_id"] == "panther_alert_handling-suspicious": + jira_comment = comment_jira_issue_status( + issue_id_or_key=payload["actions"][0]["value"], + comment={ + "content": [ + { + "content": [ + { + "text": "Alert was flagged as suspicious.", + "type": "text", + } + ], + "type": "paragraph", + }, + ], + "type": "doc", + "version": 1, + }, + secrets={"JIRA_SECRET": "jira_secret"}, + ) + + if payload["actions"][0]["action_id"] == "panther_alert_handling-non-suspicious": + jira_comment = comment_jira_issue_status( + issue_id_or_key=payload["actions"][0]["value"], + comment={ + "content": [ + { + "content": [ + { + "text": "Alert was flagged as non-suspicious. Issue is automatically closed.", + "type": "text", + } + ], + "type": "paragraph", + }, + ], + "type": "doc", + "version": 1, + }, + secrets={"JIRA_SECRET": "jira_secret"}, + ) + update_jira_issue_status( + issue_id_or_key=payload["actions"][0]["value"], + transition_id="31", + secrets={"JIRA_SECRET": "jira_secret"}, + run_after=[jira_comment], + ) diff --git a/workflows/retool_access_review/README.md b/workflows/retool_access_review/README.md new file mode 100644 index 0000000..2e83365 --- /dev/null +++ b/workflows/retool_access_review/README.md @@ -0,0 +1,54 @@ +# Retool Access Review + +This workflow automates the process of reviewing Retool access permissions by sending Slack messages to managers for their teams' access review. Managers can review each user's group membership and select whether to approve or remove access for different scenarios. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Retool](https://docs.admyral.dev/integrations/retool/retool) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> +> - **Retool**: `retool_secret` \ +> - **Slack**: `slack_secret` +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"RETOOL_SECRET": "your_secret_name"}` and for Slack respectively. \ + +## Set Up Workflow + +1. Open the `retool_access_review.py` file +2. Adjust the `manager` parameter in the `build_review_requests_as_slack_message_for_managers` function with the email of the person who should be notified via Slack. + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push build_review_requests_as_slack_message_for_managers -a workflows/retool_access_review/retool_access_review.py +``` + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push retool_access_review -f workflows/retool_access_review/retool_access_review.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger retool_access_review +``` diff --git a/workflows/retool_access_review/__init__.py b/workflows/retool_access_review/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/retool_access_review/retool_access_review.py b/workflows/retool_access_review/retool_access_review.py new file mode 100644 index 0000000..ba05c3b --- /dev/null +++ b/workflows/retool_access_review/retool_access_review.py @@ -0,0 +1,175 @@ +from typing import Annotated +import json + +from admyral.workflow import workflow +from admyral.typings import JsonValue +from admyral.actions import ( + list_groups_per_user, + batched_send_slack_message_to_user_by_email, +) +from admyral.action import action, ArgumentMetadata + + +""" + +Pt. 1: Prompting Workflow + +1. Fetch Retool users and their permissions +2. Group users by managers and the associated permissions +3. Send manager slack message and ask for review + Dropdown: + - Approve access + - Appropriate in the past but not needed anymore + - Terminated user + - Suspicious access + + +Pt. 2: Feedback Workflow + +4. If manager disapproves: + 4.1 Remove permissions + 4.2 Check again that permissions were removed + +""" + + +@action( + display_name="Group Users and Permissions by Managers", + display_namespace="Access Review", + description="Groups Retool users and their permissions by their manager.", +) +def build_review_requests_as_slack_message_for_managers( + groups_per_user: Annotated[ + dict[str, JsonValue], + ArgumentMetadata( + display_name="Groups", + description="A list of Retool groups with their members.", + ), + ], +) -> list[tuple[str, str | None, JsonValue]]: + # Group by Manager + # TODO(admyral): fetch managers from Okta + manager = "TODO(user): set some email of a Slack user which will receive the Slack message" + user_groups_per_manager = {manager: groups_per_user} + + # Build review requests + messages = [] + + for manager, groups_per_user_for_manager in user_groups_per_manager.items(): + blocks = [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Hello, here is the annual Access Review for the Retool access of your team:", + }, + } + ] + + for user, groups_and_last_active in groups_per_user_for_manager.items(): + blocks.append({"type": "divider"}) + blocks.append( + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"User {user} (Last active: {groups_and_last_active['last_active']}) is assigned to the following groups:", + }, + }, + ) + + for group in groups_and_last_active["groups"]: + blocks.append( + { + "type": "section", + "text": {"type": "mrkdwn", "text": group}, + "accessory": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Select an option", + "emoji": True, + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Approved access", + "emoji": True, + }, + "value": json.dumps( + { + "group": group, + "user": user, + "response": "keep", + "reason": "Approved access", + } + ), + }, + { + "text": { + "type": "plain_text", + "text": "Appropriate in the past but not needed anymore", + "emoji": True, + }, + "value": json.dumps( + { + "group": group, + "user": user, + "response": "remove", + "reason": "Appropriate in the past but not needed anymore", + } + ), + }, + { + "text": { + "type": "plain_text", + "text": "Terminated user", + "emoji": True, + }, + "value": json.dumps( + { + "group": group, + "user": user, + "response": "remove", + "reason": "Terminated user", + } + ), + }, + { + "text": { + "type": "plain_text", + "text": "Suspicious access", + "emoji": True, + }, + "value": json.dumps( + { + "group": group, + "user": user, + "response": "remove", + "reason": "Suspicious access", + } + ), + }, + ], + "action_id": "access_review", + }, + }, + ) + + messages.append((manager, None, blocks)) + + return messages + + +@workflow( + description="This workflow sends Slack messages to managers for Retool access review.", +) +def retool_access_review(payload: dict[str, JsonValue]): + groups_per_user = list_groups_per_user(secrets={"RETOOL_SECRET": "retool_secret"}) + messages = build_review_requests_as_slack_message_for_managers( + groups_per_user=groups_per_user, + ) + batched_send_slack_message_to_user_by_email( + messages=messages, secrets={"SLACK_SECRET": "slack_secret"} + ) diff --git a/workflows/retool_use_it_or_loose_it/README.md b/workflows/retool_use_it_or_loose_it/README.md new file mode 100644 index 0000000..2589e3b --- /dev/null +++ b/workflows/retool_use_it_or_loose_it/README.md @@ -0,0 +1,55 @@ +# Retool User Inactivity Check Workflow + +This workflow checks for inactive Retool users who have not logged in for a specified period (default: 60 days) and sends a Slack message to that user, asking if they still need access to Retool. + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Retool](https://docs.admyral.dev/integrations/retool/retool) +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> +> - **Retool**: `retool_secret` \ +> - **Slack**: `slack_secret` +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"RETOOL_SECRET": "your_secret_name"}` and for Slack respectively. \ + +## Set Up Workflow + +There are no adjustments required for the workflow to work, however you can optionally: + +Adjust the threshold, determining if a user should be counted as inactive by changing the value of `inactivity_threshold` + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push build_retool_inactivity_question_as_slack_messages -a workflows/retool_use_it_or_loose_it/retool_use_it_or_loose_it.py +``` + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push retool_use_it_or_loose_it -f workflows/retool_use_it_or_loose_it/retool_use_it_or_loose_it.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger retool_use_it_or_loose_it +``` diff --git a/workflows/retool_use_it_or_loose_it/__init__.py b/workflows/retool_use_it_or_loose_it/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/retool_use_it_or_loose_it/retool_use_it_or_loose_it.py b/workflows/retool_use_it_or_loose_it/retool_use_it_or_loose_it.py new file mode 100644 index 0000000..acb3bb7 --- /dev/null +++ b/workflows/retool_use_it_or_loose_it/retool_use_it_or_loose_it.py @@ -0,0 +1,102 @@ +from typing import Annotated +import json + +from admyral.workflow import workflow, Schedule +from admyral.typings import JsonValue +from admyral.action import action, ArgumentMetadata +from admyral.actions import ( + batched_send_slack_message_to_user_by_email, + list_retool_inactive_users, +) + + +@action( + display_name="Build Retool Inactivity Question as Slack Messages", + display_namespace="Use It or Loose It", + description="Build a list of Slack messages to send to inactive Retool users " + "and asking them whether they still need access.", +) +def build_retool_inactivity_question_as_slack_messages( + inactive_users: Annotated[ + list[dict[str, JsonValue]], + ArgumentMetadata( + display_name="Inactive Users", + description="A list of inactive users to send messages to.", + ), + ], +) -> list[tuple[str, str | None, JsonValue]]: + messages = [] + for user in inactive_users: + messages.append( + ( + user["email"], + f"Hello {user['first_name']}, you have not logged into Retool for a while. " + "Please confirm if you still need access.", + [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Hello {user['first_name']}, you have not logged into Retool for a while. " + "Please confirm if you still need access.", + }, + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "action_id": "use_it_or_loose_it-yes", + "value": json.dumps( + { + "user": user["email"], + "workflow": "use_it_or_loose_it", + "response": "yes", + } + ), + "text": { + "type": "plain_text", + "text": "Yes, I need access", + }, + }, + { + "type": "button", + "action_id": "use_it_or_loose_it-no", + "value": json.dumps( + { + "user": user["email"], + "workflow": "use_it_or_loose_it", + "response": "no", + } + ), + "text": { + "type": "plain_text", + "text": "No, I no longer need access", + }, + }, + ], + }, + ], + ) + ) + return messages + + +@workflow( + description="Check Retool user inactivity and ask if access is still required. This worfklow " + "handles the extraction of inactive users and sending messages to them. The response is handled " + "in the Slack interactivity workflow.", + triggers=[Schedule(interval_days=1)], +) +def retool_use_it_or_loose_it(payload: dict[str, JsonValue]): + inactive_users = list_retool_inactive_users( + inactivity_threshold_in_days=60, + secrets={"RETOOL_SECRET": "retool_secret"}, + ) + messages = build_retool_inactivity_question_as_slack_messages( + inactive_users=inactive_users, + ) + batched_send_slack_message_to_user_by_email( + messages=messages, + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/slack_interactivity/README.md b/workflows/slack_interactivity/README.md new file mode 100644 index 0000000..2cd7578 --- /dev/null +++ b/workflows/slack_interactivity/README.md @@ -0,0 +1,66 @@ +# Handle Slack Interactivity Responses + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Slack](https://docs.admyral.dev/integrations/slack/slack) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Slack**: `slack_secret` +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"SLACK_SECRET": "your_secret_name"}` \ + +## Set Up Workflow + +1. Open the `slack_interactivity.py` file +2. Adjust the `channel_id` with your Channel ID + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push slack_interactivity -f workflows/slack_interactivity/slack_interactivity.py --activate +``` + +## Expected Payload + +The workflow expects the following payload schema: + +```json +{ + "actions": [ + { + "action_id": "your_action_id", // one of: {use_it_or_loose_it, access_review} + "value": { + "user": "your_user", + "group": "your, group", + "reason": "your_reason" + } + } + ] +} +``` + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger -p '"actions": [ +{ + "action_id": "your_action_id", // one of: {use_it_or_loose_it, access_review} + "value": { + "user": "your_user", + "group": "your, group", + "reason": "your_reason" + } + }] +}' +``` diff --git a/workflows/slack_interactivity/__init__.py b/workflows/slack_interactivity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/slack_interactivity/slack_interactivity.py b/workflows/slack_interactivity/slack_interactivity.py new file mode 100644 index 0000000..96b207a --- /dev/null +++ b/workflows/slack_interactivity/slack_interactivity.py @@ -0,0 +1,43 @@ +from admyral.workflow import workflow, Webhook +from admyral.typings import JsonValue +from admyral.actions import ( + send_slack_message, + deserialize_json_string, +) + + +""" +Setup + +1. Create a Slack app (https://api.slack.com/apps) +2. Go to "Interactivity & Shortcuts" and enable interactivity +3. In the Request URL field enter the URL of the Admyral Webhook trigger + +""" + + +@workflow( + description="This workflow handles Slack interactivity responses.", + triggers=[Webhook()], +) +def slack_interactivity(payload: dict[str, JsonValue]): + # Workflow: Use it or loose it + if payload["actions"][0]["action_id"] == "use_it_or_loose_it-no": + value = deserialize_json_string(serialized_json=payload["actions"][0]["value"]) + send_slack_message( + channel_id="TODO(user): set Slack channel ID", + text=f"Please deactivate Retool access for the following user: {value["user"]}. Reason: User responded with no longer needed.", + secrets={"SLACK_SECRET": "slack_secret"}, + ) + + # Workflow: Access Review + if payload["actions"][0]["action_id"] == "access_review": + value = deserialize_json_string( + serialized_json=payload["actions"][0]["selected_option"]["value"] + ) + if value["response"] == "remove": + send_slack_message( + channel_id="TODO(user): set Slack channel ID", + text=f"Please remove the user {value['user']} from the group \"{value['group']}\" in Retool. Reason: {value['reason']}", + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/template/README.md b/workflows/template/README.md index 6517be2..7b4f4e2 100644 --- a/workflows/template/README.md +++ b/workflows/template/README.md @@ -1,4 +1,4 @@ -# Monitor GitHub Org Owner Changes +# ## Required Secrets @@ -49,7 +49,7 @@ Use the Admyral UI: 2. Click on **Run** 3. Click on **Run Workflow** -Use the CLI to trigger the workflow: +Or use the CLI to trigger the workflow: ```bash poetry run admyral workflow trigger diff --git a/workflows/vulnerability_sla_breach/README.md b/workflows/vulnerability_sla_breach/README.md new file mode 100644 index 0000000..f00749e --- /dev/null +++ b/workflows/vulnerability_sla_breach/README.md @@ -0,0 +1,51 @@ +# Vulnerability SLA Breach Monitoring + +## Required Secrets + +To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. + +- [Slack](https://docs.admyral.dev/integrations/slack/slack) +- [Jira](https://docs.admyral.dev/integrations/jira/jira) + +> [!IMPORTANT] +> The workflow currently expects the following secret names: \ +> **Slack**: `slack_secret` \ +> **Jira**: `jira_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"SLACK_SECRET": "your_secret_name"}` and for Jira respectively. + +## Set Up Workflow + +1. Open the `vulnerability_sla_breach.py` file +2. Adjust the `email` parameter in `transform_jira_to_slack` with the email adress of the person who should be notified via Slack + +Use the CLI to push the custom actions: + +```bash +poetry run admyral action push transform_jira_to_slack -a workflows/transform_jira_to_slack/transform_jira_to_slack.py +``` + +Use the CLI to push the workflow: + +```bash +poetry run admyral workflow push vulnerability_sla_breach -f workflows/transform_jira_to_slack/transform_jira_to_slack.py --activate +``` + +## Expected Payload + +> [!IMPORTANT] +> The workflow doesn't expect any payload. + +## Run Workflow + +Use the Admyral UI: + +1. Open the workflow in the workflow No-Code editor +2. Click on **Run** +3. Click on **Run Workflow** + +Or use the CLI to trigger the workflow: + +```bash +poetry run admyral workflow trigger vulnerability_sla_breach +``` diff --git a/workflows/vulnerability_sla_breach/__init__.py b/workflows/vulnerability_sla_breach/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workflows/vulnerability_sla_breach/vulnerability_sla_breach.py b/workflows/vulnerability_sla_breach/vulnerability_sla_breach.py new file mode 100644 index 0000000..bb21939 --- /dev/null +++ b/workflows/vulnerability_sla_breach/vulnerability_sla_breach.py @@ -0,0 +1,112 @@ +from typing import Annotated +from admyral.workflow import workflow, Schedule +from admyral.typings import JsonValue +from admyral.action import action, ArgumentMetadata +from admyral.actions import ( + search_jira_issues, + batched_send_slack_message_to_user_by_email, +) + + +""" +Setup + +1. Create a Slack app (https://api.slack.com/apps) +2. Go to "Interactivity & Shortcuts" and enable interactivity +3. In the Request URL field enter the URL of the Admyral Webhook trigger + +""" + + +@action( + display_name="Transform Jira Issues to Slack", + display_namespace="Jira", + description="Transform Jira Issues to Slack", +) +def transform_jira_to_slack( + jira_tickets: Annotated[ + JsonValue, + ArgumentMetadata( + display_name="Jira tickets", description="Result of Jira Ticket Search" + ), + ], + message_input: Annotated[ + str, + ArgumentMetadata( + display_name="Message Input", + description="Describe the type of message that should be displayed. E.g., Recent SLA breach", + ), + ], +): + + email = "" #TODO: Add email of the user who should receive the Slack messages + result = [] + for jira_ticket in jira_tickets: + key = jira_ticket["key"] + title = jira_ticket["fields"]["summary"] + creator = jira_ticket["fields"]["creator"]["displayName"] + status = jira_ticket["fields"]["status"]["name"] + created = jira_ticket["fields"]["created"] + + result.append( + ( + email, + f"{message_input}: [{key}] {title} \nCreated by: {creator} \nStatus: {status} \nCreated on: {created}", + None, + ) + ) + + return result + + +@workflow( + description="Monitoring of vulnerability SLA breaches", + triggers=[Schedule(interval_days=1)], +) +def vulnerability_sla_breach(payload: dict[str, JsonValue]): + # filter for jira tickets that didn't change in the last 7 days + no_change_last_7_days = search_jira_issues( + jql='project = SJ AND status IN ("In Progress", "To Do") AND updated < -1w AND updated >= -8d', # AND priority IN (Highest, High) + limit=1000, + secrets={"JIRA_SECRET": "jira_secret"}, + ) + + transformed_no_change = transform_jira_to_slack( + jira_tickets=no_change_last_7_days, + message_input="🚨 No progress in the last 7 days 🚨", + ) + + batched_send_slack_message_to_user_by_email( + messages=transformed_no_change, secrets={"SLACK_SECRET": "slack_secret"} + ) + + # filter for jira tickets whose SLA is about to be breached (10 days left) + soon_breached_slas = search_jira_issues( + jql='project = SJ AND status IN ("In Progress", "To Do") AND created = -80d', # AND priority IN (Highest, High) + limit=1000, + secrets={"JIRA_SECRET": "jira_secret"}, + ) + + transformed_soon_breached = transform_jira_to_slack( + jira_tickets=soon_breached_slas, + message_input="🚨 About to be breached SLAs in 10 days 🚨", + ) + + batched_send_slack_message_to_user_by_email( + messages=transformed_soon_breached, secrets={"SLACK_SECRET": "slack_secret"} + ) + + # filter for jira tickets that just breached SLA + breached_slas = search_jira_issues( + jql='project = SJ AND status IN ("In Progress", "To Do") AND created = -91d', # AND priority IN (Highest, High) + limit=1000, + secrets={"JIRA_SECRET": "jira_secret"}, + ) + + transformed_breached = transform_jira_to_slack( + jira_tickets=breached_slas, message_input="🚨 Just breached SLAs 🚨" + ) + + batched_send_slack_message_to_user_by_email( + messages=transformed_breached, secrets={"SLACK_SECRET": "slack_secret"} + ) From ca07d84104392d2cd7332246fae9290516bd72e0 Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Tue, 17 Sep 2024 11:13:53 +0200 Subject: [PATCH 7/8] refactor: put together connected workflows and minor changes in documentation --- workflows/analyze_url/README.md | 2 +- .../jira_notification_user_created/README.md | 6 +- .../jira_notification_user_created.py | 2 +- workflows/list_okta_admins/README.md | 10 +-- .../README.md | 2 +- .../monitor_github_org_owner_changes.py | 4 +- .../okta_password_policy_monitoring/README.md | 1 - .../okta_password_policy_monitoring.py | 6 +- workflows/okta_use_it_or_lose_it/README.md | 10 ++- .../slack_interactivity.py | 30 +++++++++ .../README.md | 2 +- .../one_password_user_added_to_vault.py | 4 +- workflows/panther_alert_handling/README.md | 12 +++- .../panther_slack_interactivity.py | 0 .../panther_slack_interactivity/README.md | 55 ---------------- .../panther_slack_interactivity/__init__.py | 0 workflows/retool_access_review/README.md | 18 +++-- .../slack_interactivity.py | 11 ---- workflows/retool_use_it_or_loose_it/README.md | 10 +-- workflows/slack_interactivity/README.md | 66 ------------------- workflows/slack_interactivity/__init__.py | 0 21 files changed, 85 insertions(+), 166 deletions(-) create mode 100644 workflows/okta_use_it_or_lose_it/slack_interactivity.py rename workflows/{panther_slack_interactivity => panther_alert_handling}/panther_slack_interactivity.py (100%) delete mode 100644 workflows/panther_slack_interactivity/README.md delete mode 100644 workflows/panther_slack_interactivity/__init__.py rename workflows/{slack_interactivity => retool_access_review}/slack_interactivity.py (67%) delete mode 100644 workflows/slack_interactivity/README.md delete mode 100644 workflows/slack_interactivity/__init__.py diff --git a/workflows/analyze_url/README.md b/workflows/analyze_url/README.md index 38beb9e..d9e2132 100644 --- a/workflows/analyze_url/README.md +++ b/workflows/analyze_url/README.md @@ -38,7 +38,7 @@ Use the Admyral UI: 1. Open the workflow in the workflow No-Code editor 2. Click on **Run** -3. Input the payload following the expeted schema +3. Input the payload following the expected schema 4. Click on **Run Workflow** Or use the CLI to trigger the workflow: diff --git a/workflows/jira_notification_user_created/README.md b/workflows/jira_notification_user_created/README.md index 552bd5d..8e5910e 100644 --- a/workflows/jira_notification_user_created/README.md +++ b/workflows/jira_notification_user_created/README.md @@ -1,4 +1,4 @@ -# Jira Notify User Creation +# Jira Notify On User Creation This workflow monitors Jira for newly created user accounts and sends a Slack notification with relevant details. @@ -20,7 +20,7 @@ To use this workflow, the following secrets are required. To set them up, please ## Set Up Workflow 1. Open the `jira_notification_user_created.py` file -2. Adjust the `email` with the email of the person to receive the slack notification +2. Adjust the `email` parameter with the email of the person to receive the slack notification Use the CLI to push the workflow: @@ -30,8 +30,6 @@ poetry run admyral workflow push jira_notification_user_created workflows/jira_n ## Expected Payload -The workflow expects the following payload schema: - > [!IMPORTANT] > The workflow doesn't expect any payload. diff --git a/workflows/jira_notification_user_created/jira_notification_user_created.py b/workflows/jira_notification_user_created/jira_notification_user_created.py index f7d057d..cfbadcc 100644 --- a/workflows/jira_notification_user_created/jira_notification_user_created.py +++ b/workflows/jira_notification_user_created/jira_notification_user_created.py @@ -9,7 +9,7 @@ "via Slack with the user ID and creation timestamp.", triggers=[Webhook(), Schedule(interval_days=1)], ) -def jira_notification_permission_change(payload: dict[str, JsonValue]): +def jira_notification_user_created(payload: dict[str, JsonValue]): # jira get audit records for newly created users records = get_jira_audit_records( filter=["User", "created"], diff --git a/workflows/list_okta_admins/README.md b/workflows/list_okta_admins/README.md index 6a7493b..e4d3463 100644 --- a/workflows/list_okta_admins/README.md +++ b/workflows/list_okta_admins/README.md @@ -1,6 +1,6 @@ -# Okta List Admins +# List Okta Admins -This workflow retrieves all admin users. +This workflow retrieves specifc or all user types and lists all admin users. ## Required Secrets @@ -10,14 +10,16 @@ To use this workflow, the following secrets are required. To set them up, please > [!IMPORTANT] > The workflow currently expects the following secret names: \ -> **Okta**: `okta_secret` +> **Okta**: `okta_secret` \ > If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ > e.g `secrets = {"OKTA_SECRET": "your_secret_name"}` \ ## Set Up Workflow +There are no adjustments required for the workflow to work, but you can optionally: + 1. Open the `list_okta_admins.py` file -2. Adjust the search query for user type of interest +2. Adjust the search query for the user type of interest Use the CLI to push the workflow: diff --git a/workflows/monitor_github_org_owner_changes/README.md b/workflows/monitor_github_org_owner_changes/README.md index 9051d41..99e4953 100644 --- a/workflows/monitor_github_org_owner_changes/README.md +++ b/workflows/monitor_github_org_owner_changes/README.md @@ -14,7 +14,7 @@ To use this workflow, the following secrets are required. To set them up, please > [!IMPORTANT] > The workflow currently expects the following secret names: \ > **Slack**: `slack_secret` \ -> **GitHub Enterprise**: `github_enterprise_secret` +> **GitHub Enterprise**: `github_enterprise_secret` \ > If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ > e.g `secrets = {"GITHUB_ENTERPRISE_SECRET": "your_secret_name"}` \ > and for **Slack** respectively diff --git a/workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py b/workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py index 13395a2..e3df7a9 100644 --- a/workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py +++ b/workflows/monitor_github_org_owner_changes/monitor_github_org_owner_changes.py @@ -17,8 +17,8 @@ ) def get_time_range_of_last_full_hour() -> tuple[str, str]: end_time = datetime.now(UTC).replace(minute=0, second=0, microsecond=0) - start_time = (end_time - timedelta(hours=1)).isoformat() + "Z" - return (start_time, end_time.isoformat() + "Z") + start_time = (end_time - timedelta(hours=1)).isoformat().replace("+00:00", "Z") + return (start_time, end_time.isoformat().replace("+00:00", "Z")) @action( diff --git a/workflows/okta_password_policy_monitoring/README.md b/workflows/okta_password_policy_monitoring/README.md index 5b27cb1..d806fec 100644 --- a/workflows/okta_password_policy_monitoring/README.md +++ b/workflows/okta_password_policy_monitoring/README.md @@ -58,5 +58,4 @@ Or use the CLI to trigger the workflow: ```bash poetry run admyral workflow trigger okta_password_policy_monitoring - ``` diff --git a/workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py b/workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py index 6c8a8ff..63c3f88 100644 --- a/workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py +++ b/workflows/okta_password_policy_monitoring/okta_password_policy_monitoring.py @@ -15,10 +15,8 @@ ) def get_time_range_of_last_full_hour() -> tuple[str, str]: end_time = datetime.now(UTC).replace(minute=0, second=0, microsecond=0) - - start_time = (end_time - timedelta(hours=1)).isoformat() + "Z" - - return (start_time, end_time.isoformat() + "Z") + start_time = (end_time - timedelta(hours=1)).isoformat().replace("+00:00", "Z") + return (start_time, end_time.isoformat().replace("+00:00", "Z")) @action( diff --git a/workflows/okta_use_it_or_lose_it/README.md b/workflows/okta_use_it_or_lose_it/README.md index fadce8d..41617b2 100644 --- a/workflows/okta_use_it_or_lose_it/README.md +++ b/workflows/okta_use_it_or_lose_it/README.md @@ -2,6 +2,10 @@ This workflow checks for inactive Okta users who have not logged in for a specified period (default: 90 days) and sends a Slack message to that user, asking if they still need access to Okta. +> [!IMPORTANT] +> In case you want to use this workflow together with the `retool_access_review` workflow, you have to combine the functionality within the `slack_interactivity.py` by adding the respective `if` condition in the `slack_interactivity.py` within the retool_access_review directory. \ +> This is because the Slack API only allows the configuration of one interactivity webhook at a time. + ## Required Secrets To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. @@ -34,12 +38,16 @@ poetry run admyral action push filter_inactive_okta_users -a workflows/okta_use_ poetry run admyral action push build_okta_inactivity_messages -a workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py ``` -Use the CLI to push the workflow: +Use the CLI to push the workflows: ```bash poetry run admyral workflow push okta_use_it_or_lose_it -f workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py --activate ``` +```bash +poetry run admyral workflow push slack_interactivity -f workflows/okta_use_it_or_lose_it/okta_use_it_or_lose_it.py --activate +``` + ## Expected Payload > [!IMPORTANT] diff --git a/workflows/okta_use_it_or_lose_it/slack_interactivity.py b/workflows/okta_use_it_or_lose_it/slack_interactivity.py new file mode 100644 index 0000000..1e783d9 --- /dev/null +++ b/workflows/okta_use_it_or_lose_it/slack_interactivity.py @@ -0,0 +1,30 @@ +from admyral.workflow import workflow, Webhook +from admyral.typings import JsonValue +from admyral.actions import ( + send_slack_message, + deserialize_json_string, +) + + +""" +Setup + +1. Create a Slack app (https://api.slack.com/apps) +2. Go to "Interactivity & Shortcuts" and enable interactivity +3. In the Request URL field enter the URL of the Admyral Webhook trigger + +""" + +@workflow( + description="This workflow handles Slack interactivity responses.", + triggers=[Webhook()], +) +def slack_interactivity(payload: dict[str, JsonValue]): + # Workflow: Use it or loose it + if payload["actions"][0]["action_id"] == "use_it_or_loose_it-no": + value = deserialize_json_string(serialized_json=payload["actions"][0]["value"]) + send_slack_message( + channel_id="TODO(user): set Slack channel ID", + text=f"Please deactivate Retool access for the following user: {value["user"]}. Reason: User responded with no longer needed.", + secrets={"SLACK_SECRET": "slack_secret"}, + ) diff --git a/workflows/one_password_user_added_to_vault/README.md b/workflows/one_password_user_added_to_vault/README.md index 34a7fe6..06a63fa 100644 --- a/workflows/one_password_user_added_to_vault/README.md +++ b/workflows/one_password_user_added_to_vault/README.md @@ -21,7 +21,7 @@ To use this workflow, the following secrets are required. To set them up, please 1. Open the `one_password_user_added_to_vault.py` file 2. Adjust the `user_email` with the email of the person who should be notified -3. Adjust the `vault_id` with your Vault ID for which the audit events should be filtered. +3. Adjust the `vault_id` with your Vault ID for which the audit events should be filtered Use the CLI to push the custom actions: diff --git a/workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py b/workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py index d602ad0..d8ecec4 100644 --- a/workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py +++ b/workflows/one_password_user_added_to_vault/one_password_user_added_to_vault.py @@ -18,8 +18,8 @@ ) def get_time_range_of_last_full_hour() -> tuple[str, str]: end_time = datetime.now(UTC).replace(minute=0, second=0, microsecond=0) - start_time = (end_time - timedelta(hours=1)).isoformat() + "Z" - return (start_time, end_time.isoformat() + "Z") + start_time = (end_time - timedelta(hours=1)).isoformat().replace("+00:00", "Z") + return (start_time, end_time.isoformat().replace("+00:00", "Z")) @action( diff --git a/workflows/panther_alert_handling/README.md b/workflows/panther_alert_handling/README.md index ecfe852..cce8dd0 100644 --- a/workflows/panther_alert_handling/README.md +++ b/workflows/panther_alert_handling/README.md @@ -1,6 +1,10 @@ # Panther Alert Handling -This workflow handles alerts from Panther, specifically focusing on AWS ALB alerts for a high volume of 4xx errors. It uses AI to generate summaries and recommendations, creates a Jira issue for tracking, and notifies the team via Slack. +This workflow handles alerts from Panther. It uses AI to generate summaries and recommendations, creates a Jira issue for tracking, and notifies the team via Slack. + +> [!IMPORTANT] +> The panther_alert_handling workflow calls the panther_slack_interactivity workflow. \ +> Note, that the Slack API only allows to configure on interactivity webhook at a time, should you also run other workflows using slack interactivity. ## Required Secrets @@ -21,12 +25,16 @@ To use this workflow, the following secrets are required. To set them up, please 1. Open the `panther_alert_handling.py` file 2. Set the Slack channel ID in `channel_id` where the notifications should be sent -Use the CLI to push the workflow: +Use the CLI to push the workflows: ```bash poetry run admyral workflow push panther_alert_handling -f workflows/panther_alert_handling/panther_alert_handling.py --activate ``` +```bash +poetry run admyral workflow push panther_slack_interactivity -f workflows/panther_alert_handling/panther_alert_handling.py --activate +``` + ## Expected Payload The workflow expects the following payload schema: diff --git a/workflows/panther_slack_interactivity/panther_slack_interactivity.py b/workflows/panther_alert_handling/panther_slack_interactivity.py similarity index 100% rename from workflows/panther_slack_interactivity/panther_slack_interactivity.py rename to workflows/panther_alert_handling/panther_slack_interactivity.py diff --git a/workflows/panther_slack_interactivity/README.md b/workflows/panther_slack_interactivity/README.md deleted file mode 100644 index 7d6d68a..0000000 --- a/workflows/panther_slack_interactivity/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Panther Handle Slack Interactivity - -This workflow manages Slack interactivity responses, specifically handling actions related to Panther alerts by updating Jira issues. - -## Required Secrets - -To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. - -- [Jira](https://docs.admyral.dev/integrations/jira/jira) - -> [!IMPORTANT] -> The workflow currently expects the following secret names: \ -> **Jira**: `jira_secret` \ -> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ -> e.g `secrets = {"JIRA_SECRET": "your_secret_name"}`. - -## Set Up Workflow - -There are no adjustments required for the workflow to work - -Use the CLI to push the workflow: - -```bash -poetry run admyral workflow push panther_slack_interactivity -f workflows/panther_slack_interactivity/panther_slack_interactivity.py --activate - -``` - -## Expected Payload - -The workflow expects the following payload schema: - -```json -{ - "actions": [ - { - "action_id": "your_action_id", // one of {panther_alert_handling-suspicious, panther_alert_handling-non-suspicious} - "value": "your_jira_issue_id_or_key" - } - ] -} -``` - -## Run Workflow - -Use the Admyral UI: - -1. Open the workflow in the workflow No-Code editor -2. Click on **Run** -3. Click on **Run Workflow** - -Or use the CLI to trigger the workflow: - -```bash -poetry run admyral workflow trigger panther_slack_interactivity -p '{"actions": [{"action_id": "your_action_id", "value": "your_jira_issue_id_or_key"}]}' -``` diff --git a/workflows/panther_slack_interactivity/__init__.py b/workflows/panther_slack_interactivity/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/workflows/retool_access_review/README.md b/workflows/retool_access_review/README.md index 2e83365..ff00247 100644 --- a/workflows/retool_access_review/README.md +++ b/workflows/retool_access_review/README.md @@ -2,6 +2,10 @@ This workflow automates the process of reviewing Retool access permissions by sending Slack messages to managers for their teams' access review. Managers can review each user's group membership and select whether to approve or remove access for different scenarios. +> [!IMPORTANT] +> In case you want to use this workflow together with the `okta_use_it_or_lose_it` workflow, you have to combine the functionality within the `slack_interactivity.py` by adding the respective `if` condition in the `slack_interactivity.py` within the okta_use_it_or_lose_it directory. \ +> This is because the Slack API only allows the configuration of one interactivity webhook at a time. + ## Required Secrets To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. @@ -12,15 +16,15 @@ To use this workflow, the following secrets are required. To set them up, please > [!IMPORTANT] > The workflow currently expects the following secret names: \ > -> - **Retool**: `retool_secret` \ -> - **Slack**: `slack_secret` -> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ -> e.g `secrets = {"RETOOL_SECRET": "your_secret_name"}` and for Slack respectively. \ +> **Retool**: `retool_secret` \ +> **Slack**: `slack_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"RETOOL_SECRET": "your_secret_name"}` and for Slack respectively. \ ## Set Up Workflow 1. Open the `retool_access_review.py` file -2. Adjust the `manager` parameter in the `build_review_requests_as_slack_message_for_managers` function with the email of the person who should be notified via Slack. +2. Adjust the `manager` parameter in the `build_review_requests_as_slack_message_for_managers` function with the email of the person who should be notified via Slack Use the CLI to push the custom actions: @@ -34,6 +38,10 @@ Use the CLI to push the workflow: poetry run admyral workflow push retool_access_review -f workflows/retool_access_review/retool_access_review.py --activate ``` +```bash +poetry run admyral workflow push slack_interactivity -f workflows/retool_access_review/retool_access_review.py --activate +``` + ## Expected Payload > [!IMPORTANT] diff --git a/workflows/slack_interactivity/slack_interactivity.py b/workflows/retool_access_review/slack_interactivity.py similarity index 67% rename from workflows/slack_interactivity/slack_interactivity.py rename to workflows/retool_access_review/slack_interactivity.py index 96b207a..08d7ed3 100644 --- a/workflows/slack_interactivity/slack_interactivity.py +++ b/workflows/retool_access_review/slack_interactivity.py @@ -15,22 +15,11 @@ """ - @workflow( description="This workflow handles Slack interactivity responses.", triggers=[Webhook()], ) def slack_interactivity(payload: dict[str, JsonValue]): - # Workflow: Use it or loose it - if payload["actions"][0]["action_id"] == "use_it_or_loose_it-no": - value = deserialize_json_string(serialized_json=payload["actions"][0]["value"]) - send_slack_message( - channel_id="TODO(user): set Slack channel ID", - text=f"Please deactivate Retool access for the following user: {value["user"]}. Reason: User responded with no longer needed.", - secrets={"SLACK_SECRET": "slack_secret"}, - ) - - # Workflow: Access Review if payload["actions"][0]["action_id"] == "access_review": value = deserialize_json_string( serialized_json=payload["actions"][0]["selected_option"]["value"] diff --git a/workflows/retool_use_it_or_loose_it/README.md b/workflows/retool_use_it_or_loose_it/README.md index 2589e3b..db38fe2 100644 --- a/workflows/retool_use_it_or_loose_it/README.md +++ b/workflows/retool_use_it_or_loose_it/README.md @@ -12,16 +12,16 @@ To use this workflow, the following secrets are required. To set them up, please > [!IMPORTANT] > The workflow currently expects the following secret names: \ > -> - **Retool**: `retool_secret` \ -> - **Slack**: `slack_secret` -> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ -> e.g `secrets = {"RETOOL_SECRET": "your_secret_name"}` and for Slack respectively. \ +> **Retool**: `retool_secret` \ +> **Slack**: `slack_secret` \ +> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ +> e.g `secrets = {"RETOOL_SECRET": "your_secret_name"}` and for Slack respectively. \ ## Set Up Workflow There are no adjustments required for the workflow to work, however you can optionally: -Adjust the threshold, determining if a user should be counted as inactive by changing the value of `inactivity_threshold` +Adjust the threshold, determining if a user should be counted as inactive by changing the value of `inactivity_threshold` within the workflow function. Use the CLI to push the custom actions: diff --git a/workflows/slack_interactivity/README.md b/workflows/slack_interactivity/README.md deleted file mode 100644 index 2cd7578..0000000 --- a/workflows/slack_interactivity/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Handle Slack Interactivity Responses - -## Required Secrets - -To use this workflow, the following secrets are required. To set them up, please follow the respective guide on the linked documentation page. - -- [Slack](https://docs.admyral.dev/integrations/slack/slack) - -> [!IMPORTANT] -> The workflow currently expects the following secret names: \ -> **Slack**: `slack_secret` -> If your secrets have a different name, please adjust the secret mappings in the workflow function accordingly \ -> e.g `secrets = {"SLACK_SECRET": "your_secret_name"}` \ - -## Set Up Workflow - -1. Open the `slack_interactivity.py` file -2. Adjust the `channel_id` with your Channel ID - -Use the CLI to push the workflow: - -```bash -poetry run admyral workflow push slack_interactivity -f workflows/slack_interactivity/slack_interactivity.py --activate -``` - -## Expected Payload - -The workflow expects the following payload schema: - -```json -{ - "actions": [ - { - "action_id": "your_action_id", // one of: {use_it_or_loose_it, access_review} - "value": { - "user": "your_user", - "group": "your, group", - "reason": "your_reason" - } - } - ] -} -``` - -## Run Workflow - -Use the Admyral UI: - -1. Open the workflow in the workflow No-Code editor -2. Click on **Run** -3. Click on **Run Workflow** - -Or use the CLI to trigger the workflow: - -```bash -poetry run admyral workflow trigger -p '"actions": [ -{ - "action_id": "your_action_id", // one of: {use_it_or_loose_it, access_review} - "value": { - "user": "your_user", - "group": "your, group", - "reason": "your_reason" - } - }] -}' -``` diff --git a/workflows/slack_interactivity/__init__.py b/workflows/slack_interactivity/__init__.py deleted file mode 100644 index e69de29..0000000 From 3913cd5ebe56a47c631c47e53b23bb9e3ba19deb Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Tue, 17 Sep 2024 11:19:08 +0200 Subject: [PATCH 8/8] refactor: change titles of some workflows in README --- workflows/okta_use_it_or_lose_it/README.md | 2 +- workflows/retool_use_it_or_loose_it/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workflows/okta_use_it_or_lose_it/README.md b/workflows/okta_use_it_or_lose_it/README.md index 41617b2..507eb5d 100644 --- a/workflows/okta_use_it_or_lose_it/README.md +++ b/workflows/okta_use_it_or_lose_it/README.md @@ -1,4 +1,4 @@ -# Okta User Inactivity Check Workflow +# Okta Notify Inactive Users This workflow checks for inactive Okta users who have not logged in for a specified period (default: 90 days) and sends a Slack message to that user, asking if they still need access to Okta. diff --git a/workflows/retool_use_it_or_loose_it/README.md b/workflows/retool_use_it_or_loose_it/README.md index db38fe2..7126c59 100644 --- a/workflows/retool_use_it_or_loose_it/README.md +++ b/workflows/retool_use_it_or_loose_it/README.md @@ -1,4 +1,4 @@ -# Retool User Inactivity Check Workflow +# Retool Notify Inactive Users This workflow checks for inactive Retool users who have not logged in for a specified period (default: 60 days) and sends a Slack message to that user, asking if they still need access to Retool.