Added POST /remote/changekey API
This commit is contained in:
parent
5838f61db6
commit
aed50e530f
8 changed files with 200 additions and 3 deletions
|
@ -24,6 +24,9 @@ $defaultConfig = [
|
|||
'config' => null
|
||||
]
|
||||
],
|
||||
'remote' => [
|
||||
'timestampWindow' => 15
|
||||
],
|
||||
'records' => [
|
||||
'allowedTypes' => [
|
||||
'A', 'A6', 'AAAA', 'AFSDB', 'ALIAS', 'CAA', 'CDNSKEY', 'CDS', 'CERT', 'CNAME', 'DHCID',
|
||||
|
|
|
@ -54,6 +54,33 @@ class Remote
|
|||
return $res->withStatus(204);
|
||||
}
|
||||
|
||||
public function updateKey(Request $req, Response $res, array $args)
|
||||
{
|
||||
$record = $req->getParsedBodyParam('record');
|
||||
$content = $req->getParsedBodyParam('content');
|
||||
$time = $req->getParsedBodyParam('time');
|
||||
$signature = $req->getParsedBodyParam('signature');
|
||||
|
||||
if ($record === null || $content === null || $time === null || $signature === null) {
|
||||
return $res->withJson(['error' => 'One of the required fields is missing.'], 422);
|
||||
}
|
||||
|
||||
$remote = new \Operations\Remote($this->c);
|
||||
|
||||
try {
|
||||
$remote->updateKey($record, $content, $time, $signature);
|
||||
} catch (\Exceptions\NotFoundException $e) {
|
||||
$this->logger->debug('User tried to update non existent record via changekey api.');
|
||||
return $res->withJson(['error' => 'The given record does not exist.'], 404);
|
||||
} catch (\Exceptions\ForbiddenException $e) {
|
||||
$this->logger->debug('User tried to update an record via changekey api with incorrect password.');
|
||||
return $res->withJson(['error' => 'The provided password was invalid.'], 403);
|
||||
}
|
||||
|
||||
$this->logger->info('Record ' . $record . ' was changed via the changekey api.');
|
||||
return $res->withStatus(204);
|
||||
}
|
||||
|
||||
public function servertime(Request $req, Response $res, array $args)
|
||||
{
|
||||
return $res->withJson([
|
||||
|
|
|
@ -26,7 +26,7 @@ class Remote
|
|||
}
|
||||
|
||||
/**
|
||||
* Add new record
|
||||
* Update given record with password
|
||||
*
|
||||
* @param $record Name of the new record
|
||||
* @param $content Type of the new record
|
||||
|
@ -65,4 +65,54 @@ class Remote
|
|||
$records = new \Operations\Records($this->c);
|
||||
$records->updateRecord($record, null, null, $content, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update given record with password
|
||||
*
|
||||
* @param $record Name of the new record
|
||||
* @param $content Type of the new record
|
||||
* @param $time Timestamp of the signature
|
||||
* @param $signature Signature
|
||||
*
|
||||
* @throws NotFoundException if the record does not exist
|
||||
* @throws ForbiddenException if the signature is not valid for the record
|
||||
*/
|
||||
public function updateKey(int $record, string $content, int $time, string $signature) : void
|
||||
{
|
||||
$timestampWindow = $this->c['config']['remote']['timestampWindow'];
|
||||
|
||||
$query = $this->db->prepare('SELECT id FROM records WHERE id=:record');
|
||||
$query->bindValue(':record', $record, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if ($query->fetch() === false) {
|
||||
throw new \Exceptions\NotFoundException();
|
||||
}
|
||||
|
||||
$query = $this->db->prepare('SELECT security FROM remote WHERE record=:record AND type=\'key\'');
|
||||
$query->bindValue(':record', $record, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if (abs($time - time()) > $timestampWindow) {
|
||||
throw new \Exceptions\ForbiddenException();
|
||||
}
|
||||
|
||||
$validKeyFound = false;
|
||||
|
||||
$verifyString = $record . $content . $time;
|
||||
|
||||
while ($row = $query->fetch()) {
|
||||
if (openssl_verify($verifyString, base64_decode($signature), $row['security'], OPENSSL_ALGO_SHA512)) {
|
||||
$validKeyFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$validKeyFound) {
|
||||
throw new \Exceptions\ForbiddenException();
|
||||
}
|
||||
|
||||
$records = new \Operations\Records($this->c);
|
||||
$records->updateRecord($record, null, null, $content, null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ $app->group('/v1', function () {
|
|||
$this->get('/remote/ip', '\Controllers\Remote:ip');
|
||||
$this->get('/remote/servertime', '\Controllers\Remote:servertime');
|
||||
$this->get('/remote/updatepw', '\Controllers\Remote:updatePassword');
|
||||
$this->post('/remote/updatekey', '\Controllers\Remote:updateKey');
|
||||
|
||||
$this->group('', function () {
|
||||
$this->delete('/sessions/{sessionId}', '\Controllers\Sessions:delete');
|
||||
|
|
|
@ -169,7 +169,7 @@ CREATE TABLE `remote` (
|
|||
INSERT INTO `remote` (`id`, `record`, `description`, `type`, `security`, `nonce`) VALUES
|
||||
(1, 1, 'Password Test', 'password', '$2y$10$abocd6jj/Tw4jzDtqTnjreNzwcerzkXwoVc.JvZBoZ6p0grEKDWoW', NULL),
|
||||
(2, 4, 'Key Test', 'key', '-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5mu3aH90uSXY9sVLgVSz\nKj4FEctrpFDPyVC4ufbJa/44fuLABFe+IizgZUheNBBO7FjpLJYvsL24o6TEeht4\no5j0KHrRHXqp4WQuAL3ZREv/AhNaOC9/xyjoGwUkKkdC2bIfh0J/ACkezxvUrPsh\nbzhzY+co/M9PqlgTbjKjvlv/pRj2dSp98FzUme3HCh7Nn1EOM3yPMtaKNA9Qkkz1\noalfR3xmJjIanoS9zcK77/yyQ8VwI//CgxvnpnWbORZG0B9W2ZBoI8Bj4zprbbFG\nKNmrb403wfDijYF3MXpSMjKvJ5YVuZsn35EWIi5tqFc0oV7Ryy9nBHzKeoYN7Szs\nrXIS5+ZcQDLuN+pqJ7ByVaw4aVn85py8IdO0IYD5xeKd1i0iqm+KSoFTS1jiNSZu\n6iVl4odixWtW7oPLYBbd/vD2F7Ua5cLd12Rs+6kEVtlpnIf7txyFQL4QHYJxB7fI\ny+m70mfufVvKbFh/mHkhe+Arv71ERDMfAV3AD8++axLqYfU/LLFzanjwIBctAA9a\nj++G0lwl1adURwnBeq8+YrMU4/wg9efquKXLR40dU9nkMJOm5tPm+XHt4o3wio4X\n2FqnD57I7qJCWVc00HtpeWno5vHL+eJu0TdxjBuYXnQfwa1z9pWvGaoBtg7tyHgv\ng7YZJzF1MW5N9ZqnkdFJVEsCAwEAAQ==\n-----END PUBLIC KEY-----', NULL),
|
||||
(3, 1, 'Key Test 2', 'key', '-----BEGIN PUBLIC KEY-----\r\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5mu3aH90uSXY9sVLgVSz\r\nKj4FEctrpFDPyVC4ufbJa/44fuLABFe+IizgZUheNBBO7FjpLJYvsL24o6TEeht4\r\no5j0KHrRHXqp4WQuAL3ZREv/AhNaOC9/xyjoGwUkKkdC2bIfh0J/ACkezxvUrPsh\r\nbzhzY+co/M9PqlgTbjKjvlv/pRj2dSp98FzUme3HCh7Nn1EOM3yPMtaKNA9Qkkz1\r\noalfR3xmJjIanoS9zcK77/yyQ8VwI//CgxvnpnWbORZG0B9W2ZBoI8Bj4zprbbFG\r\nKNmrb403wfDijYF3MXpSMjKvJ5YVuZsn35EWIi5tqFc0oV7Ryy9nBHzKeoYN7Szs\r\nrXIS5+ZcQDLuN+pqJ7ByVaw4aVn85py8IdO0IYD5xeKd1i0iqm+KSoFTS1jiNSZu\r\n6iVl4odixWtW7oPLYBbd/vD2F7Ua5cLd12Rs+6kEVtlpnIf7txyFQL4QHYJxB7fI\r\ny+m70mfufVvKbFh/mHkhe+Arv71ERDMfAV3AD8++axLqYfU/LLFzanjwIBctAA9a\r\nj++G0lwl1adURwnBeq8+YrMU4/wg9efquKXLR40dU9nkMJOm5tPm+XHt4o3wio4X\r\n2FqnD57I7qJCWVc00HtpeWno5vHL+eJu0TdxjBuYXnQfwa1z9pWvGaoBtg7tyHgv\r\ng7YZJzF1MW5N9ZqnkdFJVEsCAwEAAQ==\r\n-----END PUBLIC KEY-----', NULL);
|
||||
(3, 1, 'Key Test 2', 'key', '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrJ/UoQoN5rO1nwrWBNDr3TgPB\nkm6UmN/B6NY7RXcYTJOFEP6iWqTj9Pw8aT8/DSn2uTMeQK6kWNUAWmRaylQI2QHQ\ndPtrI6piTpjvKm+KbR+n3e4QJ/zOcg06cHYJJiyhPjfC12j3ZxINOV3LDbEKq4s0\nHxMGYZHPu+UezapeeQIDAQAB\n-----END PUBLIC KEY-----', NULL);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
|
|
13
backend/test/package-lock.json
generated
13
backend/test/package-lock.json
generated
|
@ -4,6 +4,11 @@
|
|||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"asn1": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
|
||||
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||
|
@ -43,6 +48,14 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"node-rsa": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
|
||||
"integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=",
|
||||
"requires": {
|
||||
"asn1": "0.2.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"description": "Dependencies for pdnsmanager test",
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"cartesian-product": "^2.1.2"
|
||||
"cartesian-product": "^2.1.2",
|
||||
"node-rsa": "^0.4.2"
|
||||
}
|
||||
}
|
||||
|
|
102
backend/test/tests/remote-changekey.js
Normal file
102
backend/test/tests/remote-changekey.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
const test = require('../testlib');
|
||||
|
||||
const NodeRSA = require('node-rsa');
|
||||
|
||||
const privkey =
|
||||
`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQCrJ/UoQoN5rO1nwrWBNDr3TgPBkm6UmN/B6NY7RXcYTJOFEP6i
|
||||
WqTj9Pw8aT8/DSn2uTMeQK6kWNUAWmRaylQI2QHQdPtrI6piTpjvKm+KbR+n3e4Q
|
||||
J/zOcg06cHYJJiyhPjfC12j3ZxINOV3LDbEKq4s0HxMGYZHPu+UezapeeQIDAQAB
|
||||
AoGAGGkbgwFxhPIP7gOMJYBQhKMA0CPVV6YyC5LsswlmQfXx+EGDP56T89sl+mu8
|
||||
VH7JJGInk0IAZnow7tr1gylmMJ0ir6KfDKZQG95tkFHwCVM3ZqUx/X8VAVuZT2mo
|
||||
6ckAC7/ZrqORiFCNDC1kWgiaNj7GldvcbNOGUIBOkStgM4ECQQDVLWI/hO0fiPhT
|
||||
QWVu+4md1NjSv9MZdaIdm+FEVKyTjN/j1fDLNFIguC24veYvsgKf2AyYAJqiAihz
|
||||
RQWey38RAkEAzYmjjZuKmtsaUknZxmYVJwZlatvHv/3V2REa3UwhVXhgpbBGahav
|
||||
khH8W5u4JJ/VUpX34wje8g/Gp2M6aCg46QJAGtux8jDMM1ntd4fYwMfeSc1kWAEl
|
||||
FqMUfsiB9Dr610g7eRgeU2vPISIzWIBMfRvfasYsqAYDdX/yGrvKfnxDEQJAcTUr
|
||||
aXbPfAXMVKCqm3Vkly8VsyrEtcHZBItAUb156rq3+OrDjfFa2MihR8/YOAv1ElzZ
|
||||
wSoEqiz4TQABjpcA6QJAX1QXYhHQpjLj4UF+8TkZg93Zmd86W5CN/gXSTFJGrZ8M
|
||||
3DOyePDIw1omSzyfvYa3Rbl/NL5BxFH6cURg++z8FA==
|
||||
-----END RSA PRIVATE KEY-----`;
|
||||
const key = new NodeRSA(privkey, 'pkcs1', { signingScheme: 'pkcs1-sha512' });
|
||||
|
||||
test.run(async function () {
|
||||
await test('admin', async function (assert, req) {
|
||||
// Test updating
|
||||
var time = Math.floor(new Date() / 1000);
|
||||
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 1,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbaz' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 204, 'Update should succeed');
|
||||
|
||||
var res = await req({
|
||||
url: '/records/1',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
assert.equal(res.data.content, 'foobarbaz', 'Updating should change content.');
|
||||
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 1,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbazdef' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403);
|
||||
|
||||
// Test not existing record
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 100,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbazdef' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 404, 'Not existing record should trigger error');
|
||||
|
||||
// Test missing fields
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 100,
|
||||
signature: key.sign('1foobarbazdef' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 422, 'Missing field should fail');
|
||||
|
||||
// Test wrong time
|
||||
var time = Math.floor(new Date() / 1000) - 60;
|
||||
var res = await req({
|
||||
url: '/remote/updatekey',
|
||||
method: 'post',
|
||||
data: {
|
||||
record: 1,
|
||||
content: 'foobarbaz',
|
||||
time: time,
|
||||
signature: key.sign('1foobarbaz' + time, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(res.status, 403, 'Wrong time should fail');
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue