Просмотр исходного кода

Feature ssh keys (#838)

* Add support for SSH keys to be added via web interface #701
Co-authored-by: Jaap Marcus <jaap@schipbreukeling.nl>
Raphael Schneeberger 5 лет назад
Родитель
Сommit
896a7c3bcb

+ 1 - 0
CHANGELOG.md

@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
 - Added support for resolving ip addresses based on geoip database for Awstats
 - Added support for resolving ip addresses based on geoip database for Awstats
 - Added Roundcube plugins newmail_notifier and zipdownload.
 - Added Roundcube plugins newmail_notifier and zipdownload.
 - Added HELO support for multiple domains and IPs.
 - Added HELO support for multiple domains and IPs.
+- Add the possibility to manage ssh keys in the backend.
 
 
 ### Bugfixes
 ### Bugfixes
 - Do not allow to show apache2 server-status page from public.
 - Do not allow to show apache2 server-status page from public.

+ 65 - 0
bin/v-add-user-ssh-key

@@ -0,0 +1,65 @@
+#!/bin/bash
+# info: add ssh key
+# options: USER key
+#
+# Function check if $user/.ssh/authorized_keys exists and create it
+# After that it append the new key(s)
+
+#----------------------------------------------------------#
+#                    Variable&Function                     #
+#----------------------------------------------------------#
+
+
+# Argument definition
+user=$1
+key=$2
+
+# Includes
+source $HESTIA/func/main.sh
+source $HESTIA/conf/hestia.conf
+
+# Additional argument formatting
+
+#----------------------------------------------------------#
+#                    Verifications                         #
+#----------------------------------------------------------#
+
+is_format_valid 'user'
+is_object_valid 'user' 'USER' "$user"
+is_object_unsuspended 'user' 'USER' "$user"
+
+# Perform verification if read-only mode is enabled
+check_hestia_demo_mode
+
+#----------------------------------------------------------#
+#                       Action                             #
+#----------------------------------------------------------#
+
+# Reading user values
+source $USER_DATA/user.conf
+#check if file exits
+AUTHKEY_FILE=$HOMEDIR/$user/.ssh/authorized_keys
+if [ ! -f "$AUTHKEY_FILE" ]; then
+    touch "$AUTHKEY_FILE"
+    chown ${user}: "${AUTHKEY_FILE}"
+fi
+TEMP=$(mktemp)
+echo "$key" >> "$TEMP"
+ssh-keygen -l -f "$TEMP"
+if [ ! $? -eq 0 ]; then
+    rm "$TEMP"
+    exit
+fi
+rm "$TEMP"
+#append key to file
+echo "$key" >> "$AUTHKEY_FILE"
+
+#----------------------------------------------------------#
+#                       Hestia                             #
+#----------------------------------------------------------#
+
+# Logging
+log_history "added ssh-key $user"
+log_event "$OK" "$ARGUMENTS"
+
+exit

+ 54 - 0
bin/v-delete-user-ssh-key

@@ -0,0 +1,54 @@
+#!/bin/bash
+# info: add ssh key
+# options: USER key
+#
+# Function check if $user/.ssh/authorized_keys exists and create it
+# After that it append the new key(s)
+
+#----------------------------------------------------------#
+#                    Variable&Function                     #
+#----------------------------------------------------------#
+
+# Argument definition
+user=$1
+keyid=$2
+
+# Includes
+source $HESTIA/func/main.sh
+source $HESTIA/conf/hestia.conf
+
+# Additional argument formatting
+
+#----------------------------------------------------------#
+#                    Verifications                         #
+#----------------------------------------------------------#
+
+is_format_valid 'user'
+is_object_valid 'user' 'USER' "$user"
+is_object_unsuspended 'user' 'USER' "$user"
+
+source $USER_DATA/user.conf
+
+FILE=$HOMEDIR/$user/.ssh/authorized_keys
+if [ ! -f "$FILE" ]; then
+    exit;
+fi
+
+# Perform verification if read-only mode is enabled
+check_hestia_demo_mode
+
+#----------------------------------------------------------#
+#                       Action                             #
+#----------------------------------------------------------#
+
+sed -i "/${keyid}/d" "$FILE"
+
+#----------------------------------------------------------#
+#                       Hestia                             #
+#----------------------------------------------------------#
+
+# Logging
+log_history "DELETE ssh-key $user"
+log_event "$OK" "$ARGUMENTS"
+
+exit

+ 111 - 0
bin/v-list-user-ssh-key

@@ -0,0 +1,111 @@
+#!/bin/bash
+# info: add ssh key
+# options: USER 
+#
+# Lists $user/.ssh/authorized_keys
+
+#----------------------------------------------------------#
+#                    Variable&Function                     #
+#----------------------------------------------------------#
+
+
+# Argument definition
+user=$1
+
+format=${2-shell}
+
+# Includes
+source $HESTIA/func/main.sh
+
+# JSON list function
+json_list() {
+    IFS=$'\n'
+    i=1
+    objects=$(echo "$keys" |wc -l)
+    echo "{"
+        for str in $keys; do
+        KEY=$(echo $str | awk '{print $(NF-1)}')
+        ID=$(echo $str | awk '{print $NF}')
+        echo -n '    "'$ID'": {
+        "ID": "'$ID'",
+        "KEY": "'$KEY'"
+        }'
+        if [ "$i" -lt "$objects" ]; then
+            echo ','
+        else
+            echo
+        fi
+        ((i++))
+        done		
+        echo "}"   
+}
+
+shell_list() {
+    IFS=$'\n'
+    echo "ID~KEY"
+    echo "----~----~---"
+    for str in $keys; do
+        KEY=$(echo $str | awk '{print $(NF-1)}')
+        ID=$(echo $str | awk '{print $NF}')
+        echo "$ID~$KEY"
+        done
+}
+
+# PLAIN list function
+plain_list() {
+    IFS=$'\n'
+    for str in $keys; do
+        KEY=$(echo $str | awk '{print $(NF-1)}')
+        ID=$(echo $str | awk '{print $NF}')
+        echo -e "$ID\t$KEY"
+    done
+}
+
+# CSV list function
+csv_list() {
+    IFS=$'\n'
+    echo "ID,KEY"
+    for str in $keys; do
+        KEY=$(echo $str | awk '{print $(NF-1)}')
+        ID=$(echo $str | awk '{print $NF}')
+        echo "\"$ID\",\"$KEY\""
+    done
+}
+
+#----------------------------------------------------------#
+#                    Verifications                         #
+#----------------------------------------------------------#
+
+is_format_valid 'user'
+is_object_valid 'user' 'USER' "$user"
+is_object_unsuspended 'user' 'USER' "$user"
+
+#----------------------------------------------------------#
+#                       Action                             #
+#----------------------------------------------------------#
+
+#check if file exsists
+
+if [ ! -f "$HOMEDIR/$user/.ssh/authorized_keys" ]; then
+    exit
+fi
+# Parsing backup config
+#cat "$HOMEDIR/$user/.ssh/authorized_keys"
+keys=$(cat "$HOMEDIR/$user/.ssh/authorized_keys")
+
+# Listing data
+case $format in
+    json)   json_list ;;
+    plain)  plain_list ;;
+    csv)    csv_list ;;
+    shell)  shell_list |column -t ;;
+esac
+
+
+#----------------------------------------------------------#
+#                       Hestia                             #
+#----------------------------------------------------------#
+
+# Logging
+
+exit

+ 109 - 0
web/add/key/index.php

@@ -0,0 +1,109 @@
+<?php
+error_reporting(E_ALL);
+$TAB = 'USER';
+
+// Main include
+include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
+
+//check for valid format ssh key. Doesn't check it is working!
+//https://gist.github.com/jupeter/3248095    
+function validateKey($value)
+{
+    $key_parts = explode(' ', $value, 3);
+    if (count($key_parts) < 2) {
+        return false;
+    }
+    if (count($key_parts) > 3) {
+        return false;
+    }
+    
+    $algorithm = $key_parts[0];
+    $key = $key_parts[1];
+    
+    if (!in_array($algorithm, array('ssh-rsa', 'ssh-dss'))) {
+        return false;
+    }
+    
+    $key_base64_decoded = base64_decode($key, true);
+    if ($key_base64_decoded == FALSE) {
+        return false;
+    }
+    
+    $check = base64_decode(substr($key,0,16));
+    $check = preg_replace("/[^\w\-]/","", $check);
+        
+    if((string) $check !== (string) $algorithm) {
+        return false;
+    }
+    return true;
+}
+
+// Check POST request
+if (!empty($_POST['ok'])) {
+    // Check token
+    if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+        header('location: /login/');
+        exit();
+    }
+    
+    if (empty($_POST['v_key'])){ 
+        $_SESSION['error_msg'] = __('Field SSH_KEY can not be blank.');
+    }
+    
+    if(!$_SESSION['error_msg']){
+        switch ($_POST['v_key']){
+            default: 
+            //key if key already exisits
+            exec (HESTIA_CMD . "v-list-user-ssh-key ".$user." json", $output, $return_var);
+            $data = json_decode(implode('', $output), true);
+            $keylist = array();
+            foreach($data as $key => $value){
+                $idlist[] = trim($data[$key]['ID']);
+                $keylist[] = trim($data[$key]['KEY']);
+            }
+        
+            if(!validateKey($_POST['v_key'])){
+                $_SESSION['error_msg']  = __('SSH KEY is invalid');
+                break;
+            }
+    
+            $v_key_parts = explode(' ',$_POST['v_key']);
+            $key_id = trim($v_key_parts[2]);
+            if($v_key_parts[2] == ''){
+                $_SESSION['error_msg']  = __('SSH KEY is invalid');
+                break;
+            }
+    
+            //for deleting / revoking key the last part user@domain is used therefore needs to be unique
+            //maybe consider adding random generated message or even an human read able string set by user?
+            if(in_array($v_key_parts[2], $idlist)){
+                $_SESSION['error_msg']  =  __('SSH KEY already exists');
+                break;
+            }
+            if(in_array($v_key_parts[1], $keylist)){
+                $_SESSION['error_msg']  =  __('SSH KEY already exists');
+                break;
+            }
+            $v_key = escapeshellarg(trim($_POST['v_key']));
+        }
+    }
+        
+    if (empty($_SESSION['error_msg'])) {
+        exec (HESTIA_CMD."v-add-user-ssh-key ".$user." ".$v_key, $output, $return_var);
+        check_return_code($return_var,$output);
+    }
+
+    unset($output);
+
+    // Flush field values on success
+    if (empty($_SESSION['error_msg'])) {
+    $_SESSION['ok_msg'] = __('SSH KEY created');
+    }
+
+}
+
+render_page($user, $TAB, 'add_key');
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);    

+ 34 - 0
web/delete/key/index.php

@@ -0,0 +1,34 @@
+<?php
+// Init
+error_reporting(NULL);
+ob_start();
+session_start();
+include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
+
+if (($_SESSION['user'] == 'admin') && (!empty($_GET['user']))) {
+    $user=$_GET['user'];;
+}
+
+// Check token
+if ((!isset($_GET['token'])) || ($_SESSION['token'] != $_GET['token'])) {
+    header('location: /login/');
+    exit();
+}
+
+if (!empty($_GET['key'])) {
+    $v_key = escapeshellarg(trim($_GET['key']));
+    $v_key = str_replace('/','\\/', $v_key);
+    exec (HESTIA_CMD."v-delete-user-ssh-key ".$user." ".$v_key);
+    check_return_code($return_var,$output);
+}
+
+unset($output);
+
+//die();
+$back = $_SESSION['back'];
+if (!empty($back)) {
+    header("Location: ".$back);
+    exit;
+}
+header("Location: /list/key/");
+exit;

+ 11 - 2
web/inc/i18n/en.php

@@ -770,6 +770,16 @@ $LANG['en'] = array(
     'Please wait while php is installed or removed' => 'Adding or removing a version of PHP will take around 1 minute per version. Please wait until the process has completed and do not refresh the page.',
     'Please wait while php is installed or removed' => 'Adding or removing a version of PHP will take around 1 minute per version. Please wait until the process has completed and do not refresh the page.',
     'Avoid adding web domains on admin account' => 'It is strongly advised to create a standard user account before adding web domains to the server due to the increased privileges the admin account possesses and potential security risks involved.',
     'Avoid adding web domains on admin account' => 'It is strongly advised to create a standard user account before adding web domains to the server due to the increased privileges the admin account possesses and potential security risks involved.',
     
     
+    //SSH Key
+    'Field SSH_KEY can not be blank.' => 'Field SSH Key may not be blank',
+    'SSH KEY is invalid' => 'SSH key is invalid',
+    'SSH KEY already exists' => 'SSH key already exsits',
+    'SSH KEY Added' => 'SSH key has been added',
+    'Add SSH Key' => 'Add SSH key',
+    'SSH KEY' => 'SSH Key',    
+    'DELETE_KEY_CONFIRM' => 'Are you sure you want to delete key %s?',
+    'SSH_ID' => 'SSH Id',
+ 
     //Header 
     //Header 
     'Fm' => 'Files',
     'Fm' => 'Files',
     //PHP Cli
     //PHP Cli
@@ -836,6 +846,5 @@ $LANG['en'] = array(
     
     
     //header
     //header
     'Hestia Control Panel' => 'Hestia Control Panel',
     'Hestia Control Panel' => 'Hestia Control Panel',
-    
-    
+
 );
 );

+ 17 - 0
web/list/key/index.php

@@ -0,0 +1,17 @@
+<?php
+error_reporting(NULL);
+$TAB = 'USER';
+
+// Main include
+include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
+
+exec (HESTIA_CMD . "v-list-user-ssh-key ".escapeshellarg($user)." json", $output, $return_var);
+
+$data = json_decode(implode('', $output), true);
+
+// Render page\
+render_page($user, $TAB, 'list_key');
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+?>

+ 72 - 0
web/templates/admin/add_key.html

@@ -0,0 +1,72 @@
+	<div class="l-center edit">
+        <div class="l-sort clearfix">
+          <div class="l-unit-toolbar__buttonstrip">
+            <a class="ui-button cancel" id="btn-back" href="/list/key/"><i class="fas fa-arrow-left status-icon blue"></i> <?=__('Back')?></a>
+          </div>
+          <div class="l-unit-toolbar__buttonstrip float-right">
+            <a href="#" class="ui-button" title="<?=__('Save')?>" data-action="submit" data-id="vstobjects"><i class="fas fa-save status-icon purple"></i> <?=__('Save')?></a>
+          </div>
+        </div>
+      </div>
+
+    <div class="l-separator"></div>
+    <!-- /.l-separator -->
+	
+    <div class="l-center animated fadeIn">
+        <?php
+          $back = $_SESSION['back'];
+          if (empty($back)) {
+            $back = "location.href='/list/key/'";
+          } else {
+            $back = "location.href='".$back."'";
+          }
+        ?>
+        <form id="vstobjects" name="v_add_key" method="post">
+            <input type="hidden" name="token" value="<?=$_SESSION['token']?>" />
+            <input type="hidden" name="ok" value="Add" />
+
+            <table class="data mode-add">
+                <tr class="data-add">
+                    <td class="data-dotted">
+                        <table class="data-col1">
+                            <tr><td></td></tr>
+                        </table>
+                    </td>
+                    <td class="data-dotted">
+                        <table class="data-col2" width="600px">
+                            <tr>
+                                <td class="step-top">
+                                    <span class="page-title"><?=__('Add SSH Key')?></span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>
+                                    <?php
+                                        if (!empty($_SESSION['error_msg'])) {
+                                            echo "<span class=\"vst-error\"> <i class=\"fas fa-exclamation-circle status-icon red\"></i> ".htmlentities($_SESSION['error_msg'])."</span>";
+                                        } else {
+                                            if (!empty($_SESSION['ok_msg'])) {
+                                                echo "<span class=\"vst-ok\"> <i class=\"fas fa-check-circle status-icon green\"></i> ".$_SESSION['ok_msg']."</span>";
+                                            }
+                                        }
+                                    ?>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="vst-text step-top">
+                                    <?php print __('SSH KEY') ?>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>
+                                    <textarea class="vst-textinput" name="v_key"><?=htmlentities(trim($v_key, "'"))?></textarea>
+                                </td>
+                            </tr>
+                        </table>
+                        <table class="data-col2">
+                        </table>
+                    </td>
+                </tr>
+            </table>
+        </form>
+      </div>

+ 69 - 0
web/templates/admin/list_key.html

@@ -0,0 +1,69 @@
+    <div class="l-center">
+      <div class="l-sort clearfix noselect">
+        <div class="l-unit-toolbar__buttonstrip">
+        <?php
+        	 echo '<a href="/add/key/" id="btn-create" class="ui-button cancel" title="'.__('Add SSH Key').'"><i class="fas fa-plus-circle status-icon green"></i>'.__('Add SSH Key').'</a>';
+        ?>
+      </div>
+     </div>
+    </div>
+    
+<div class="l-separator"></div>
+
+<div class="l-center units animated fadeIn">
+  <div class="header table-header">     
+    <div class="l-unit__col l-unit__col--right">
+      <div class="clearfix l-unit__stat-col--left wide-3"><b><?php print __('SSH_ID');?></b></div>
+      <div class="clearfix l-unit__stat-col--left compact-2">
+        &nbsp;
+      </div> 
+      <div class="clearfix l-unit__stat-col--left wide-7"><b><?php print __('SSH KEY');?></b></div>
+    </div>
+  </div>
+
+ <?php
+ 	$i = 0;
+    foreach ($data as $key => $value) {
+    ++$i;
+  ?>
+    <div class="l-unit header">
+      <div class="l-unit__col l-unit__col--right">
+        <div class="clearfix l-unit__stat-col--left wide-3"><b><?=$data[$key]['ID'];?></b></div>
+            <div class="clearfix l-unit__stat-col--left text-left compact-2">
+                <div class="l-unit-toolbar__col l-unit-toolbar__col--right noselect">
+                    <div class="actions-panel clearfix">
+                        <div class="actions-panel__col actions-panel__delete shortcut-delete" key-action="js">
+                            <a id="delete_link_<?=$i?>" class="data-controls do_delete" title="<?=__('delete')?>">
+                                <i class="fas fa-trash status-icon red status-icon dim do_delete"></i>
+                                <input type="hidden" name="delete_url" value="/delete/key/?key=<?=$key?>&token=<?=$_SESSION['token']?>" />
+                                <div id="delete_dialog_<?=$i?>" class="confirmation-text-delete hidden" title="<?=__('Confirmation')?>">
+                                    <p class="confirmation"><?=__('DELETE_KEY_CONFIRM',$key)?></p>
+                                </div>
+                            </a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        <div class="clearfix l-unit__stat-col--left wide-7"><b><?=htmlspecialchars(substr($data[$key]['KEY'],0,50).'.....'.substr($data[$key]['ID'], -10,10), ENT_QUOTES);?></b></div>
+      </div>
+    </div>
+  <?}?>
+</div>
+
+<div id="vstobjects">
+    <div class="l-separator"></div>
+        <div class="l-center">
+            <div class="l-unit-ft">
+                <table class='data'></table>
+                <div class="data-count l-unit__col l-unit__col--right clearfix">
+                <?php if ( $i == 1) {
+                    echo __('1 SSH Key');
+                } else {
+                    echo __('%s SSH Keys',$i);
+                }
+        ?>
+      </div>
+    </div>
+  </div>
+</div>
+