Compare commits

...

5 commits

3 changed files with 179 additions and 13 deletions

View file

@ -1,11 +1,31 @@
<?php
use OTPHP\TOTP;
$DOMAIN = 'machines.frogeye.fr';
$TOTP = new TOTP(
'newcommer@machines.frogeye.fr', // The label (string)
'EUPJMTU6M7XPHG5P', // The secret encoded in base 32 (string)
'CHANGEMECHANGEME', // 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');
$GIT_APIS = array(
'github' => array(
'api' => 'https://api.github.com',
'token' => 'CHANGEME'
),
'gogs' => array(
'api' => 'https://try.gogs.io/api/v1',
'token' => 'CHANGEME'
),
'gitlab' => array(
'api' => 'https://gitlab.com/api/v3',
'token' => 'CHANGEME',
'authHeader' => 'PRIVATE-TOKEN: ',
),
);
?>

20
default.php Normal file
View file

@ -0,0 +1,20 @@
<?php
$title = 'Machines';
$longTitle = 'Gestion des machines';
include('../header.inc.php');
?>
<p>Voici le composant serveur de mon projet « Machines », qui me permet d'améliorer la sécurité des ordinateurs et serveurs sur lequel j'ai un accès en définissant des règles d'autorisation de connexion avec une clef SSH par machine, tout en facilitant la connexion aux machines par la génération automatique de fichiers de configuration SSH.</p>
<p>Bien que ce domaine ne soit pas d'une grande utilité pour vous, vous pouvez étudier le code si vous le désirez :
<ul class="fa-ul" >
<li><a href="https://git.frogeye.fr/geoffrey/dotfiles/src/master/scripts/machines.sh"><i class="fa fa-li fa-git"></i>&nbsp; Composante client</a></li>
<li>
<i class="fa fa-li fa-lock"></i>
&nbsp; Composante serveur <br/>
<em>(pas encore ouverte pour des raisons de sécurité)</em>
</li>
</ul>
</p>
<p>Malheureusement, comme la plupart des projets que je réalise pour moi-même, le code n'est pas très bien commenté, cela est notamment dû au manque d'objectif précis et de cahier des charges lors de la rédaction du code. Cependant, si vous êtes interessés par le système, <a href="mailto:geoffrey@frogeye.fr">dites-le moi</a> et je ferais un effort de clarification.</p>
<?php
include('../footer.inc.php');
?>

150
index.php
View file

@ -1,14 +1,21 @@
<?php
require __DIR__ . '/vendor/autoload.php';
use OTPHP\TOTP;
include_once('config.inc.php');
require_once('config.inc.php');
if (!array_key_exists('REDIRECT_URL', $_SERVER) || rtrim($_SERVER['REDIRECT_URL'], '/') == '') {
include('default.php');
exit;
}
$route = explode('/', trim(substr(explode('?', $_SERVER['REDIRECT_URL'])[0], strrpos($_SERVER['SCRIPT_NAME'], '/')), '/'));
$meth = $_SERVER['REQUEST_METHOD'];
header('Content-Type: text/plain');
$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
// $FQDN_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
$FQDN_REGEX = '.+'; // From http://stackoverflow.com/a/38477788/2766106
$IP4_REGEX = '/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}/'; // From http://stackoverflow.com/a/5284410
$SSH_KEY_REGEX = '/^(ssh-(rsa|ed25519|dss)|ecdsa-sha2-nistp256) [a-zA-Z0-9+=\/]+/';
$machineArgs = array(
@ -18,9 +25,15 @@ $machineArgs = array(
'repeatable' => false,
'optional' => false
),
'host' => array(
'host' => array( # DEPRECATED
'type' => 'string',
'pattern' => '/^'.$DOMAIN_NAME_REGEX.'(:\d+)?$/',
'pattern' => '/^'.$FQDN_REGEX.'(:\d+)?$/',
'repeatable' => true,
'optional' => true
),
'extIp4' => array(
'type' => 'string',
'pattern' => '/^'.$FQDN_REGEX.'(:\d+)?$/',
'repeatable' => true,
'optional' => true
),
@ -28,7 +41,7 @@ $machineArgs = array(
'type' => 'string',
'pattern' => '/^[a-zA-Z0-9\-_]+$/',
'repeatable' => false,
'optional' => false
'optional' => true
),
'network' => array(
'type' => 'string',
@ -186,16 +199,28 @@ function load($elname) {
return unserialize(file_get_contents($elname.'.ser.db'));
}
// Get keys that can be used to connect
// to the network
function getKeys($network) {
global $SSH_KEY_REGEX;
global $DOMAIN;
$machines = load('machine');
$networks = load('networks');
$networks = load('network');
$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']))
(
isset($machine['network'])
&& isset($networks[$machine['network']])
) && (
(
$networks[$machine['network']]['secure'] == 'true'
) || (
$network
&& isset($network['allowed'])
&& 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";
@ -302,6 +327,68 @@ function argAssert($arg, $data, $args) {
return false;
}
// Hooks
//
function updateGitKeys($api, $keys) {
function apiRequest($api, $route, $meth = 'GET', $data = null) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api['api'].'/'.$route);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $meth);
curl_setopt($ch, CURLOPT_USERAGENT, 'Machines Frogeye');
if ($data) {
$dataStr = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $dataStr);
} else {
$dataStr = '';
}
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
(isset($api['authHeader']) ? $api['authHeader'] : 'Authorization: token').' '.$api['token'],
'Content-Type: application/json',
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$raw = curl_exec($ch);
curl_close($ch);
return json_decode($raw);
}
global $SSH_KEY_REGEX;
$existing = apiRequest($api, 'user/keys');
if ($existing === null) {
return 1;
}
$toDelete = [];
foreach ($existing as $ekey) {
$toDelete[$ekey->id] = $ekey->key;
}
foreach (explode("\n", $keys) as $key) {
if ($key == '') {
continue;
}
$found = false;
foreach ($toDelete as $id => $ekey) {
if (strpos($key, $ekey) !== false) {
unset($toDelete[$id]);
$found = true;
break;
}
}
if (!$found) {
apiRequest($api, 'user/keys', 'POST', array(
"title" => ltrim(preg_replace($SSH_KEY_REGEX, '', $key)),
"key" => $key
));
}
}
foreach ($toDelete as $id => $ekey) {
echo "387 deleting $id $ekey\n";
apiRequest($api, 'user/keys/'.$id, 'DELETE');
}
}
switch ($route[0]) {
case 'machine':
case 'network':
@ -436,8 +523,9 @@ case 'network':
} elseif (count($route) == 2 && $meth == 'DELETE') {
requireSigned();
$elements = load($elname);
if (isset($elements[$route[1]])) {
unset($elements[$route[1]]);
$name = $route[1];
if (isset($elements[$name])) {
unset($elements[$name]);
save($elname, $elements);
http_response_code(204);
logActivity("Removed $elname \"$name\"");
@ -504,8 +592,12 @@ case 'akey':
file_put_contents('akey/'.$networkName.'.authorized_keys', getKeys($networkName ? $network : null));
file_put_contents('akey/'.$networkName.'.authorized_keys.sha256', $sign);
if (array_key_exists($networkName, $GIT_APIS)) {
updateGitKeys($GIT_APIS[$networkName], getKeys($network));
}
http_response_code(201);
logActivity('Updated key '.$networkName);
logActivity('Updated akeys '.$networkName);
} else {
http_response_code(404);
die("Unknown network\n");
@ -516,6 +608,40 @@ case 'akey':
}
break;
// Authorized keys for networks
case 'config':
// GET /config/{machine}
if (count($route) == 2 && $meth == 'GET') {
$machineName = $route[1];
$machines = load('machine');
$networks = load('network');
if (isset($machines[$machineName])) {
$machine = $machines[$machineName];
if (!isset($machine['network']) || !isset($networks[$machine['network']])) {
break;
}
$network = $network[$machine['network']];
foreach ($machines as $dMachineName => $dMachine) {
if ($network['secure'] == 'true') {
}
}
var_dump($machine);
} else {
http_response_code(404);
die("Unknown machine\n");
}
} else {
http_response_code(501);
die("Unkown route\n");
}
break;
// Activity log
case 'log':
if (count($route) == 1 && $meth == 'GET') {