← Professional Cloud Security Engineer: controlos e evidência
17 / 18 · 135 MIN

Operações: pipelines, evidência e processamento de alertas

Rever identidades de execução, âmbito de análise, exceções, manutenção, logs e efeitos de retentativas em casos APS.

1. Identificar quem consegue executar alterações

Uma equipa fictícia de operações de fundos prepara uma atualização do serviço de preços. O fornecedor altera código, a equipa de plataforma gere o trigger e APS acompanha a passagem a produção. Este caso não descreve procedimentos internos do BNP Paribas. O objetivo é conseguir ligar uma alteração concreta à identidade que a executa e à evidência necessária para aceitar o resultado. Começa por desenhar três relações: quem modifica o repositório, quem autoriza a execução e qual a conta usada durante o build. Não concluas que uma pessoa sem acesso à consola não influencia recursos cloud. Nos builds iniciados por trigger, a conta configurada nesse trigger prevalece sobre a indicada no ficheiro de build. A execução com conta gerida pelo utilizador exige também um destino de logs suportado: Cloud Logging ou Cloud Storage pertencente ao utilizador. Um bucket criado pela Google mas pertencente ao utilizador não é o mesmo que um bucket pertencente à Google. Estas diferenças devem aparecer na revisão técnica. Propõe um ensaio de aceitação: registar revisão do código, identidade efetiva, destino dos logs e operação permitida esperada. Uma aprovação no pull request não substitui essa observação. Para triggers GitHub, Comment control restringe o início da execução; o revisor continua a analisar o conteúdo e os privilégios usados. No comité de mudança, explica o impacto: aprovar uma revisão diferente da executada deixa a decisão sem ligação ao artefacto entregue.

2. Definir o âmbito e a atualidade da análise de imagens

O serviço de preços usa uma imagem de recuperação guardada desde o último trimestre. A equipa promove uma tag e apresenta o relatório antigo como prova de uma nova análise. Em Artifact Analysis, a análise inicial acompanha o digest; mudar a tag não desencadeia, por si só, outra análise. A atualização de metadados depende de utilização recente. Resultados de imagens sem pulls há mais de 30 dias podem estar desatualizados; efetuar um pull não demonstra atualização instantânea. Numa manifest list, a seleção documentada de uma imagem não prova que todas as plataformas foram analisadas. Constrói uma tabela de decisão própria com cinco colunas: plataforma de destino, digest resolvido, momento observado dos resultados, critério de aceitação e responsável. Supõe que o grupo de recuperação vai usar arm64, enquanto o relatório apresentado corresponde a amd64. O problema não se resolve acrescentando a palavra aprovado à tag. Falta evidência referente ao artefacto que esse grupo vai executar. Uma ausência de findings no relatório disponível pode ser verdadeira para o seu âmbito e insuficiente para a decisão proposta. Como exercício, prepara uma mensagem curta para o responsável da release. Indica qual a plataforma coberta, qual permanece sem demonstração e qual a próxima observação necessária. Mantém a autorização de exceção separada da conclusão técnica. Se o prazo não permitir obter resultados atuais, regista a lacuna e pede uma decisão ao responsável competente; não transformes pressão de calendário em evidência de segurança.

3. Encerrar exceções e confirmar cobertura de manutenção

Durante uma recuperação urgente, APS utiliza uma exceção Binary Authorization no manifesto de um Pod. O serviço volta a responder e o incidente é encerrado. A revisão deve ainda procurar a label de breakglass no manifesto usado para futuras criações. O estado do ticket não retira configuração persistente. A label atualmente documentada é image-policy.k8s.io/break-glass; uma annotation antiga pode continuar a funcionar, mas não deve ser apresentada como configuração recomendada. Retirar a exceção exige coordenar o ciclo de vida do workload, restaurar os controlos previstos e observar uma admissão adequada. Não significa que Pods existentes sejam automaticamente terminados. Numa atividade diferente, um relatório de patching contém SUCCEEDED_REBOOT_REQUIRED e SKIPPED. O primeiro deixa um reinício por fazer. O segundo pode corresponder a membros de um MIG cuja inclusão não foi ativada no job. Separar estas categorias evita declarar cobertura apenas porque não existe FAILED. No caso fictício, desenha a sequência de encerramento de uma janela: confirmar os membros abrangidos, identificar ações pendentes, acordar impacto do reinício e observar o serviço após execução. Se a estratégia for substituir instâncias a partir de um template corrigido, pede evidência das instâncias efetivamente substituídas. Um template novo não demonstra que todos os membros existentes o usam. Apresenta dois resultados no handover: capacidade de servir pedidos e estado dos controlos. Ambos são necessários para uma aceitação informada.

4. Reconstruir o percurso dos logs e o intervalo em falta

Às 10:00, a equipa corrige um sink que não enviou logs para o arquivo central durante uma hora. Os eventos novos chegam, mas isso não demonstra recuperação do intervalo anterior. Um sink corrigido não encaminha automaticamente o histórico; é necessário verificar o que ficou retido na origem e planear uma recuperação separada, quando possível. O armazenamento temporário do Log Router não deve ser tratado como proteção contra erros de configuração. Noutro incidente, um sink agregado com interceção num antecessor altera o encaminhamento para sinks filhos, conservando a exceção documentada de _Required na origem. Desenha duas linhas temporais para o caso: chegada de eventos ao serviço de origem e observação no destino central. Marca a hora da configuração, o intervalo afetado e a primeira evidência depois da correção. O relatório deve conservar três possibilidades: histórico recuperado, histórico ainda disponível mas não recuperado e histórico cuja disponibilidade não está demonstrada. Uma amostra posterior não decide entre elas. Para investigar um projeto sem logs no destino esperado, começa pela hierarquia e pelo percurso, antes de recriar filtros. Depois identifica a identidade que escreve no destino e os erros disponíveis. A equipa de segurança pode precisar de evidência de um período que o dashboard operacional não mostra. Combina com essa equipa o intervalo necessário, a origem alternativa e o responsável pela reconciliação. Não inventes completude a partir de uma contagem que apenas abrange o destino atual.

5. Distinguir ausência de dados, silêncio e resolução

Uma pesquisa procura jsonPayload.result!="ok". Alguns eventos não têm o campo result e não aparecem nos resultados. Na linguagem de pesquisa de Logging, essa comparação num campo de payload em falta não corresponde à entrada. Trata a presença do campo e o valor observado como perguntas separadas. No exercício, divide uma amostra em três grupos: resultado conhecido como favorável, resultado conhecido como desfavorável e resultado ausente. O terceiro exige investigação de instrumentação ou formato; não deve ser contado automaticamente como sucesso. O mesmo cuidado aplica-se ao Security Command Center. Silenciar um finding altera a sua visibilidade habitual, mas não demonstra correção do recurso; os findings silenciados continuam pesquisáveis. No workflow de toxic combinations aqui assumido como já configurado, mudar a prioridade de um caso não muda a severidade do finding. Estas propriedades não são intercambiáveis. O exemplo não promete disponibilidade dessa integração em qualquer edição ou ambiente. Prepara uma nota para a reunião diária: o que mudou no recurso, o que mudou apenas na apresentação e o que ainda falta observar. Se uma regra de mute foi criada para reduzir ruído durante manutenção, atribui um responsável à revisão do seu âmbito. Uma redução de alertas pode ser desejada sem demonstrar redução da exposição. Propõe dois indicadores para o cenário: condições técnicas corrigidas com evidência e ocorrências silenciadas com justificação. Explica por que razão somá-los num único indicador de resolução ocultaria trabalho pendente.

6. Conservar progresso entre o efeito e a confirmação

Um consumidor cria o incidente INC-73 para um evento de segurança e falha ao confirmar a mensagem. Na entrega seguinte, o incidente não deve ser recriado apenas porque o transporte repetiu a entrega. O consumidor precisa de conservar progresso até à confirmação bem-sucedida e usá-lo quando essa confirmação falha. Também existe a falha inversa: confirmar primeiro e perder o processo antes de guardar o efeito. Uma garantia de entrega não integra automaticamente a transação do sistema de incidentes. Para este caso, propõe uma identidade lógica formada por ambiente, origem e identificador do evento. Assume explicitamente um contrato de conteúdo imutável por identidade. Duas entregas com a mesma chave e conteúdo equivalente podem apontar para o mesmo incidente persistido. A mesma chave com conteúdo diferente exige investigação do contrato; não a descartes silenciosamente como repetição. Esta proposta é um exercício de desenho, não uma implementação de transação distribuída fornecida pela Google. O encaminhamento para dead-letter topic também tem limites. O máximo de tentativas é aproximado e o reencaminhamento é best effort. A conta de serviço Pub/Sub necessita de permissões para publicar no destino e confirmar na subscrição de origem. Um teste com a conta humana não verifica essas permissões. Define quem investiga mensagens encaminhadas, que evidência permite retentar e como evitar repetir efeitos já concluídos. O desenho deve responder tanto a trabalho perdido como a trabalho duplicado.

7. Exercício guiado: auditar um registo de eventos

Guarda o código apresentado como run.py e executa python3 run.py num ambiente local com Python 3.13. Só usa a biblioteca padrão e dados sintéticos. Antes de ler o resultado, prevê o caso normal: entrega no minuto 10, efeito guardado no minuto 11 e confirmação bem-sucedida no minuto 12. O programa compara registos declarados; não contacta um broker, não grava incidentes e não verifica persistência real. O hash compara os bytes do payload, sem autenticar a sua origem. Examina retry-reuses-effect. A primeira confirmação falha, mas outra entrega equivalente reutiliza INC-73. A entrega antiga continua listada sem confirmação própria; isso descreve o histórico, não prova backlog atual. Compara com duplicate-effects, onde a mesma identidade tem dois identificadores de efeito. Em repeated-evidence-same-effect existem dois registos do mesmo efeito e o programa não inventa um segundo incidente. Depois observa content-conflict e explica por que uma chave igual não basta para concluir equivalência. Altera os tempos para colocar a confirmação antes do commit. O resultado identifica falta de commit anterior demonstrado. Tempos iguais também deixam a ordem por provar, devido à resolução em minutos deste modelo. Finalmente muda o ambiente ou a origem de um commit: um registo de outro contexto não satisfaz a entrega observada. Entrega como resposta três evidências em falta, o responsável por as obter e a decisão que cada uma pode mudar. Não apresentes a saída como autorização automática para reprocessamento.

8. Decidir e comunicar a passagem para operação

Volta ao serviço fictício de preços. A release passou no pipeline, mas a imagem de recuperação tem evidência antiga, um grupo de VMs aguarda reinício e há uma hora de arquivo por reconciliar. Não apresentes um único estado verde para ocultar estas diferenças. Constrói um quadro com objeto, âmbito, revisão, evidência, ação pendente, responsável e prazo. O comité consegue assim decidir sobre disponibilidade, exposição e esforço restante sem confundir resultados de execução com aceitação operacional. Pratica uma passagem de turno em inglês com quatro frases: qual o serviço afetado, qual o resultado comprovado, qual a incerteza e quem executa o próximo passo. Para o caso INC-73, uma resposta adequada indica que o incidente está registado, que a confirmação inicial falhou e que a nova entrega deve ser reconciliada com o progresso existente. Uma resposta inadequada declara resolvido apenas porque a mensagem deixou de aparecer num painel. A aula associa automação de segurança ao objetivo 4.1 e recolha, deteção e resposta ao objetivo 4.2 do guia consultado. Não cobre sozinha todo o domínio. Usa as perguntas para justificar decisões e explicar as alternativas, em vez de memorizar nomes de estados. O exercício local não aprova produção: não verifica integridade dos registos, relógios distribuídos, permissões ou estado real dos serviços. A aceitação continua a exigir evidência do ambiente abrangido e decisão dos responsáveis definidos no projeto.

"""Offline audit of a fictional event ledger; no broker or transaction implementation."""
import copy
import hashlib
import itertools
import json
import re
from pathlib import Path


def require(value,message):
    if not value: raise ValueError(message)


def nonempty(value):
    return isinstance(value,str) and bool(value.strip())


def minute(value):
    return type(value) is int and value >= 0


def digest(value):
    return hashlib.sha256(value.encode('utf-8')).hexdigest()


def key(row):
    require(isinstance(row,dict),'record object')
    require(all(nonempty(row.get(k))for k in ('environment','source','eventId')),'logical context')
    return tuple(row[k]for k in ('environment','source','eventId'))


def inspect(data):
    require(isinstance(data,dict) and minute(data.get('asOf')),'input and time')
    require(type(data.get('inventoryComplete')) is bool,'inventory flag')
    for name in ('deliveries','commits','acknowledgments'):
        require(isinstance(data.get(name),list),'record lists')
        ids=set()
        for row in data[name]:
            require(isinstance(row,dict) and nonempty(row.get('id')),'record id')
            require(row['id'] not in ids,'unique record id');ids.add(row['id'])
            require(minute(row.get('at')) and row['at']<=data['asOf'],'record time')
            if name!='acknowledgments':key(row)
            if name=='deliveries':require(nonempty(row.get('payload')),'synthetic payload')
            if name=='commits':
                require(nonempty(row.get('effectId')),'effect id')
                require(isinstance(row.get('payloadSha256'),str) and re.fullmatch('[0-9a-f]{64}',row['payloadSha256']),'digest')
            if name=='acknowledgments':
                require(nonempty(row.get('deliveryId')),'delivery reference')
                require(row.get('outcome')in('success','failed'),'ack outcome')
    deliveries={r['id']:r for r in data['deliveries']}
    for ack in data['acknowledgments']:
        require(ack['deliveryId']in deliveries,'known delivery')
        require(ack['at']>=deliveries[ack['deliveryId']]['at'],'ack not before delivery')
    groups={key(r)for r in data['deliveries']+data['commits']};result=[]
    for k in sorted(groups):
        ds=[r for r in data['deliveries']if key(r)==k]
        cs=[r for r in data['commits']if key(r)==k]
        hashes=sorted({digest(r['payload'])for r in ds}|{r['payloadSha256']for r in cs})
        effects=sorted({r['effectId']for r in cs})
        gaps=[];unconfirmed=[];failed=[]
        for delivery in sorted(ds,key=lambda r:r['id']):
            acks=[a for a in data['acknowledgments']if a['deliveryId']==delivery['id']]
            successes=[a for a in acks if a['outcome']=='success']
            if not successes:unconfirmed.append(delivery['id'])
            failed.extend(a['id']for a in acks if a['outcome']=='failed')
            for ack in successes:
                # Strictly earlier: equal minute values cannot prove event ordering.
                matching=[c for c in cs if c['payloadSha256']==digest(delivery['payload']) and c['at']<ack['at']]
                if not matching:gaps.append(ack['id'])
        result.append(dict(environment=k[0],source=k[1],eventId=k[2],deliveryIds=sorted(r['id']for r in ds),
                           payloadHashes=hashes,effectIds=effects,contentConflict=len(hashes)>1,
                           duplicateEffects=len(effects)>1,ackWithoutProvenPriorCommit=sorted(gaps),
                           unconfirmedDeliveryIds=unconfirmed,failedAcknowledgmentIds=sorted(failed),
                           commitWithoutObservedDelivery=bool(cs)and not ds))
    return dict(events=result,inventoryCoverageUnproven=not data['inventoryComplete'],
                actualPersistenceVerified=False,brokerStateVerified=False,reprocessingAuthorized=False)


def sample():
    context=dict(environment='training',source='detector-a',eventId='evt-41')
    payload='SYNTHETIC batch security observation'
    return dict(asOf=100,inventoryComplete=True,
                deliveries=[dict(context,id='d1',at=10,payload=payload)],
                commits=[dict(context,id='c1',at=11,payloadSha256=digest(payload),effectId='INC-73')],
                acknowledgments=[dict(id='a1',deliveryId='d1',at=12,outcome='success')])


def checks():
    fixtures=[]
    def check(name,data,**expected):
        before=copy.deepcopy(data);actual=inspect(data);assert data==before
        row=actual['events'][0]if actual['events']else{}
        for k,v in expected.items():assert row[k]==v,(name,k,row[k],v)
        fixtures.append(dict(id=name,**actual));return actual
    check('normal',sample(),contentConflict=False,duplicateEffects=False,ackWithoutProvenPriorCommit=[])
    x=sample();x['acknowledgments'][0]['outcome']='failed';x['deliveries'].append(dict(x['deliveries'][0],id='d2',at=20));x['acknowledgments'].append(dict(id='a2',deliveryId='d2',at=21,outcome='success'))
    check('retry-reuses-effect',x,effectIds=['INC-73'],duplicateEffects=False,unconfirmedDeliveryIds=['d1'],ackWithoutProvenPriorCommit=[])
    x=sample();x['commits'][0]['at']=13;check('ack-before-commit',x,ackWithoutProvenPriorCommit=['a1'])
    x=sample();x['commits']=[];check('ack-without-commit',x,ackWithoutProvenPriorCommit=['a1'])
    x=sample();x['commits'][0]['at']=12;check('same-minute-order-unproven',x,ackWithoutProvenPriorCommit=['a1'])
    x=sample();x['acknowledgments'][0]['outcome']='failed';check('ack-failed',x,unconfirmedDeliveryIds=['d1'],failedAcknowledgmentIds=['a1'])
    x=sample();x['commits'].append(dict(x['commits'][0],id='c2',effectId='INC-74',at=15));check('duplicate-effects',x,duplicateEffects=True)
    x=sample();x['deliveries'].append(dict(x['deliveries'][0],id='d2',payload='SYNTHETIC changed meaning',at=20));check('content-conflict',x,contentConflict=True)
    x=sample();x['commits'][0]['payloadSha256']=digest('SYNTHETIC different');check('wrong-payload-commit',x,contentConflict=True,ackWithoutProvenPriorCommit=['a1'])
    x=sample();x['commits'][0]['environment']='other-training';a=check('environment-boundary',x);assert len(a['events'])==2;assert next(r for r in a['events']if r['environment']=='training')['ackWithoutProvenPriorCommit']==['a1']
    x=sample();x['commits'][0]['source']='detector-b';a=check('source-boundary',x);assert len(a['events'])==2;assert next(r for r in a['events']if r['source']=='detector-a')['ackWithoutProvenPriorCommit']==['a1']
    x=sample();x['inventoryComplete']=False;assert check('incomplete-ledger',x)['inventoryCoverageUnproven']
    x=sample();x['deliveries']=[];x['acknowledgments']=[];check('commit-outside-observation',x,commitWithoutObservedDelivery=True)
    x=sample();x['commits'].append(dict(x['commits'][0],id='c2'));check('repeated-evidence-same-effect',x,duplicateEffects=False)
    combinations=0
    for commit_time,ack_time,outcome in itertools.product((10,11,12),(10,11,12),('success','failed')):
        x=sample();x['commits'][0]['at']=commit_time;x['acknowledgments'][0].update(at=ack_time,outcome=outcome)
        r=inspect(x)['events'][0];assert bool(r['ackWithoutProvenPriorCommit'])==(outcome=='success'and commit_time>=ack_time);combinations+=1
    x=sample();x['deliveries'] += [dict(x['deliveries'][0],id='d2',at=20),dict(x['deliveries'][0],id='d3',at=30)];reference=inspect(x);permutations=0
    for order in itertools.permutations(x['deliveries']):
        y=copy.deepcopy(x);y['deliveries']=list(order);assert inspect(y)==reference;permutations+=1
    invalid=[]
    for field,value in [('asOf',True),('asOf',-1),('inventoryComplete','yes'),('deliveries',None)]:
        x=sample();x[field]=value;invalid.append(x)
    for field,value in [('id',''),('at',101),('at',1.5),('environment',''),('source',''),('payload','')]:
        x=sample();x['deliveries'][0][field]=value;invalid.append(x)
    for field,value in [('effectId',''),('payloadSha256','bad'),('at',True)]:
        x=sample();x['commits'][0][field]=value;invalid.append(x)
    for field,value in [('deliveryId','missing'),('at',9),('outcome','unknown')]:
        x=sample();x['acknowledgments'][0][field]=value;invalid.append(x)
    for name in ['deliveries','commits','acknowledgments']:
        x=sample();x[name].append(dict(x[name][0]));invalid.append(x)
    x=sample();del x['commits'][0]['eventId'];invalid.append(x)
    for x in invalid:
        try:inspect(x)
        except ValueError:pass
        else:raise AssertionError('invalid input accepted')
    return dict(fixtures=fixtures,timingCombinations=combinations,inputPermutations=permutations,invalidInputs=len(invalid),
                inputPreserved=True,orderIndependent=True,network=False,cloudExecuted=False,persistentWrites=False,
                scriptSha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest())


if __name__=='__main__':print(json.dumps(checks(),ensure_ascii=False,indent=2))
NA PRÁTICA

Após criar INC-73, o consumidor perde a confirmação. A nova entrega deve ser reconciliada com o efeito persistido, conservando diferenças de conteúdo.

Armadilhas comuns

Confundir tag com análise nova; SKIPPED com corrigido; mute com remediação; confirmação de mensagem com transação de negócio.

Tópicos relacionados: Identidades e confiança no pipeline · Cobertura e conservação de logs · Idempotência e recuperação de incidentes

Leva esta ideia contigo

Cada conclusão operacional precisa de um âmbito e de uma observação adequada. Preservar progresso e lacunas evita perder trabalho ou duplicar efeitos.

Criar conta

Referência: Run builds with a user-managed service account · Current linked guide; edition date unconfirmed (2026-09-30 inspection)

Google Cloud é uma marca comercial de Google LLC. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por Google. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.