mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-11-04 00:02:37 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			509 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			509 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
#!/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 ! response=$(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 "$response" ]
 | 
						|
    then
 | 
						|
        outputLog "Did not receive any public keys from userdata server"
 | 
						|
        return 1
 | 
						|
    fi
 | 
						|
    outputLog "Successfully get public key from userdata server"
 | 
						|
    echo "$response"
 | 
						|
    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 "/ cloudstack@apache.org$/d" "$authorizedKeysFile"
 | 
						|
    echo "$publicKey cloudstack@apache.org" >> "$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
 |