Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Construir um bot ChatOps com slash commands (/deploy, /scale, /rollback, /approve)
Implementar RBAC real baseado em roles de usuário
Implementar geração de diff de mudanças para preview antes de aprovação
Projetar um workflow de aprovação com expiração e múltiplos aprovadores
Completar os 2 TODOs: temPermissao com RBAC real e diff de versões
Por que isso importa
ChatOps é o padrão operacional de equipes de alto desempenho: operações críticas (deploy, rollback, scale) são executadas via Slack/Teams, com contexto visível para todo o time, trilha de auditoria e gate de aprovação integrado.
O problema com bots simples: qualquer um pode executar qualquer comando. RBAC (Role-Based Access Control) define que um developer pode fazer /deploy staging mas não /deploy prod. Um sre pode fazer tudo exceto certas operações administrativas. Um manager pode aprovar mas não executar.
Conceitos Fundamentais
Matriz de Permissões
const PERMISSOES: Record<string, string[]> = {
developer: ['/deploy staging', '/scale staging', '/logs', '/status'],
sre: ['/deploy staging', '/deploy prod', '/scale', '/rollback', '/logs', '/status', '/restart'],
manager: ['/status', '/logs', '/scale down'],
admin: ['*'], // todas as permissões
};A lógica de verificação precisa ser implementada:
TODO 1: temPermissao(usuario, comando)
function temPermissao(usuario: Usuario, comando: string): boolean {
// Admin tem acesso a tudo
if (usuario.roles.includes('admin')) return true;
// Para cada role do usuário, verificar se o comando está permitido
for (const role of usuario.roles) {
const permissoesDaRole = PERMISSOES[role] || [];
// Wildcard: role tem acesso a tudo
if (permissoesDaRole.includes('*')) return true;
// Match exato: /deploy prod
if (permissoesDaRole.includes(comando)) return true;
// Match por prefixo: /deploy permite /deploy staging E /deploy prod?
// Não! Usar match exato para mais segurança
// Mas podemos permitir prefixo sem ambiente:
// '/scale' no sre permite '/scale staging' e '/scale prod'
const base = comando.split(' ')[0]; // extrai '/scale' de '/scale prod'
if (permissoesDaRole.includes(base)) return true;
}
return false;
}Exemplo de comportamento:
const dev = { roles: ['developer'] }
temPermissao(dev, '/deploy staging') // true — na lista
temPermissao(dev, '/deploy prod') // false — não na lista
temPermissao(dev, '/rollback') // false — não na lista
const sre = { roles: ['sre'] }
temPermissao(sre, '/scale prod') // true — '/scale' está na lista do sreTODO 2: Diff de Mudanças
Antes de requerer aprovação para /deploy prod, mostrar o que vai mudar:
async function gerarDiff(servico: string, versaoNova: string): Promise<string> {
// Em produção: chamar API do GitHub/GitLab para comparar tags
// GET /repos/{owner}/{repo}/compare/{versao_atual}...{versao_nova}
// Mock realista para o lab
const versaoAtual = '2.0.5'; // buscar do cluster/registry real
const response = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 400,
messages: [{
role: 'user',
content: `Gere um diff de changelog realista para deploy de ${servico} de ${versaoAtual} para ${versaoNova}.
Formato:
## Mudanças ${versaoAtual} → ${versaoNova}
### Features
- ...
### Fixes
- ...
### Breaking Changes (se houver)
- ...`
}]
})
return response.content[0].text
}
// Na função executarDeploy, antes de criar a aprovação:
const diff = await gerarDiff(servico, versao)
const acao = criarAcaoAprovacao(
`Deploy ${servico}:${versao} em ${ambiente}\n\n${diff}`,
`/deploy ${args.join(' ')}`,
usuario,
)Workflow de Aprovação
interface AcaoAprovacao {
id: string;
descricao: string;
autor: Usuario;
aprovadores_necessarios: string[]; // roles que podem aprovar
aprovacoes: string[]; // IDs dos usuários que já aprovaram
status: 'pendente' | 'aprovado' | 'rejeitado';
expirado_em: Date; // 30 min de validade
}Fluxo completo:
/deploy api v2.1.0 prod (por dev Ana)
→ RBAC check: developer pode /deploy staging, não /deploy prod → BLOQUEADO
/deploy api v2.1.0 prod (por sre Carlos)
→ RBAC check: sre pode /deploy prod → OK
→ diff gerado → aprovação criada APR-001 (30 min)
→ mensagem: "🔐 Deploy requer aprovação! ID: APR-001. Aguardando: manager, sre"
/approve APR-001 (por mgr Paula)
→ RBAC check: Paula tem role 'manager' que está em aprovadores_necessarios
→ ação marcada como aprovada
→ comando executado: deploy iniciadoAprofundamento Técnico
RBAC com Herança de Roles
Para sistemas complexos, roles herdam de outras:
const HIERARQUIA: Record<string, string[]> = {
'sre': ['developer'], // sre herda tudo de developer
'admin': ['sre', 'manager'], // admin herda tudo
}
function expandirRoles(roles: string[]): string[] {
const todas = new Set(roles)
for (const role of roles) {
for (const herdada of HIERARQUIA[role] || []) {
todas.add(herdada)
}
}
return [...todas]
}
function temPermissao(usuario: Usuario, comando: string): boolean {
const rolesExpandidas = expandirRoles(usuario.roles)
// verificação com todas as roles herdadas
}Expiração de Aprovações
// Na verificação de aprovação:
if (new Date() > acao.expirado_em) {
aprovacoesPendentes.delete(acaoId)
return { mensagem: `⏰ Aprovação ${acaoId} expirou. Crie um novo deploy.` }
}
// Limpeza periódica de aprovações expiradas:
setInterval(() => {
const agora = new Date()
for (const [id, acao] of aprovacoesPendentes) {
if (agora > acao.expirado_em) {
aprovacoesPendentes.delete(id)
console.log(`[LIMPEZA] Aprovação ${id} expirou e foi removida`)
}
}
}, 60_000) // a cada minutoExemplos Anotados
Exemplo 1: Sequência Completa com RBAC
async function demonstrarRBAC() {
const usuarios = {
dev: { id: 'u1', nome: 'Ana', roles: ['developer'] },
sre: { id: 'u2', nome: 'Carlos', roles: ['sre'] },
mgr: { id: 'u3', nome: 'Paula', roles: ['manager'] },
}
// 1. Dev tenta deploy em prod → bloqueado
const r1 = await processarComando({
comando: '/deploy', args: ['api', 'v2.1.0', 'prod'],
usuario: usuarios.dev, canal: '#devops', timestamp: new Date(),
})
console.log(r1) // "❌ Sem permissão para /deploy prod"
// 2. SRE faz deploy em prod → cria aprovação
const r2 = await processarComando({
comando: '/deploy', args: ['api', 'v2.1.0', 'prod'],
usuario: usuarios.sre, canal: '#devops', timestamp: new Date(),
})
console.log(r2) // "🔐 Deploy em prod requer aprovação! ID: APR-1234..."
// 3. Manager aprova
const acaoId = extrairId(r2) // extrai APR-1234 da mensagem
const r3 = await processarComando({
comando: '/approve', args: [acaoId],
usuario: usuarios.mgr, canal: '#devops', timestamp: new Date(),
})
console.log(r3) // "✅ APR-1234 aprovado por Paula! Executando: /deploy api v2.1.0 prod"
}Exemplo 2: TODO 1 Completo
function temPermissao(usuario: Usuario, comando: string): boolean {
if (usuario.roles.includes('admin')) return true;
for (const role of usuario.roles) {
const perms = PERMISSOES[role] || [];
if (perms.includes('*')) return true;
if (perms.includes(comando)) return true;
// base command: '/scale prod' → base '/scale'
const base = comando.split(' ')[0];
if (perms.includes(base)) return true;
}
return false;
}
// Testes:
// temPermissao({roles: ['developer']}, '/logs') → true
// temPermissao({roles: ['developer']}, '/deploy prod') → false
// temPermissao({roles: ['sre']}, '/scale prod 5') → true (base '/scale' está na lista)
// temPermissao({roles: ['admin']}, '/qualquer-coisa') → truePadrões e Armadilhas
Padrões
Padrão 1: Aprovação com diff visível
const acao = criarAcaoAprovacao(
`Deploy ${servico}:${versao} em prod\n\nDIFF:\n${diff}`,
...
)O aprovador precisa ver o que vai mudar. Aprovação sem contexto é teatro de segurança.
Padrão 2: ID único e imprevisível
id: `APR-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`
// APR-1703123456789-k2p9
Date.now() evita colisões, o sufixo aleatório previne enumeração.Padrão 3: Feedback claro sobre o que falta para aprovar
return `👍 Aprovação registrada por ${usuario.nome}.
Ainda necessário: ${faltam} aprovações de ${rolesNecessarias}.
Use /approve ${acaoId} para aprovar.`Armadilhas
⚠️ Armadilha 1: RBAC com return true como placeholder
// O starter tem isso como placeholder:
return true; // ← TODO não implementado!Com esse placeholder, QUALQUER usuário passa no RBAC. Implemente antes de testar segurança.
⚠️ Armadilha 2: Aprovação sem verificação de expiração
// ERRADO: aprovação de 3 dias atrás ainda válida
if (!acao.aprovacoes.includes(usuario.id)) {
acao.aprovacoes.push(usuario.id) // ← não checa expiração!
}
// CORRETO: checar antes
if (new Date() > acao.expirado_em) return { mensagem: 'Aprovação expirada.' }⚠️ Armadilha 3: Mesmo usuário aprovando a própria ação
// O autor não deveria aprovar sua própria ação
if (acao.autor.id === usuario.id) {
return { mensagem: '❌ Você não pode aprovar sua própria ação.' }
}Se não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
2 TODOs no starter.ts:
TODO 1 — temPermissao(usuario, comando):
function temPermissao(usuario: Usuario, comando: string): boolean {
if (usuario.roles.includes('admin')) return true;
for (const role of usuario.roles) {
const perms = PERMISSOES[role] || [];
if (perms.includes('*') || perms.includes(comando)) return true;
const base = comando.split(' ')[0];
if (perms.includes(base)) return true;
}
return false;
}TODO 2 — diff de mudanças em executarDeploy:
const diff = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 300,
messages: [{ role: 'user', content: `Gere changelog resumido para deploy de ${servico} versão ${versao} em prod.` }]
})
const textoiff = diff.content[0].textRode com npx ts-node starter.ts e observe o fluxo: status → tentativa de deploy em prod → criação de aprovação → aprovação executada.
Agora você está pronto para o lab.