ssh-copy-id fails when run from within a remote session - linux

I have a task to copy ssh keys from one node to all others in an array. For this, I wrote a simple bash script which copies itself to other nodes and runs it there. What confuses me is the fact that ssh-copy-id works fine on the node where the script is executed manually but it fails if run remotely in an ssh session. Here’s the script:
1 #!/bin/bash
2 # keys-exchange.sh
4 nodes=( main worker-01 worker-02 worker-03 )
6 for n in $( echo "${nodes[#]}" ); do
7 [ $n != $HOSTNAME ] && ssh-copy-id $n
8 done
10 if [ -z $REMOTE ]; then
11 for n in $( echo ${nodes[#]} ); do
12 if [ $n != $HOSTNAME ]; then
13 scp $0 $USER#$n:$0 > /dev/null
14 ssh $USER#$n "REMOTE=yes HOSTNAME=$n $0 ; rm -f $0"
15 fi
16 done
17 fi
The code in rows 6-8 works fine copying the ssh key to all nodes other than itself. Then, if the REMOTE variable is not set, code in rows 11-16 copies the script to remote nodes (except the node it’s running on, row 12) and runs it there. In row 14, I set and pass the variable REMOTE to skip the code block in rows 10-17 (so the script copies itself only from the source node to others), and the variable HOSTNAME because I found it’s not set in an ssh session. The user’s name and the script path are completely the same on the source node and all destination nodes.
When running on the source node, it works properly asking for a confirmation and the remote host's password. But the script that has just run successfully on the source node fails running in the remote ssh session: ssh-copy-id fails with the following error:
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/username/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: ERROR: Host key verification failed.
At that moment, no .ssh/known_hosts file is present on a remote node so I can't do ssh-keygen -R. What am I missing and how to make it work?

ssh $USER#$n "REMOTE=yes HOSTNAME=$n $0 ; rm -f $0"
Try running ssh with the "-tt" option to request a PTY (pseudo-TTY) for the remote session:
ssh -tt $USER#$n "REMOTE=yes HOSTNAME=$n $0 ; rm -f $0"
^^^
In the case that you're describing, you're launching ssh on the remote system to connect to a third system. The ssh instance doesn't have a saved copy of the third host's host key. So you'd normally expect ssh to prompt the user whether to continue connecting to the third host. Except that it's not prompting the user--it's just refusing to connect to the third host.
When ssh is invoked with a command to run on the remote system, by default it runs that command without a TTY. In this case, the remote ssh instance sees that it's running without a TTY and runs non-interactively. When it's non-interactive, it doesn't prompt the user for things like passwords and whether to accept a host key or not.
Running the local ssh instance with "-tt" causes it to request a PTY for the remote session. So the remote ssh instance will have a TTY and it will prompt the user--you--for things like host key confirmations.

ssh-copy-id is not copying your keys to remote hosts, it's adding them to ~/.ssh/authorized_keys and when you jump to that remote host there are no keys(or are they?) so there is nothig to copy further. And if ssh-copy-id run without -i option it'll copy(add to authorized_keys) all .pub keys from your ~/.ssh dir wich could be not desired so i suggest to run it like this ssh-copy-id -i $key $host

Be sure that on the destination side, the /etc/ssh/sshd_config is configured to accept the type of key that was generated.
PubkeyAcceptedKeyTypes ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa
I generated the key using ssh-keygen -t rsa -b 4096 ... however, up above the following line did not include the ,ssh-rsa at the end, so even though the ssh-copy-id updated my destination, the sshd did not accept rsa type generated keys. Once i added ,sha-rsa, the did systemctl restart sshd it worked!

Related

How can I pass the contents of a pem file in the scp command? [duplicate]

I can connect to a server via SSH using the -i option to specify the private key:
ssh -i ~/.ssh/id_dsa user#hostname
I am creating a script that takes the id_dsa text from the database but I am not sure how I can give that string to SSH. I would need something like:
ssh --option $STRING user#hostname
Where $STRING contains the value of id_dsa. I need to know the --option if there is one.
Try the following:
echo $KEY | ssh -i /dev/stdin username#host command
The key doesn't appear from a PS statement, but because stdin is redirected it's only useful for single commands or tunnels.
There is no such switch - as it would leak sensitive information. If there were, anyone could get your private key by doing a simple ps command.
EDIT: (because of theg added details in comment)
You really should store the key in to a temporary file. Make sure you set the permissions correctly before writing to the file, if you do not use command like mktemp to create the temporary file.
Make sure you run the broker (or agent in case of OpenSSH) process and load the key using <whatever command you use to fetch it form the database> | ssh-add -
Passing cryptokey as a string is not advisable but for the sake of the question, I would say I came across the same situation where I need to pass key as a string in a script. I could use key stored in a file too but the nature of the script is to make it very flexible, containing everything in itself was a requirement. so I used to assign variable and pass it and echo it as follows :
#!/bin/bash
KEY="${ YOUR SSH KEY HERE INSIDE }"
echo "${KEY}" | ssh -q -i /dev/stdin username#IP 'hostnamectl'
exit 0
Notes:
-q suppress all warnings
By the way , the catch here in above script, since we are using echo it will print the ssh key which is again not recommended , to hide that you can use grep to grep some anything which will not be printed for sure but still stdin will have the value from the echo. So the final cmd can be modified as follows :
#!/bin/bash
KEY="${ YOUR SSH KEY HERE INSIDE }"
echo "${KEY}" | grep -qw "less" | ssh -q -i /dev/stdin username#IP 'hostnamectl'
exit 0
This worked for me.
I was looking at the same problem. Adding private key content to ssh command via stdin did not work for me. I found out that its possible to add the private key file contents to ssh-agent using the command ssh-add. This will let you ssh into the remote host without explicitly specifying the identity file. My particular usecase was that I didn't want to store the SSH key in cleartext on my machine and was dynamically getting it from a secrets vault. This answer is mostly a collection of other answers on StackOverflow.
ssh-agent is a program to hold private keys used for public key
authentication. Through use of environment variables the agent can
be located and automatically used for authentication when logging
in to other machines using ssh
Source
This is what I have done.
First start the ssh-agent.
You can start it from your terminal by simply executing ssh-agent.
OPTIONAL: If you'd like to make sure ssh-agent is running on every login, you can add something like the following to your shell config.
This is what I have added to my ~/.bashrc file.
# set SSH_AUTH_SOCK env var to a fixed value
export SSH_AUTH_SOCK=~/.ssh/ssh-agent.sock
# test whether $SSH_AUTH_SOCK is valid
ssh-add -l 2>/dev/null >/dev/null
# if not valid, then start ssh-agent using $SSH_AUTH_SOCK
[ $? -ge 2 ] && ssh-agent -a "$SSH_AUTH_SOCK" >/dev/null
Source
(This particular snippet also makes sure new ssh-agent processes are not getting created when there's one already running.)
Now you have the ssh-agent running.
Since we're interested in loading SSH key as a string, I'll assume a scenario where private key contents has already been loaded in to a variable, $SSH_PRIVATE_KEY.
I can now add this Key contents to the ssh-agent by executing the following command.
ssh-add - <<< "${SSH_PRIVATE_KEY}"
This can just be added to the bashrc file as well.
You can confirm that your key has been added by listing all keys by executing ssh-agent -l. Aaand you're done now.
Try connecting to the remote host and you don't need a private key file.
ssh username#hostname
This does come with extra security risks. These are some I could think of:
Adding the private key to the ssh-agent will let any process on the machine access the key to authenticate remote hosts without explicitly providing any information.
Since the goal is to load Private key as a string, it will either be stored in a variable or the contents embedded directly in the command. This might make the key available in command history, the shell variable and other places.

How to make scp in Shell Script ask me for password

I am making a script to securely transfer data between my two machines through scp.
But in the script, it shows an error due to no password. So how can I make my shell script to ask me for password after executing scp command?
Here is my csh script.
# ssh shahk#sj-shahk
# ls -al
echo "Source Location of Remote Server - $1"
echo "Destination Location of Local Server - $2"
echo "File/Folder to be Transferred from Remote Server - $3"
echo "File Transfer Starts"
scp -rv $1/$3 <username>#<hostname>:$2
echo "File Transfer Completed"
# exit
Now I am using the above script with ssh in following way.
ssh <username>#<hostname> "<script name> <args>"
When I use in the above manner, it does not prompt for password while executing scp command.
You can use sshpass
https://www.cyberciti.biz/faq/noninteractive-shell-script-ssh-password-provider/
I have used it once to directly scp or ssh without prompting password.
For example :
sshpass -p 'password' scp file.tar.gz root#xxx.xxx.xxx.194:/backup
As mentioned by the other answer, sshpass will do the job perfectly. In the case where you can not install new packages on your local computer, you can also use expect (installed by default on most distros) to automate your interactive session.
The basic syntax of expect is to wait for the program to display a specific string (expect mystring), which triggers a specific behaviour (send command)
The following script shows the basic structure to implement what you need :
#!/usr/bin/expect -f
# syntax to specify which command to monitor
spawn scp myfile user#remote.host:/dest_folder
# this syntax means we expect the spawned program to display "password: "
# expect can understand regex and glob as well. read the man page for more details
expect "password: "
# the \r is needed to submit the command
send "PASSWORD\r"
# expect "$ " means we wait for anything to be written.
# change if you want to handle incorrect passwords
expect "$ "
send "other_command_to_execute_on_remote\r"
expect "$ "
send "exit\r"
As a side note, you can also set up passwordless authorizations through ssh keys.
#1) create a new ssh key on your local computer
> ssh-keygen -t rsa
#2) copy your public key to your remote server
# you will need to login, but only once. Once the key is on the remote server, you'll be able to connect without password.
> ssh-copy-id -i ~/.ssh/id_rsa.pub user#ip_machine
# OR
> cat ~/.ssh/id_rsa.pub | ssh user#ip_machine "cat - >> ~/.ssh/authorized_keys"
This tutorial explains how to use the keychain tool to manage several ssh keys and users.
ssh <username>#<hostname> "<script name> <args>"
scp will only read a password from a TTY, and it doesn't have a TTY in this case. When you run ssh and specify a command to be executed on the remote system (as you're doing here), ssh by default doesn't allocate a PTY (pseudo-tty) on the remote system. Your script and all of the commands launched from it--including scp--end up running without a TTY.
You can use the ssh -t option to make it allocate a tty on the remote system:
ssh -t <username>#<hostname> "<script name> <args>"
If you get a message like "Pseudo-terminal will not be allocated because stdin is not a terminal", then add another -t:
ssh -tt <username>#<hostname> "<script name> <args>"

how to write a shell script to make an ssh connection to a machine and continue remaining script on that machine

I am writing a script where I run the script on one server through which I create an ssh connection to another machine and want to continue the following script code on the remote machine.... Can any body tell what is the way to do it?
if you want to execute a command on remote host through ssh and get the output on local host, that what i understand from you question then use.
ssh -n <hostname/IP> 'command'
where -n will Redirects stdin from /dev/null
you can store the output to a variable or file as
var =`ssh -n <hostname/IP> 'command'`
or
ssh -n <hostname/IP> 'command' >> output.txt
also if you want to send multiple command use ; for command separator.
NOTE: ssh without password should be enable from local host to remote host.
else you need to specify password explicitly.

Crontab to send dump data to windows machine

I have mysql Database in Linux machine which should be dumped by using crontab and the data directly should have to store in a remote windows system. Is this possible? if yes, how?
You will need a script similar to the one below.
It would be best if you tested the script before running it from cron.
The scp command will prompt for the user's password on the destination machine - unless the ssh key setup on the scp destination machine contains public key authorization. For this to work with cron the scp command must be able to copy without user input of a password.
Once it works then set up the crontab entry. Specify the full path of the script in the entry.
export DB_DUMP_DIR=/home/database_dump
export DB_NAME=database_name_$(date '+%Y_%m_%d').sql
mysqldump -u root -p database_name > ${DB_DUMP_DIR}/${DB_NAME}
if [ $? -eq 0 ];then
scp ${DB_DUMP_DIR}/${DB_NAME} user#windows_machine:
else
echo "Error generating database dump"
fi

write a shell script to ssh to a remote machine and execute commands

I have two questions:
There are multiple remote linux machines, and I need to write a shell script which will execute the same set of commands in each machine. (Including some sudo operations). How can this be done using shell scripting?
When ssh'ing to the remote machine, how to handle when it prompts for RSA fingerprint authentication.
The remote machines are VMs created on the run and I just have their IPs. So, I cant place a script file beforehand in those machines and execute them from my machine.
There are multiple remote linux machines, and I need to write a shell script which will execute the same set of commands in each machine. (Including some sudo operations). How can this be done using shell scripting?
You can do this with ssh, for example:
#!/bin/bash
USERNAME=someUser
HOSTS="host1 host2 host3"
SCRIPT="pwd; ls"
for HOSTNAME in ${HOSTS} ; do
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
done
When ssh'ing to the remote machine, how to handle when it prompts for RSA fingerprint authentication.
You can add the StrictHostKeyChecking=no option to ssh:
ssh -o StrictHostKeyChecking=no -l username hostname "pwd; ls"
This will disable the host key check and automatically add the host key to the list of known hosts. If you do not want to have the host added to the known hosts file, add the option -o UserKnownHostsFile=/dev/null.
Note that this disables certain security checks, for example protection against man-in-the-middle attack. It should therefore not be applied in a security sensitive environment.
Install sshpass using, apt-get install sshpass then edit the script and put your linux machines IPs, usernames and password in respective order. After that run that script. Thats it ! This script will install VLC in all systems.
#!/bin/bash
SCRIPT="cd Desktop; pwd; echo -e 'PASSWORD' | sudo -S apt-get install vlc"
HOSTS=("192.168.1.121" "192.168.1.122" "192.168.1.123")
USERNAMES=("username1" "username2" "username3")
PASSWORDS=("password1" "password2" "password3")
for i in ${!HOSTS[*]} ; do
echo ${HOSTS[i]}
SCR=${SCRIPT/PASSWORD/${PASSWORDS[i]}}
sshpass -p ${PASSWORDS[i]} ssh -l ${USERNAMES[i]} ${HOSTS[i]} "${SCR}"
done
This work for me.
Syntax : ssh -i pemfile.pem user_name#ip_address 'command_1 ; command 2; command 3'
#! /bin/bash
echo "########### connecting to server and run commands in sequence ###########"
ssh -i ~/.ssh/ec2_instance.pem ubuntu#ip_address 'touch a.txt; touch b.txt; sudo systemctl status tomcat.service'
There are a number of ways to handle this.
My favorite way is to install http://pamsshagentauth.sourceforge.net/ on the remote systems and also your own public key. (Figure out a way to get these installed on the VM, somehow you got an entire Unix system installed, what's a couple more files?)
With your ssh agent forwarded, you can now log in to every system without a password.
And even better, that pam module will authenticate for sudo with your ssh key pair so you can run with root (or any other user's) rights as needed.
You don't need to worry about the host key interaction. If the input is not a terminal then ssh will just limit your ability to forward agents and authenticate with passwords.
You should also look into packages like Capistrano. Definitely look around that site; it has an introduction to remote scripting.
Individual script lines might look something like this:
ssh remote-system-name command arguments ... # so, for exmaple,
ssh target.mycorp.net sudo puppet apply
The accepted answer sshes to machines sequentially. In case you want to ssh to multiple machines and run some long-running commands like scp concurrently on them, run the ssh command as a background process.
#!/bin/bash
username="user"
servers=("srv-001" "srv-002" "srv-002" "srv-003");
script="pwd;"
for s in "${servers[#]}"; do
echo "sshing ${username}#${s} to run ${script}"
(ssh ${username}#${s} ${script})& # Run in background
done
wait # If removed, you can run some other script here
If you are able to write Perl code, then you should consider using Net::OpenSSH::Parallel.
You would be able to describe the actions that have to be run in every host in a declarative manner and the module will take care of all the scary details. Running commands through sudo is also supported.
For this kind of tasks, I repeatedly use Ansible which allows to duplicate coherently bash scripts in several containets or VM. Ansible (more precisely Red Hat) now has an additional web interface AWX which is the open-source edition of their commercial Tower.
Ansible: https://www.ansible.com/
AWX:https://github.com/ansible/awx
Ansible Tower: commercial product, you will probably fist explore the free open-source AWX, rather than the 15days free-trail of Tower
There is are multiple ways to execute the commands or script in the multiple remote Linux machines.
One simple & easiest way is via pssh (parallel ssh program)
pssh: is a program for executing ssh in parallel on a number of hosts. It provides features such as sending input to all of the processes, passing a password to ssh, saving the output to files, and timing out.
Example & Usage:
Connect to host1 and host2, and print "hello, world" from each:
pssh -i -H "host1 host2" echo "hello, world"
Run commands via a script on multiple servers:
pssh -h hosts.txt -P -I<./commands.sh
Usage & run a command without checking or saving host keys:
pssh -h hostname_ip.txt -x '-q -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -o PubkeyAuthentication=yes' -i 'uptime; hostname -f'
If the file hosts.txt has a large number of entries, say 100, then the parallelism option may also be set to 100 to ensure that the commands are run concurrently:
pssh -i -h hosts.txt -p 100 -t 0 sleep 10000
Options:
-I: Read input and sends to each ssh process.
-P: Tells pssh to display output as it arrives.
-h: Reads the host's file.
-H : [user#]host[:port] for single-host.
-i: Display standard output and standard error as each host completes
-x args: Passes extra SSH command-line arguments
-o option: Can be used to give options in the format used in the configuration file.(/etc/ssh/ssh_config) (~/.ssh/config)
-p parallelism: Use the given number as the maximum number of concurrent connections
-q Quiet mode: Causes most warning and diagnostic messages to be suppressed.
-t: Make connections time out after the given number of seconds. 0 means pssh will not timeout any connections
When ssh'ing to the remote machine, how to handle when it prompts for
RSA fingerprint authentication.
Disable the StrictHostKeyChecking to handle the RSA authentication prompt.
-o StrictHostKeyChecking=no
Source: man pssh
This worked for me. I made a function. Put this in your shell script:
sshcmd(){
ssh $1#$2 $3
}
sshcmd USER HOST COMMAND
If you have multiple machines that you want to do the same command on you would repeat that line with a semi colon. For example, if you have two machines you would do this:
sshcmd USER HOST COMMAND ; sshcmd USER HOST COMMAND
Replace USER with the user of the computer. Replace HOST with the name of the computer. Replace COMMAND with the command you want to do on the computer.
Hope this helps!
You can follow this approach :
Connect to remote machine using Expect Script. If your machine doesn't support expect you can download the same. Writing Expect script is very easy (google to get help on this)
Put all the action which needs to be performed on remote server in a shell script.
Invoke remote shell script from expect script once login is successful.

Resources