sync password hashes of users UID>1000 between host A and B
Clash Royale CLAN TAG#URR8PPP
up vote
1
down vote
favorite
I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow
, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).
I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.
As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems
I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd
or /etc/group
. I am not a Perl programmer so I am looking for help here. Thank you in advance.
#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;
use constant TRUE => (1==1);
use constant FALSE => (1==0);
#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;
#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';
#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');
# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');
#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");
# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];
# Any line that reaches this point is valid
print TMPFILE $line;
close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;
#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;
users perl
add a comment |Â
up vote
1
down vote
favorite
I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow
, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).
I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.
As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems
I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd
or /etc/group
. I am not a Perl programmer so I am looking for help here. Thank you in advance.
#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;
use constant TRUE => (1==1);
use constant FALSE => (1==0);
#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;
#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';
#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');
# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');
#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");
# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];
# Any line that reaches this point is valid
print TMPFILE $line;
close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;
#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;
users perl
I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
â DonJ
Mar 6 at 21:48
You refer to UID<1000 and UID>1000. What about UID=1000?
â roaima
Mar 6 at 22:37
add a comment |Â
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow
, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).
I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.
As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems
I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd
or /etc/group
. I am not a Perl programmer so I am looking for help here. Thank you in advance.
#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;
use constant TRUE => (1==1);
use constant FALSE => (1==0);
#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;
#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';
#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');
# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');
#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");
# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];
# Any line that reaches this point is valid
print TMPFILE $line;
close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;
#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;
users perl
I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow
, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).
I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.
As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems
I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd
or /etc/group
. I am not a Perl programmer so I am looking for help here. Thank you in advance.
#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;
use constant TRUE => (1==1);
use constant FALSE => (1==0);
#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;
#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';
#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');
# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');
#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");
# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];
# Any line that reaches this point is valid
print TMPFILE $line;
close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;
#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;
users perl
edited Mar 6 at 21:14
ilkkachu
49.2k672136
49.2k672136
asked Mar 6 at 20:59
DonJ
768
768
I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
â DonJ
Mar 6 at 21:48
You refer to UID<1000 and UID>1000. What about UID=1000?
â roaima
Mar 6 at 22:37
add a comment |Â
I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
â DonJ
Mar 6 at 21:48
You refer to UID<1000 and UID>1000. What about UID=1000?
â roaima
Mar 6 at 22:37
I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
â DonJ
Mar 6 at 21:48
I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
â DonJ
Mar 6 at 21:48
You refer to UID<1000 and UID>1000. What about UID=1000?
â roaima
Mar 6 at 22:37
You refer to UID<1000 and UID>1000. What about UID=1000?
â roaima
Mar 6 at 22:37
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
1
down vote
Based on @roaiama's use of the join
command, this answer uses getent
to get the passwd and shadow files rather than reading them directly, and then uses chpasswd
on the remote host to change the passwords.
The password-changing code is simpler because of chpasswd
, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow
on the remote host too.
join -t : -j 1 -o 2.1..2
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'
It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd
to change the passwords as specified on stdin.
The -e
option tells chpasswd
that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.
chpasswd
will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd
's stderr can be redirected to /dev/null as i've shown above.
NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd
for non-existent users look like this:
# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed
Join your input tochpasswd
with the first column fromgetent passwd
to remove the "user not found" issue.
â roaima
Mar 10 at 13:43
yes, that would work. or exclude UIDs <= 1000 again. something likejoin -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e
as the last line.
â cas
Mar 10 at 23:38
add a comment |Â
up vote
1
down vote
This will synchronise the entries in /etc/shadow
from the local system to the remote system (here called remotehost
) for all user accounts with UID > 1000 that exist on both systems:
getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'
I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp
on the last line until you are confident that it is working as you expect.
Essentially
- Extract a list of usernames from
/etc/passwd
with UID > 1000 - Use this list to extract the corresponding lines from
/etc/shadow
- Copy across to the remote system
- Write out members of the new
shadow
list that exist in the current/etc/shadow
- Write out lines from the old
/etc/shadow
whose usernames have not already been output - Save the original and new copies of
shadow
(in known places, for emergency rescue if needed) - Install the resulting merged file as
/etc/shadow
+1. BTW, instead of usingjoin
,awk
,sort
, andcat
on the remote host, you could extract theusername:password
pairs on the local machine and pipe them tochpasswd -e
on the remote host.chpasswd
will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
â cas
Mar 7 at 4:11
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
Based on @roaiama's use of the join
command, this answer uses getent
to get the passwd and shadow files rather than reading them directly, and then uses chpasswd
on the remote host to change the passwords.
The password-changing code is simpler because of chpasswd
, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow
on the remote host too.
join -t : -j 1 -o 2.1..2
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'
It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd
to change the passwords as specified on stdin.
The -e
option tells chpasswd
that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.
chpasswd
will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd
's stderr can be redirected to /dev/null as i've shown above.
NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd
for non-existent users look like this:
# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed
Join your input tochpasswd
with the first column fromgetent passwd
to remove the "user not found" issue.
â roaima
Mar 10 at 13:43
yes, that would work. or exclude UIDs <= 1000 again. something likejoin -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e
as the last line.
â cas
Mar 10 at 23:38
add a comment |Â
up vote
1
down vote
Based on @roaiama's use of the join
command, this answer uses getent
to get the passwd and shadow files rather than reading them directly, and then uses chpasswd
on the remote host to change the passwords.
The password-changing code is simpler because of chpasswd
, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow
on the remote host too.
join -t : -j 1 -o 2.1..2
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'
It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd
to change the passwords as specified on stdin.
The -e
option tells chpasswd
that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.
chpasswd
will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd
's stderr can be redirected to /dev/null as i've shown above.
NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd
for non-existent users look like this:
# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed
Join your input tochpasswd
with the first column fromgetent passwd
to remove the "user not found" issue.
â roaima
Mar 10 at 13:43
yes, that would work. or exclude UIDs <= 1000 again. something likejoin -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e
as the last line.
â cas
Mar 10 at 23:38
add a comment |Â
up vote
1
down vote
up vote
1
down vote
Based on @roaiama's use of the join
command, this answer uses getent
to get the passwd and shadow files rather than reading them directly, and then uses chpasswd
on the remote host to change the passwords.
The password-changing code is simpler because of chpasswd
, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow
on the remote host too.
join -t : -j 1 -o 2.1..2
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'
It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd
to change the passwords as specified on stdin.
The -e
option tells chpasswd
that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.
chpasswd
will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd
's stderr can be redirected to /dev/null as i've shown above.
NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd
for non-existent users look like this:
# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed
Based on @roaiama's use of the join
command, this answer uses getent
to get the passwd and shadow files rather than reading them directly, and then uses chpasswd
on the remote host to change the passwords.
The password-changing code is simpler because of chpasswd
, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow
on the remote host too.
join -t : -j 1 -o 2.1..2
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'
It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd
to change the passwords as specified on stdin.
The -e
option tells chpasswd
that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.
chpasswd
will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd
's stderr can be redirected to /dev/null as i've shown above.
NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd
for non-existent users look like this:
# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed
edited Mar 7 at 8:46
answered Mar 7 at 5:00
cas
37.6k44392
37.6k44392
Join your input tochpasswd
with the first column fromgetent passwd
to remove the "user not found" issue.
â roaima
Mar 10 at 13:43
yes, that would work. or exclude UIDs <= 1000 again. something likejoin -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e
as the last line.
â cas
Mar 10 at 23:38
add a comment |Â
Join your input tochpasswd
with the first column fromgetent passwd
to remove the "user not found" issue.
â roaima
Mar 10 at 13:43
yes, that would work. or exclude UIDs <= 1000 again. something likejoin -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e
as the last line.
â cas
Mar 10 at 23:38
Join your input to
chpasswd
with the first column from getent passwd
to remove the "user not found" issue.â roaima
Mar 10 at 13:43
Join your input to
chpasswd
with the first column from getent passwd
to remove the "user not found" issue.â roaima
Mar 10 at 13:43
yes, that would work. or exclude UIDs <= 1000 again. something like
join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e
as the last line.â cas
Mar 10 at 23:38
yes, that would work. or exclude UIDs <= 1000 again. something like
join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e
as the last line.â cas
Mar 10 at 23:38
add a comment |Â
up vote
1
down vote
This will synchronise the entries in /etc/shadow
from the local system to the remote system (here called remotehost
) for all user accounts with UID > 1000 that exist on both systems:
getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'
I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp
on the last line until you are confident that it is working as you expect.
Essentially
- Extract a list of usernames from
/etc/passwd
with UID > 1000 - Use this list to extract the corresponding lines from
/etc/shadow
- Copy across to the remote system
- Write out members of the new
shadow
list that exist in the current/etc/shadow
- Write out lines from the old
/etc/shadow
whose usernames have not already been output - Save the original and new copies of
shadow
(in known places, for emergency rescue if needed) - Install the resulting merged file as
/etc/shadow
+1. BTW, instead of usingjoin
,awk
,sort
, andcat
on the remote host, you could extract theusername:password
pairs on the local machine and pipe them tochpasswd -e
on the remote host.chpasswd
will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
â cas
Mar 7 at 4:11
add a comment |Â
up vote
1
down vote
This will synchronise the entries in /etc/shadow
from the local system to the remote system (here called remotehost
) for all user accounts with UID > 1000 that exist on both systems:
getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'
I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp
on the last line until you are confident that it is working as you expect.
Essentially
- Extract a list of usernames from
/etc/passwd
with UID > 1000 - Use this list to extract the corresponding lines from
/etc/shadow
- Copy across to the remote system
- Write out members of the new
shadow
list that exist in the current/etc/shadow
- Write out lines from the old
/etc/shadow
whose usernames have not already been output - Save the original and new copies of
shadow
(in known places, for emergency rescue if needed) - Install the resulting merged file as
/etc/shadow
+1. BTW, instead of usingjoin
,awk
,sort
, andcat
on the remote host, you could extract theusername:password
pairs on the local machine and pipe them tochpasswd -e
on the remote host.chpasswd
will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
â cas
Mar 7 at 4:11
add a comment |Â
up vote
1
down vote
up vote
1
down vote
This will synchronise the entries in /etc/shadow
from the local system to the remote system (here called remotehost
) for all user accounts with UID > 1000 that exist on both systems:
getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'
I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp
on the last line until you are confident that it is working as you expect.
Essentially
- Extract a list of usernames from
/etc/passwd
with UID > 1000 - Use this list to extract the corresponding lines from
/etc/shadow
- Copy across to the remote system
- Write out members of the new
shadow
list that exist in the current/etc/shadow
- Write out lines from the old
/etc/shadow
whose usernames have not already been output - Save the original and new copies of
shadow
(in known places, for emergency rescue if needed) - Install the resulting merged file as
/etc/shadow
This will synchronise the entries in /etc/shadow
from the local system to the remote system (here called remotehost
) for all user accounts with UID > 1000 that exist on both systems:
getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'
I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp
on the last line until you are confident that it is working as you expect.
Essentially
- Extract a list of usernames from
/etc/passwd
with UID > 1000 - Use this list to extract the corresponding lines from
/etc/shadow
- Copy across to the remote system
- Write out members of the new
shadow
list that exist in the current/etc/shadow
- Write out lines from the old
/etc/shadow
whose usernames have not already been output - Save the original and new copies of
shadow
(in known places, for emergency rescue if needed) - Install the resulting merged file as
/etc/shadow
edited Mar 11 at 0:39
answered Mar 6 at 22:34
roaima
39.5k545107
39.5k545107
+1. BTW, instead of usingjoin
,awk
,sort
, andcat
on the remote host, you could extract theusername:password
pairs on the local machine and pipe them tochpasswd -e
on the remote host.chpasswd
will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
â cas
Mar 7 at 4:11
add a comment |Â
+1. BTW, instead of usingjoin
,awk
,sort
, andcat
on the remote host, you could extract theusername:password
pairs on the local machine and pipe them tochpasswd -e
on the remote host.chpasswd
will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
â cas
Mar 7 at 4:11
+1. BTW, instead of using
join
, awk
, sort
, and cat
on the remote host, you could extract the username:password
pairs on the local machine and pipe them to chpasswd -e
on the remote host. chpasswd
will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.â cas
Mar 7 at 4:11
+1. BTW, instead of using
join
, awk
, sort
, and cat
on the remote host, you could extract the username:password
pairs on the local machine and pipe them to chpasswd -e
on the remote host. chpasswd
will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.â cas
Mar 7 at 4:11
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f428611%2fsync-password-hashes-of-users-uid1000-between-host-a-and-b%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
â DonJ
Mar 6 at 21:48
You refer to UID<1000 and UID>1000. What about UID=1000?
â roaima
Mar 6 at 22:37