Skip to content

Commit 401d859

Browse files
committed
add acceptance test for tailscale style webhooks
1 parent 84a9968 commit 401d859

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

spec/acceptance/acceptance_tests.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ def generate_slack_signature(payload, secret, timestamp)
5252
"v0=#{digest}"
5353
end
5454

55+
def generate_tailscale_signature(payload, secret, timestamp = unix_timestamp)
56+
signing_payload = "#{timestamp}.#{payload}"
57+
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, signing_payload)
58+
"t=#{timestamp},v1=#{signature}"
59+
end
60+
5561
def current_timestamp
5662
Time.now.utc.iso8601
5763
end
@@ -520,5 +526,47 @@ def expired_unix_timestamp(seconds_ago = 600)
520526
expect(response.code).to eq("500")
521527
end
522528
end
529+
530+
describe "tailscale" do
531+
it "successfully processes a valid POST request from a tailscale style webhook" do
532+
payload = { event: "user.login", user: { id: "12345" } }
533+
json_payload = payload.to_json
534+
timestamp = unix_timestamp
535+
signature = generate_tailscale_signature(json_payload, FAKE_ALT_HMAC_SECRET, timestamp)
536+
headers = json_headers("Tailscale-Webhook-Signature" => signature)
537+
response = make_request(:post, "/webhooks/tailscale", json_payload, headers)
538+
expect_response(response, Net::HTTPSuccess)
539+
540+
body = parse_json_response(response)
541+
expect(body["status"]).to eq("success")
542+
end
543+
544+
it "rejects request with invalid signature" do
545+
payload = { event: "user.login", user: { id: "12345" } }
546+
json_payload = payload.to_json
547+
headers = json_headers("Tailscale-Webhook-Signature" => "t=1663781880,v1=invalidsignature")
548+
response = make_request(:post, "/webhooks/tailscale", json_payload, headers)
549+
expect_response(response, Net::HTTPUnauthorized, "authentication failed")
550+
end
551+
552+
it "rejects request with missing signature header" do
553+
payload = { event: "user.login", user: { id: "12345" } }
554+
json_payload = payload.to_json
555+
response = make_request(:post, "/webhooks/tailscale", json_payload, json_headers)
556+
expect_response(response, Net::HTTPUnauthorized, "authentication failed")
557+
end
558+
559+
it "rejects request with wrong signature algorithm" do
560+
payload = { event: "user.login", user: { id: "12345" } }
561+
json_payload = payload.to_json
562+
timestamp = unix_timestamp
563+
# Generate with sha1 instead of sha256
564+
signing_payload = "#{timestamp}.#{json_payload}"
565+
wrong_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha1"), FAKE_ALT_HMAC_SECRET, signing_payload)
566+
headers = json_headers("Tailscale-Webhook-Signature" => "t=#{timestamp},v1=#{wrong_signature}")
567+
response = make_request(:post, "/webhooks/tailscale", json_payload, headers)
568+
expect_response(response, Net::HTTPUnauthorized, "authentication failed")
569+
end
570+
end
523571
end
524572
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
path: /tailscale
2+
handler: Hello
3+
4+
auth:
5+
type: hmac
6+
secret_env_key: ALT_WEBHOOK_SECRET
7+
header: Tailscale-Webhook-Signature
8+
algorithm: sha256
9+
format: "signature_only" # produces "abc123..." (no prefix)
10+
header_format: "structured" # enables parsing of "t=123,v1=abc" format, this is what tailscale uses
11+
signature_key: "v1" # key for signature in structured header
12+
timestamp_key: "t" # key for timestamp in structured header
13+
payload_template: "{timestamp}.{body}" # dot-separated format
14+
timestamp_tolerance: 300 # 5 minutes

0 commit comments

Comments
 (0)