Skip to content

Commit 443bbff

Browse files
Merge pull request #1387 from TheHive-Project/slack-integration-improvements
Slack responder - Improvements
2 parents b4f5743 + b206a47 commit 443bbff

File tree

2 files changed

+95
-35
lines changed

2 files changed

+95
-35
lines changed

responders/Slack/README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ This directory contains two Slack responders for TheHive integration:
1818
- Invites default participants by email
1919
- Sets channel visibility (private or public)
2020
- Posts case summary and/or case description (optional)
21+
- **Automatically tags the case** with `slack:<channel_name>` for easy tracking
2122

22-
### Slack_SyncChannel
23-
- Retrieves all conversation history from channels with format `#case-CASEID`
23+
### Slack_SyncChannel
24+
- **Syncs all Slack channels tagged with `slack:` prefix** on the case
25+
- Retrieves all conversation history from tagged channels
26+
- **Fallback**: If no tags found, uses default format `#case-CASEID`
2427
- Creates TheHive tasks in "Communication" category with individual task logs for each message
2528
- Downloads and attaches file attachments (images, documents) to task logs
2629
- Chronologically ordered messages with timestamps and usernames
2730
- Prevents duplicate syncing by tracking message timestamps
2831
- Converts Slack user IDs to readable usernames for better readability
32+
- **Multi-channel support**: Syncs multiple channels if multiple `slack:` tags exist
2933

3034
## Preview
3135

@@ -85,5 +89,26 @@ This directory contains two Slack responders for TheHive integration:
8589

8690
## 2. Enable and configure the Responders
8791

88-
Log into your Cortex instance, go to Organization > Responders and enable the desired JIRA responders with the appropriate configuration & API keys.
92+
Log into your Cortex instance, go to Organization > Responders and enable the desired Slack responders with the appropriate configuration & API keys.
8993

94+
---
95+
96+
## Privacy & Security Considerations
97+
98+
### Channel Tagging
99+
When `Slack_CreateChannel` runs, it automatically adds a `slack:<channel_name>` tag to the case. This tag:
100+
- Makes it easy to identify which Slack channel(s) are associated with a case
101+
- Enables `Slack_SyncChannel` to reliably find channels without reconstructing names
102+
- Can be manually added to sync additional channels (see warning below)
103+
104+
### Multi-Channel Syncing
105+
`Slack_SyncChannel` will sync **all** channels that have `slack:` tags on the case:
106+
- Each channel creates its own separate task in TheHive
107+
- Partial failures are handled gracefully (some channels may sync, others may fail)
108+
- Failed channels are reported in the responder output
109+
110+
### Access Control Warning ⚠️
111+
- The bot can only read channels it has been **invited to**
112+
- Syncing brings Slack conversations into TheHive: ensure case permissions align with channel access
113+
- Private Slack channels synced to non-private TheHive cases may expose sensitive information
114+
- Consider your organization's data governance policies before syncing channels

responders/Slack/slack.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def run(self):
6969
.lower()
7070
)
7171
channel_name = channel_name[:80]
72+
self.channel_name = channel_name
7273

7374
headers = {
7475
"Authorization": f"Bearer {self.slack_token}",
@@ -192,49 +193,72 @@ def format_tlp(level):
192193
case_id = self.get_param("data.caseId")
193194
case_unique_id = self.get_param("data.id")
194195

195-
# Channel name format: case-CASEID
196-
channel_name = (
197-
f"{self.channel_prefix}{case_id}".replace(" ", "-")
198-
.replace(".", "")
199-
.replace(",", "")
200-
.lower()
201-
)
202-
channel_name = channel_name[:80]
196+
# Collect all channel names from tags
197+
channel_names = []
198+
case_tags = self.get_param("data.tags", [])
199+
for tag in case_tags:
200+
if tag.startswith("slack:"):
201+
channel_names.append(tag[6:]) # Remove "slack:" prefix
202+
203+
# Fallback to reconstructing channel name if no tags found
204+
if not channel_names:
205+
channel_name = (
206+
f"{self.channel_prefix}{case_id}".replace(" ", "-")
207+
.replace(".", "")
208+
.replace(",", "")
209+
.lower()
210+
)
211+
channel_name = channel_name[:80]
212+
channel_names.append(channel_name)
203213

204214
headers = {
205215
"Authorization": f"Bearer {self.slack_token}",
206216
"Content-Type": "application/json",
207217
}
208218

209-
# Find the channel
210-
channel_id = self.find_existing_channel(channel_name, headers)
211-
if not channel_id:
212-
self.error(
213-
f"Channel '{channel_name}' not found. Create the channel first."
214-
)
219+
# Sync all channels
220+
synced_channels = []
221+
errors = []
215222

216-
# Get channel conversation history
217-
conversation_data = self.get_channel_conversations(channel_id, headers)
223+
for channel_name in channel_names:
224+
try:
225+
# Find the channel
226+
channel_id = self.find_existing_channel(channel_name, headers)
227+
if not channel_id:
228+
errors.append(f"Channel '{channel_name}' not found")
229+
continue
230+
231+
# Get channel conversation history
232+
conversation_data = self.get_channel_conversations(channel_id, headers)
233+
234+
# Create or update TheHive task with conversation data
235+
task_id, action = self.create_or_update_thehive_task(
236+
case_unique_id, channel_name, conversation_data
237+
)
218238

219-
# Create or update TheHive task with conversation data
220-
task_id, action = self.create_or_update_thehive_task(
221-
case_unique_id, channel_name, conversation_data
222-
)
239+
synced_channels.append({
240+
"channel_name": channel_name,
241+
"channel_id": channel_id,
242+
"task_id": task_id,
243+
"action": action
244+
})
245+
246+
except Exception as e:
247+
errors.append(f"Error syncing '{channel_name}': {str(e)}")
248+
249+
# Build report
250+
if not synced_channels and errors:
251+
self.error(f"Failed to sync channels: {', '.join(errors)}")
223252

224-
# Include debug info in the main report
225253
report_data = {
226-
"channel_name": channel_name,
227-
"channel_id": channel_id,
228-
"task_id": task_id,
229-
"action": action,
230-
"message": f"Slack channel `{channel_name}` conversation {action} in TheHive task {task_id}.",
254+
"synced_channels": synced_channels,
255+
"total_synced": len(synced_channels),
256+
"message": f"Synced {len(synced_channels)} channel(s)"
231257
}
232-
233-
# Add debug info if we have it
234-
if hasattr(self, '_last_debug_info'):
235-
report_data["debug_info"] = self._last_debug_info
236-
delattr(self, '_last_debug_info')
237-
258+
259+
if errors:
260+
report_data["errors"] = errors
261+
238262
self.report(report_data)
239263

240264
def get_channel_conversations(self, channel_id, headers):
@@ -705,6 +729,17 @@ def create_or_update_thehive_task(self, case_id, channel_name, conversations):
705729

706730
except Exception as e:
707731
self.error(f"Failed to create task: {str(e)}")
732+
733+
def operations(self, raw):
734+
artifacts = []
735+
# AddTagToArtifact ({ "type": "AddTagToArtifact", "tag": "tag to add" }): add a tag to the artifact related to the object
736+
# AddTagToCase ({ "type": "AddTagToCase", "tag": "tag to add" }): add a tag to the case related to the object
737+
# MarkAlertAsRead: mark the alert related to the object as read
738+
# AddCustomFields ({"name": "key", "value": "value", "tpe": "type"): add a custom field to the case related to the object
739+
if self.service == "createchannel":
740+
if hasattr(self, 'channel_name'):
741+
artifacts.append(self.build_operation("AddTagToCase", tag=f"slack:{self.channel_name}"))
742+
return artifacts
708743

709744

710745
if __name__ == "__main__":

0 commit comments

Comments
 (0)