Skip to content

Commit ce68f5f

Browse files
committed
add slack incoming request signature validation
1 parent d753eb4 commit ce68f5f

File tree

4 files changed

+20
-0
lines changed

4 files changed

+20
-0
lines changed

documentation/secret_docs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ A secrets file stores sensitive information. Unlike the repository configuration
1919
| `gh_hook_token` | specify to ensure the bot only receives GitHub notifications from pre-approved repositories | Yes | - |
2020
| `slack_access_token` | slack bot access token to enable message posting to the workspace | Yes | try to use webhooks defined in `slack_hooks` instead |
2121
| `slack_hooks` | list of channel names and their corresponding webhook endpoint | Yes | try to use token defined in `slack_access_token` instead |
22+
| `slack_signing_secret` | specify to verify incoming slack requests | Yes | - |
2223

2324
Note that either `slack_access_token` or `slack_hooks` must be defined.
2425

lib/common.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ let get_local_file path = try Ok (Std.input_file path) with exn -> fmt_error "%s
3535
let write_to_local_file ~data path =
3636
try Ok (Devkit.Files.save_as path (fun oc -> Stdio.Out_channel.fprintf oc "%s" data))
3737
with exn -> fmt_error "%s" (Exn.to_string exn)
38+
39+
let sign_string_sha256 ~key ~basestring =
40+
Cstruct.of_string basestring |> Nocrypto.Hash.SHA256.hmac ~key:(Cstruct.of_string key) |> Hex.of_cstruct |> Hex.show

lib/config.atd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,6 @@ type secrets = {
4747
~slack_hooks <ocaml default="[]"> : webhook list;
4848
(* Slack bot token obtained via OAuth, enabling message posting to the workspace *)
4949
?slack_access_token : string nullable;
50+
(* Slack uses this secret to sign requests; provide to verify incoming Slack requests *)
51+
?slack_signing_secret : string nullable;
5052
}

lib/slack.ml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,17 @@ let generate_commit_comment_notification api_commit notification channel =
358358
}
359359
in
360360
{ channel; text = None; attachments = Some [ attachment ]; blocks = None }
361+
362+
let validate_signature ?(version = "v0") ?signing_key ~headers body =
363+
match signing_key with
364+
| None -> Ok ()
365+
| Some key ->
366+
match List.Assoc.find headers "x-slack-signature" ~equal:String.equal with
367+
| None -> Error "unable to find header X-Slack-Signature"
368+
| Some signature ->
369+
match List.Assoc.find headers "x-slack-request-timestamp" ~equal:String.equal with
370+
| None -> Error "unable to find header X-Slack-Request-Timestamp"
371+
| Some timestamp ->
372+
let basestring = Printf.sprintf "%s:%s:%s" version timestamp body in
373+
let expected_signature = Printf.sprintf "%s=%s" version (Common.sign_string_sha256 ~key ~basestring) in
374+
if String.equal expected_signature signature then Ok () else Error "signatures don't match"

0 commit comments

Comments
 (0)