Update endpoints.php to use whck for secret validation
This commit is contained in:
parent
20c7059fd8
commit
4cffbb64b3
|
@ -1,3 +1,6 @@
|
|||
[submodule "spec"]
|
||||
path = spec
|
||||
url = https://tildegit.org/forge/endpoints
|
||||
[submodule "whck"]
|
||||
path = whck
|
||||
url = https://tildegit.org/forge/whck
|
||||
|
|
199
index.php
199
index.php
|
@ -54,70 +54,102 @@ function extract_header($header) {
|
|||
return $value;
|
||||
}
|
||||
|
||||
function verify_signature($payload, $secret, $claimed_signature) {
|
||||
$payload_signature = hash_hmac('sha256', $payload, $secret, false);
|
||||
|
||||
// check payload signature against header signature
|
||||
if ($claimed_signature != $payload_signature) {
|
||||
error('FAILED - payload signature mismatch', 403);
|
||||
function verify_claim(string $payload, string $claim, string $id, string $validator) {
|
||||
$whck = find_whck();
|
||||
switch ($validator) {
|
||||
case "hmac-sha256":
|
||||
$kind = "hmac-sha256";
|
||||
break;
|
||||
case "token":
|
||||
$kind = "token";
|
||||
break;
|
||||
default:
|
||||
error("Programming error", 500);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function verify_token($secret, $claimed_secret) {
|
||||
if ($secret !== $claimed_secret) {
|
||||
error('FAILED - secret token mismatch', 403);
|
||||
$descriptors = [ array("pipe", "r"), array("pipe", "w"), array("pipe", "w") ];
|
||||
//$cmd = $whck." " . $kind . " ".escapeshellarg($id) . " " . escapeshellarg($claim);
|
||||
$cmd = [ $whck, $kind, $id, $claim ];
|
||||
var_dump($cmd);
|
||||
//echo "$cmd";
|
||||
$proc = proc_open($cmd, $descriptors, $pipes);
|
||||
|
||||
if (is_resource($proc)) {
|
||||
// Write payload to STDIN of whck
|
||||
fwrite($pipes[0], $payload);
|
||||
fclose($pipes[0]);
|
||||
$stdout = stream_get_contents($pipes[1]);
|
||||
fclose($pipes[1]);
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
} else {
|
||||
error("FAILED - could not start whck", 500);
|
||||
}
|
||||
|
||||
$status = proc_close($proc);
|
||||
if ($status != 0) {
|
||||
error("Process failed due to error. STDOUT:\n$stdout\n\nSTDERR:\n$stderr", 500);
|
||||
}
|
||||
}
|
||||
|
||||
// find_secret($repo_url)
|
||||
// Find the secret corresponding to the repo_url, if any. Returns empty string otherwise
|
||||
function find_secret($repo_url) {
|
||||
$forgehook = isset($_ENV['FORGEHOOK']) ? $_ENV['FORGEHOOK'] // from $ENV
|
||||
: (file_exists('forgehook') ? './forgehook' // from current directory
|
||||
: 'forgehook'); // from $PATH
|
||||
|
||||
$repo = escapeshellarg($repo_url);
|
||||
|
||||
$lines = [];
|
||||
$status = NULL;
|
||||
$secret = exec($forgehook." secret ".$repo, $lines, $status);
|
||||
|
||||
if (($secret == NULL) or ($status != 0)) {
|
||||
error("Secret not found for \"".$repo."\"");
|
||||
}
|
||||
|
||||
if (empty($secret)) {
|
||||
error("Secret empty for ".$repo);
|
||||
}
|
||||
return $secret;
|
||||
// Decide which whck executable to use
|
||||
function find_whck() {
|
||||
// BUG: https://bugs.php.net/bug.php?id=77782
|
||||
// $_ENV['whck'] and getenv("whck") are empty, but getenv()["whck"] works!
|
||||
return isset(getenv()['WHCK']) ? getenv()['WHCK'] // from $ENV
|
||||
: (file_exists('whck') ? './whck' // from current directory
|
||||
: 'whck');
|
||||
}
|
||||
|
||||
class GiteaWebhook {
|
||||
function __construct($payload) {
|
||||
$this->data = json_to_array($payload);
|
||||
//enum Validator {
|
||||
// case HMAC_SHA256;
|
||||
// case Token;
|
||||
//}
|
||||
|
||||
class JSONWebhook {
|
||||
function __construct(string $validator) {
|
||||
$this->validator = $validator;
|
||||
$this->plaintext = extract_payload();
|
||||
$this->data = json_to_array($this->plaintext);
|
||||
}
|
||||
|
||||
function repo_url() {
|
||||
function findId() {
|
||||
return base64_encode($this->repo);
|
||||
}
|
||||
|
||||
function verify() {
|
||||
verify_claim($this->plaintext, $this->claim, $this->id, $this->validator);
|
||||
}
|
||||
}
|
||||
|
||||
class GiteaWebhook extends JSONWebhook {
|
||||
function __construct() {
|
||||
parent::__construct("hmac-sha256");
|
||||
$this->repo = $this->findRepo();
|
||||
$this->id = $this->findId();
|
||||
$this->claim = extract_header("HTTP_X_GITEA_SIGNATURE");
|
||||
}
|
||||
|
||||
function findRepo() {
|
||||
$repo_url = isset($this->data["repository"]["html_url"]) ?
|
||||
$this->data["repository"]["html_url"] : "";
|
||||
if (empty($repo_url)) {
|
||||
error('Could not find Gitea repository URL');
|
||||
}
|
||||
return $repo_url;
|
||||
}
|
||||
|
||||
function secret() {
|
||||
return extract_header("HTTP_X_GITEA_SIGNATURE");
|
||||
return $repo_url;
|
||||
}
|
||||
}
|
||||
|
||||
class GitlabWebhook {
|
||||
function __construct($payload) {
|
||||
$this->data = json_to_array($payload);
|
||||
class GitlabWebhook extends JSONWebhook {
|
||||
function __construct() {
|
||||
parent::__construct("token");
|
||||
$this->repo = $this->findRepo();
|
||||
$this->id = $this->findId();
|
||||
$this->claim = extract_header("HTTP_X_GITLAB_TOKEN");
|
||||
}
|
||||
|
||||
function repo_url() {
|
||||
function findRepo() {
|
||||
$repo_url = isset($this->data["project"]["git_http_url"]) ?
|
||||
$this->data["project"]["git_http_url"] : "";
|
||||
if (empty($repo_url)) {
|
||||
|
@ -125,18 +157,18 @@ class GitlabWebhook {
|
|||
}
|
||||
return $repo_url;
|
||||
}
|
||||
|
||||
function secret() {
|
||||
return extract_header("HTTP_X_GITLAB_TOKEN");
|
||||
}
|
||||
}
|
||||
|
||||
class GithubWebhook {
|
||||
function __construct($payload) {
|
||||
$this->data = json_to_array($payload);
|
||||
|
||||
class GithubWebhook extends JSONWebhook {
|
||||
function __construct() {
|
||||
parent::__construct("hmac-sha256");
|
||||
$this->repo = $this->findRepo();
|
||||
$this->id = $this->findId();
|
||||
$this->claim = extract_header("HTTP_X_HUB_SIGNATURE");
|
||||
}
|
||||
|
||||
function repo_url() {
|
||||
function findRepo() {
|
||||
$repo_url = isset($this->data["repository"]["html_url"]) ?
|
||||
$this->data["repository"]["html_url"] : "";
|
||||
if (empty($repo_url)) {
|
||||
|
@ -144,25 +176,6 @@ class GithubWebhook {
|
|||
}
|
||||
return $repo_url;
|
||||
}
|
||||
|
||||
function secret() {
|
||||
return extract_header("HTTP_X_HUB_SIGNATURE");
|
||||
}
|
||||
}
|
||||
|
||||
function notify($repo) {
|
||||
//$notify = getenv('FORGEHOOKNOTIFY') ? : 'forgehook-notify';
|
||||
$notify = isset($_ENV['FORGEHOOKNOTIFY']) ? $_ENV['FORGEHOOKNOTIFY'] // from $ENV
|
||||
: (file_exists('forgehook-notify') ? './forgehook-notify' // from current directory
|
||||
: 'forgehook-notify'); // from $PATH
|
||||
|
||||
$lines = [];
|
||||
$status = NULL;
|
||||
exec($notify." ".$repo, $lines, $status);
|
||||
|
||||
if ($status != 0) {
|
||||
error("Notify failed (".$notify.") with:\n".print_r($lines));
|
||||
}
|
||||
}
|
||||
|
||||
function action() {
|
||||
|
@ -170,32 +183,18 @@ function action() {
|
|||
error("You need to specify an action (gitea, gitlab) like this: ?action=gitea", 404);
|
||||
}
|
||||
|
||||
$payload = extract_payload();
|
||||
|
||||
switch($_GET['action']) {
|
||||
case 'github':
|
||||
$webhook = new GithubWebhook($payload);
|
||||
$claimed_secret = $webhook->secret();
|
||||
$repo_url = $webhook->repo_url();
|
||||
$secret = find_secret($repo_url);
|
||||
verify_signature($payload, $secret, $claimed_secret);
|
||||
notify($repo_url);
|
||||
$webhook = new GithubWebhook();
|
||||
$webhook->verify();
|
||||
break;
|
||||
case 'gitea':
|
||||
$webhook = new GiteaWebhook($payload);
|
||||
$claimed_secret = $webhook->secret();
|
||||
$repo_url = $webhook->repo_url();
|
||||
$secret = find_secret($repo_url);
|
||||
verify_signature($payload, $secret, $claimed_secret);
|
||||
notify($repo_url);
|
||||
$webhook = new GiteaWebhook();
|
||||
$webhook->verify();
|
||||
break;
|
||||
case 'gitlab':
|
||||
$webhook = new GitlabWebhook($payload);
|
||||
$claimed_secret = $webhook->secret();
|
||||
$repo_url = $webhook->repo_url();
|
||||
$secret = find_secret($repo_url);
|
||||
verify_token($secret, $claimed_secret);
|
||||
notify($repo_url);
|
||||
$webhook = new GitlabWebhook();
|
||||
$webhook->verify();
|
||||
break;
|
||||
default:
|
||||
error("Unrecognized action: ".$_GET['action'], 400);
|
||||
|
@ -205,4 +204,22 @@ function action() {
|
|||
action();
|
||||
echo("OK");
|
||||
|
||||
|
||||
// verify_secret($repo_url, $secret)
|
||||
// Verify a secret for the given URL. Useful when we don't have permission to read the secret.
|
||||
function verify_secret($repo_url, $secret) {
|
||||
$whck = find_whck();
|
||||
$repo = escapeshellarg($repo_url);
|
||||
$secret = escapeshellarg($secret);
|
||||
$lines = [];
|
||||
$status = NULL;
|
||||
$secret = exec($forgehook." verify ".$repo." ".$secret, $lines, $status);
|
||||
|
||||
if (($secret == NULL) or ($status != 0)) {
|
||||
echo "$secret";
|
||||
error("Incorrect secret for \"".$repo."\": \"".$secret."\"", 403);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
|
2
spec
2
spec
|
@ -1 +1 @@
|
|||
Subproject commit a72ceda21cfde2ed9e6e19f65e007f2bc8778660
|
||||
Subproject commit 71804c4b3a5f19bb53fdab0bbcecb39ed0919aca
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 510a0b13b99f85379fe10dabadce408aa95a795d
|
Loading…
Reference in New Issue