Começar
Visão geral
API REST somente leitura, em JSON, sobre HTTPS. Endereço base: https://api-vigia.itool.com.br
Autenticação
Envie a sua chave no cabeçalho Authorization em toda chamada:
Authorization: Bearer vg_SUA_CHAVE
O cabeçalho X-API-Key: vg_SUA_CHAVE também é aceito. A chave dá acesso somente aos domínios do seu usuário (ou ao subconjunto escolhido ao gerá-la). Se um domínio for retirado do seu usuário, ou se a conta for desativada, a chave perde o acesso na hora.
Guias How to
Referência
GET /v1/credentials
GEThttps://api-vigia.itool.com.br/v1/credentials
Sem parâmetros, devolve as 10 credenciais descobertas mais recentes de todos os seus domínios, da mais nova para a mais antiga. Use os filtros abaixo para refinar e limit para trazer até 100.
| Parâmetro | Valores | Descrição |
limit | 1 a 100 (padrão 10) | Quantidade de credenciais. |
domain | ex.: exemplo.com.br | Só um domínio. Precisa estar no escopo da chave. |
type | employees, customers, third_parties | Categoria: funcionários, clientes ou terceiros. Sem o parâmetro, todas. |
axis | collected (padrão), exposed | Data usada para ordenar e filtrar: coleta pelo Vigia ou exposição da credencial. |
since | 2026-10-01 ou 2026-10-01T12:00:00Z | A partir desta data (inclusive), no eixo escolhido. UTC. |
until | 2026-10-31 ou 2026-10-31T23:59:59Z | Até esta data (inclusive; uma data sem hora vale até o fim do dia). UTC. |
Exemplo:
curl -s -H "Authorization: Bearer $VIGIA_API_KEY" \
"https://api-vigia.itool.com.br/v1/credentials?limit=50&type=employees&since=2026-10-01"
Resposta 200:
{
"count": 1,
"limit": 50,
"axis": "collected",
"items": [
{
"domain": "exemplo.com.br",
"type": "employees",
"username": "[email protected]",
"password": "Exemplo@2024",
"password_strength": 4,
"url": "https://portal.exemplo.com.br/login",
"exposed_at": "2026-10-20T14:03:11.000000Z",
"collected_at": "2026-10-27T09:00:42.118000Z"
}
]
}
| Campo | Descrição |
domain | Domínio monitorado. |
type | Categoria: employees, customers ou third_parties. |
username | Login exposto (mascarado quando a credencial ainda não foi desbloqueada). |
password | Senha exposta. Pode vir null quando ainda não está disponível. |
password_strength | Força da senha de 0 a 10 (0–2 crítica, 3–4 fraca, 5–7 média, 8–10 forte). |
url | Endereço onde a credencial era usada. |
exposed_at | Data de exposição (ISO 8601, UTC). |
collected_at | Data em que o Vigia descobriu a credencial (ISO 8601, UTC). |
Referência
GET /v1/domains
GEThttps://api-vigia.itool.com.br/v1/domains
Lista os domínios que a chave pode consultar: {"domains": ["exemplo.com.br"]}
Referência
Erros
Toda resposta de erro traz {"error": "codigo"}.
| HTTP | Código | Significado |
| 400 | invalid_limit, invalid_type, invalid_axis, invalid_date | Parâmetro inválido. |
| 401 | invalid_key | Chave ausente, errada, revogada, ou usuário inativo. |
| 401 | key_expired | A chave passou da validade. Gere outra no painel. |
| 403 | forbidden_domain | O domínio pedido não está no escopo da chave. |
| 404 / 405 | not_found, method_not_allowed | Rota inexistente ou método diferente de GET. |
| 429 | — | Limite de requisições excedido. Aguarde alguns segundos e tente de novo. |
| 503 | api_disabled | API desabilitada no momento. |
Boas práticas
- A resposta contém senhas reais: guarde a chave num cofre de segredos, nunca no código ou em repositório.
- Se a chave vazar, revogue-a aqui e gere outra. A revogação vale na hora.
- A coleta acontece uma vez por dia, às 13h00 (horário de Brasília): consultar a cada poucos minutos não traz dados novos. Para buscar só o que é novo sem perder registros, use
since sobre collected_at com uma margem de sobreposição (veja Consumir a API com segurança).
How to
Consumir a API com segurança
Base para qualquer integração: consultar sem perder registros, proteger as senhas da resposta e decidir quando agir. Leia antes dos guias de Entra ID e Google Workspace.
Os exemplos de código são ilustrativos. Adapte nomes e exclusões, comece em modo simulação (dry_run) e valide em contas de teste antes de ligar em produção.
1. Visão geral
Um job agendado consulta GET /v1/credentials?type=employees&axis=collected, descarta a senha assim que lê a resposta e procura o funcionário no diretório pelo e-mail. Ele só age quando a conta é única, está ativa, não é de administrador nem de emergência, e a senha dela não foi trocada depois do vazamento. Para agir, força a troca de senha e, em seguida, revoga as sessões.
- Consultar a API de forma incremental, sem perder registros.
- Filtrar: descartar a senha, ignorar registros mascarados e repetidos.
- Casar o e-mail com uma única conta do diretório.
- Decidir se a credencial ainda é válida (data do vazamento × última troca de senha).
- Agir: forçar a troca de senha e revogar as sessões.
- Auditar: registrar só metadados (nunca a senha).
2. Antes de começar
- Crie um usuário de serviço no Vigia só para a integração. A chave pertence a um usuário e perde o acesso na hora (
401 invalid_key) se ele for desativado, perder um domínio ou ficar com troca de senha pendente. Com a chave de uma pessoa, a integração para quando ela sai de férias ou da empresa.
- Gere a chave com validade de 90 ou 365 dias, limitada aos domínios corporativos que serão remediados. Ela aparece uma única vez: copie direto para um cofre (Azure Key Vault ou Google Secret Manager) e registre a data de expiração no próprio segredo para receber o aviso de vencimento.
- Faça um inventário: contas de emergência e administradores, contas sincronizadas do Active Directory, domínios federados ou com SSO de terceiros. Eles mudam o que a integração pode e deve fazer.
| Item | Microsoft Entra ID | Google Workspace |
| Onde rodar | Runbook do Azure Automation (PowerShell 7.4+) ou Azure Functions (plano Flex Consumption) | Cloud Run job ou Cloud Functions, acionado pelo Cloud Scheduler |
| Identidade do job | Identidade gerenciada atribuída pelo sistema (fora do Azure: app registration com certificado ou credencial federada, nunca segredo de cliente) | Conta de serviço com papel de administrador atribuído diretamente, sem delegação em todo o domínio |
| Permissões | User.Read.All, User.RevokeSessions.All, User-PasswordProfile.ReadWrite.All (+ IdentityRiskyUser.ReadWrite.All com Entra ID P2) | Escopos admin.directory.user e admin.directory.user.security |
| Papel administrativo | User Administrator (nunca Global Admin nem Privileged Authentication Administrator) | Papel personalizado: USERS_RETRIEVE, ORGANIZATION_UNITS_RETRIEVE, USERS_RESET_PASSWORD, USERS_FORCE_PASSWORD_CHANGE, USER_SECURITY_ALL, limitado às UOs de funcionários |
| Chave do Vigia | Key Vault + papel Key Vault Secrets User para a identidade | Secret Manager + secretmanager.secretAccessor só nesse segredo |
3. Consultar sem perder registros
- Use sempre
axis=collected. Um vazamento de anos atrás pode ser coletado hoje: o collected_at é recente e o exposed_at é antigo. Uma consulta por exposed_at perderia esses registros.
- Guarde como marca d'água o maior
collected_at já processado (não o relógio do servidor) e consulte com since = marca − 24 h. A sobreposição traz repetições, então deduplique.
- A API não tem paginação. Se vierem 100 itens, pode haver mais: repita com
until igual ao menor collected_at da página até vir menos de 100. Só avance a marca d'água quando a varredura terminar sem erro.
- Frequência: uma vez por dia, a partir das 13h30 (horário de Brasília). A coleta roda às 13h00; consultar mais que isso não traz nada novo.
- Erros: repita só em
429 e 5xx, com espera exponencial, variação aleatória (jitter) e limite de tentativas. Em 400, 401 e 403, pare e gere alerta: indicam bug, chave inválida/expirada ou domínio fora do escopo.
Exemplo em Python: consulta incremental com descarte da senha e deduplicação
# EXEMPLO ILUSTRATIVO: adapte e teste antes de usar em produção.
import hmac, hashlib, os, time, random, requests
from datetime import timedelta
API = "https://api-vigia.itool.com.br/v1/credentials"
KEY = os.environ["VIGIA_API_KEY"] # vem do Key Vault / Secret Manager
PEPPER = os.environ["DEDUP_PEPPER"].encode() # segredo próprio para o HMAC de deduplicação
def chamar(params, tentativas=6):
for n in range(tentativas):
r = requests.get(API, params=params, timeout=30,
headers={"Authorization": f"Bearer {KEY}"})
if r.status_code == 200:
return r.json()
if r.status_code in (429, 500, 502, 503, 504):
time.sleep(random.uniform(0, min(60, 2 ** n))) # espera exponencial com jitter
continue
# 400 / 401 / 403: não repetir. Parar e alertar.
raise RuntimeError(f"Vigia {r.status_code}: {r.text[:200]}")
raise RuntimeError("Vigia: tentativas esgotadas")
def coletar(marca_dagua, sobreposicao=timedelta(hours=24)):
# Lista completa desde a marca d'água. Só avance a marca se nada falhar.
since = (marca_dagua - sobreposicao).strftime("%Y-%m-%dT%H:%M:%SZ")
until, vistos, saida = None, set(), []
while True:
p = {"type": "employees", "axis": "collected", "limit": 100, "since": since}
if until:
p["until"] = until
pagina = chamar(p)
for it in pagina["items"]:
senha = it.pop("password", None) or "" # a senha sai do registro aqui
chave = hmac.new(PEPPER, "|".join([it["domain"], it["type"],
(it["username"] or "").lower(), it["url"] or "", senha]).encode(),
hashlib.sha256).hexdigest()
senha = None
if chave not in vistos:
vistos.add(chave); it["dedup"] = chave; saida.append(it)
if pagina["count"] < pagina["limit"]:
return saida
datas = [i["collected_at"] for i in pagina["items"]]
if min(datas) == max(datas):
raise RuntimeError("100+ registros com o mesmo collected_at: consulte por domain")
until = min(datas) # inclusivo: as repetições caem na deduplicação
4. Filtrar, casar e decidir
- Descarte o campo
password logo após o parse. A remediação não precisa dele. Nunca grave a resposta bruta em log, chamado ou SIEM. Para deduplicar, use HMAC com uma chave secreta, não um SHA-256 simples da senha.
- Nunca "teste" a senha vazada tentando fazer login. Não há API para isso, a tentativa pode bloquear a conta e se parece com um ataque de password spray.
username mascarado (credencial ainda não desbloqueada no painel) não identifica ninguém: mande para revisão manual.
- Case o e-mail com uma única conta. Normalize para minúsculas e procure no UPN,
mail, proxyAddresses e otherMails (Entra) ou no e-mail principal e aliases (Google). Mais de uma correspondência = revisão manual. Aja sempre pelo ID imutável da conta.
- Compare a data do vazamento com a última troca de senha. No Entra, use
lastPasswordChangeDateTime. No Google não há campo equivalente no usuário: consulte o evento password_edit (aplicação user_accounts) na Reports API. Se a senha foi trocada depois do exposed_at, a credencial provavelmente não vale mais.
| Situação | Decisão recomendada |
Funcionário, e-mail visível, uma conta ativa, fora das exclusões, exposed_at posterior à última troca de senha | Agir: forçar troca e revogar sessões (ou pedir aprovação, se houver) |
exposed_at anterior à última troca de senha | Avisar o funcionário. Revogar sessões se houver indício de infostealer |
exposed_at no mesmo dia da troca, ou histórico de troca indisponível | Revisão humana |
| Usuário mascarado, várias contas, convidado ou conta desativada | Só registrar |
| Conta de emergência, administrador, conta de serviço ou caixa compartilhada | Nunca agir automaticamente: escalar para uma pessoa |
url de login corporativo (login.microsoftonline.com, accounts.google.com, SSO, VPN) | Evidência forte: priorizar |
url de site de terceiros | Risco de reutilização de senha: avisar e revisar antes de forçar troca |
Ordem das ações: primeiro force a troca de senha, depois revogue as sessões. Revogar antes deixa uma janela para o atacante entrar de novo com a senha vazada. Se o vazamento veio de um infostealer (malware que rouba cookies junto com a senha), revogue as sessões mesmo que a senha já tenha sido trocada, e verifique regras de caixa postal, métodos de MFA adicionados e o computador do funcionário.
Proteções: comece em dry_run, limite o número de ações por execução e interrompa o job (com alerta) quando a quantidade de candidatos fugir do normal.
5. Problemas comuns e como evitar
| Problema | Causa | Como evitar |
| Bloqueio indevido em massa | Vazamentos antigos coletados hoje; senha já trocada; e-mail casado com a conta errada | Comparar exposed_at com a última troca; rejeitar correspondências múltiplas; dry_run; limite por execução |
| Senha vazada em logs | O campo password vem em texto claro; Logic Apps, Application Insights e Automation guardam entradas e saídas | Descartar o campo no parse; nunca registrar a resposta bruta; log nunca em nível Trace; no Logic Apps, Secure Outputs na ação HTTP e só os campos necessários |
| Administrador ou conta de emergência bloqueada | Papel alto demais ou sem lista de exclusão | Papel User Administrator (Entra) ou personalizado por UO (Google); excluir por ID; tratar 403 como escalonamento |
| Troca de senha sem efeito | Conta sincronizada do AD, domínio federado, SSO de terceiros ou Password Sync | Detectar o tipo de conta; trocar no AD ou no IdP; na nuvem, só revogar sessões |
| Atacante atende ao pedido de troca | Sinalizador de troca sem MFA e sem senha nova | forceChangePasswordNextSignInWithMfa ou senha aleatória |
| Sessões sobrevivem à troca | Infostealers roubam cookies; tokens valem até ~1 h | Revogar sessões / signOut depois da troca; revisar regras de e-mail, MFA e OAuth; investigar o computador |
| Integração para sem aviso | Chave do Vigia expirada, dono desativado ou com troca de senha pendente | Usuário de serviço no Vigia; chave de 90/365 dias; alerta em 401; aviso de expiração no cofre |
429 em cascata | Vários workers; repetição sem jitter | Um worker sequencial por chave; espera com jitter e limite de tentativas |
| Registros perdidos | Mais de 100 itens na janela; consulta por exposed_at | Varrer para trás com until; marca d'água em collected_at com sobreposição |
| Aviso ao funcionário parece phishing | E-mail com link "redefina aqui" | Canal interno conhecido, sem link e sem senha; helpdesk avisado antes |
6. LGPD
- O empregador é o controlador dos dados dos funcionários tratados pela integração. A base usual é o legítimo interesse (art. 7º, IX), com tratamento só do estritamente necessário e transparência (art. 10). Documente o teste de balanceamento.
- Pela Resolução CD/ANPD nº 15/2024, incidentes de risco relevante com dados de autenticação exigem comunicação à ANPD e aos titulares em até 3 dias úteis, e todo incidente deve ser registrado e guardado por pelo menos 5 anos.
- Nem todo alerta é um incidente nos sistemas da empresa (o vazamento pode ter sido num site de terceiros ou num computador pessoal), mas registre internamente cada ação tomada, só com metadados. Valide o fluxo com o jurídico.
Referências
How to
Integrar com Microsoft Entra ID
Revogar sessões e forçar a troca de senha no Microsoft Entra ID quando um funcionário tiver a credencial vazada.
Antes: leia Consumir a API com segurança (consulta, filtros e decisão). Os exemplos de código são ilustrativos: comece em dry_run e valide em contas de teste.
Configuração
- Ative a identidade gerenciada atribuída pelo sistema na conta do Azure Automation ou na Function.
- Conceda as permissões de aplicativo do Microsoft Graph à identidade. Não há tela no portal para isso: um Privileged Role Administrator faz uma vez, via PowerShell (
New-MgServicePrincipalAppRoleAssignment) ou REST (/servicePrincipals/{id}/appRoleAssignments):
User.RevokeSessions.All, para revokeSignInSessions;
User-PasswordProfile.ReadWrite.All, porque User.ReadWrite.All não cobre o passwordProfile;
User.Read.All, para ler accountEnabled, onPremisesSyncEnabled e proxyAddresses.
- Atribua ao service principal da identidade o papel User Administrator (
New-MgRoleManagementDirectoryRoleAssignment). A documentação da Microsoft exige esse papel para alterar passwordProfile em chamadas só de aplicativo.
- Guarde a chave do Vigia no Key Vault e dê à identidade o papel Key Vault Secrets User.
- Espere algumas horas (até cerca de 24) antes do primeiro teste: o token da identidade gerenciada fica em cache e as permissões novas demoram a valer.
O que funciona em cada tipo de conta
| Tipo de conta | Como identificar | Ação |
| Somente nuvem | onPremisesSyncEnabled diferente de true | Forçar troca (PATCH passwordProfile) e depois revokeSignInSessions |
| Sincronizada do AD (PHS/PTA) | onPremisesSyncEnabled = true | Revogar sessões na nuvem e trocar a senha no AD (chamado ou runbook local). A senha da nuvem é sobrescrita pelo AD |
| Domínio federado (AD FS ou outro IdP) | Domínio com autenticação federada | Só revogar sessões; a senha muda no IdP |
| Convidado | userType = Guest | Fora do escopo; só registrar |
| Locatário com Entra ID P2 | Licença | Preferir confirmCompromised + política de Acesso Condicional com "Exigir correção de risco", que funciona também em contas híbridas com write-back |
- Use
forceChangePasswordNextSignInWithMfa, não só forceChangePasswordNextSignIn. Sem MFA, quem conhece a senha vazada (o atacante) pode atender ao pedido de troca. Alternativa: definir uma senha aleatória que ninguém conhece, com recuperação pelo helpdesk.
revokeSignInSessions funciona em qualquer membro do locatário, mas leva alguns minutos. O access token já emitido pode valer até cerca de 1 hora; Exchange, SharePoint e Teams aplicam a revogação quase em tempo real.
- Contas privilegiadas e de emergência ficam fora do alcance do job. Com User Administrator, a tentativa sobre um administrador falha com
403 Authorization_RequestDenied: trate como "escalar para o SOC" e nunca repita. Exclua as contas de emergência pelo ID do objeto e, se possível, coloque-as numa unidade administrativa de gerenciamento restrito, onde permissões de aplicativo não se aplicam.
- Limites: o
$batch do Graph aceita até 20 requisições; use dependsOn para garantir a ordem. As APIs de riskyUsers aceitam 1 requisição por segundo por locatário: agrupe os usuários numa única chamada de confirmCompromised.
- Auditoria: no log de auditoria do Entra, a revogação aparece como "Update StsRefreshTokenValidFrom Timestamp" e a troca forçada como "Set force change user password", com o service principal do job como autor.
Valide em laboratório: o PATCH só com os sinalizadores de troca (sem enviar password) e o comportamento do sinalizador em contas sincronizadas do AD.
Exemplo em PowerShell: runbook do Azure Automation com identidade gerenciada
# EXEMPLO ILUSTRATIVO: adapte nomes, exclusões e tratamento de erros. Comece com $dryRun = $true.
Connect-AzAccount -Identity | Out-Null
Connect-MgGraph -Identity -NoWelcome
$key = Get-AzKeyVaultSecret -VaultName 'kv-vigia' -Name 'vigia-api-key' -AsPlainText
$since = (Get-Date).ToUniversalTime().AddHours(-30).ToString('yyyy-MM-ddTHH:mm:ssZ') # use sua marca d'água
$resp = Invoke-RestMethod -Headers @{ Authorization = "Bearer $key" } `
-Uri "https://api-vigia.itool.com.br/v1/credentials?type=employees&axis=collected&limit=100&since=$since"
$itens = $resp.items | Select-Object domain, username, url, exposed_at, collected_at # nunca a senha
$resp = $null # não escreva a resposta bruta no log
$excluidos = @('<objectId-emergencia-1>', '<objectId-emergencia-2>')
$dryRun = $true; $limite = 10; $acoes = 0
$sel = 'id,userPrincipalName,accountEnabled,userType,onPremisesSyncEnabled,lastPasswordChangeDateTime'
foreach ($i in $itens) {
if (-not $i.username -or $i.username.Contains('*')) { continue } # mascarado: revisão manual
$e = $i.username.ToLower()
$f = [uri]::EscapeDataString("userPrincipalName eq '$e' or mail eq '$e' or proxyAddresses/any(p:p eq 'smtp:$e')")
$u = @((Invoke-MgGraphRequest -Method GET -Uri "v1.0/users?`$filter=$f&`$select=$sel").value)
if ($u.Count -ne 1) { Write-Output "REVISAR $e ($($u.Count) contas)"; continue }
$u = $u[0]
if (-not $u.accountEnabled -or $u.userType -ne 'Member' -or $u.id -in $excluidos) { continue }
if ([datetime]$i.exposed_at -le [datetime]$u.lastPasswordChangeDateTime) { continue } # senha já trocada
if ($u.onPremisesSyncEnabled) { Write-Output "HIBRIDO $($u.id): abrir chamado para troca no AD" }
if ($dryRun -or $acoes -ge $limite) { Write-Output "SIMULACAO $($u.id)"; continue }
# 1) forçar troca com MFA (só contas nuvem) 2) revogar sessões, nessa ordem
$req = @()
if (-not $u.onPremisesSyncEnabled) {
$req += @{ id = '1'; method = 'PATCH'; url = "/users/$($u.id)"; headers = @{ 'Content-Type' = 'application/json' }
body = @{ passwordProfile = @{ forceChangePasswordNextSignIn = $true; forceChangePasswordNextSignInWithMfa = $true } } }
}
$rev = @{ id = '2'; method = 'POST'; url = "/users/$($u.id)/revokeSignInSessions" }
if ($req.Count) { $rev.dependsOn = @('1') }
$res = Invoke-MgGraphRequest -Method POST -Uri 'v1.0/$batch' -Body (@{ requests = $req + $rev } | ConvertTo-Json -Depth 6)
$res.responses | ForEach-Object { Write-Output "ACAO $($u.id) req=$($_.id) status=$($_.status)" } # 403 = escalar
$acoes++
}
Referências
How to
Integrar com Google Workspace
Encerrar sessões e forçar a troca de senha no Google Workspace quando um funcionário tiver a credencial vazada.
Antes: leia Consumir a API com segurança (consulta, filtros e decisão). Os exemplos de código são ilustrativos: comece em dry_run e valide em contas de teste.
Configuração
- Crie um projeto dedicado no Google Cloud, ative a Admin SDK API e crie uma conta de serviço para o job.
- No Admin Console, um super admin cria um papel personalizado com
USERS_RETRIEVE, ORGANIZATION_UNITS_RETRIEVE, USERS_RESET_PASSWORD, USERS_FORCE_PASSWORD_CHANGE e USER_SECURITY_ALL.
- Em Conta > Funções de administrador > Atribuir contas de serviço, atribua o papel ao e-mail da conta de serviço, limitado às UOs de funcionários. Assim ela chama a Admin SDK em nome próprio, sem delegação em todo o domínio (que o próprio Google recomenda evitar).
- Rode o job no Cloud Run com essa conta de serviço como identidade, sem chave JSON. Se rodar no Azure ou na AWS, use Workload Identity Federation, também sem chave.
- Guarde a chave do Vigia no Secret Manager, com acesso só para a conta de serviço.
Ações
- Forçar a troca:
users.patch com changePasswordAtNextLogin: true e uma senha nova aleatória enviada em hash (hashFunction). A redefinição pelo administrador encerra as sessões, revoga as senhas de app e pede novo login nos celulares.
- Derrubar as sessões: depois da troca,
POST /admin/directory/v1/users/{userKey}/signOut. Ele não encerra apps de desktop (Gmail, Drive para desktop) nem revoga acessos OAuth de terceiros.
- Modo agressivo (opcional, para infostealer): revogar tokens OAuth (
tokens.delete) e senhas de app (asps.delete). Também derruba integrações legítimas, por isso não deve ser o padrão.
- Nunca desligue a verificação em duas etapas nem gere códigos de backup no fluxo automático.
- A senha aleatória deixa o usuário sem acesso até falar com o helpdesk. Usar só
changePasswordAtNextLogin é mais brando, mas quem tem a senha vazada é quem escolheria a nova. Deixe isso configurável.
Não aja automaticamente nestes casos
- Administradores (
isAdmin, isDelegatedAdmin): só um super admin redefine a senha deles. Escale para uma pessoa.
- UOs com SSO de terceiros (Entra, Okta via SAML):
changePasswordAtNextLogin não tem efeito. Troque a senha no provedor de identidade; no Google, use só o signOut. Como a API não informa se o usuário é federado, mantenha uma lista configurável dessas UOs.
- Password Sync com o AD: a senha vazada continua viva no AD. Remedie lá.
- Contas suspensas ou arquivadas já estão contidas. Um
404 deve ser registrado, não repetido.
- Limites: 2.400 consultas por minuto por projeto (padrão). O
429 rateLimitExceeded vale para a conta do Workspace inteira: espere 1, 2, 4, 8 e 16 s com jitter.
- Auditoria: as ações aparecem na Reports API (
applicationName=admin) como CHANGE_PASSWORD, CHANGE_PASSWORD_ON_NEXT_LOGIN e RESET_SIGNIN_COOKIES. O Alert Center do Google também tem um alerta nativo de senha vazada: cruze com o Vigia para não agir em dobro.
Valide em uma UO de teste que users.patch e users.signOut funcionam com a conta de serviço e o papel atribuído, antes de liberar para toda a empresa.
Exemplo em Python: remediação com conta de serviço (Cloud Run)
# EXEMPLO ILUSTRATIVO: adapte exclusões e tratamento de erros; valide em uma UO de teste.
import google.auth, secrets, hashlib
from google.auth.transport.requests import AuthorizedSession
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/admin.directory.user.security"]
creds, _ = google.auth.default(scopes=SCOPES)
gs = AuthorizedSession(creds)
DIR = "https://admin.googleapis.com/admin/directory/v1/users"
UOS_EXCLUIDAS = ("/TI/Administradores", "/Federados-SSO", "/Contas-de-servico")
def remediar(email: str, dry_run: bool = True) -> str:
r = gs.get(f"{DIR}/{email}") # aceita e-mail principal ou alias
if r.status_code == 404:
return "nao_encontrado" # registrar, não repetir
r.raise_for_status()
u = r.json()
if u.get("isAdmin") or u.get("isDelegatedAdmin"):
return "admin_escalar_para_pessoa"
if u.get("suspended") or u.get("archived"):
return "ja_contido"
if u.get("orgUnitPath", "/").startswith(UOS_EXCLUIDAS):
return "excluido"
if dry_run:
return "simulacao"
nova = secrets.token_urlsafe(32) # ninguém conhece; recuperação pelo helpdesk
gs.patch(f"{DIR}/{u['id']}", json={
"password": hashlib.sha1(nova.encode()).hexdigest(),
"hashFunction": "SHA-1",
"changePasswordAtNextLogin": True}).raise_for_status()
nova = None
gs.post(f"{DIR}/{u['id']}/signOut").raise_for_status() # depois da troca de senha
return "remediado"
Referências