, ,

Controle de Horário de Logon via Condicional Access – Entra ID

Quem trabalha com Microsoft Entra ID há tempo suficiente já sentiu falta de um recurso trivial do Active Directory clássico: as boas e velhas Logon Hours, que permitem restringir o horário em que um usuário pode autenticar. No Entra, essa funcionalidade nunca chegou pela interface gráfica — mas está lá, escondida em uma condição recém-liberada no schema do Acesso Condicional chamada times, acessível apenas via Microsoft Graph.

Para facilitar a vida de quem quer implementar esse controle sem perder tempo montando o payload do zero, desenvolvi com apoio do GitHub Copilot e disponibilizei em meu GitHub o arquivo ca.json pronto para ser aplicado via Graph API ou PowerShell, com a estrutura completa da condição times — dias da semana, janela horária, fuso e ação de bloqueio já configurados como template.

Ao final deste guia, o seu tenant estará respondendo a três perguntas que qualquer time de segurança que trilha um caminho Zero Trust deveria conseguir responder:

  1. Quais usuários podem autenticar somente no horário comercial?
  2. Como reduzir a janela de ataque de identidades sensíveis fora do expediente?
  3. Como aplicar isso sem depender do portal, versionando a política como código?

Por que essa política não aparece no portal?

A primeira reação de quem descobre essa condição costuma ser desconfiança: se o recurso existe e funciona, por que a Microsoft não o expôs na interface do Entra? A resposta curta é que a condição times é uma funcionalidade experimental, disponível no endpoint beta do Graph e ainda sem data confirmada para GA. Ela funciona, é enforced pelo engine do Acesso Condicional e aparece nos logs de sign-in — mas o portal admin não a renderiza nem permite editá-la pela GUI.

Isso tem duas consequências práticas. A primeira: qualquer administrador que abrir a política depois vai ver as configurações “normais” (usuários, apps, controles), sem enxergar a janela horária — o que exige documentação clara para o time. A segunda: como o único caminho de configuração é via API, o JSON deste repositório vira a fonte da verdade — é ele que você versiona, revisa e aplica.

AbordagemOnde configuraEnforcementAuditável como código
Portal Entra ID (GUI)Interface gráfica✅ Sim, para condições clássicas❌ Não expõe a condição times
Microsoft Graph betaREST / Graph Explorer✅ Sim, incluindo times✅ Payload JSON versionável
PowerShell Microsoft.GraphScript local ou pipeline✅ Sim, incluindo times✅ Ideal para CI/CD e IaC
Comparativo entre as três formas de criar/atualizar a política de Acesso Condicional com controle de horário.

Pré-requisitos

ItemRequisito
Licença Entra IDP1 ou P2 (ou Microsoft 365 Business Premium) — obrigatório para qualquer política de Acesso Condicional
Função administrativaAdministrador de Acesso Condicional ou Administrador Global
Permissão no GraphEscopo delegado Policy.ReadWrite.ConditionalAccess
EndpointGraph API na versão beta (a condição times ainda não está em v1.0)
Conta de emergênciaAo menos uma conta break-glass excluída da política, para evitar lockout do tenant
Requisitos mínimos para aplicar a política de horário de logon no Entra ID.

Para manter a organização, comecei separando um grupo de piloto no Entra ID — no meu caso, GR-Horário-De-Logon. Assim consigo aplicar a política a um subconjunto controlado antes de expandir para toda a força de trabalho.

Usuário de teste:

Guia de implantação passo a passo

1. Clonar o repositório e revisar o JSON

O ponto de partida é obter o template. Clone o repositório e abra o ca.json no seu editor favorito:

git clone https://github.com/ErickMedeiros/ac-json-controledehorario.git
cd ac-json-controledehorario
code ca.json

O JSON já vem com a condição times configurada para bloquear acessos fora do intervalo de 08:00 às 18:00 UTC, de segunda a sexta. O bloco central que interessa é este:

{
"state": "enabled",
"conditions": {
"times": {
"excludeDays": {
"daysOfWeek": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"timeZone": "UTC",
"startTime": "08:00:00",
"endTime": "18:00:00"
}
}
},
"grantControls": {
"builtInControls": ["block"]
}
}

⚠️ Detalhe intuitivo: o campo excludeDays combinado com startTime e endTime define o horário permitido. Ou seja, o bloqueio é aplicado fora dessa janela. É uma inversão que pega muita gente na primeira leitura.

2. Ajustar fuso horário e escopo de usuários

Antes de aplicar, dois ajustes são obrigatórios. Primeiro, o fuso horário: o template está em UTC porque, no momento em que escrevo este post, o endpoint do Graph só aceita esse valor com segurança. Se quiser tentar outro fuso, ajuste o campo timeZone — mas testes recentes mostram que UTC é o único valor consistentemente aceito. Na prática, converta o horário desejado para UTC: para Brasília (UTC−3), 08:00–18:00 locais equivalem a 11:00–21:00 UTC.

Segundo, o escopo de usuários. Localize o bloco users.includeGroups (ou includeUsers) e substitua os IDs pelos do seu ambiente. Você recupera o ObjectId do grupo piloto pelo próprio portal ou pelo Graph:

Connect-MgGraph -Scopes "Group.Read.All"
Get-MgGroup -Filter "displayName eq 'GR-Horário-De-Logon'" | Select-Object Id, DisplayName
Recuperação do ObjectId do grupo via Microsoft Graph PowerShell
ObjectId do grupo recuperado via Get-MgGroup.

Break-glass primeiro. Antes de qualquer outra edição, adicione o ObjectId das suas contas de emergência em users.excludeUsers. Sem essa exclusão, um ajuste errado de fuso pode trancar o tenant fora do horário.

3. Conectar ao Microsoft Graph

A aplicação pode ser feita por dois caminhos: PowerShell (recomendado para produção e pipelines) ou Graph Explorer (útil para testes pontuais). Vamos pelo PowerShell, com o módulo oficial Microsoft.Graph. Se ainda não tiver o módulo, instale-o:

Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess"

Usei a alternativa Via Graph Explorer

Alternativa via Graph Explorer: em ambientes onde o PowerShell não está disponível, o mesmo resultado pode ser obtido em developer.microsoft.com/graph/graph-explorer, autenticando-se com uma conta que tenha o escopo Policy.ReadWrite.ConditionalAccess consentido.

4. Aplicar a política via PowerShell

Com o Graph conectado, três linhas resolvem: carregar o JSON, converter em objeto e enviar para o endpoint de criação. Antes de rodar em produção, ajuste o campo state para enabledForReportingButNotEnforced — é a versão em modo “Somente relatório” (Report-only), que deixa a política registrar tudo sem bloquear ninguém.

$body = Get-Content -Path "./ca.json" -Raw | ConvertFrom-Json
New-MgIdentityConditionalAccessPolicy -BodyParameter $body

Alternativa REST direta: se preferir o Graph puro, o mesmo efeito é obtido com um POST em https://graph.microsoft.com/beta/identity/conditionalAccess/policies, enviando o conteúdo de ca.json como corpo da requisição.

Depois valide novamente se a mudança surtiu efeito na regra

5. Validar no portal do Entra ID

Acesse entra.microsoft.com e navegue até Proteção > Acesso Condicional > Políticas. A política recém-criada aparece na lista normalmente — clique nela para inspecionar.

Você vai reparar que o portal não mostra a janela horária. As condições aparecem “vazias” ou incompletas, ainda que a política esteja ativa e sendo aplicada. Isso é esperado — o Entra reconhece que existe uma condição extra, mas a GUI não sabe renderizá-la ainda. Nos logs de sign-in, o comportamento é o mesmo: o motivo do bloqueio aparece atribuído a essa política, mas o detalhe da condição não é exposto.

6. Testar em modo Report-only

Com a política em enabledForReportingButNotEnforced, faça alguns sign-ins de teste dentro e fora do intervalo configurado. Nos logs de sign-in (Entra ID > Monitoramento > Entradas), abra a aba Report-only — a política vai marcar cada tentativa como “reportOnlySuccess” ou “reportOnlyFailure” conforme o horário.

Um exemplo de matriz de teste que costumo usar antes de habilitar em produção:

Data/Hora (UTC)Resultado esperadoMotivo
Segunda, 12:00✅ PermitidoDentro do horário comercial
Segunda, 22:00❌ BloqueadoFora do horário comercial
Sábado, 12:00❌ BloqueadoDia não incluído em daysOfWeek
Domingo, 15:00❌ BloqueadoDia não incluído em daysOfWeek
Sexta, 17:59✅ PermitidoÚltima janela do expediente
Matriz mínima de validação — reproduza no seu ambiente antes do enforcement.

7. Habilitar em produção

Após pelo menos 5 a 7 dias em Report-only sem surpresas nos logs, é hora de virar o enforcement. Atualize a política mudando o state para enabled:

$policyId = "<ID_DA_POLITICA_CRIADA>"
Update-MgIdentityConditionalAccessPolicy `
-ConditionalAccessPolicyId $policyId `
-State "enabled"

A partir desse momento, qualquer tentativa de sign-in fora da janela recebe a tela padrão de bloqueio do Acesso Condicional (“Você não pode acessar isto no momento”) e o evento correspondente aparece nos logs como Falha — 53003 — Access blocked by Conditional Access policies.

8. Monitorar continuamente

Como o portal não expõe a condição, o monitoramento fica ainda mais crítico. Configure um alerta no Azure Monitor apontando para a tabela SigninLogs com uma KQL simples que capture bloqueios atribuídos ao ID da política:

SigninLogs
| where TimeGenerated > ago(1d)
| where ConditionalAccessPolicies has "<ID_DA_POLITICA>"
| where ResultType == "53003"
| summarize Bloqueios = count() by UserPrincipalName, bin(TimeGenerated, 1h)
| order by Bloqueios desc

Como o JSON funciona (por dentro)

O ca.json segue o mesmo schema de qualquer política de Acesso Condicional, com uma seção extra dentro de conditions que é o coração deste post. A responsabilidade de cada bloco:

Bloco JSONO que faz
stateDefine se a política está enabled, disabled ou em Report-only (enabledForReportingButNotEnforced)
conditions.usersEscopo de usuários e grupos — inclusive as exclusões de break-glass
conditions.applicationsAplicações-alvo — no template, All cloud apps para cobrir o acesso ao tenant como um todo
conditions.platformsPlataformas aceitas (Windows, iOS, Android, macOS…). Default: all
conditions.clientAppTypesCobre browser, apps móveis/desktop, Exchange ActiveSync e clientes legados
conditions.timesO bloco novo — recebe excludeDays com daysOfWeek, timeZone, startTime, endTime. Define a janela permitida
grantControls.builtInControlsAção a executar quando a condição é violada — no template, block
Anatomia do ca.json — cada bloco tem uma responsabilidade isolada.

Vale destacar duas particularidades técnicas que costumam tropeçar quem reproduz esse padrão. Primeiro, o campo excludeDays tem semântica invertida em relação ao que o nome sugere: ele define quando a política não bloqueia, ou seja, o horário útil. É o Entra assumindo que o “modo padrão” da política é bloquear, e você lista as exceções. Segundo, o fuso timeZone hoje aceita apenas UTC de forma consistente — outros valores retornam erro ou são silenciosamente ignorados. Enquanto o recurso está em beta, o mais seguro é fazer a conversão para UTC antes de aplicar.

Resolução de problemas

SintomaCausa provávelSolução
Insufficient privileges to complete the operationConta sem o escopo Policy.ReadWrite.ConditionalAccess consentidoRode Connect-MgGraph com o escopo correto e aceite o consentimento
400 Bad Request ao aplicar o JSONtimeZone diferente de UTC ou payload malformadoVolte para "timeZone": "UTC" e valide o JSON em um linter
Política criada mas não aparece condição no portalComportamento esperado — GUI não renderiza timesValide a condição via GET no Graph, não pelo portal
Usuário bloqueado dentro do horário útilFuso horário não convertido para UTCRecalcule a janela local em UTC e reaplique
Admin sem acesso ao tenantConta break-glass não excluída da políticaUse outra conta com Global Admin para reverter state para disabled e ajustar excludeUsers
Sign-in logs sem detalhe da condiçãoComportamento esperado — recurso em betaCorrelacione pelo ID da política e pelo horário do evento
Problemas mais comuns na primeira aplicação da política e como destravar cada um.

Boas práticas de produção

Antes de levar esse controle para o ambiente real, alguns cuidados fazem diferença. Sempre passe pelo Report-only por no mínimo uma semana — a janela cobre casos que você não previu, como aquele backup que roda de madrugada com uma conta de serviço interativa. Documente que a política existe em local visível para o time de suporte: como o portal não mostra a condição, o próximo admin que abrir o CA sem contexto vai concluir que a política “não faz nada” e pode desabilitá-la achando que está limpando lixo. Exclua contas break-glass e contas de serviço críticas — este ponto é tão importante que vale repetir; um lockout aqui é um lockout do tenant inteiro fora do horário. Aplique por grupo, não por escopo global, começando por um piloto pequeno e expandindo em ondas conforme o comportamento se estabiliza. Considere combinar com o Entra ID PIM para roles sensíveis — restringir a ativação de funções privilegiadas a janelas de manutenção reduz drasticamente a superfície de ataque de identidades administrativas. E, por fim, versione o ca.json em source control, tratando a política como código: revisão via pull request, histórico de mudanças e possibilidade de rollback rápido são benefícios óbvios que a GUI nunca vai entregar.

Vale lembrar que essa condição ainda é experimental. Standards como NIST SP 800-53 Rev. 5 reconhecem controles de horário como um mecanismo válido de aplicação do princípio de menor privilégio, mas isso não significa que toda empresa precisa disso. Trate como um controle guiado por avaliação de risco: faz sentido para roles administrativas, mailboxes executivas e ambientes regulados; pode ser overkill para toda a força de trabalho.

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 — variantes do JSON para diferentes fusos, scripts de automação end-to-end, template Bicep/Terraform para deployment declarativo, ou até um Workbook do Sentinel para monitorar bloqueios em tempo real — fique à vontade para abrir uma Issue ou enviar um Pull Request.

Se este projeto te ajudou a fortalecer o Acesso Condicional do seu tenant, acesse o link github.com/ErickMedeiros/ac-json-controledehorario, deixe uma ⭐ e ajude a fazer com que ele chegue a mais profissionais!

Forte abraço pessoal 👊