호스트 A와 B 사이에서 사용자 UID>1000에 대한 비밀번호 해시를 동기화합니다.

호스트 A와 B 사이에서 사용자 UID>1000에 대한 비밀번호 해시를 동기화합니다.

두 개의 CentOS 시스템이 있고 /etc/shadow시스템 A(로컬)에서 시스템 B(원격)로 저장된 비밀번호 해시만 동기화하려고 합니다. 단, 두 시스템 모두에 존재하는 UID > 1000인 사용자에 대해서만(UID가 아닌 사용자 이름 기반) A와 B의 동일한 사용자 이름에 대해 UID가 다를 수 있습니다.

rsync, LDAP 또는 NIS와 같은 솔루션을 사용할 수 없습니다. 또한 이 시스템에서는 UID가 1000 미만인 계정을 건드릴 수 없습니다.

호스트 A와 B의 사용자 UID가 다를 수 있으므로 A에서 B로 비밀번호 해시를 동기화하려면 다음이 중요합니다. (1) 사용자 이름이 두 시스템 모두에 존재해야 합니다. (2) 사용자 이름의 UID는 1000보다 커야 합니다( 다를 수 있음) 시스템 A와 시스템 B

좋은 Perl 스크립트를 찾았어요르노 Bumpuis이를 위해서는 내 요구 사항에 대한 일부 조정이 필요할 수 있으며 수정해서는 안 /etc/passwd됩니다 /etc/group. 저는 Perl 프로그래머가 아니기 때문에 여기에 도움을 요청합니다. 미리 감사드립니다.

#!/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 = $ENV{TMPDIR} || $ENV{TMP} || $ENV{TEMP} || '/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;
        }
}

답변1

join@roaiama 의 명령 사용을 기반으로 이 답변은 getentpasswd 및 섀도우 파일을 직접 읽은 다음 chpasswd원격 호스트에서 사용하여 비밀번호를 변경하는 대신 가져오는 것입니다.

비밀번호 변경 코드는 더 간단 하지만 이전 섀도우 항목의 백업 복사본을 만드는 것은 원격 호스트에서도 사용하기 chpasswd때문에 약간 복잡합니다 .getent shadow

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'

처음 두 필드인 사용자 이름과 암호화된 비밀번호(출력 형식은 한 줄에 하나의 사용자 이름:비밀번호 쌍임)만 ssh로 전송합니다. 이전 섀도우 파일의 백업 복사본을 만든 후 chpasswd표준 입력에 지정된 비밀번호를 변경하기 위해 원격 쉘이 실행됩니다 .

-e옵션은 chpasswd비밀번호가 암호화되었음을 나타냅니다. 이 옵션이 없으면 제공된 비밀번호를 다시 암호화합니다.

chpasswd원격 시스템에 존재하지 않는 사용자 이름은 stderr에서 오류가 발생하지만 존재하는 사용자 이름에 대한 비밀번호는 계속 변경됩니다. chpasswd위에 표시된 대로 stderr을 /dev/null로 리디렉션할 수 있습니다.

참고: 다른 오류는 계속 표시하면서 예상되고 무해한 "사용자 이름이 존재하지 않습니다" 오류만 제거하는 스크립트에 stderr을 파이프하는 것이 좋습니다. 내 테스트 VM에서 존재하지 않는 사용자의 오류 출력은 chpasswd다음과 같습니다.

# printf '%s\n' "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

답변2

/etc/shadow그러면 로컬 시스템에서 원격 시스템(여기서 호출됨)까지 두 시스템 모두에 존재하는 UID > 1000을 갖는 모든 사용자 계정에 대한 항목이 동기화됩니다 .remotehost

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
    '

: cp파이프라인의 각 단계에서 명령이 수행하는 작업을 확인하기 위해 명령을 여러 부분으로 나누고 명령이 예상대로 작동한다고 확신할 때까지 마지막 줄에서 무작동 콜론을 제거하지 않는 것이 좋습니다 .

본질적으로

  1. /etc/passwdUID > 1000에서 사용자 이름 목록 추출
  2. 이 목록을 사용하여 해당 행을 추출하십시오./etc/shadow
  3. 원격 시스템에 복사
  4. shadow현재 목록에 존재하는 새 목록의 구성원을 작성합니다./etc/shadow
  5. /etc/shadow사용자 이름이 아직 출력되지 않은 이전 줄을 작성합니다 .
  6. 원본과 새 사본을 보관하십시오 shadow(필요한 경우 긴급 구조를 위해 알려진 장소에 보관).
  7. 결과 병합 파일을 다음과 같이 설치하십시오./etc/shadow

관련 정보