Skip to content
This repository was archived by the owner on Mar 14, 2023. It is now read-only.

Commit a250f32

Browse files
author
Bernhard Grünewaldt
committed
webhook secret validation working
1 parent 8de4560 commit a250f32

File tree

5 files changed

+141
-58
lines changed

5 files changed

+141
-58
lines changed

DEVELOPMENT.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ mvn compile && rm -rf work/ && mvn hpi:run
55

66
curl -X POST \
77
-H "Content-Type: application/json" \
8+
-H "x-hub-signature: sha1=5b8a54ee48efb1fbe06e3e4fc5d120680eaa2a22" \
89
-d @test-webhook-payload.json \
910
http://localhost:8080/jenkins/github-webhook-notifier/receive
1011
```
1112

13+
* With GitHub Secret being: `foobar23`
14+
1215
### Build hpi
1316

1417
```

src/main/java/io/codeclou/jenkins/githubwebhooknotifierplugin/GithubWebhookNotifyAction.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import hudson.triggers.SCMTrigger;
1313
import hudson.triggers.Trigger;
1414
import hudson.util.HttpResponses;
15+
import io.codeclou.jenkins.githubwebhooknotifierplugin.config.GithubWebhookNotifierPluginBuilder;
16+
import io.codeclou.jenkins.githubwebhooknotifierplugin.webhooksecret.GitHubWebhookUtility;
1517
import jenkins.model.Jenkins;
1618
import org.apache.commons.io.IOUtils;
1719
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -30,6 +32,7 @@
3032
import javax.servlet.http.HttpServletRequest;
3133
import java.io.BufferedReader;
3234
import java.io.IOException;
35+
import java.io.StringWriter;
3336
import java.security.KeyManagementException;
3437
import java.security.KeyStoreException;
3538
import java.security.NoSuchAlgorithmException;
@@ -62,10 +65,23 @@ public String getDisplayName() {
6265
*/
6366
@RequirePOST
6467
public HttpResponse doReceive(HttpServletRequest request, StaplerRequest staplerRequest) throws IOException, ServletException {
65-
BufferedReader reader = request.getReader();
68+
StringWriter writer = new StringWriter();
69+
IOUtils.copy(request.getInputStream(), writer, "UTF-8");
70+
String requestBody = writer.toString();
71+
String githubSignature = request.getHeader("x-hub-signature");
6672
Gson gson = new Gson();
6773
try {
68-
GithubWebhookPayload githubWebhookPayload = gson.fromJson(reader, GithubWebhookPayload.class);
74+
String webhookSecretAsConfiguredByUser = GithubWebhookNotifierPluginBuilder.DescriptorImpl.getDescriptor().getWebhookSecret();
75+
String webhookSecretMessage ="validating webhook payload against wevhook secret.";
76+
if (webhookSecretAsConfiguredByUser == null) {
77+
webhookSecretMessage = "no webhook secret in global config specified. skipping validation.";
78+
} else {
79+
Boolean isValid = GitHubWebhookUtility.verifySignature(requestBody, githubSignature, webhookSecretAsConfiguredByUser);
80+
if (!isValid) {
81+
return HttpResponses.error(500, this.getTextEnvelopedInBanner("github webhook secret signature check failed. Check your webhook secret."));
82+
}
83+
}
84+
GithubWebhookPayload githubWebhookPayload = gson.fromJson(requestBody, GithubWebhookPayload.class);
6985
GithubWebhookEnvironmentContributionAction environmentContributionAction = new GithubWebhookEnvironmentContributionAction(githubWebhookPayload);
7086
String jobNamePrefix = this.normalizeRepoFullName(githubWebhookPayload.getRepository().getFull_name());
7187
StringBuilder jobsTriggered = new StringBuilder();
@@ -92,6 +108,7 @@ public HttpResponse doReceive(HttpServletRequest request, StaplerRequest stapler
92108
}
93109
StringBuilder info = new StringBuilder();
94110
info.append(">> webhook content to env vars").append("\n");
111+
info.append("webhooksecret: ").append(webhookSecretMessage).append("\n");
95112
info.append(environmentContributionAction.getEnvVarInfo());
96113
info.append("\n");
97114
info.append(">> jobs triggered with name matching '").append(jobNamePrefix).append("*'").append("\n");

src/main/java/io/codeclou/jenkins/githubwebhooknotifierplugin/config/GithubWebhookNotifierPluginBuilder.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
/*
2+
* Licensed under MIT License
3+
* Copyright (c) 2017 Bernhard Grünewaldt
4+
*/
15
package io.codeclou.jenkins.githubwebhooknotifierplugin.config;
26

37
import hudson.Extension;
48
import hudson.model.AbstractProject;
59
import hudson.tasks.BuildStepDescriptor;
610
import hudson.tasks.Builder;
7-
import hudson.tasks.Publisher;
811
import net.sf.json.JSONObject;
912
import org.kohsuke.stapler.StaplerRequest;
1013

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Licensed under MIT License
3+
* Copyright (c) 2017 Bernhard Grünewaldt
4+
*/
5+
package io.codeclou.jenkins.githubwebhooknotifierplugin.webhooksecret;
6+
7+
import java.security.InvalidKeyException;
8+
import java.security.NoSuchAlgorithmException;
9+
import javax.crypto.Mac;
10+
import javax.crypto.spec.SecretKeySpec;
11+
12+
public class GitHubWebhookUtility {
13+
14+
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
15+
private static final char[] HEX = {
16+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
17+
};
18+
19+
public static boolean verifySignature(String payload, String signature, String secret) {
20+
boolean isValid;
21+
try {
22+
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
23+
SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM);
24+
mac.init(signingKey);
25+
byte[] rawHmac = mac.doFinal(payload.getBytes());
26+
String expected = signature.substring(5);
27+
String actual = new String(encode(rawHmac));
28+
isValid = expected.equals(actual);
29+
} catch (NoSuchAlgorithmException | InvalidKeyException | IllegalStateException ex) {
30+
throw new RuntimeException(ex.getLocalizedMessage());
31+
}
32+
return isValid;
33+
}
34+
35+
private static char[] encode(byte[] bytes) {
36+
final int amount = bytes.length;
37+
char[] result = new char[2 * amount];
38+
int j = 0;
39+
for (int i = 0; i < amount; i++) {
40+
result[j++] = HEX[(0xF0 & bytes[i]) >>> 4];
41+
result[j++] = HEX[(0x0F & bytes[i])];
42+
}
43+
return result;
44+
}
45+
46+
}

test-webhook-payload.json

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
{
22
"ref": "refs/heads/master",
3-
"before": "495140cf2b763b8bc3821e25b4c26dd1273d6206",
4-
"after": "3be1cb4b6b86533b5dab2b0083fa9fb8b401b430",
3+
"before": "3be1cb4b6b86533b5dab2b0083fa9fb8b401b430",
4+
"after": "2c9522c9618864808eaaede8353dbeafb996c605",
55
"created": false,
66
"deleted": false,
77
"forced": false,
88
"base_ref": null,
9-
"compare": "https://github.com/codeclou/test-webhook/compare/b18bd676208c...79ee8604ed23",
9+
"compare": "https://github.com/codeclou/test-webhook/compare/3be1cb4b6b86...2c9522c96188",
1010
"commits": [
1111
{
12-
"id": "79ee8604ed236fa87e4184c28ce59f5bffbe13bc",
13-
"tree_id": "dacc30b085e2452dd6076204ab7c36b03fbb0ef6",
12+
"id": "2c9522c9618864808eaaede8353dbeafb996c605",
13+
"tree_id": "6a2a4a86b6c6076c8a54d05bab30680c4e0b6456",
1414
"distinct": true,
15-
"message": "fix pom.xml version fix",
16-
"timestamp": "2017-04-01T17:23:34+02:00",
17-
"url": "https://github.com/codeclou/test-webhook/commit/79ee8604ed236fa87e4184c28ce59f5bffbe13bc",
15+
"message": "foo",
16+
"timestamp": "2017-04-02T15:21:54+02:00",
17+
"url": "https://github.com/codeclou/test-webhook/commit/2c9522c9618864808eaaede8353dbeafb996c605",
1818
"author": {
19-
"name": "Fox Forrester",
20-
"email": "fox@forrest",
21-
"username": "fox"
19+
"name": "Bernhard Grünewaldt",
20+
"email": "github@gruenewaldt.net",
21+
"username": "clouless"
2222
},
2323
"committer": {
24-
"name": "Fox Forrester",
25-
"email": "fox@forrest",
26-
"username": "fox"
24+
"name": "Bernhard Grünewaldt",
25+
"email": "github@gruenewaldt.net",
26+
"username": "clouless"
2727
},
2828
"added": [
2929

@@ -32,26 +32,26 @@
3232

3333
],
3434
"modified": [
35-
"jenkins.sh"
35+
"dummyfile.txt"
3636
]
3737
}
3838
],
3939
"head_commit": {
40-
"id": "79ee8604ed236fa87e4184c28ce59f5bffbe13bc",
41-
"tree_id": "dacc30b085e2452dd6076204ab7c36b03fbb0ef6",
40+
"id": "2c9522c9618864808eaaede8353dbeafb996c605",
41+
"tree_id": "6a2a4a86b6c6076c8a54d05bab30680c4e0b6456",
4242
"distinct": true,
43-
"message": "fix pom.xml version fix",
44-
"timestamp": "2017-04-01T17:23:34+02:00",
45-
"url": "https://github.com/codeclou/test-webhook/commit/79ee8604ed236fa87e4184c28ce59f5bffbe13bc",
43+
"message": "foo",
44+
"timestamp": "2017-04-02T15:21:54+02:00",
45+
"url": "https://github.com/codeclou/test-webhook/commit/2c9522c9618864808eaaede8353dbeafb996c605",
4646
"author": {
47-
"name": "Fox Forrester",
48-
"email": "fox@forrest",
49-
"username": "fox"
47+
"name": "Bernhard Grünewaldt",
48+
"email": "github@gruenewaldt.net",
49+
"username": "clouless"
5050
},
5151
"committer": {
52-
"name": "Fox Forrester",
53-
"email": "fox@forrest",
54-
"username": "fox"
52+
"name": "Bernhard Grünewaldt",
53+
"email": "github@gruenewaldt.net",
54+
"username": "clouless"
5555
},
5656
"added": [
5757

@@ -60,19 +60,19 @@
6060

6161
],
6262
"modified": [
63-
"jenkins.sh"
63+
"dummyfile.txt"
6464
]
6565
},
6666
"repository": {
67-
"id": 66210545,
67+
"id": 86972993,
6868
"name": "test-webhook",
6969
"full_name": "codeclou/test-webhook",
7070
"owner": {
7171
"name": "codeclou",
72-
"email": "int@codeclou.io",
72+
"email": "",
7373
"login": "codeclou",
74-
"id": 17686535,
75-
"avatar_url": "https://avatars2.githubusercontent.com/u/17686535?v=3",
74+
"id": 15359905,
75+
"avatar_url": "https://avatars2.githubusercontent.com/u/15359905?v=3",
7676
"gravatar_id": "",
7777
"url": "https://api.github.com/users/codeclou",
7878
"html_url": "https://github.com/codeclou",
@@ -85,12 +85,12 @@
8585
"repos_url": "https://api.github.com/users/codeclou/repos",
8686
"events_url": "https://api.github.com/users/codeclou/events{/privacy}",
8787
"received_events_url": "https://api.github.com/users/codeclou/received_events",
88-
"type": "User",
88+
"type": "Organization",
8989
"site_admin": false
9090
},
91-
"private": true,
91+
"private": false,
9292
"html_url": "https://github.com/codeclou/test-webhook",
93-
"description": null,
93+
"description": "test repository",
9494
"fork": false,
9595
"url": "https://github.com/codeclou/test-webhook",
9696
"forks_url": "https://api.github.com/repos/codeclou/test-webhook/forks",
@@ -129,22 +129,22 @@
129129
"labels_url": "https://api.github.com/repos/codeclou/test-webhook/labels{/name}",
130130
"releases_url": "https://api.github.com/repos/codeclou/test-webhook/releases{/id}",
131131
"deployments_url": "https://api.github.com/repos/codeclou/test-webhook/deployments",
132-
"created_at": 1471799402,
133-
"updated_at": "2017-03-24T14:11:34Z",
134-
"pushed_at": 1491060219,
132+
"created_at": 1491121491,
133+
"updated_at": "2017-04-02T08:26:38Z",
134+
"pushed_at": 1491139319,
135135
"git_url": "git://github.com/codeclou/test-webhook.git",
136136
"ssh_url": "git@github.com:codeclou/test-webhook.git",
137137
"clone_url": "https://github.com/codeclou/test-webhook.git",
138138
"svn_url": "https://github.com/codeclou/test-webhook",
139139
"homepage": null,
140-
"size": 101039,
140+
"size": 2,
141141
"stargazers_count": 0,
142142
"watchers_count": 0,
143-
"language": "Java",
143+
"language": "Shell",
144144
"has_issues": true,
145145
"has_projects": true,
146146
"has_downloads": true,
147-
"has_wiki": false,
147+
"has_wiki": true,
148148
"has_pages": false,
149149
"forks_count": 0,
150150
"mirror_url": null,
@@ -154,28 +154,42 @@
154154
"watchers": 0,
155155
"default_branch": "master",
156156
"stargazers": 0,
157-
"master_branch": "master"
157+
"master_branch": "master",
158+
"organization": "codeclou"
158159
},
159160
"pusher": {
160-
"name": "fox",
161-
"email": "fox@forrest"
161+
"name": "clouless",
162+
"email": "github@gruenewaldt.net"
163+
},
164+
"organization": {
165+
"login": "codeclou",
166+
"id": 15359905,
167+
"url": "https://api.github.com/orgs/codeclou",
168+
"repos_url": "https://api.github.com/orgs/codeclou/repos",
169+
"events_url": "https://api.github.com/orgs/codeclou/events",
170+
"hooks_url": "https://api.github.com/orgs/codeclou/hooks",
171+
"issues_url": "https://api.github.com/orgs/codeclou/issues",
172+
"members_url": "https://api.github.com/orgs/codeclou/members{/member}",
173+
"public_members_url": "https://api.github.com/orgs/codeclou/public_members{/member}",
174+
"avatar_url": "https://avatars2.githubusercontent.com/u/15359905?v=3",
175+
"description": ""
162176
},
163177
"sender": {
164-
"login": "fox",
178+
"login": "clouless",
165179
"id": 12599965,
166180
"avatar_url": "https://avatars3.githubusercontent.com/u/12599965?v=3",
167181
"gravatar_id": "",
168-
"url": "https://api.github.com/users/fox",
169-
"html_url": "https://github.com/fox",
170-
"followers_url": "https://api.github.com/users/fox/followers",
171-
"following_url": "https://api.github.com/users/fox/following{/other_user}",
172-
"gists_url": "https://api.github.com/users/fox/gists{/gist_id}",
173-
"starred_url": "https://api.github.com/users/fox/starred{/owner}{/repo}",
174-
"subscriptions_url": "https://api.github.com/users/fox/subscriptions",
175-
"organizations_url": "https://api.github.com/users/fox/orgs",
176-
"repos_url": "https://api.github.com/users/fox/repos",
177-
"events_url": "https://api.github.com/users/fox/events{/privacy}",
178-
"received_events_url": "https://api.github.com/users/fox/received_events",
182+
"url": "https://api.github.com/users/clouless",
183+
"html_url": "https://github.com/clouless",
184+
"followers_url": "https://api.github.com/users/clouless/followers",
185+
"following_url": "https://api.github.com/users/clouless/following{/other_user}",
186+
"gists_url": "https://api.github.com/users/clouless/gists{/gist_id}",
187+
"starred_url": "https://api.github.com/users/clouless/starred{/owner}{/repo}",
188+
"subscriptions_url": "https://api.github.com/users/clouless/subscriptions",
189+
"organizations_url": "https://api.github.com/users/clouless/orgs",
190+
"repos_url": "https://api.github.com/users/clouless/repos",
191+
"events_url": "https://api.github.com/users/clouless/events{/privacy}",
192+
"received_events_url": "https://api.github.com/users/clouless/received_events",
179193
"type": "User",
180194
"site_admin": false
181195
}

0 commit comments

Comments
 (0)