Initial commit
Well, here we are. New set of scripts that is meant to improve security of my machines by using an unique SSH key per host and not to carry them using .dotfile synchronisation. Might be even less secure than before if there are any flaw, that's why I kept this repository secret for now.
This commit is contained in:
commit
931a6ae2b3
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
composer.phar
|
||||
/vendor/
|
||||
*.ser.db
|
||||
config.inc.php
|
||||
akey/
|
||||
*.log
|
||||
*.crt
|
5
composer.json
Normal file
5
composer.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"require": {
|
||||
"spomky-labs/otphp": "^8.3"
|
||||
}
|
||||
}
|
407
composer.lock
generated
Normal file
407
composer.lock
generated
Normal file
|
@ -0,0 +1,407 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "74060f5f76c8544e8b8d383e40e290f1",
|
||||
"content-hash": "dc782cdfab3468b251f22bc32c601f94",
|
||||
"packages": [
|
||||
{
|
||||
"name": "beberlei/assert",
|
||||
"version": "v2.6.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/beberlei/assert.git",
|
||||
"reference": "848c8f0bde97b48d1e159075e20a6667583f3978"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/beberlei/assert/zipball/848c8f0bde97b48d1e159075e20a6667583f3978",
|
||||
"reference": "848c8f0bde97b48d1e159075e20a6667583f3978",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.0",
|
||||
"phpunit/phpunit": "@stable"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Assert\\": "lib/Assert"
|
||||
},
|
||||
"files": [
|
||||
"lib/Assert/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Richard Quadling",
|
||||
"email": "rquadling@gmail.com",
|
||||
"role": "Collaborator"
|
||||
}
|
||||
],
|
||||
"description": "Thin assertion library for input validation in business models.",
|
||||
"keywords": [
|
||||
"assert",
|
||||
"assertion",
|
||||
"validation"
|
||||
],
|
||||
"time": "2016-12-05 11:33:17"
|
||||
},
|
||||
{
|
||||
"name": "christian-riesen/base32",
|
||||
"version": "1.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ChristianRiesen/base32.git",
|
||||
"reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa",
|
||||
"reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*",
|
||||
"satooshi/php-coveralls": "0.*"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Base32\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Riesen",
|
||||
"email": "chris.riesen@gmail.com",
|
||||
"homepage": "http://christianriesen.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Base32 encoder/decoder according to RFC 4648",
|
||||
"homepage": "https://github.com/ChristianRiesen/base32",
|
||||
"keywords": [
|
||||
"base32",
|
||||
"decode",
|
||||
"encode",
|
||||
"rfc4648"
|
||||
],
|
||||
"time": "2016-05-05 11:49:03"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e",
|
||||
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/random.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2016-11-07 23:38:38"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/otphp",
|
||||
"version": "v8.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/otphp.git",
|
||||
"reference": "8c90e16ba48fe7c306832611e22c5bad2d663a98"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/8c90e16ba48fe7c306832611e22c5bad2d663a98",
|
||||
"reference": "8c90e16ba48fe7c306832611e22c5bad2d663a98",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"beberlei/assert": "^2.4",
|
||||
"christian-riesen/base32": "^1.1",
|
||||
"paragonie/random_compat": "^2.0",
|
||||
"php": "^5.5|^7.0",
|
||||
"symfony/polyfill-mbstring": "^1.1",
|
||||
"symfony/polyfill-php56": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0|^5.0",
|
||||
"satooshi/php-coveralls": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "8.2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"OTPHP\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"homepage": "https://github.com/Spomky"
|
||||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/Spomky-Labs/otphp/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator",
|
||||
"homepage": "https://github.com/Spomky-Labs/otphp",
|
||||
"keywords": [
|
||||
"FreeOTP",
|
||||
"RFC 4226",
|
||||
"RFC 6238",
|
||||
"google authenticator",
|
||||
"hotp",
|
||||
"otp",
|
||||
"totp"
|
||||
],
|
||||
"time": "2016-12-08 10:46:02"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
|
||||
"reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"time": "2016-11-14 01:06:16"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php56",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php56.git",
|
||||
"reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c",
|
||||
"reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"symfony/polyfill-util": "~1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php56\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"time": "2016-11-14 01:06:16"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-util",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-util.git",
|
||||
"reference": "746bce0fca664ac0a575e465f65c6643faddf7fb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb",
|
||||
"reference": "746bce0fca664ac0a575e465f65c6643faddf7fb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Util\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony utilities for portability of PHP codes",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compat",
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"shim"
|
||||
],
|
||||
"time": "2016-11-14 01:06:16"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
}
|
11
config.inc.php.example
Normal file
11
config.inc.php.example
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
$DOMAIN = 'machines.frogeye.fr';
|
||||
$TOTP = new TOTP(
|
||||
'newcommer@machines.frogeye.fr', // The label (string)
|
||||
'EUPJMTU6M7XPHG5P', // The secret encoded in base 32 (string)
|
||||
10, // The period (int)
|
||||
'sha512', // The digest algorithm (string)
|
||||
8 // The number of digits (int)
|
||||
);
|
||||
$TOTP->setIssuer('Machines Frogeye');
|
||||
?>
|
558
index.php
Normal file
558
index.php
Normal file
|
@ -0,0 +1,558 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
use OTPHP\TOTP;
|
||||
|
||||
include_once('config.inc.php');
|
||||
|
||||
$route = explode('/', trim(substr(explode('?', $_SERVER['REDIRECT_URL'])[0], strrpos($_SERVER['SCRIPT_NAME'], '/')), '/'));
|
||||
$meth = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
$DOMAIN_NAME_REGEX = '[a-zA-Z0-9\p{L}][a-zA-Z0-9\p{L}-\.]{1,61}[a-zA-Z0-9\p{L}]\.[a-zA-Z0-9\p{L}][a-zA-Z\p{L}-]*[a-zA-Z0-9\p{L}]+'; // From http://stackoverflow.com/a/38477788/2766106
|
||||
$SSH_KEY_REGEX = '/^(ssh-(rsa|ed25519|dss)|ecdsa-sha2-nistp256) [a-zA-Z0-9+=\/]+/';
|
||||
|
||||
$machineArgs = array(
|
||||
'name' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => '/^[a-zA-Z0-9\-_]+$/',
|
||||
'repeatable' => false,
|
||||
'optional' => false
|
||||
),
|
||||
'host' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => '/^'.$DOMAIN_NAME_REGEX.'(:\d+)?$/',
|
||||
'repeatable' => true,
|
||||
'optional' => true
|
||||
),
|
||||
'user' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => '/^[a-zA-Z0-9\-_]+$/',
|
||||
'repeatable' => false,
|
||||
'optional' => false
|
||||
),
|
||||
'network' => array(
|
||||
'type' => 'string',
|
||||
'oneof' => 'network',
|
||||
'repeatable' => false,
|
||||
'optional' => true
|
||||
),
|
||||
'userkey' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => $SSH_KEY_REGEX,
|
||||
'repeatable' => false,
|
||||
'optional' => true
|
||||
),
|
||||
'hostkey' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => $SSH_KEY_REGEX,
|
||||
'repeatable' => false,
|
||||
'optional' => true
|
||||
)
|
||||
);
|
||||
|
||||
$networkArgs = array(
|
||||
'name' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => '/^[a-zA-Z0-9]+$/',
|
||||
'repeatable' => false,
|
||||
'optional' => false
|
||||
),
|
||||
'allowed' => array(
|
||||
'type' => 'string',
|
||||
'oneof' => 'network',
|
||||
'repeatable' => true,
|
||||
'optional' => true
|
||||
),
|
||||
'secure' => array(
|
||||
'type' => 'boolean',
|
||||
'repeatable' => false,
|
||||
'optional' => false
|
||||
)
|
||||
);
|
||||
|
||||
// Compute a diff between two arrays
|
||||
// From http://stackoverflow.com/a/22021254/2766106
|
||||
function computeDiff($from, $to)
|
||||
{
|
||||
$diffValues = array();
|
||||
$diffMask = array();
|
||||
|
||||
$dm = array();
|
||||
$n1 = count($from);
|
||||
$n2 = count($to);
|
||||
|
||||
for ($j = -1; $j < $n2; $j++) $dm[-1][$j] = 0;
|
||||
for ($i = -1; $i < $n1; $i++) $dm[$i][-1] = 0;
|
||||
for ($i = 0; $i < $n1; $i++)
|
||||
{
|
||||
for ($j = 0; $j < $n2; $j++)
|
||||
{
|
||||
if ($from[$i] == $to[$j])
|
||||
{
|
||||
$ad = $dm[$i - 1][$j - 1];
|
||||
$dm[$i][$j] = $ad + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$a1 = $dm[$i - 1][$j];
|
||||
$a2 = $dm[$i][$j - 1];
|
||||
$dm[$i][$j] = max($a1, $a2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$i = $n1 - 1;
|
||||
$j = $n2 - 1;
|
||||
while (($i > -1) || ($j > -1))
|
||||
{
|
||||
if ($j > -1)
|
||||
{
|
||||
if ($dm[$i][$j - 1] == $dm[$i][$j])
|
||||
{
|
||||
$diffValues[] = $to[$j];
|
||||
$diffMask[] = 1;
|
||||
$j--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ($i > -1)
|
||||
{
|
||||
if ($dm[$i - 1][$j] == $dm[$i][$j])
|
||||
{
|
||||
$diffValues[] = $from[$i];
|
||||
$diffMask[] = -1;
|
||||
$i--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
{
|
||||
$diffValues[] = $from[$i];
|
||||
$diffMask[] = 0;
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
}
|
||||
|
||||
$diffValues = array_reverse($diffValues);
|
||||
$diffMask = array_reverse($diffMask);
|
||||
|
||||
return array('values' => $diffValues, 'mask' => $diffMask);
|
||||
}
|
||||
|
||||
function requireToken() {
|
||||
global $TOTP;
|
||||
if (isset($_SERVER['HTTP_X_TOTP']) && $_SERVER['HTTP_X_TOTP']) {
|
||||
if (!$TOTP->verify($_SERVER['HTTP_X_TOTP'], null, 1)) {
|
||||
http_response_code(403);
|
||||
die("Invalid token\n");
|
||||
}
|
||||
} else {
|
||||
http_response_code(401);
|
||||
die("Authorization required\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function requireSigned() {
|
||||
if ($_SERVER['SSL_CLIENT_VERIFY'] == 'NONE') {
|
||||
http_response_code(401);
|
||||
die("Authorization required\n");
|
||||
} elseif ($_SERVER['SSL_CLIENT_VERIFY'] != 'SUCCESS') {
|
||||
http_response_code(403);
|
||||
die("Wrong certificate\n");
|
||||
}
|
||||
}
|
||||
|
||||
function logActivity($text) {
|
||||
$ex = explode("\n", $text);
|
||||
$t = '';
|
||||
foreach ($ex as $line) {
|
||||
if ($line == '') {
|
||||
continue;
|
||||
}
|
||||
$t .= strval(time()).'|'.$line."\n";
|
||||
}
|
||||
file_put_contents('machines.log', $t, FILE_APPEND);
|
||||
}
|
||||
|
||||
function save($elname, $elements) {
|
||||
file_put_contents($elname.'.ser.db', serialize($elements));
|
||||
}
|
||||
|
||||
function load($elname) {
|
||||
if (!file_exists($elname.'.ser.db')) {
|
||||
save($elname, []);
|
||||
}
|
||||
return unserialize(file_get_contents($elname.'.ser.db'));
|
||||
}
|
||||
|
||||
function getKeys($network) {
|
||||
global $SSH_KEY_REGEX;
|
||||
global $DOMAIN;
|
||||
$machines = load('machine');
|
||||
$networks = load('networks');
|
||||
$t = '';
|
||||
foreach ($machines as $machineName => $machine) {
|
||||
if (
|
||||
(isset($machine['network']) && isset($networks[$machine['network']]) && $networks[$machine['network']]['secure'] == 'true')
|
||||
|| ($network && in_array($machine['network'], $network['allowed']))
|
||||
) {
|
||||
if (isset($machine['userkey']) && preg_match($SSH_KEY_REGEX, $machine['userkey'], $matches)) {
|
||||
$t .= $matches[0].' '.$machineName.'@'.($machine['network'] ? $machine['network'].'.' : '').$DOMAIN."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $t;
|
||||
}
|
||||
|
||||
// Display an element in a simple, easily-editable,
|
||||
// easily-diffable, plain text format
|
||||
function displayElement($element, $elementArgs) {
|
||||
$t = '';
|
||||
ksort($element);
|
||||
foreach ($element as $arg => $value) {
|
||||
if ($arg == 'name') {
|
||||
continue;
|
||||
}
|
||||
if (array_key_exists($arg, $elementArgs)) {
|
||||
if ($value) {
|
||||
$argData = $elementArgs[$arg];
|
||||
if ($argData['repeatable']) {
|
||||
sort($value);
|
||||
foreach ($value as $arrValue) {
|
||||
$t .= $arg.'[]='.$arrValue."\n";
|
||||
}
|
||||
} else {
|
||||
$t .= $arg.'='.$value."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $t;
|
||||
}
|
||||
|
||||
// Verifiy if one argument is valid
|
||||
// return false if valid, string with
|
||||
// description if not
|
||||
function argAssertOne($argData, $data) {
|
||||
// Type casting
|
||||
switch ($argData['type']) {
|
||||
case 'boolean':
|
||||
switch ($data) {
|
||||
case 'true':
|
||||
$data = true;
|
||||
break;
|
||||
case 'false':
|
||||
$data = false;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Type validation
|
||||
if (gettype($data) != $argData['type']) {
|
||||
return "should be of type ".$argData['type'];
|
||||
}
|
||||
|
||||
// Pattern validation
|
||||
if (isset($argData['pattern']) && !preg_match($argData['pattern'], $data)) {
|
||||
return "should match pattern ".$argData['pattern'];
|
||||
}
|
||||
|
||||
// OneOf validation
|
||||
if (isset($argData['oneof'])) {
|
||||
$elname = $argData['oneof'];
|
||||
$elements = load($elname);
|
||||
if (!isset($elements[$data])) {
|
||||
return "should be an existing $elname";
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function argAssert($arg, $data, $args) {
|
||||
$argData = $args[$arg];
|
||||
# TODO oneof
|
||||
|
||||
if (!$data) {
|
||||
if ($argData['optional']) {
|
||||
return false;
|
||||
} else {
|
||||
return "Argument $arg is required";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($argData['repeatable']) {
|
||||
if (gettype($data) == 'array') {
|
||||
foreach ($data as $key => $dat) {
|
||||
if ($err = argAssertOne($argData, $dat)) {
|
||||
return "Argument $arg"."[$key] $err";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "Argument $arg should be an array";
|
||||
}
|
||||
} else {
|
||||
if ($err = argAssertOne($argData, $data)) {
|
||||
return "Argument $arg ".$err;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($route[0]) {
|
||||
case 'machine':
|
||||
case 'network':
|
||||
switch ($elname = $route[0]) {
|
||||
case 'machine':
|
||||
$elementArgs = $machineArgs;
|
||||
break;
|
||||
case 'network':
|
||||
$elementArgs = $networkArgs;
|
||||
break;
|
||||
}
|
||||
|
||||
// GET /element
|
||||
if (count($route) == 1 && $meth == 'GET') {
|
||||
requireSigned();
|
||||
$elements = load($elname);
|
||||
foreach ($elements as $key => $value) {
|
||||
echo $key."\n";
|
||||
}
|
||||
|
||||
// GET /element/{name}
|
||||
} elseif (count($route) == 2 && $meth == 'GET') {
|
||||
if ($elname != 'machine') {
|
||||
requireSigned();
|
||||
}
|
||||
$elements = load($elname);
|
||||
if (isset($elements[$route[1]])) {
|
||||
$element = $elements[$route[1]];
|
||||
echo displayElement($element, $elementArgs);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
die("Unknown $elname\n");
|
||||
}
|
||||
|
||||
// POST /element
|
||||
} elseif (count($route) == 1 && $meth == 'POST') {
|
||||
if (isset($_SERVER['HTTP_X_TOTP']) && $elname == 'machine') {
|
||||
$restrained = true;
|
||||
requireToken();
|
||||
} else {
|
||||
$restrained = false;
|
||||
requireSigned();
|
||||
}
|
||||
foreach ($_POST as $arg => $value) {
|
||||
if ($restrained && $arg == 'network') {
|
||||
http_response_code(403);
|
||||
die("You can't add a network authenticated like that\n");
|
||||
}
|
||||
|
||||
if (!array_key_exists($arg, $elementArgs)) {
|
||||
die("Unknown argument $arg\n");
|
||||
}
|
||||
}
|
||||
$elements = load($elname);
|
||||
$element = array();
|
||||
foreach ($elementArgs as $arg => $argData) {
|
||||
if (!isset($_POST[$arg])) {
|
||||
$_POST[$arg] = null;
|
||||
}
|
||||
if ($err = argAssert($arg, $_POST[$arg], $elementArgs)) {
|
||||
http_response_code(400);
|
||||
die("$err\n");
|
||||
}
|
||||
|
||||
if ($arg == 'name') {
|
||||
$name = $_POST[$arg];
|
||||
} else {
|
||||
$element[$arg] = $_POST[$arg];
|
||||
}
|
||||
}
|
||||
if (isset($elements[$name])) {
|
||||
http_response_code(409);
|
||||
die("$elname with the same name already exists\n");
|
||||
} else {
|
||||
$elements[$name] = $element;
|
||||
save($elname, $elements);
|
||||
http_response_code(201);
|
||||
logActivity("Created $elname \"$name\"\n".displayElement($element, $elementArgs));
|
||||
}
|
||||
|
||||
// POST /element/{name}
|
||||
} elseif (count($route) == 2 && $meth == 'POST') {
|
||||
requireSigned();
|
||||
foreach ($_POST as $arg => $value) {
|
||||
if (!array_key_exists($arg, $elementArgs)) {
|
||||
die("Unknown argument $arg\n");
|
||||
}
|
||||
}
|
||||
$elements = load($elname);
|
||||
if (isset($elements[$route[1]])) {
|
||||
$element = $elements[$route[1]];
|
||||
foreach ($elementArgs as $arg => $typ) {
|
||||
if (isset($_POST[$arg])) {
|
||||
if ($arg == 'name') {
|
||||
http_response_code(501);
|
||||
die("Can't change name\n");
|
||||
}
|
||||
if ($err = argAssert($arg, $_POST[$arg], $elementArgs)) {
|
||||
http_response_code(400);
|
||||
die("$err\n");
|
||||
}
|
||||
$element[$arg] = $_POST[$arg];
|
||||
}
|
||||
}
|
||||
$oldEls = explode("\n", displayElement($elements[$route[1]], $elementArgs));
|
||||
|
||||
$elements[$route[1]] = $element;
|
||||
save($elname, $elements);
|
||||
http_response_code(204);
|
||||
|
||||
$newEls = explode("\n", displayElement($elements[$route[1]], $elementArgs));
|
||||
$diff = computeDiff($oldEls, $newEls);
|
||||
$t = '';
|
||||
foreach ($diff['values'] as $i => $line) {
|
||||
switch ($diff['mask'][$i]) {
|
||||
case -1:
|
||||
$t .= '- '.$line."\n";
|
||||
break;
|
||||
case 1:
|
||||
$t .= '+ '.$line."\n";
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
logActivity("Modified $elname \"$route[1]\"\n".$t);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
die("Unknown $elname\n");
|
||||
}
|
||||
|
||||
// DELETE /element/{name}
|
||||
} elseif (count($route) == 2 && $meth == 'DELETE') {
|
||||
requireSigned();
|
||||
$elements = load($elname);
|
||||
if (isset($elements[$route[1]])) {
|
||||
unset($elements[$route[1]]);
|
||||
save($elname, $elements);
|
||||
http_response_code(204);
|
||||
logActivity("Removed $elname \"$name\"");
|
||||
} else {
|
||||
http_response_code(404);
|
||||
die("Unknown $elname\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
http_response_code(501);
|
||||
die("Unkown route\n");
|
||||
}
|
||||
break;
|
||||
|
||||
// Authorized keys for networks
|
||||
case 'akey':
|
||||
|
||||
// GET /akey/{network}
|
||||
if ((count($route) == 1 || count($route) == 2) && $meth == 'GET') {
|
||||
if (count($route) == 2) {
|
||||
$networkName = $route[1];
|
||||
} else {
|
||||
$networkName = '';
|
||||
}
|
||||
$networks = load('network');
|
||||
if (!$networkName || isset($networks[$networkName])) {
|
||||
if ($networkName) {
|
||||
$network = $networks[$networkName];
|
||||
}
|
||||
|
||||
// GET /akey/{network}?signature
|
||||
if (isset($_GET['signature'])) {
|
||||
echo file_get_contents('akey/'.$networkName.'.authorized_keys.sha256');
|
||||
|
||||
// GET /akey/{network}?unsigned
|
||||
} elseif (isset($_GET['unsigned'])) {
|
||||
requireSigned();
|
||||
echo getKeys($networkName ? $network : null);
|
||||
|
||||
// GET /akey/{network}
|
||||
} else {
|
||||
echo file_get_contents('akey/'.$networkName.'.authorized_keys');
|
||||
}
|
||||
} else {
|
||||
http_response_code(404);
|
||||
die("Unknown network\n");
|
||||
}
|
||||
|
||||
// PUT /akey/{network}
|
||||
} elseif ((count($route) == 1 || count($route) == 2) && $meth == 'PUT') {
|
||||
requireSigned();
|
||||
if (count($route) == 2) {
|
||||
$networkName = $route[1];
|
||||
} else {
|
||||
$networkName = '';
|
||||
}
|
||||
$networks = load('network');
|
||||
if (!$networkName || isset($networks[$networkName])) {
|
||||
if ($networkName) {
|
||||
$network = $networks[$networkName];
|
||||
}
|
||||
|
||||
$sign = file_get_contents('php://input');
|
||||
file_put_contents('akey/'.$networkName.'.authorized_keys', getKeys($networkName ? $network : null));
|
||||
file_put_contents('akey/'.$networkName.'.authorized_keys.sha256', $sign);
|
||||
|
||||
http_response_code(201);
|
||||
logActivity('Updated key '.$networkName);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
die("Unknown network\n");
|
||||
}
|
||||
} else {
|
||||
http_response_code(501);
|
||||
die("Unkown route\n");
|
||||
}
|
||||
break;
|
||||
|
||||
// Activity log
|
||||
case 'log':
|
||||
if (count($route) == 1 && $meth == 'GET') {
|
||||
requireSigned();
|
||||
if (!isset($_GET['from']) || !$from = intval($_GET['from'])) {
|
||||
$from = 0;
|
||||
}
|
||||
$file = fopen('machines.log', 'r');
|
||||
while (!feof($file)) {
|
||||
$line = fgets($file);
|
||||
if ($line == '') {
|
||||
continue;
|
||||
}
|
||||
preg_match('/^(\d+)\|(.+)$/', $line, $matches);
|
||||
if (intval($matches[1]) > $from) {
|
||||
echo $matches[2]."\n";
|
||||
}
|
||||
}
|
||||
fclose($file);
|
||||
}
|
||||
break;
|
||||
case 'totp':
|
||||
global $TOTP;
|
||||
if (count($route) == 1 && $meth == 'GET') {
|
||||
requireSigned();
|
||||
echo $TOTP->getProvisioningUri();
|
||||
}
|
||||
break;
|
||||
case 'cert':
|
||||
if (count($route) == 1 && $meth == 'GET') {
|
||||
echo file_get_contents('machines.crt');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
http_response_code(501);
|
||||
die("Unkown route\n");
|
||||
}
|
||||
|
||||
|
||||
?>
|
Reference in a new issue