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