mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +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
|