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