sync hashes de senha de usuários UID1000 entre o host A e B

1

Eu tenho dois sistemas CentOS, quero fazer a sincronização de hashes de senha (somente) que são armazenados em /etc/shadow , do sistema A (local) para o sistema B (remoto), mas apenas para esses usuários que têm UID > 1000 e existem em ambos os sistemas (baseando-se no nome de usuário, não no UID, que pode variar para o mesmo nome de usuário em A e B).

Não consigo usar rsync ou soluções como LDAP ou NIS. Eu também não consigo tocar em nenhuma conta com UID < 1000 nesses sistemas.

Como o UID de um usuário pode diferir no host A e B - para sincronizar o hash de senha de A para B, é importante: (1) o nome de usuário deve existir em ambos os sistemas (2) o UID do nome de usuário deve ser > 1000 (pode diferir) nos sistemas A e B

Eu encontrei um bom script perl de Renaud Bompuis que provavelmente precisaria de alguns ajustes em minhas necessidades, não deve modificar /etc/passwd ou /etc/group . Eu não sou um programador de Perl, então estou procurando ajuda aqui. Obrigado antecipadamente.

#!/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;
        }
}
    
por DonJ 06.03.2018 / 21:59

2 respostas

1

Com base no uso do @ roaiama do comando join , essa resposta usa getent para obter os arquivos passwd e shadow em vez de lê-los diretamente e, em seguida, usa chpasswd no host remoto para alterar as senhas. / p>

O código de alteração de senha é mais simples por causa de chpasswd , mas fazer uma cópia de backup das entradas de sombra antigas é um pouco mais complicado porque estamos usando getent shadow no host remoto também.

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'

Ele canaliza apenas os dois primeiros campos, o nome de usuário e a senha criptografada (o formato de saída é um par "username: password" por linha), em ssh. Depois de fazer uma cópia de backup do arquivo shadow antigo, o shell remoto executa chpasswd para alterar as senhas conforme especificado em stdin.

A opção -e informa chpasswd que as senhas já estão criptografadas. Sem essa opção, ele criptografaria novamente as senhas fornecidas.

chpasswd irá reclamar no stderr sobre qualquer nome de usuário que não exista no sistema remoto, mas ainda mudará as senhas dos nomes de usuário que existirem. O stderr de chpasswd pode ser redirecionado para / dev / null como mostrado acima.

OBSERVAÇÃO: seria melhor canalizar o stderr para um script que reduza apenas o & esperado; Inofensivo "nome de usuário não existe" erros enquanto ainda exibindo outros erros. Na minha VM de teste, os erros gerados por chpasswd para usuários inexistentes são assim:

# 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
    
por 07.03.2018 / 06:00
1

Isso sincronizará as entradas em /etc/shadow do sistema local para o sistema remoto (aqui chamado remotehost ) para todas as contas de usuário com UID > 1000 que existem em ambos os sistemas:

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[\]++" - /etc/shadow >/etc/shadow.new &&
        : cp -f /etc/shadow.new /etc/shadow
    '

Eu recomendo strongmente que você divida o comando em partes para ver o que está fazendo em cada estágio do pipeline, e que você NÃO remova o sinal de não-op da : cp na última linha até ter certeza de que está funcionando como você espera.

Essencialmente

  1. Extraia uma lista de nomes de usuário de /etc/passwd com UID > 1000
  2. Use esta lista para extrair as linhas correspondentes de /etc/shadow
  3. Copiar para o sistema remoto
  4. Escreva os membros da nova lista shadow que existem no atual /etc/shadow
  5. Escreva as linhas do antigo /etc/shadow cujos nomes de usuários ainda não foram enviados
  6. Salve as cópias originais e novas de shadow (em lugares conhecidos, para resgate de emergência, se necessário)
  7. Instale o arquivo mesclado resultante como /etc/shadow
por 06.03.2018 / 23:34

Tags