| #!/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 |