Skip to content

Commit bf060e4

Browse files
committed
webhooks: add test with TODOs for github/gitea authentitcation
1 parent 2f4a7fc commit bf060e4

File tree

1 file changed

+257
-0
lines changed

1 file changed

+257
-0
lines changed

t/Hydra/Controller/API/webhooks.t

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
use strict;
2+
use warnings;
3+
use Setup;
4+
use Test2::V0;
5+
use Test2::Tools::Subtest qw(subtest_streamed);
6+
use Catalyst::Test ();
7+
use HTTP::Request;
8+
use HTTP::Request::Common;
9+
use JSON::MaybeXS qw(decode_json encode_json);
10+
use Digest::SHA qw(hmac_sha256_hex);
11+
12+
my $ctx = test_context();
13+
Catalyst::Test->import('Hydra');
14+
15+
# Create webhook configuration
16+
my $github_secret = "github-test-secret-12345";
17+
my $gitea_secret = "gitea-test-secret-abcdef";
18+
19+
# Write test configuration
20+
my $config = {
21+
webhooks => {
22+
github => {
23+
secret_files => [ $ctx->tmpdir . "/github-secret" ]
24+
},
25+
gitea => {
26+
secret_files => [ $ctx->tmpdir . "/gitea-secret" ]
27+
}
28+
}
29+
};
30+
31+
# Write secret files
32+
write_file($ctx->tmpdir . "/github-secret", $github_secret);
33+
write_file($ctx->tmpdir . "/gitea-secret", $gitea_secret);
34+
chmod 0600, $ctx->tmpdir . "/github-secret";
35+
chmod 0600, $ctx->tmpdir . "/gitea-secret";
36+
37+
# Create a project and jobset for testing
38+
my $user = $ctx->db()->resultset('Users')->create({
39+
username => "webhook-test",
40+
emailaddress => '[email protected]',
41+
password => ''
42+
});
43+
44+
my $project = $ctx->db()->resultset('Projects')->create({
45+
name => "webhook-test",
46+
displayname => "webhook-test",
47+
owner => $user->username
48+
});
49+
50+
my $jobset = $project->jobsets->create({
51+
name => "test-jobset",
52+
nixexprinput => "src",
53+
nixexprpath => "default.nix",
54+
emailoverride => ""
55+
});
56+
57+
my $jobsetinput = $jobset->jobsetinputs->create({name => "src", type => "git"});
58+
$jobsetinput->jobsetinputalts->create({altnr => 0, value => "https://github.com/owner/repo.git"});
59+
60+
# Create another jobset for Gitea
61+
my $jobset_gitea = $project->jobsets->create({
62+
name => "test-jobset-gitea",
63+
nixexprinput => "src",
64+
nixexprpath => "default.nix",
65+
emailoverride => ""
66+
});
67+
68+
my $jobsetinput_gitea = $jobset_gitea->jobsetinputs->create({name => "src", type => "git"});
69+
$jobsetinput_gitea->jobsetinputalts->create({altnr => 0, value => "https://gitea.example.com/owner/repo.git"});
70+
71+
subtest "GitHub webhook authentication" => sub {
72+
my $payload = encode_json({
73+
repository => {
74+
owner => { name => "owner" },
75+
name => "repo"
76+
}
77+
});
78+
79+
subtest "without authentication (current behavior - should succeed but shouldn't)" => sub {
80+
my $req = POST '/api/push-github',
81+
"Content-Type" => "application/json",
82+
"Content" => $payload;
83+
84+
my $response = request($req);
85+
is($response->code, 200, "Unauthenticated request currently succeeds (VULNERABILITY)");
86+
87+
my $data = decode_json($response->content);
88+
is($data->{jobsetsTriggered}, ["webhook-test:test-jobset"], "Jobset was triggered without authentication");
89+
};
90+
91+
subtest "with valid signature (will fail until implemented)" => sub {
92+
my $signature = "sha256=" . hmac_sha256_hex($payload, $github_secret);
93+
94+
my $req = POST '/api/push-github',
95+
"Content-Type" => "application/json",
96+
"X-Hub-Signature-256" => $signature,
97+
"Content" => $payload;
98+
99+
my $response = request($req);
100+
todo "Not implemented yet" => sub {
101+
is($response->code, 200, "Valid signature should be accepted");
102+
};
103+
};
104+
105+
subtest "with invalid signature (will fail until implemented)" => sub {
106+
my $signature = "sha256=" . hmac_sha256_hex($payload, "wrong-secret");
107+
108+
my $req = POST '/api/push-github',
109+
"Content-Type" => "application/json",
110+
"X-Hub-Signature-256" => $signature,
111+
"Content" => $payload;
112+
113+
my $response = request($req);
114+
todo "Not implemented yet" => sub {
115+
is($response->code, 401, "Invalid signature should be rejected");
116+
};
117+
};
118+
119+
subtest "without signature when configured (will fail until implemented)" => sub {
120+
my $req = POST '/api/push-github',
121+
"Content-Type" => "application/json",
122+
"Content" => $payload;
123+
124+
my $response = request($req);
125+
todo "Not implemented yet - currently allows unauthenticated requests" => sub {
126+
is($response->code, 401, "Missing signature should be rejected when authentication is configured");
127+
};
128+
};
129+
};
130+
131+
subtest "Gitea webhook authentication" => sub {
132+
my $payload = encode_json({
133+
repository => {
134+
owner => { username => "owner" },
135+
name => "repo",
136+
clone_url => "https://gitea.example.com/owner/repo.git"
137+
}
138+
});
139+
140+
subtest "without authentication (current behavior - should succeed but shouldn't)" => sub {
141+
my $req = POST '/api/push-gitea',
142+
"Content-Type" => "application/json",
143+
"Content" => $payload;
144+
145+
my $response = request($req);
146+
is($response->code, 200, "Unauthenticated request currently succeeds (VULNERABILITY)");
147+
148+
my $data = decode_json($response->content);
149+
is($data->{jobsetsTriggered}, ["webhook-test:test-jobset-gitea"], "Jobset was triggered without authentication");
150+
};
151+
152+
subtest "with valid signature (will fail until implemented)" => sub {
153+
# Note: Gitea doesn't use sha256= prefix
154+
my $signature = hmac_sha256_hex($payload, $gitea_secret);
155+
156+
my $req = POST '/api/push-gitea',
157+
"Content-Type" => "application/json",
158+
"X-Gitea-Signature" => $signature,
159+
"Content" => $payload;
160+
161+
my $response = request($req);
162+
todo "Not implemented yet" => sub {
163+
is($response->code, 200, "Valid signature should be accepted");
164+
};
165+
};
166+
167+
subtest "with invalid signature (will fail until implemented)" => sub {
168+
my $signature = hmac_sha256_hex($payload, "wrong-secret");
169+
170+
my $req = POST '/api/push-gitea',
171+
"Content-Type" => "application/json",
172+
"X-Gitea-Signature" => $signature,
173+
"Content" => $payload;
174+
175+
my $response = request($req);
176+
todo "Not implemented yet" => sub {
177+
is($response->code, 401, "Invalid signature should be rejected");
178+
};
179+
};
180+
};
181+
182+
subtest "Multiple secrets support" => sub {
183+
# Test configuration with multiple secrets
184+
my $alt_github_secret = "github-alternative-secret";
185+
write_file($ctx->tmpdir . "/github-secret-alt", $alt_github_secret);
186+
chmod 0600, $ctx->tmpdir . "/github-secret-alt";
187+
188+
# This would require updating config to include both secret files
189+
# secret_files = [
190+
# /path/to/github-secret
191+
# /path/to/github-secret-alt
192+
# ]
193+
194+
my $payload = encode_json({
195+
repository => {
196+
owner => { name => "owner" },
197+
name => "repo"
198+
}
199+
});
200+
201+
subtest "with first valid secret (will fail until implemented)" => sub {
202+
my $signature = "sha256=" . hmac_sha256_hex($payload, $github_secret);
203+
204+
my $req = POST '/api/push-github',
205+
"Content-Type" => "application/json",
206+
"X-Hub-Signature-256" => $signature,
207+
"Content" => $payload;
208+
209+
my $response = request($req);
210+
todo "Not implemented yet" => sub {
211+
is($response->code, 200, "First valid secret should be accepted");
212+
};
213+
};
214+
215+
subtest "with second valid secret (will fail until implemented)" => sub {
216+
my $signature = "sha256=" . hmac_sha256_hex($payload, $alt_github_secret);
217+
218+
my $req = POST '/api/push-github',
219+
"Content-Type" => "application/json",
220+
"X-Hub-Signature-256" => $signature,
221+
"Content" => $payload;
222+
223+
my $response = request($req);
224+
todo "Not implemented yet" => sub {
225+
is($response->code, 200, "Second valid secret should be accepted");
226+
};
227+
};
228+
229+
subtest "with invalid secret when multiple configured (will fail until implemented)" => sub {
230+
my $signature = "sha256=" . hmac_sha256_hex($payload, "neither-secret");
231+
232+
my $req = POST '/api/push-github',
233+
"Content-Type" => "application/json",
234+
"X-Hub-Signature-256" => $signature,
235+
"Content" => $payload;
236+
237+
my $response = request($req);
238+
todo "Not implemented yet" => sub {
239+
is($response->code, 401, "Invalid secret should be rejected even with multiple configured");
240+
};
241+
};
242+
};
243+
244+
subtest "Security considerations" => sub {
245+
subtest "file permission warnings" => sub {
246+
my $insecure_secret_path = $ctx->tmpdir . "/insecure-secret";
247+
write_file($insecure_secret_path, "insecure-secret");
248+
chmod 0644, $insecure_secret_path; # Too permissive
249+
250+
todo "Not implemented yet" => sub {
251+
# This would need to check logs for warnings about insecure permissions
252+
ok(0, "Should warn about insecure file permissions");
253+
};
254+
};
255+
};
256+
257+
done_testing;

0 commit comments

Comments
 (0)