When managing multiple servers with identical credentials, manually typing passwords for each ssh-copy-id
operation becomes tedious. The standard tool doesn't support command-line password input for security reasons, breaking automation workflows.
The expect
utility solves this by programmatically interacting with interactive prompts. Here's a basic implementation:
#!/usr/bin/expect -f
set username "your_user"
set password "your_password"
set hosts {
"server1.example.com"
"server2.example.com"
"192.168.1.100"
}
foreach host $hosts {
spawn ssh-copy-id $username@$host
expect {
"password:" {
send "$password\r"
exp_continue
}
"yes/no" {
send "yes\r"
exp_continue
}
eof
}
}
For production use, we should add error handling and logging:
#!/usr/bin/expect -f
set timeout 30
log_file ssh_copy_id.log
proc copy_key {user pass host} {
spawn ssh-copy-id $user@$host
expect {
timeout {
puts "Timeout connecting to $host"
return 1
}
"password:" {
send "$pass\r"
exp_continue
}
"yes/no" {
send "yes\r"
exp_continue
}
eof
}
return [wait]
}
array set servers {
web1 "192.168.1.101"
web2 "192.168.1.102"
db "db.internal"
}
foreach {name host} [array get servers] {
set result [copy_key "admin" "s3cur3P@ss" $host]
if {[lindex $result 3] == 0} {
puts "Successfully copied key to $name ($host)"
} else {
puts "Failed to copy key to $name ($host)"
}
}
For systems without expect, we can use SSH_ASKPASS:
#!/bin/bash
USER="admin"
PASS="s3cur3P@ss"
HOSTS=("server1" "server2" "server3")
# Create simple password provider
export SSH_ASKPASS=$(mktemp)
cat > $SSH_ASKPASS <
While convenient, these methods expose passwords:
- Passwords appear in process listings
- Scripts containing credentials need strict permissions
- Consider using SSH certificates or temporary credentials
- For production, use configuration management tools like Ansible
For larger deployments, Ansible provides a cleaner solution:
---
- hosts: all
become: false
vars:
ansible_user: admin
ansible_password: "s3cur3P@ss"
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
tasks:
- name: Copy SSH key
ansible.builtin.authorized_key:
user: "{{ ansible_user }}"
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
When managing multiple servers with identical credentials, manually typing passwords for ssh-copy-id
becomes tedious. The standard command:
ssh-copy-id user@server1
ssh-copy-id user@server2
...
requires interactive password entry for each server, defeating automation purposes.
The expect
utility can automate interactive sessions. Create an expect script (auto_ssh_copy_id.exp
):
#!/usr/bin/expect -f
set timeout 20
set username [lindex $argv 0]
set password [lindex $argv 1]
set hostname [lindex $argv 2]
spawn ssh-copy-id $username@$hostname
expect {
"*yes/no*" {
send "yes\r"
exp_continue
}
"*password*" {
send "$password\r"
}
}
expect eof
Execute it with:
./auto_ssh_copy_id.exp USER PASSWORD HOST
For simpler cases, sshpass
provides direct password passing:
sshpass -p "your_password" ssh-copy-id user@host
Combine with a server list:
for server in $(cat server_list.txt); do
sshpass -p "your_password" ssh-copy-id user@$server
done
For infrastructure-as-code environments, use Ansible:
- name: Deploy SSH keys
hosts: all
gather_facts: no
vars:
ansible_user: "your_user"
ansible_password: "your_pass"
ansible_become_pass: "your_pass"
tasks:
- name: Copy SSH key
ansible.builtin.shell: |
grep "{{ ansible_user }}" ~/.ssh/authorized_keys ||
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
While convenient, these methods expose passwords:
- Use temporary credential files with restricted permissions
- Clean command history afterward
- Consider certificate-based authentication for production
For large server fleets, speed up deployment:
cat servers.txt | parallel -j 10 \
'sshpass -p "PASSWORD" ssh-copy-id -o StrictHostKeyChecking=no user@{}'
This processes 10 servers simultaneously while suppressing host key prompts.