Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/pages/operate/slack-etl.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ posting to Slack.

Create a Slack user token for ETL reads and store it as `SLACK_ETL_TOKEN` in
the same secret source used by tools. The Slack tool declares it as an optional
HTTP secret for `slack.com` and `files.slack.com`; iron-proxy injects the real
value when the tool calls Slack.
HTTP secret scoped to the Slack Web API endpoints below and `GET` file downloads
from `files.slack.com`; iron-proxy injects the real value when the workflow
calls Slack.

The token must be able to call:

Expand Down
5 changes: 3 additions & 2 deletions docs/public/md/operate/slack-etl.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ posting to Slack.

Create a Slack user token for ETL reads and store it as `SLACK_ETL_TOKEN` in
the same secret source used by tools. The Slack tool declares it as an optional
HTTP secret for `slack.com` and `files.slack.com`; iron-proxy injects the real
value when the tool calls Slack.
HTTP secret scoped to the Slack Web API endpoints below and `GET` file downloads
from `files.slack.com`; iron-proxy injects the real value when the workflow
calls Slack.

The token must be able to call:

Expand Down
56 changes: 56 additions & 0 deletions services/api-rs/crates/centaur-perms/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,62 @@ fn real_slack_tool_parses_and_translates() {
),
"expected the SLACK_BOT_TOKEN static secret"
);

let etl_inputs = out
.inputs
.iter()
.filter_map(|input| match input {
SecretInput::Static(secret) if secret.name == "SLACK_ETL_TOKEN" => Some(secret),
_ => None,
})
.collect::<Vec<_>>();
assert_eq!(etl_inputs.len(), 2);

let slack_api = etl_inputs
.iter()
.find(|secret| {
secret
.rules
.iter()
.any(|rule| rule.host.as_deref() == Some("slack.com"))
})
.expect("expected Slack Web API ETL token rules");
let mut slack_hosts = slack_api
.rules
.iter()
.map(|rule| rule.host.as_deref().unwrap_or_default().to_owned())
.collect::<Vec<_>>();
slack_hosts.sort();
assert_eq!(
slack_hosts,
vec!["slack.com".to_owned(), "www.slack.com".to_owned()]
);
for rule in &slack_api.rules {
assert_eq!(rule.http_methods, vec!["GET".to_owned(), "POST".to_owned()]);
assert_eq!(
rule.paths,
vec![
"/api/conversations.list".to_owned(),
"/api/conversations.history".to_owned(),
"/api/conversations.replies".to_owned(),
"/api/users.list".to_owned(),
]
);
}

let files = etl_inputs
.iter()
.find(|secret| {
secret
.rules
.iter()
.any(|rule| rule.host.as_deref() == Some("files.slack.com"))
})
.expect("expected Slack file download ETL token rule");
assert_eq!(files.rules.len(), 1);
assert_eq!(files.rules[0].host.as_deref(), Some("files.slack.com"));
assert_eq!(files.rules[0].http_methods, vec!["GET".to_owned()]);
assert!(files.rules[0].paths.is_empty());
}

#[test]
Expand Down
3 changes: 2 additions & 1 deletion tools/productivity/slack/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ optional_secrets = [
{type = "http", name = "SLACK_BOT_TOKEN", mode = "inject", inject_header = "Authorization", inject_formatter = "Bearer {{ .Value }}", hosts = ["slack.com"]},
{type = "http", name = "SLACK_SEARCH_TOKEN", mode = "inject", inject_header = "Authorization", inject_formatter = "Bearer {{ .Value }}", hosts = ["slack.com", "www.slack.com"], methods = ["POST"], paths = ["/api/assistant.search.context"]},
{type = "http", name = "SLACK_UPLOAD_TOKEN", mode = "inject", inject_header = "Authorization", inject_formatter = "Bearer {{ .Value }}", hosts = ["slack.com", "files.slack.com"]},
{type = "http", name = "SLACK_ETL_TOKEN", mode = "inject", inject_header = "Authorization", inject_formatter = "Bearer {{ .Value }}", hosts = ["slack.com", "files.slack.com"]},
{type = "http", name = "SLACK_ETL_TOKEN", mode = "inject", inject_header = "Authorization", inject_formatter = "Bearer {{ .Value }}", hosts = ["slack.com", "www.slack.com"], methods = ["GET", "POST"], paths = ["/api/conversations.list", "/api/conversations.history", "/api/conversations.replies", "/api/users.list"]},
{type = "http", name = "SLACK_ETL_TOKEN", mode = "inject", inject_header = "Authorization", inject_formatter = "Bearer {{ .Value }}", hosts = ["files.slack.com"], methods = ["GET"]},
]
Loading