Compare commits
5 commits
931a6ae2b3
...
77b77a3c70
Author | SHA1 | Date | |
---|---|---|---|
Geoffrey Frogeye | 77b77a3c70 | ||
Geoffrey Frogeye | baf3cfd510 | ||
Geoffrey Frogeye | 96fd5c6a7b | ||
Geoffrey Frogeye | b7b2ab744a | ||
Geoffrey Frogeye | f308830095 |
|
@ -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
20
default.php
Normal 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> Composante client</a></li>
|
||||
<li>
|
||||
<i class="fa fa-li fa-lock"></i>
|
||||
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
150
index.php
|
@ -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') {
|
||||
|
|
Reference in a new issue