Bloqueio automático de convidados sem MFA no Microsoft Entra ID
Quem gerencia um tenant Microsoft Entra ID com colaboração B2B ativa conhece bem esse ciclo: o time de negócio convida um fornecedor, o convite fica pendente por dias, o Guest aceita, mas nunca completa o registro de MFA. Alguns esquecem, outros usam apenas o e-mail pontualmente e seguem a vida — e a conta continua ativa no seu tenant, autenticada só por senha, com acesso a Teams, SharePoint e, em muitos casos, aplicações internas. Cada Guest sem MFA é uma porta lateral: credencial fraca em uma identidade que já está dentro do seu diretório.
Para resolver essa demanda clássica de governança de identidades externas, desenvolvi com apoio do GitHub Copilot e disponibilizei em meu GitHub um Runbook de Azure Automation em PowerShell que faz a varredura periódica de um grupo específico de convidados, valida quem realmente tem MFA registrado, bloqueia quem está fora da política (ou apenas simula, em modo DryRun) e envia um relatório em Excel ou CSV por e-mail via Microsoft Graph.
A cada execução, a automação responde a três perguntas que todo time de segurança deveria conseguir responder a qualquer momento:
Quais convidados do grupo monitorado não registraram MFA dentro da janela definida?
O que aconteceu com cada um: foi bloqueado, simulado (DryRun) ou ignorado por ainda estar com convite pendente?
Qual é a lista consolidada — pronta para auditoria — que precisa chegar na caixa do SecOps hoje?
Por que filtrar por grupo em vez de sufixo de UPN?
A primeira tentação, quando se precisa isolar convidados em um tenant, é filtrar por sufixo do UPN (#EXT#) ou por domínio. Funciona… até parar de funcionar. Basta o negócio começar a receber Guests de vários parceiros, ou fazer a colaboração via cross-tenant sync, para o filtro por string virar um pesadelo de manutenção — e ainda deixar passar exceções que deveriam estar isentas do bloqueio.
A segunda tentação é tratar todos os Guests do tenant. Esse caminho é ainda pior: contas de suporte de fornecedor estratégico, auditores externos ou parceiros em fase de onboarding acabam sendo bloqueados no primeiro job. Não dá.
A solução do runbook aposta em algo simples e auditável: membership em grupo do Entra ID como fonte de verdade sobre quem entra no escopo do bloqueio.
Abordagem
Como funciona
Problema / Vantagem
Filtro por sufixo de UPN
String matching em #EXT# ou domínio
❌ Frágil, sem exceção fácil, mistura convidados legítimos
Todos os Guests do tenant
Consulta ampla via Graph
❌ Sem granularidade; bloqueia parceiros estratégicos
Grupo dedicado do Entra ID
Membership em grupo atribuído ou dinâmico
✅ Escopo explícito, versionável, exceções fáceis
Como bônus, o mesmo runbook suporta grupos atribuídos (você controla quem entra) e grupos dinâmicos (uma regra tipo (user.userType -eq "Guest") and (user.companyName -eq "AcmeCorp") mantém a lista viva sozinha). O código não muda.
Pré-requisitos
Item
Requisito
Subscription Azure
Ativa, com permissão Owner ou Contributor para criar Automation Account
Microsoft Entra ID
Tenant com grupo de segurança contendo os convidados a monitorar
Conta executora
Global Administrator ou Privileged Role Administrator (para conceder permissões de aplicação no Graph)
Mailbox de envio
Caixa Exchange Online com licença ativa (E1/E3/E5 ou Exchange Plan)
Módulos PowerShell
Microsoft.Graph.* na Automation Account (opcional: ImportExcel para XLSX)
Runtime
PowerShell 7.2 ou 5.1, conforme compatibilidade do ambiente
Para organizar todos os recursos da solução, comecei criando um Resource Group dedicado — no exemplo, rg-governance. Manter Automation Account, identidade gerenciada e agendamento sob o mesmo grupo facilita o controle de custo, o RBAC e a limpeza futura.
Guia de implantação passo a passo
1. Criar a Automation Account
No portal do Azure, vá em Create a resource → Automation. Aponte para o Resource Group da solução e escolha a região — no exemplo, aa-disablegueusers, alinhado à convenção de nomes da conta.
Na aba Advanced, garanta que a Managed Identity → System assigned esteja habilitada. É ela que autentica o runbook sem armazenar nenhuma credencial no código.
Alternativa via Azure CLI:
az automation account create \
--name aa-disablegueusers \
--resource-group rg-governance \
--location eastus \
--sku Basic \
--assign-identity
2. Anotar o Object ID da Managed Identity
Após a criação, abra o recurso e vá em Account Settings → Identity → System assigned. Copie o Object (principal) ID — ele será usado para conceder as permissões do Graph nos próximos passos.
3. Instalar os módulos PowerShell
O runbook depende de módulos do Microsoft Graph SDK. A ordem de importação importa por causa das dependências internas do Graph e o runtime precisa bater com o do runbook. Instale nesta sequência, aguardando cada módulo ficar Available antes do próximo:
Microsoft.Graph.Authentication
Microsoft.Graph.Users
Microsoft.Graph.Groups
Microsoft.Graph.Reports
Microsoft.Graph.Users.Actions
ImportExcel(opcional, para gerar XLSX)
No portal, o caminho é Automation Account → Shared Resources → Modules → + Add a module, escolhendo PowerShell Gallery como origem.
Obs.: Importe todos os módulos na sequência
Se preferir automatizar, dá para importar todos os módulos via PowerShell — o método é mais confiável em lote, e o próprio repositório traz um snippet pronto:
Tempo estimado: 10 a 15 minutos para os cinco módulos do Graph. Não pule a espera — se um deles ainda estiver Creating quando o próximo começar, é comum ver o runbook falhar depois com Connect-MgGraph is not recognized.
4. Conceder as permissões do Graph à Managed Identity
A Managed Identity precisa de cinco permissões de aplicação no Microsoft Graph para operar:
Permissão
Tipo
Para que serve
GroupMember.Read.All
Graph (App)
Ler os membros do grupo de Guests
User.ReadWrite.All
Graph (App)
Alterar accountEnabled = false nos convidados
UserAuthenticationMethod.Read.All
Graph (App)
Verificar métodos de MFA por usuário
Mail.Send
Graph (App)
Enviar o relatório via Graph sendMail
AuditLog.Read.All
Graph (App)
Reserva operacional (troubleshooting)
Abra o Cloud Shell, selecione PowerShell e execute o script abaixo, substituindo o nome da Automation Account:
Cloud Shell em modo PowerShell — sem necessidade de instalar módulo local.
# Ajustar para o nome real da sua Automation Account
Write-Output "Já existe ou falhou: $permName - $($_.Exception.Message)"
}
} else {
Write-Output "Role não encontrada: $permName"
}
}
Para conferir visualmente, vá em Entra ID → Enterprise Applications → All applications → Application Type = Managed Identities → aa-disablegueusers → Permissions. As cinco permissões devem estar listadas.
Atenção ao Mail.Send: como permissão de aplicação, ele permite enviar como qualquer caixa do tenant. Em produção, aplique uma Application Access Policy do Exchange Online para restringir o envio à mailbox específica da automação.
Propagação: permissões recém-concedidas podem levar 15 a 60 minutos para refletir no token da Managed Identity. Se o primeiro teste retornar Forbidden, aguarde e tente de novo.
5. Criar o grupo de Guests monitorado
O runbook lê membros de um grupo de segurança do Entra ID — atribuído ou dinâmico. Meu conselho: sempre que possível, prefira o dinâmico, para não depender de disciplina manual da equipe de service desk.
Em Entra ID → Groups → + New group, preencha:
Group type: Security
Group name:GR-UsersGuest
Membership type: Dynamic User
Na regra dinâmica, use:
(user.userType -eq "Guest")
Ou, se quiser segmentar por parceiro/domínio (recomendado, para ter granularidade):
(user.userType -eq "Guest") and (user.userPrincipalName -contains "fornecedor.com")
Regra dinâmica: convidados entram e saem do grupo automaticamente.
Após salvar, abra o grupo e copie o Object ID — é o valor que vai no parâmetro GuestGroupId do runbook.
6. Importar e publicar o Runbook
Ainda no Automation Account, vá em Process Automation → Runbooks → + Create a runbook.
Preencha o formulário:
Name:RumGuestDisable
Runbook type: PowerShell
Runtime version: 5.1 (ou 7.2, conforme a versão dos módulos que você instalou)
No editor, cole o conteúdo de Runbook-GuestsSemMfa.ps1 e ajuste os defaults do bloco param() com os valores do seu ambiente:
[string] $Subject = "Relatório - Guests bloqueados sem MFA",
[bool] $DryRun = $false
)
Salve (Ctrl+S) e clique em Publish.
Bloqueio ocorrerá após a conta esta habilitada por 24 horas, caso não habilite o MFA neste período, a conta recebe a desativação
7. Validação em modo DryRun
Nunca deixe o primeiro teste em produção real. Abra o Test pane, ajuste DryRun = True e HoursWithoutMfa = 1 (para forçar o job a “ver” convidados recentes) e clique em Start.
Parâmetro
Valor de teste
HoursWithoutMfa
1
SkipPending
False
GuestGroupId
(default já configurado)
SenderUpn
(default já configurado)
To
(default já configurado)
DryRun
True ← importante
A saída esperada segue mais ou menos esta ordem:
=== Início do runbook ... ===
Janela sem MFA: 1 h | SkipPending: False | DryRun: True
Conectando ao Microsoft Graph com Managed Identity...
Data/hora de corte: ...
Listando convidados (Guest) do tenant...
Buscando membros do grupo '...'...
Total de convidados considerados após filtro: N
Verificando métodos de autenticação de cada convidado...
SEM MFA: usuario1@...
COM MFA: usuario2@... (2 método(s))
Candidatos a bloqueio (sem MFA e dentro da janela): X
Total processado: X | Bloqueados/Simulados: X
Relatório gerado (CSV): ...
E-mail enviado a partir de '...'.
=== Fim do runbook ... ===
Rumbook publicado:
E o relatório chega na caixa de entrada — CSV ou XLSX, dependendo se o ImportExcel foi instalado — pronto para abrir no Excel e circular com o time.
Confira também a pasta de spam — domínios sem SPF/DKIM completos costumam cair em spam no Hotmail/Gmail no primeiro envio.
8. Agendar a execução em produção
Com o DryRun aprovado, vá em Shared Resources → Schedules → + Add a schedule e crie um agendamento diário — no exemplo, Daily-02h-BRT, começando às 02:00 no fuso de Brasília.
Depois, vincule o schedule ao runbook em Runbook → Schedules → + Add a schedule e configure os parâmetros de produção — desta vez com DryRun = False, SkipPending = True e HoursWithoutMfa = 24.
9. Alertas para falhas do próprio job (opcional, mas recomendado)
Automação que quebra em silêncio é pior do que não ter automação. Crie um alerta em Monitor → Alerts → Create alert rule apontando para o Automation Account, com o sinal Total Jobs, dimensão Status = Failed, threshold > 0. Vincule a um Action Group com o e-mail do time SecOps.
Como o script funciona (por dentro)
Cada bloco do Runbook-GuestsSemMfa.ps1 tem uma responsabilidade isolada:
Bloco
O que faz
1. Connect-MgGraph -Identity
Autentica como Managed Identity no Graph, sem segredos em código
2. Listagem do grupo
Get-MgGroupMember traz os convidados do GuestGroupId — funciona para grupos atribuídos e dinâmicos
3. Filtro de janela
Só entra na análise quem foi criado há mais de HoursWithoutMfa horas
4. Verificação de MFA
Invoke-MgGraphRequest per-user em /users/{id}/authentication/methods — evita falsos negativos do relatório agregado
5. Ação (bloqueio ou simulação)
Se DryRun=False, aplica accountEnabled=false; se True, apenas marca como Simulado
6. Relatório CSV/XLSX
ImportExcel quando disponível; CSV como fallback (separador ; e BOM UTF-8, Excel PT-BR friendly)
7. sendMail via Graph
Invoke-MgGraphRequest + ConvertTo-Json -Depth 20 para serializar o payload sem quebrar
Vale destacar duas particularidades técnicas que costumam tropeçar quem reproduz esse padrão. Primeiro, a verificação de MFA per-user em vez do relatório agregado Get-MgReportAuthenticationMethodUserRegistrationDetail: o relatório é eventualmente consistente, com atraso de horas — o suficiente para bloquear um Guest que acabou de registrar MFA e sair mal na foto. Consultar /authentication/methods direto no usuário custa uma chamada a mais por convidado, mas dá o estado real. Segundo, o envio via Invoke-MgGraphRequest em vez de Send-MgUserMail: o cmdlet SDK serializa o payload de forma inconsistente em alguns runtimes e devolve o clássico StartObject error — usar REST direto elimina o problema.
Resolução de problemas
Sintoma
Causa provável
Solução
Connect-MgGraph is not recognized
Módulos ainda em Creating ou runtime errado
Confirme Available em todos e o runtime do runbook igual ao dos módulos
403 Forbidden logo na primeira chamada
Permissões da MI ainda não propagaram
Aguarde 15–60 min após o New-AzADServicePrincipalAppRoleAssignment
sendMail → Forbidden
Mail.Send não estava no token cacheado pelo Connect-MgGraph
Rode o runbook novamente após a propagação; se persistir, pegue token fresco via REST
sendMail → MailboxNotEnabledForRESTAPI
Remetente sem licença Exchange Online
Atribua E1/E3/E5 ao SenderUpn ou use uma caixa compartilhada licenciada
400 Bad Request no sendMail
Payload JSON inválido (estrutura ou -Depth baixo)
Confirme o ConvertTo-Json -Depth 20 e a estrutura message.toRecipients[].emailAddress.address
Relatório sai como CSV mesmo com Excel esperado
ImportExcel não instalado
Instale o módulo opcional na Automation Account
Test pane trava em [switch]
Runbook usando [switch] em vez de [bool]
Use [bool] $DryRun; o Test pane não lida bem com [switch]
E-mail cai no spam
Domínio remetente sem SPF/DKIM/DMARC
Configure os registros DNS do domínio do SenderUpn
Boas práticas de produção
Antes de levar isso para o ambiente real, alguns cuidados fazem diferença. Sempre valide primeiro com DryRun=True, especialmente após qualquer mudança de escopo do grupo — nada pior do que bloquear parceiro estratégico no primeiro job de produção. Restrinja o Mail.Send com uma Application Access Policy no Exchange Online, limitando a Managed Identity a enviar apenas pela mailbox de automação; sem isso, ela tecnicamente pode enviar como qualquer caixa do tenant. Marque SkipPending=True em produção — convite pendente é status transitório, não falha de política. Agende fora do horário comercial e configure alerta em cima do Total Jobs Failed, para que uma quebra silenciosa não vire dívida acumulada. Revise trimestralmente as permissões da MI, e complemente a governança com Access Reviews do próprio Entra ID e políticas de Conditional Access exigindo MFA para Guests — o runbook é o mecanismo de enforcement de última milha, não substitui o controle preventivo.
Contribua com a comunidade!
Este repositório é open-source (licença MIT) e focado na evolução contínua dos profissionais de nuvem. Se você identificar uma melhoria — suporte a múltiplos grupos, integração com um webhook de ITSM para abrir ticket automaticamente antes do bloqueio, exportação para uma tabela em Application Insights / Sentinel, ou até uma variante que notifica o convidado por e-mail antes do bloqueio dando 24 h para registrar MFA — fique à vontade para abrir uma Issue ou enviar um Pull Request.
Se este projeto te ajudou a fortalecer a governança de identidades externas do seu tenant, acesse o link github.com/ErickMedeiros/runbookGuestsSemMfa, deixe uma ⭐ e ajude a fazer com que ele chegue a mais profissionais!
Sou Erick Medeiros – Microsoft MVP na categoria Azure Compute Infrastructure, Azure Well-Architected & Resiliency – Atuo como Analista de Infraestrutura Sênior – Cloud Engineer. Compartilho conteúdo técnico, insights e melhores práticas da nuvem.