Skip to main content

Script de validação de segurança de usuário | Tarefas rotineiras | Linux

  • Script de validação de segurança de usuário | Tarefas rotineiras | Linux


#!/usr/bin/env bash
set -u

USER_TO_CHECK="${1:-}"

if [[ -z "$USER_TO_CHECK" ]]; then
echo "Uso: $0 <usuario>"
exit 1
fi

RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'

WARN_COUNT=0
FAIL_COUNT=0
INFO_COUNT=0

print_header() {
echo -e "\n${BLUE}============================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================================${NC}"
}

ok() {
echo -e "${GREEN}[OK]${NC} $1"
}

warn() {
echo -e "${YELLOW}[ALERTA]${NC} $1"
WARN_COUNT=$((WARN_COUNT + 1))
}

fail() {
echo -e "${RED}[FALHA]${NC} $1"
FAIL_COUNT=$((FAIL_COUNT + 1))
}

info() {
echo -e "[INFO] $1"
INFO_COUNT=$((INFO_COUNT + 1))
}

file_mode_octal() {
stat -c "%a" "$1" 2>/dev/null
}

file_owner() {
stat -c "%U:%G" "$1" 2>/dev/null
}

line_in_sshd_config() {
local key="$1"
local file
for file in /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf; do
[[ -e "$file" ]] || continue
grep -Ei "^[[:space:]]*${key}[[:space:]]+" "$file" 2>/dev/null | tail -n 1
done
}

user_exists() {
getent passwd "$USER_TO_CHECK" >/dev/null 2>&1
}

is_user_in_group() {
id -nG "$USER_TO_CHECK" 2>/dev/null | tr ' ' '\n' | grep -Fxq "$1"
}

check_home_permissions() {
local home="$1"

if [[ ! -d "$home" ]]; then
fail "Home directory não existe: $home"
return
fi

local mode owner
mode="$(file_mode_octal "$home")"
owner="$(file_owner "$home")"

info "Home: $home | owner=$owner | perm=$mode"

if [[ "$owner" != "$USER_TO_CHECK:"* ]]; then
fail "Home não pertence ao usuário."
fi

local other_perm=$(( mode % 10 ))
local group_perm=$(( (mode / 10) % 10 ))

if (( other_perm > 0 )); then
warn "Home com permissão para 'others' ($mode). Idealmente 700 ou 750."
else
ok "Home sem acesso para 'others'."
fi

if (( group_perm > 5 )); then
warn "Home com permissão de grupo ampla ($mode)."
fi
}

check_ssh_dir() {
local home="$1"
local ssh_dir="${home}/.ssh"
local auth_keys="${ssh_dir}/authorized_keys"

if [[ ! -d "$ssh_dir" ]]; then
warn "Diretório .ssh não existe. Se o usuário usa SSH, isso pode indicar uso só por senha."
return
fi

local dir_mode dir_owner
dir_mode="$(file_mode_octal "$ssh_dir")"
dir_owner="$(file_owner "$ssh_dir")"

info ".ssh owner=$dir_owner | perm=$dir_mode"

if [[ "$dir_owner" != "$USER_TO_CHECK:"* ]]; then
fail ".ssh não pertence ao usuário."
fi

if [[ "$dir_mode" != "700" ]]; then
warn ".ssh com permissão diferente de 700."
else
ok ".ssh com permissão 700."
fi

if [[ -f "$auth_keys" ]]; then
local ak_mode ak_owner
ak_mode="$(file_mode_octal "$auth_keys")"
ak_owner="$(file_owner "$auth_keys")"

info "authorized_keys owner=$ak_owner | perm=$ak_mode"

if [[ "$ak_owner" != "$USER_TO_CHECK:"* ]]; then
fail "authorized_keys não pertence ao usuário."
fi

if [[ "$ak_mode" != "600" ]]; then
warn "authorized_keys com permissão diferente de 600."
else
ok "authorized_keys com permissão 600."
fi
else
warn "authorized_keys não existe."
fi
}

check_password_status() {
if ! command -v passwd >/dev/null 2>&1; then
warn "Comando passwd não disponível."
return
fi

local passwd_status
passwd_status="$(passwd -S "$USER_TO_CHECK" 2>/dev/null || true)"

if [[ -z "$passwd_status" ]]; then
warn "Não foi possível obter status da senha. Rode como root para checagem completa."
return
fi

info "passwd -S: $passwd_status"

local status_field
status_field="$(awk '{print $2}' <<< "$passwd_status")"

case "$status_field" in
P|PS)
ok "Conta com senha definida."
;;
L|LK)
warn "Conta bloqueada."
;;
NP)
fail "Conta sem senha definida."
;;
*)
warn "Status de senha não reconhecido: $status_field"
;;
esac
}

check_password_aging() {
if ! command -v chage >/dev/null 2>&1; then
warn "Comando chage não disponível."
return
fi

local chage_out
chage_out="$(chage -l "$USER_TO_CHECK" 2>/dev/null || true)"

if [[ -z "$chage_out" ]]; then
warn "Não foi possível ler política de senha. Rode como root para checagem completa."
return
fi

echo "$chage_out" | sed 's/^/[INFO] /'

local max_days inactive_days
max_days="$(echo "$chage_out" | awk -F': ' '/Maximum number of days between password change/ {print $2}')"
inactive_days="$(echo "$chage_out" | awk -F': ' '/Password inactive/ {print $2}')"

if [[ "$max_days" == "99999" || "$max_days" == "never" ]]; then
warn "Senha sem expiração prática."
else
ok "Senha com expiração configurada."
fi

if [[ "$inactive_days" == "never" ]]; then
warn "Conta não possui política de inatividade após expiração."
fi
}

check_uid_shell() {
local passwd_line uid gid shell home gecos
passwd_line="$(getent passwd "$USER_TO_CHECK")"

IFS=':' read -r _ _ uid gid gecos home shell <<< "$passwd_line"

info "UID=$uid GID=$gid SHELL=$shell HOME=$home GECOS=$gecos"

if (( uid == 0 )); then
fail "Usuário com UID 0. Equivale a root."
elif (( uid < 1000 )); then
warn "UID < 1000. Pode ser conta de sistema/serviço."
else
ok "UID dentro do padrão de conta humana."
fi

case "$shell" in
*/nologin|*/false)
warn "Usuário sem shell interativo."
;;
*)
ok "Shell interativo: $shell"
;;
esac

echo "$home"
}

check_sudo_access() {
local sudo_found=0

if command -v sudo >/dev/null 2>&1; then
if sudo -l -U "$USER_TO_CHECK" >/dev/null 2>&1; then
sudo_found=1
fi
fi

if is_user_in_group sudo || is_user_in_group wheel; then
sudo_found=1
fi

if grep -RqsE "^[[:space:]]*${USER_TO_CHECK}[[:space:]]+ALL=" /etc/sudoers /etc/sudoers.d 2>/dev/null; then
sudo_found=1
fi

if (( sudo_found == 1 )); then
warn "Usuário possui sudo ou potencial privilégio administrativo. Validar necessidade."
else
ok "Usuário sem sudo aparente."
fi

if grep -RqsE "^[[:space:]]*${USER_TO_CHECK}[[:space:]].*NOPASSWD" /etc/sudoers /etc/sudoers.d 2>/dev/null; then
fail "Usuário possui sudo sem senha (NOPASSWD)."
fi
}

check_duplicate_uid() {
local uid
uid="$(id -u "$USER_TO_CHECK" 2>/dev/null || true)"
[[ -n "$uid" ]] || return

local matches
matches="$(awk -F: -v uid="$uid" '$3 == uid {print $1}' /etc/passwd)"
local count
count="$(wc -l <<< "$matches" | tr -d ' ')"

if (( count > 1 )); then
fail "UID duplicado detectado: $(tr '\n' ' ' <<< "$matches")"
else
ok "UID não duplicado."
fi
}

check_ssh_policy_for_user() {
print_header "SSH do servidor"

local permit_root password_auth pubkey_auth allow_users deny_users allow_groups deny_groups
permit_root="$(line_in_sshd_config PermitRootLogin || true)"
password_auth="$(line_in_sshd_config PasswordAuthentication || true)"
pubkey_auth="$(line_in_sshd_config PubkeyAuthentication || true)"
allow_users="$(line_in_sshd_config AllowUsers || true)"
deny_users="$(line_in_sshd_config DenyUsers || true)"
allow_groups="$(line_in_sshd_config AllowGroups || true)"
deny_groups="$(line_in_sshd_config DenyGroups || true)"

[[ -n "$permit_root" ]] && info "$permit_root" || warn "PermitRootLogin não encontrado explicitamente."
[[ -n "$password_auth" ]] && info "$password_auth" || warn "PasswordAuthentication não encontrado explicitamente."
[[ -n "$pubkey_auth" ]] && info "$pubkey_auth" || warn "PubkeyAuthentication não encontrado explicitamente."

if grep -Eiq '^[[:space:]]*PermitRootLogin[[:space:]]+no' <<< "$permit_root"; then
ok "Root login por SSH desabilitado."
else
warn "Root login por SSH pode estar habilitado."
fi

if grep -Eiq '^[[:space:]]*PasswordAuthentication[[:space:]]+no' <<< "$password_auth"; then
ok "Autenticação por senha no SSH desabilitada."
else
warn "Autenticação por senha no SSH habilitada."
fi

if grep -Eiq '^[[:space:]]*PubkeyAuthentication[[:space:]]+yes' <<< "$pubkey_auth"; then
ok "Autenticação por chave pública habilitada."
else
warn "Autenticação por chave pública não está claramente habilitada."
fi

if [[ -n "$allow_users" ]]; then
info "$allow_users"
if ! awk '{for(i=2;i<=NF;i++) print $i}' <<< "$allow_users" | grep -Fxq "$USER_TO_CHECK"; then
warn "Usuário não está em AllowUsers."
fi
fi

if [[ -n "$deny_users" ]]; then
info "$deny_users"
if awk '{for(i=2;i<=NF;i++) print $i}' <<< "$deny_users" | grep -Fxq "$USER_TO_CHECK"; then
fail "Usuário está em DenyUsers."
fi
fi

if [[ -n "$allow_groups" ]]; then
info "$allow_groups"
local matched=0
while read -r grp; do
[[ -z "$grp" ]] && continue
if is_user_in_group "$grp"; then
matched=1
break
fi
done < <(awk '{for(i=2;i<=NF;i++) print $i}' <<< "$allow_groups")

if (( matched == 0 )); then
warn "Usuário não pertence a nenhum grupo listado em AllowGroups."
fi
fi

if [[ -n "$deny_groups" ]]; then
info "$deny_groups"
while read -r grp; do
[[ -z "$grp" ]] && continue
if is_user_in_group "$grp"; then
fail "Usuário pertence a grupo negado no SSH: $grp"
fi
done < <(awk '{for(i=2;i<=NF;i++) print $i}' <<< "$deny_groups")
fi
}

check_authorized_keys_content() {
local home="$1"
local auth_keys="${home}/.ssh/authorized_keys"

[[ -f "$auth_keys" ]] || return

if grep -Eq '(^|,)(from=|command=|no-port-forwarding|no-agent-forwarding|no-pty|no-user-rc)' "$auth_keys"; then
info "authorized_keys possui restrições em algumas chaves."
else
warn "authorized_keys sem restrições visíveis. Pode estar ok, mas vale revisar em contas sensíveis."
fi

if grep -Eq 'ssh-rsa ' "$auth_keys"; then
warn "Há chave ssh-rsa. Revisar compatibilidade e política criptográfica."
fi
}

print_header "Auditoria de usuário Linux"

if ! user_exists; then
fail "Usuário '$USER_TO_CHECK' não existe."
exit 2
fi

HOME_DIR="$(check_uid_shell | tail -n 1)"

print_header "Conta"
check_duplicate_uid
check_password_status
check_password_aging
check_sudo_access

print_header "Arquivos e permissões"
check_home_permissions "$HOME_DIR"
check_ssh_dir "$HOME_DIR"
check_authorized_keys_content "$HOME_DIR"

check_ssh_policy_for_user

print_header "Resumo"
echo -e "Falhas : ${RED}${FAIL_COUNT}${NC}"
echo -e "Alertas: ${YELLOW}${WARN_COUNT}${NC}"
echo -e "Infos : ${INFO_COUNT}"

if (( FAIL_COUNT > 0 )); then
echo -e "${RED}Resultado final: CRÍTICO${NC}"
exit 3
elif (( WARN_COUNT > 0 )); then
echo -e "${YELLOW}Resultado final: REVISAR${NC}"
exit 4
else
echo -e "${GREEN}Resultado final: OK${NC}"
exit 0
fi
chmod +x audit-user.sh
sudo ./audit-user.sh nome_do_usuario
sudo ./audit-user.sh adolfo