1. Ensaiar o caminho de acesso sob a falha prevista
A contingência começa por uma pergunta operacional: quem consegue executar a primeira ação quando o caminho habitual falha? Uma conta de emergência guardada num cofre não resolve o problema se abrir esse cofre exige o SSO indisponível. No exercício fictício APS, o incidente afeta a federação de identidade e o runbook remete para um portal que depende dela. O plano possui uma credencial, mas não demonstrou um caminho utilizável. Regista a cadeia completa: operador, mecanismo de autenticação, aprovação, obtenção da credencial, rede e serviço de destino. Define a finalidade do acesso. Recuperar a federação, reparar o pipeline e executar uma ação operacional podem exigir capacidades diferentes. O nome breakglass não atribui automaticamente todos os papéis. Para cada finalidade, identifica âmbito, responsável, condições de utilização e evidência de encerramento. O acesso excecional deve ser rastreável e regressar ao modelo normal depois da intervenção. As regras concretas dependem da organização; este exercício não descreve procedimentos internos BNP Paribas. Um bom ensaio remove deliberadamente a dependência que se pretende tolerar, num ambiente autorizado. Confirmar o acesso num dia em que o SSO funciona não responde à pergunta anterior. Preserva o resultado, as limitações e as ações em falta. Se uma aprovação também depende do sistema indisponível, essa dependência precisa de uma solução operacional previamente acordada, não de uma improvisação durante o incidente.
2. Confirmar identidade e destino em cada ferramenta
Uma sessão de terminal pode conter várias configurações de autenticação. As credenciais usadas pelo gcloud são distintas da configuração Application Default Credentials que uma biblioteca Python pode consultar. ADC procura primeiro a configuração indicada por GOOGLE_APPLICATION_CREDENTIALS, depois o ficheiro local convencional e finalmente a conta associada obtida através do metadata server. Esta ordem descreve resolução de credenciais, não a qualidade ou segurança relativa de cada opção. Uma variável herdada pelo runner pode explicar por que razão a aplicação não usa a conta associada que o operador esperava. Antes de uma ação de contingência, confirma a identidade efetiva do processo e o destino selecionado. O projeto configurado não substitui essa verificação. Da mesma forma, obter credenciais GKE pode alterar o contexto atual do kubectl. Se passaste do cluster de ensaio para produção para consultar informação, uma operação seguinte sem contexto explícito pode atingir produção. Usa uma verificação compatível com o teu processo e conserva o destino no registo da mudança. Também não confundas obter informação de acesso ao cluster com receber autorização para alterar workloads. A capacidade de consultar metadados e gerar configuração local não demonstra que uma operação de criação esteja permitida. Na revisão, separa estas perguntas: quem apresenta o pedido, a que cluster, para fazer o quê, e que controlos ainda o podem rejeitar? Esta separação ajuda a diagnosticar falhas sem alargar permissões por tentativa.
3. Coordenar alterações manuais com a fonte gerida
Config Sync distingue a prevenção de drift por webhook da reconciliação que repõe o estado definido na fonte. Desligar a primeira não elimina a segunda. Num ensaio, a equipa altera um campo manualmente, observa sucesso imediato e declara a correção concluída. Minutos depois o campo regressa ao valor da fonte. A sequência é compatível com reconciliação; não prova que o pedido inicial falhou ou que um operador contrariou a equipa. Identifica o controlador e a fonte responsáveis antes de repetir a alteração. A primeira opção deve ser alinhar a correção com o processo gerido aplicável. Se uma exceção aprovada exigir suspensão temporária, o runbook precisa de indicar o método instalado, o âmbito a suspender, os objetos ou configurações a preservar e a forma de retomar. Parar um Pod isolado pode não bastar porque outro componente o gere. A documentação distingue caminhos de gestão da fonte raiz e de fontes limitadas a namespaces; não copies comandos de um caminho para outro sem confirmar a configuração real. No fecho, compara a intenção aprovada, o estado observado e a fonte que voltará a reconciliar. Uma alteração temporária pode precisar de ser incorporada ou revertida de forma controlada. Define quem toma essa decisão e quando. A pergunta operacional é se o próximo ciclo de gestão manterá o estado pretendido, não apenas se o último comando devolveu sucesso.
4. Desenhar fronteiras de identidade entre clusters
Numa fleet, algumas funcionalidades usam sameness para tratar certos recursos com os mesmos nomes como equivalentes. As consequências dependem da funcionalidade e dos seus seletores. Nesta aula, focamo-nos numa forma concreta de principal de workload: pool, namespace e nome da Kubernetes ServiceAccount. Se estes elementos coincidem num âmbito partilhado, mudar o nome do cluster não demonstra separação dessa identidade. Projetos distintos também podem partilhar o âmbito quando usam fleet Workload Identity Federation da forma descrita no cenário. Para rever a fronteira, começa pela concessão real e identifica o que seleciona. Não concluas que todos os workloads da fleet receberam todas as permissões. Também não concluas que nomes iguais em pools distintos são o mesmo principal. O identificador completo e o mecanismo de autorização importam. No exercício, produção e ensaio são classes de confiança fornecidas pelo autor do inventário; não são propriedades criptográficas nem campos Google Cloud que imponham isolamento automaticamente. A criação de namespaces e ServiceAccounts merece revisão porque pode permitir criar nomes já selecionados por uma concessão. Num cenário fictício, uma equipa com menos confiança cria o par ledger/exporter num cluster do mesmo pool. O problema não é resolvido ao chamar trial ao cluster. Analisa quem controla esses objetos, quais workloads podem usá-los e se o âmbito da concessão corresponde à intenção. Mantém os dados e a hipótese separados até confirmar o acesso efetivo.
5. Incluir identidades fora da lista de membros
Uma lista de membros responde à pergunta administrativa sobre registo na fleet. Pode não responder à pergunta de segurança sobre todos os workloads que partilham um pool. A documentação descreve o caso de clusters no projeto anfitrião, incluindo um não membro, com Workload Identity Federation no mesmo pool. Identidades que correspondem ao seletor por namespace e ServiceAccount podem ser relevantes mesmo fora da lista inicial. Esta possibilidade torna a delimitação do inventário parte da revisão, em vez de um simples passo de exportação. Na ficha local, cada registo tem identificador, cluster, projeto, pool, namespace, ServiceAccount, classe de confiança e indicador de membership. Projeto e membership são conservados como contexto, mas não separam o triplo escolhido para comparação. Um registo sem pool confirmado usa null. O programa marca o inventário como incompleto; não inventa um pool separado nem herda o valor da linha anterior. O mesmo cuidado aplica-se a uma classe de confiança ainda desconhecida. Quando receberes o inventário de várias equipas, confirma a origem e a data dos dados antes de usar o resultado numa mudança. O exercício não consulta serviços para verificar atualidade. Um relatório sem sobreposições só descreve as linhas fornecidas e a regra implementada. Para o comité, apresenta abrangência, lacunas, achados e quem vai confirmar os pontos em falta. Não transformes ausência de informação em garantia de isolamento.
6. Recuperar configuração compatível com o destino
Uma revisão antiga de configuração pode ser identificável e continuar incompatível com o destino. Um pacote que contém Deployment em extensions/v1beta1 não pode depender de um servidor posterior a Kubernetes 1.16 que já não serve essa API. A migração para uma API suportada exige rever campos e comportamento, incluindo seletores obrigatórios. Não basta editar uma etiqueta de versão e assumir equivalência. Usa o destino previsto no ensaio e verifica a interpretação dos objetos antes de ligar a aplicação ao tráfego real. CustomResourceDefinitions acrescentam outra dependência. A definição estabelece a API dos recursos personalizados; os objetos armazenados podem conter estado necessário à recuperação. Apagar uma CRD elimina também os objetos associados e recriá-la não os repõe automaticamente. Uma proposta de “limpar e reinstalar” precisa, por isso, de uma análise do que será removido, da cópia recuperável e do caminho de validação. O nome igual de um objeto novo não demonstra continuidade dos dados anteriores. Relaciona estas dependências com o plano de arranque. Se o pipeline que cria a rede só corre em runners dessa rede ainda inexistente, conservar código e estado não basta. É necessário um ambiente autorizado que consiga iniciar o trabalho. No exercício, a equipa desenha primeiro esse caminho e depois ensaia a compatibilidade dos manifests. Regista a sequência real, com responsáveis por identidade, rede e configuração, para que o plano possa ser executado por outra pessoa.
7. Tratar admissão como dependência operacional
Uma identidade autenticada e autorizada pode apresentar um pedido que falha posteriormente na admissão. No cenário, um webhook corresponde ao pedido, não há exclusões e a chamada termina por timeout. Com failurePolicy: Fail, o erro da chamada impede aceitar esse pedido. Repetir autenticação ou conceder mais papéis não repara o endpoint indisponível. A investigação deve distinguir rejeição explícita de uma política, erro de comunicação e falha num controlo anterior. Esta distinção importa na recuperação porque o serviço de admissão também precisa de um caminho operacional. Se a aplicação que queremos restaurar é necessária para o webhook que admite o próprio restauro, o plano pode ter outra dependência circular. Antes da janela, revê âmbito, disponibilidade e procedimento autorizado para esse modo de falha. Não alteres genericamente a política para Ignore só para obter um resultado verde: isso pode retirar um controlo que a organização exige. A solução tem de preservar o objetivo do controlo e ser ensaiada no âmbito aprovado. No registo do incidente, guarda uma observação concreta: operação pedida, destino, fase da rejeição e erro recebido. Um relatório “sem permissões” quando ocorreu timeout de admissão encaminha a equipa para uma correção errada. Para a passagem de turno, indica o bloqueio real, o responsável pela dependência e a próxima ação validada. O diagnóstico deve reduzir incerteza antes de aumentar privilégios ou mudar configuração.
8. Executar o inventário de sobreposições
Executa python3 run.py com Python 3.13 ou compatível. O código usa apenas a biblioteca padrão e produz JSON no terminal. Não contacta Google Cloud, não lê políticas IAM nem altera recursos. Compara exatamente o triplo pool, namespace e ServiceAccount dos registos fornecidos. O âmbito assume a forma de principal baseada nesses nomes; não implementa sujeitos por UID, todos os tipos de seletores IAM ou avaliação de condições. Os aliases de pool representam identificadores completos e únicos, incluindo o projeto anfitrião; nomes curtos de pools em projetos diferentes não bastam para esta comparação. São dados fictícios, não credenciais reais. Começa pelos nove casos nomeados. mixed-trust produz um grupo com classes conhecidas distintas. same-trust-sharing mostra uma identidade partilhada sem diferença de classe declarada. nonmember-overlap mantém o não membro na análise. unknown-pool e unknown-trust deixam a conclusão incompleta. Compara depois different-pool, different-namespace e different-service-account para perceber que dimensões definem a chave. Nenhum resultado coloca accessProven ou productionAuthorized a true. O programa percorre 5184 pares de combinações e rejeita 14 entradas inválidas. Também confirma que inverter os pares e testar as seis ordens de três registos preserva o resultado e que a entrada não é modificada. Estas verificações demonstram propriedades do comparador, não completude do inventário real. Como tarefa, acrescenta um terceiro cluster, justifica a sua classe de confiança e explica que evidência pedirias antes de aprovar a mudança. Uma sobreposição é um ponto de investigação; a ausência de achados não é uma autorização.
"""Original inventory exercise for name-based workload principals; not an IAM evaluator."""
from copy import deepcopy
from itertools import product, permutations
import hashlib
import json
from pathlib import Path
FIELDS = {'id', 'cluster', 'project', 'pool', 'namespace', 'serviceAccount', 'trust', 'fleetMember'}
def inspect_inventory(records):
"""Compare supplied pool/namespace/serviceAccount tuples exactly.
Scope assumes the namespace/name principal form, not UID-based subjects or
all IAM principal selectors. Pool and trust may be unknown (None). Project
and fleet membership remain inventory context, not separators for a shared
pool. Findings require review; no actual access or effective policy is read.
"""
if not isinstance(records, list) or not records:
raise ValueError('nonempty inventory required')
ids, groups, unknown = set(), {}, []
for record in records:
if not isinstance(record, dict) or set(record) != FIELDS:
raise ValueError('exact inventory fields required')
for field in FIELDS - {'fleetMember'}:
value = record[field]
if value is None and field in {'pool', 'trust'}:
continue
if not isinstance(value, str) or not value.strip():
raise ValueError('nonempty string required: ' + field)
if type(record['fleetMember']) is not bool:
raise ValueError('membership must be boolean')
if record['id'] in ids:
raise ValueError('duplicate record ID')
ids.add(record['id'])
missing = [field for field in ('pool', 'trust') if record[field] is None]
if missing:
unknown.append({'id': record['id'], 'fields': missing})
if record['pool'] is not None:
key = (record['pool'], record['namespace'], record['serviceAccount'])
groups.setdefault(key, []).append(record)
shared = []
for key, members in sorted(groups.items()):
if len(members) < 2:
continue
trusts = sorted({r['trust'] for r in members if r['trust'] is not None})
shared.append({'principal': dict(zip(('pool', 'namespace', 'serviceAccount'), key)),
'ids': sorted(r['id'] for r in members),
'clusters': sorted({r['cluster'] for r in members}),
'projects': sorted({r['project'] for r in members}),
'knownTrusts': trusts,
'mixedKnownTrust': len(trusts) > 1,
'unknownTrust': any(r['trust'] is None for r in members),
'includesNonMember': any(not r['fleetMember'] for r in members)})
return {'records': len(records), 'sharedPrincipals': shared,
'mixedTrustGroups': sum(g['mixedKnownTrust'] for g in shared),
'unknownRecords': sorted(unknown, key=lambda r: r['id']),
'inventoryComplete': not unknown,
'accessProven': False, 'productionAuthorized': False}
def workload(name, **changes):
return {'id': name, 'cluster': 'cluster-' + name, 'project': 'project-' + name,
'pool': 'pool-a', 'namespace': 'funds', 'serviceAccount': 'processor',
'trust': 'production', 'fleetMember': True, **changes}
def evidence():
a = workload('a')
variants = [
('mixed-trust', workload('b', trust='trial')),
('different-pool', workload('b', pool='pool-b', trust='trial')),
('different-namespace', workload('b', namespace='trial', trust='trial')),
('different-service-account', workload('b', serviceAccount='trial', trust='trial')),
('same-trust-sharing', workload('b')),
('nonmember-overlap', workload('b', trust='trial', fleetMember=False)),
('unknown-pool', workload('b', pool=None, trust='trial')),
('unknown-trust', workload('b', trust=None)),
]
fixtures = []
for name, b in variants:
inventory = [a, b]
before = deepcopy(inventory)
result = inspect_inventory(inventory)
assert inventory == before
assert inspect_inventory(list(reversed(inventory))) == result
fixtures.append({'id': name, **result})
assert fixtures[0]['mixedTrustGroups'] == 1
assert all(f['mixedTrustGroups'] == 0 for f in fixtures[1:5])
assert len(fixtures[4]['sharedPrincipals']) == 1
assert fixtures[5]['sharedPrincipals'][0]['includesNonMember']
assert fixtures[5]['mixedTrustGroups'] == 1
assert not fixtures[6]['inventoryComplete'] and not fixtures[7]['inventoryComplete']
triple = [a, workload('b', trust='trial'), workload('c', trust=None, fleetMember=False)]
triple_before = deepcopy(triple)
triple_result = inspect_inventory(triple)
assert triple_result['mixedTrustGroups'] == 1
assert triple_result['sharedPrincipals'][0]['unknownTrust']
assert not triple_result['inventoryComplete']
for ordering in permutations(triple):
assert inspect_inventory(list(ordering)) == triple_result
assert triple == triple_before
fixtures.append({'id': 'mixed-and-unknown', **triple_result})
values = list(product(('pool-a', 'pool-b', None), ('funds', 'trial'),
('processor', 'observer'), ('production', 'trial', None),
(True, False)))
combinations = 0
for left, right in product(values, repeat=2):
def row(name, values):
return workload(name, **dict(zip(('pool','namespace','serviceAccount','trust','fleetMember'), values)))
result = inspect_inventory([row('a', left), row('b', right)])
same = left[0] is not None and right[0] is not None and left[:3] == right[:3]
mixed = same and left[3] is not None and right[3] is not None and left[3] != right[3]
assert len(result['sharedPrincipals']) == int(same)
assert result['mixedTrustGroups'] == int(mixed)
assert result['inventoryComplete'] == all(x[0] is not None and x[3] is not None for x in (left,right))
assert not result['accessProven'] and not result['productionAuthorized']
combinations += 1
invalid = [[], {}, [a, a], [{**a, 'id': ''}], [{**a, 'cluster': None}],
[{**a, 'pool': ''}], [{**a, 'trust': ' '}], [{**a, 'fleetMember': 1}],
[{**a, 'namespace': None}], [{**a, 'serviceAccount': []}],
[{k:v for k,v in a.items() if k != 'project'}], [{**a, 'role': 'admin'}],
[{**a, 'project': False}], [None]]
for records in invalid:
try:
inspect_inventory(records)
except ValueError:
pass
else:
raise AssertionError('invalid input accepted')
return {'fixtures': fixtures, 'pairCombinations': combinations, 'invalidInputs': len(invalid),
'inputPreserved': True, 'orderIndependent': True, 'triplePermutations': 6, 'network': False,
'cloudExecuted': False, 'iamPoliciesRead': False, 'persistentWrites': False,
'independentVerification': False,
'scriptSha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}
if __name__ == '__main__':
print(json.dumps(evidence(), sort_keys=True, indent=2))
Dois clusters com nomes diferentes podem partilhar o mesmo principal selecionado por pool, namespace e ServiceAccount; a membership não delimita necessariamente todo o inventário relevante.
Armadilhas comuns
SSO no caminho de emergência; identidade ADC presumida pelo CLI; contexto kubectl anterior; webhook desligado como reconciliação suspensa; nomes de cluster como fronteira IAM.
Tópicos relacionados: Evidência e dependências de recuperação · Gestão de ambientes e controlo de alterações · Identidade e segurança de pipelines
Contingência exige acesso utilizável, destino confirmado, configuração recuperável e identidades com âmbitos compreendidos e verificados.
Referência: Operations best practices · Current linked guide; edition date unconfirmed (2026-09-30 inspection)