This is an automated email from the ASF dual-hosted git repository.

weizhou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new daa183d  Universal sshkey and password manager script (#4890)
daa183d is described below

commit daa183d9fd62c726bfcc02d0e72c4749dce6ab17
Author: aleskxyz <[email protected]>
AuthorDate: Tue Sep 21 11:30:58 2021 +0430

    Universal sshkey and password manager script (#4890)
    
    * Add sshkey and password manager script that works with userdata server 
and configdrive
    
    * fix duplicated sshkey insertion
    
    * Add Alpine support
    
    * Force curl to fail on server errors
    
    * Keep user defined ssh-keys only
---
 ...t-guest-sshkey-password-userdata-configdrive.in | 508 +++++++++++++++++++++
 1 file changed, 508 insertions(+)

diff --git 
a/setup/bindir/cloud-set-guest-sshkey-password-userdata-configdrive.in 
b/setup/bindir/cloud-set-guest-sshkey-password-userdata-configdrive.in
new file mode 100644
index 0000000..5ac6795
--- /dev/null
+++ b/setup/bindir/cloud-set-guest-sshkey-password-userdata-configdrive.in
@@ -0,0 +1,508 @@
+#!/usr/bin/env bash
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+username=root
+userDataServerPort=8080
+configDriveLabel=config-2
+
+function findPrimaryNetwork(){
+    outputLog "Detecting primary network"
+    if command -v ip &> /dev/null
+    then
+        primaryNet=$(ip -o -4 route show to default | awk '{print $5}')
+    elif command -v netstat &> /dev/null
+    then
+        primaryNet=$(netstat -r4 | grep default | awk '{print $(NF)}')
+    elif command -v route &> /dev/null
+    then
+        primaryNet=$(route -4 2> /dev/null | grep default | awk '{print 
$(NF)}')
+        if [ -z "$primaryNet" ]
+        then
+            primaryNet=$(route get default 2> /dev/null | grep interface | tr 
-d ' ' | awk '{split($0,a,":"); print a[2]}')
+        fi
+    fi
+    if [ -z "$primaryNet" ]
+    then
+        outputLog "Could not find primary network"
+        return 1
+    fi
+    echo "$primaryNet"
+    return 0
+}
+
+function findUserDataServer(){
+    primaryNet=$1
+    outputLog "Trying to find userdata server"
+    if [ -z "$primaryNet" ]
+    then
+        outputLog "Unable to determine the userdata server, falling back to 
data-server"
+        echo "data-server"
+        return 0
+    fi
+
+    if command -v netplan &> /dev/null
+    then
+        outputLog "Operating System is using netplan"
+
+        userDataServer=$(netplan ip leases "$primaryNet" | grep SERVER_ADDRESS 
| awk '{split($0,a,"="); print a[2]}')
+
+        if [ -n "$userDataServer" ]
+        then
+            outputLog "Found userdata server IP $userDataServer in netplan 
config"
+            echo "$userDataServer"
+            return 0
+        fi
+    fi
+
+    if command -v nmcli &> /dev/null
+    then
+        outputLog "Operating System is using NetworkManager"
+
+        userDataServer=$(nmcli -t connection show "$(nmcli -t -f UUID,DEVICE 
connection | grep "$primaryNet" | awk '{split($0,a,":"); print a[1]}')" | grep 
next_server | tr -d ' ' |awk '{split($0,a,"="); print a[2]}')
+
+        if [ -n "$userDataServer" ]
+        then
+            outputLog "Found userdata server IP $userDataServer in 
NetworkManager config"
+            echo "$userDataServer"
+            return 0
+        fi
+    fi
+
+    if command -v wicked &> /dev/null
+    then
+        outputLog "Operating System is using wicked"
+
+        userDataServer=$(grep SERVERID /run/wicked/leaseinfo."$primaryNet"* | 
tr -d "'" | awk '{split($0,a,"="); print a[2]}')
+
+        if [ -n "$userDataServer" ]
+        then
+            outputLog "Found userdata server IP $userDataServer in wicked 
config"
+            echo "$userDataServer"
+            return 0
+        fi
+    fi
+
+    if command -v udhcpc &> /dev/null
+    then
+        outputLog "Operating System is using udhcpc"
+
+        userDataServer=$(< /run/dhcp-server-ip."$primaryNet")
+
+        if [ -n "$userDataServer" ]
+        then
+            outputLog "Found userdata server IP $userDataServer in udhcpc"
+            echo "$userDataServer"
+            return 0
+        fi
+    fi
+
+    outputLog "Searching for DHCP server in lease files"
+
+    primaryLease=$(
+        dhcpFolders="/var/lib/dhclient/* /var/lib/dhcp3/* /var/lib/dhcp/* 
/var/lib/NetworkManager/* /var/db/dhclient*"
+        for files in $dhcpFolders
+        do
+            if [ -e "$files" ]
+            then
+                < "$files" tr -d '\n' | sed 's/  //g ; s/lease {//g ; 
s/}/\n/g' | grep 'option routers'
+            fi
+        done
+    )
+
+    serverList=$(
+        IFS=$'\n'
+        for line in $(echo -e "$primaryLease")
+        do
+            splitLine=$(echo "$line" | sed -e 's/;/\n/g')
+            if date -j &> /dev/null
+            then
+                timestamp=$(date -j -f "%Y/%m/%d %H:%M:%S" "$(echo 
"$splitLine" | grep 'expire' | sed -r 's/.*expire [0-9]+ (.*)/\1/')" +"%s")
+            else
+                timestamp=$(date -d "$(echo "$splitLine" | grep 'expire' | sed 
-e 's/.*expire [0-9]\+ \(.*\)/\1/')" +"%s")
+            fi
+            interface=$(echo "$splitLine" | grep 'interface' | sed -e 
's/.*interface "\(.*\)"/\1/')
+            server=$(echo "$splitLine" | grep 'dhcp-server-identifier' | sed 
-e 's/.*dhcp-server-identifier \(.*\)/\1/')
+            echo "$timestamp","$interface","$server"
+        done
+    )
+
+    userDataServer=$(echo "$serverList" | grep "$primaryNet" | sort -n | tail 
-1 | awk '{split($0,a,","); print a[3]}')
+
+    if [ -n "$userDataServer" ]
+    then
+        outputLog "Userdata server found: $userDataServer"
+        echo "$userDataServer"
+        return 0
+    fi
+
+    outputLog "Unable to determine the userdata server, falling back to 
data-server"
+    echo "data-server"
+    return 0
+}
+
+function getPasswordFromUserDataServer(){
+    userDataServer=$1
+    userDataServerPort=$2
+    outputLog "Sending request to userdata server at $userDataServer to get 
the password"
+    if ! response=$(curl --fail --silent --connect-timeout 20 --retry 3 
--header "DomU_Request: send_my_password" 
http://"$userDataServer":"$userDataServerPort";)
+    then
+        outputLog "Failed to send request to userdata server at 
$userDataServer"
+        return 4
+    fi
+    outputLog "Got response from userdata server at $userDataServer"
+    response=$(echo "$response" | tr -d '\r')
+    case $response in
+        "")
+            outputLog "Userdata server at $userDataServer did not have any 
password for the VM"
+            return 2
+        ;;
+        "bad_request")
+            outputLog "VM sent an invalid request to userdata server at 
$userDataServer"
+            return 3
+        ;;
+        "saved_password")
+            outputLog "VM has already saved a password from the userdata 
server at $userDataServer"
+            return 1
+        ;;
+        *)
+            outputLog "VM got a valid password from server at $userDataServer"
+            echo "$response"
+            return 0
+    esac
+}
+
+function findHomeDirectory(){
+    username=$1
+    getent passwd "$username"|awk -F ":" '{print $6}'
+}
+
+function setPassword(){
+    username=$1
+    homeDir=$2
+    password=$3
+    if command -v md5sum &> /dev/null
+    then
+        newMd5=$(echo "$password" | md5sum | awk '{print $1}')
+    elif command -v md5 &> /dev/null
+    then
+        newMd5=$(echo "$password" | md5)
+    else
+        newMd5='N/A'
+    fi
+    if [ $newMd5 != 'N/A' ]
+    then
+        if [ -f "$homeDir"/.password.md5 ]
+        then
+            oldMd5=$(cat "$homeDir"/.password.md5)
+        fi
+        if [ "$newMd5" ==  "$oldMd5" ]
+        then
+            outputLog  "There is no update of VM password"
+            return 0
+        fi
+    else
+        outputLog "Cannot determine change of password"
+    fi
+    outputLog "Changing password for user $username"
+    if command -v chpasswd &> /dev/null
+    then
+        echo "$username":"$password" | chpasswd
+    elif command -v usermod &> /dev/null && command -v mkpasswd &> /dev/null
+    then
+        usermod -p "$(mkpasswd -m SHA-512 "$password")" "$username"
+    elif command -v pw &> /dev/null
+    then
+        echo "$password" | pw mod user "$username" -h 0
+    else
+        outputLog "Failed to change password for user $username"
+        return 1
+    fi
+    outputLog "Successfully changed password for user $username"
+    if [ $newMd5 != 'N/A' ]
+    then
+        echo "$newMd5" > "$homeDir"/.password.md5
+        chmod 600 "$homeDir"/.password.md5
+        chown "$username": "$homeDir"/.password.md5
+    fi
+    return 0
+}
+
+function sendAckToUserDataServer(){
+    userDataServer=$1
+    userDataServerPort=$2
+    outputLog "Sending acknowledgment to userdata server at $userDataServer"
+    if ! curl --fail --silent --connect-timeout 20 --retry 3 --header 
"DomU_Request: saved_password" "$userDataServer":"$userDataServerPort" &> 
/dev/null
+    then
+        outputLog "Failed to sent acknowledgment to userdata server at 
$userDataServer"
+        return 1
+    fi
+    outputLog "Successfully sent acknowledgment to userdata server at 
$userDataServer"
+    return 0
+}
+
+function getPublicKeyFromUserDataServer(){
+    userDataServer=$1
+    outputLog "Sending request to userdata server at $userDataServer to get 
public key"
+    if ! reponse=$(curl --fail --silent --connect-timeout 20 --retry 3 
http://"$userDataServer"/latest/public-keys)
+    then
+        outputLog "Failed to get public key from userdata server"
+        return 2
+    fi
+    outputLog "Got response from userdata server at $userDataServer"
+    if [ -z "$reponse" ]
+    then
+        outputLog "Did not receive any public keys from userdata server"
+        return 1
+    fi
+    outputLog "Successfully get public key from userdata server"
+    echo "$reponse"
+    return 0
+}
+
+function setPublicKey(){
+    username=$1
+    homeDir=$2
+    publicKey=$3
+    outputLog "Applying public key for $username"
+    sshDir=$homeDir/.ssh
+    authorizedKeysFile=$sshDir/authorized_keys
+
+    if [ ! -d "$sshDir" ]
+    then
+        outputLog ".ssh directory for $username not found, creating .ssh 
directory"
+        mkdir "$sshDir"
+    fi
+
+    if [ ! -f "$authorizedKeysFile" ]
+    then
+        outputLog "authorized_keys file for $username not found, creating 
authorized_keys file"
+        touch "$authorizedKeysFile"
+    fi
+    if grep "$(echo "$publicKey" | awk '{print $2}')" "$authorizedKeysFile" > 
/dev/null
+    then
+        outputLog "No need to update authorized_keys file"
+        return 0
+    fi
+    outputLog "Writing public key in authorized_keys file"
+    sed -i "/ [email protected]$/d" "$authorizedKeysFile"
+    echo "$publicKey [email protected]" >> "$authorizedKeysFile"
+    chmod 600 "$authorizedKeysFile"
+    chmod 700 "$sshDir"
+    chown -R "$username": "$sshDir"
+    which restorecon &> /dev/null && restorecon -R -v "$sshDir"
+    return 0
+}
+
+function findConfigDrive(){
+    configDriveLabel=$1
+    outputLog "Searching for ConfigDrive"
+
+    if [ -e /dev/disk/by-label/"$configDriveLabel" ]
+    then
+        outputLog "ConfigDrive found at /dev/disk/by-label/$configDriveLabel"
+        echo "/dev/disk/by-label/$configDriveLabel"
+        return 0
+    fi
+
+    if [ -e /dev/iso9660/"$configDriveLabel" ]
+    then
+        outputLog "ConfigDrive found at /dev/iso9660/$configDriveLabel"
+        echo "/dev/iso9660/$configDriveLabel"
+        return 0
+    fi
+
+    blockDevice=$(blkid -t LABEL="$configDriveLabel" /dev/hd? /dev/sd? 
/dev/xvd? /dev/vd? /dev/sr? -o device 2> /dev/null)
+    if [ -n "$blockDevice" ]
+    then
+        outputLog "ConfigDrive found at $blockDevice"
+        echo "$blockDevice"
+        return 0
+    fi
+    outputLog "ConfigDrive not found"
+    return 1
+}
+
+function mountConfigDrive(){
+    disk=$1
+    outputLog "Mounting ConfigDrive"
+    mountDir=$(mktemp -d)
+    if [ ! -e "$mountDir" ]
+    then
+        mkdir "$mountDir"
+        chmod 700 "$mountDir"
+    fi
+
+    mounted=0
+    if [ $mounted == 0 ] && mount -r "$disk" "$mountDir" &> /dev/null
+    then
+        mounted=1
+    fi
+    if [ $mounted == 0 ] && mount -r -t cd9660 "$disk" "$mountDir" &> /dev/null
+    then
+        mounted=1
+    fi
+    if [ $mounted == 0 ] && mount -r -t iso9660 "$disk" "$mountDir" &> 
/dev/null
+    then
+        mounted=1
+    fi
+
+    if [ $mounted == 1 ]
+    then
+        outputLog "$disk successfully mounted on $mountDir"
+        echo "$mountDir"
+        return 0
+    fi
+
+    outputLog "Failed mounting $disk on $mountDir"
+    rm -rf "$mountDir"
+    return 1
+}
+
+function unmountConfigDrive(){
+    mountDir=$1
+    outputLog "Unmounting ConfigDrive"
+    if ! umount "$mountDir"
+    then
+        outputLog "Failed unmounting $mountDir"
+        return 1
+    fi
+    rm -rf "$mountDir"
+    outputLog "Successfully unmount $mountDir"
+    return 0
+}
+
+function getPasswordFromConfigDrive(){
+    mountDir=$1
+    passwordFile=$mountDir/cloudstack/password/vm_password.txt
+    if [ ! -f "$passwordFile" ]
+    then
+        outputLog "Password file not found in ConfigDrivee"
+        return 3
+    fi
+    outputLog "Password file found in ConfigDrive"
+    content=$(< "$passwordFile" tr -d '\r')
+
+    case $content in
+
+        "")
+            outputLog "ConfigDrive did not have any password for the VM"
+            return 2
+        ;;
+
+        "saved_password")
+            outputLog "VM has already saved a password"
+            return 1
+        ;;
+
+        *)
+            outputLog "VM got a valid password"
+            echo "$content"
+            return 0
+    esac
+}
+
+function getPublicKeyFromConfigDrive() {
+    mountDir=$1
+    publicKeyFile=$mountDir/cloudstack/metadata/public-keys.txt
+
+    if [ ! -f "$publicKeyFile" ]
+    then
+        outputLog "Public key file not found in ConfigDrive"
+        return 2
+    fi
+    content=$(< "$publicKeyFile" tr -d '\r')
+
+    if [ -z "$content" ]
+    then
+        outputLog "Did not receive any public keys"
+        return 1
+    fi
+    echo "$content"
+    outputLog "Public key successfully received."
+    return 0
+}
+
+function outputLog() {
+    stderr=1
+    logger=1
+    message=$1
+    if [ $stderr == 1 ]
+    then
+        echo "Cloud Password Manager: $message" 1>&2
+    fi
+    if [ $logger == 1 ]
+    then
+        logger -t "Cloud Password Manager" "$message"
+    fi
+}
+
+publicKeyReceived=0
+passwordReceived=0
+dataSource=''
+
+if disk=$(findConfigDrive "$configDriveLabel")
+then
+    if mountDir=$(mountConfigDrive "$disk")
+    then
+        dataSource='ConfigDrive'
+        if publicKey=$(getPublicKeyFromConfigDrive "$mountDir")
+        then
+            publicKeyReceived=1
+        fi
+        if password=$(getPasswordFromConfigDrive "$mountDir")
+        then
+            passwordReceived=1
+        fi
+        unmountConfigDrive "$mountDir"
+    fi
+fi
+if [ $publicKeyReceived == 0 ] || [ $passwordReceived == 0 ]
+then
+    primaryNet=$(findPrimaryNetwork)
+    userDataServer=$(findUserDataServer "$primaryNet")
+    if [ $publicKeyReceived == 0 ]
+    then
+        if publicKey=$(getPublicKeyFromUserDataServer "$userDataServer")
+        then
+            dataSource='UserDataServer'
+            publicKeyReceived=1
+        fi
+    fi
+    if [ $passwordReceived == 0 ]
+    then
+        if password=$(getPasswordFromUserDataServer "$userDataServer" 
"$userDataServerPort")
+        then
+            dataSource='UserDataServer'
+            passwordReceived=1
+        fi
+    fi
+fi
+homeDir=$(findHomeDirectory "$username")
+if [ $passwordReceived == 1 ]
+then
+    setPassword "$username" "$homeDir" "$password"
+    if [ $dataSource == 'UserDataServer' ]
+    then
+        sendAckToUserDataServer "$userDataServer" "$userDataServerPort"
+    fi
+fi
+if [ $publicKeyReceived == 1 ]
+then
+    setPublicKey "$username" "$homeDir" "$publicKey"
+fi

Reply via email to