← Professional Cloud Security Engineer: controlos e evidência
12 / 13 · 135 MIN

Segurança operacional e evidência de release

Relaciona artefactos, políticas, patching e deteções com decisões que a equipa APS consegue executar e verificar.

Ligar a decisão à imagem que vai ser instalada

Uma release não deve ser aprovada apenas porque o dashboard mostra a última execução a verde. Identifica a imagem candidata, o digest, a origem do código, o processo de construção e os critérios em vigor. Num caso APS fictício, a tag candidata mudou depois dos testes. O relatório positivo descreve A, mas a instalação pretende B. O nome comum não demonstra que o conteúdo e as dependências são os mesmos. O caso não descreve procedimentos internos BNP Paribas. Proveniência ajuda a relacionar um artefacto com a forma como foi construído. A validação deve verificar a declaração e confrontar origem e builder com a política de confiança, em vez de aceitar qualquer assinatura válida. No Cloud Build, revê o caminho de geração: a publicação declarada em images e a opção de verificação fazem parte do desenho documentado. Um passo docker push explícito não cria por si a proveniência esperada. Distingue metadata não gerada de metadata existente que o observador não consegue ler. Prepara um pacote de decisão com identificadores consistentes: digest candidato, revisão dos critérios, resultados, estado de conclusão e responsáveis. Se uma equipa reexecuta testes, guarda a ligação à imagem realmente usada. Não atualizes apenas rótulos num relatório antigo. Para a reunião de go-live, formula a questão concreta: que evidência demonstra que este conteúdo satisfaz estes critérios? Isso permite identificar uma lacuna sem declarar que a aplicação está necessariamente vulnerável ou que a imagem anterior era incorreta.

Exigir conclusão e cobertura da análise

Uma análise iniciada ainda não é um resultado. Se o estado está pendente, uma lista vazia de findings não demonstra ausência de problemas. Define no pipeline os estados que permitem avançar, os que bloqueiam e os que exigem investigação. No exemplo desta aula, o critério exige análise concluída para o digest candidato e nenhum finding bloqueante. Um timeout continua a ser falta de evidência de conclusão, mesmo que o passo anterior de construção tenha terminado com sucesso. Confirma também o âmbito suportado. Guardar uma imagem num repositório não prova que o scanner analisa todos os seus componentes. A documentação de Artifact Analysis delimita sistemas e tipos de pacote e não suporta containers Windows Server. A equipa precisa de reconhecer o que ficou fora da análise e atribuir uma solução adequada. Um indicador agregado deve distinguir sem findings, análise em falta, tipo não suportado e execução falhada. Juntar essas situações na mesma categoria verde esconde decisões relevantes. Depois da instalação, as condições podem mudar. Continuous validation com check-based platform policies, assinalada como preview na consulta das fontes, pode registar violações para imagens de Pods em execução. Um finding demonstra deteção, não prova que o workload foi terminado ou corrigido. Define responsável, prazo de resposta e evidência de fecho. O percurso de decisão continua depois da admissão: observa o serviço, confirma o efeito da ação e regista o risco que permanece enquanto a correção definitiva não está disponível.

Distinguir simulação, aplicação e alteração efetiva

Uma política pode existir sem bloquear operações. Em Binary Authorization, DRYRUN_AUDIT_LOG_ONLY permite observar violações sem impedir os deployments correspondentes. Uma instalação bem-sucedida com um evento de violação pode ser precisamente o comportamento configurado. Antes de tratar isso como falha do produto, confirma a regra aplicável e o modo. Depois compara o resultado com o requisito: se produção exige bloqueio, uma fase de observação não demonstra que o requisito já está cumprido. Organization Policy também distingue a política live da configuração dryRunSpec. Para constraints suportadas, a simulação permite observar efeitos antes de aplicar a restrição. Não generalizes esse suporte a todos os tipos de constraint antigos. A revisão da mudança deve identificar recurso, herança, âmbito, modo e operações de criação ou alteração que serão afetadas. Um ficheiro com a intenção correta não prova que a configuração efetiva foi aplicada no projeto pretendido. No exemplo de um pipeline com mudança de política, compara o estado aprovado com o observado e investiga diferenças. Uma alteração manual pode ser uma intervenção autorizada ainda não refletida na automação ou uma modificação indevida. A diferença exige classificação e dono, não uma reposição automática cega. Regista a decisão e reconcilia a fonte de configuração para evitar que a próxima execução volte a introduzir a divergência. No handover, demonstra um pedido permitido e outro que deve ser recusado no âmbito certo, mantendo a distinção entre ensaio e enforcement real.

Coordenar patching com inventário e serviço

O denominador de uma métrica de patching importa tanto como a percentagem. Se o inventário de ativos inclui 40 VMs e só 35 têm visibilidade no VM Manager, não declares as outras cinco conformes. A vista de sistemas operativos depende do agente OS Config e da gestão de inventário. A ausência de dados pode ter várias causas e deve aparecer como lacuna atribuída a um responsável. Um dashboard saudável do subconjunto observado não representa automaticamente todo o perímetro. Para a janela de sábado, planeia as dependências do serviço: sequência de nós, compatibilidade, validação funcional, observação e contingência. O mecanismo de patch do sistema operativo não sabe por si se a reconciliação financeira ficou correta. Um serviço que arranca pode continuar a falhar num batch raro. Define verificações representativas com a equipa aplicacional e identifica quem pode decidir continuar, parar ou adiar o grupo seguinte. Usa critérios de negócio e técnicos suficientemente concretos para a equipa de prevenção. Se um patch job for cancelado, o agente tenta concluir a tarefa que tem em curso antes de parar. Não confundas o pedido central com paragem instantânea em todas as máquinas ou com rollback. Recolhe estado por VM e alterações já aplicadas antes de retomar tráfego. A nota de passagem de turno deve identificar sistemas em estados diferentes e ações pendentes. Isso evita que uma equipa seguinte repita uma intervenção sem saber o que ficou instalado ou em conclusão.

Seguir o evento desde a origem até ao destino

Uma estratégia de logs precisa de explicar o caminho completo: evento ocorrido, categoria gerada, filtros, encaminhamento, escrita no destino, retenção e consulta autorizada. Um erro num destes pontos pode produzir um dashboard vazio, mas as correções são diferentes. No caso fictício, o analista vê o evento na origem e o sink apresenta recusa de escrita no projeto central. A investigação deve verificar a identidade escritora e as permissões do destino, em vez de ampliar o acesso do analista. Dentro do mesmo recurso, sinks independentes avaliam cada entrada separadamente. Uma exclusão no sink A não remove automaticamente a rota B. Ao reduzir exposição ou custos, identifica todos os destinos relevantes e verifica o efeito em cada um. Esta observação não substitui a análise de regras específicas de sinks agregados e interceção na hierarquia. Desenha o fluxo que existe no caso concreto antes de aplicar conclusões de um desenho mais simples. Na geração de Data Access, verifica também identidades isentas. A categoria pode estar ativa e existir uma exceção aplicável à service account da aplicação. Isso explica uma lacuna de recolha diferente de falta de permissão para consultar. Regista a configuração válida à data do evento e evita inferir que nenhuma leitura ocorreu apenas porque não encontras um registo. Para aceitar a integração com o SIEM, usa um evento sintético identificado, observa a chegada e confirma o tratamento esperado no destino, sem incluir dados sensíveis desnecessários.

Dar acesso útil sem perder o âmbito dos logs

Centralizar logs facilita investigação, mas também concentra informação de várias aplicações. Uma view filtrada pode dar acesso a um subconjunto; isso só representa isolamento efetivo se as outras concessões não permitirem ler mais. roles/logging.viewAccessor no projeto sem condição dá um âmbito mais amplo do que uma concessão a uma view específica. Acrescentar uma view restrita não remove o papel amplo que já existe. A revisão precisa de considerar os caminhos efetivos de consulta do utilizador. Define o que cada equipa precisa de consultar e porquê. Um analista APS pode precisar dos erros da aplicação e de um identificador de correlação sem precisar de payloads completos de outras equipas. Usa exemplos sintéticos para verificar consultas permitidas e recusadas. Mantém separado o acesso aos logs, a administração de sinks e a capacidade de mudar retenção. A investigação operacional deve ser possível sem dar a todos os operadores controlo total sobre a evidência que será usada numa auditoria posterior. O bloqueio de um log bucket e da sua retenção é irreversível. Antes dessa decisão, confirma o período aprovado, o âmbito e o impacto operacional. Não é um teste que se desfaz no dia seguinte mudando a descrição ou apagando um bucket com entradas ainda dentro do prazo. No plano, inclui responsáveis pela consulta, retenção e resposta a falhas de entrega. Uma política escrita precisa de um modo de operação sustentável para continuar útil depois do projeto terminar.

Converter deteções em trabalho operacional rastreável

Uma ferramenta instalada não demonstra cobertura de deteção. Um endpoint Cloud IDS precisa de políticas de Packet Mirroring que lhe enviem o tráfego relevante. A existência do endpoint não mostra que as VMs pretendidas estão abrangidas. Da mesma forma, secondary sampling rate=1.0 em VPC Flow Logs conserva os registos produzidos pela fase anterior, não elimina a amostragem primária nem cria captura de todos os pacotes. Escreve o que a observação permite concluir e o que permanece fora do âmbito. Na triagem, verifica se configurações de apresentação escondem resultados que a equipa espera rever. Em SCC, uma static mute rule tem precedência sobre uma dynamic mute rule correspondente. A expiração da dinâmica pode não produzir o efeito esperado enquanto a regra estática continuar aplicável. A revisão de exceções deve considerar o conjunto de regras, os responsáveis e o motivo operacional, em vez de olhar apenas para a data mostrada numa regra isolada. Ao automatizar abertura de tickets, distingue mensagem de evento de negócio. Um produtor pode publicar o mesmo acontecimento duas vezes com IDs diferentes, mesmo quando o consumidor usa exactly-once delivery. Define uma chave estável adequada ao evento e um comportamento idempotente para evitar trabalho duplicado sem perder acontecimentos diferentes. Não uses “primeiro alerta do dia” como chave universal. A entrega a produção deve incluir repetição, falhas de confirmação e destino indisponível, com evidência de que a resposta operacional continua coerente nessas situações.

Exercício: avaliar evidência sem fabricar aprovação

O exercício local recebe um candidato e relatórios fictícios. Exige correspondência exata de digest e revisão de política, além de resultados recentes para todas as verificações declaradas. Não verifica assinaturas, não descarrega imagens e não executa scanners. O seu resultado descreve apenas a consistência das afirmações fornecidas. Os valores de digest são sintéticos e as datas são números relativos para tornar os limites fáceis de observar. Antes de executar python3 run.py, prevê três situações. Com duas verificações positivas e atuais para o candidato, os critérios locais ficam satisfeitos. Se o scan pertence a outro digest, não cobre o candidato. Se o scan ainda está pendente e a integração passou, a conclusão permanece incompleta. O programa mantém também falhas conhecidas quando outro controlo está por esclarecer. Uma evidência negativa atual não desaparece silenciosamente porque existe outro relatório positivo; o conflito precisa de investigação e reconciliação explícita. Experimenta o limite de idade: idade 10 passa com max_age=10; idade 11 fica por esclarecer. Uma data futura ou em falta também não prova atualidade. Inverter a ordem dos relatórios não deve alterar a decisão. Depois escreve a nota para o comité com o artefacto, os critérios, a lacuna e a próxima ação. Mesmo criteriaSatisfied=true não autoriza produção. Resume a aula distinguindo identidade, conclusão, cobertura, enforcement e resposta: cada dimensão precisa da sua própria evidência, ligada ao serviço e à mudança em avaliação.

"""Offline release-evidence exercise; not a scanner or signature verifier.

Only exact digest and policy revision match. Freshness is inclusive.
All required checks need positive, current evidence. Applicable current failures
remain visible even when other checks are unknown. Conflicting current reports
withhold satisfaction; a later pass does not silently erase another current fail.
"""
from copy import deepcopy
from hashlib import sha256
from itertools import permutations, product
from math import isfinite
from pathlib import Path
import json
import re


def text(value):
    if not isinstance(value, str) or not value.strip():
        raise ValueError('expected nonempty string')
    return value


def digest(value):
    if not isinstance(value, str) or not re.fullmatch(r'sha256:[0-9a-f]{64}', value):
        raise ValueError('expected canonical fictional SHA256 digest')
    return value


def number(value):
    if type(value) not in (int, float) or not isfinite(value):
        raise ValueError('expected finite number')
    return value


def assess(candidate, reports, now=100, max_age=10):
    number(now)
    number(max_age)
    if max_age < 0 or not isinstance(candidate, dict) or not isinstance(reports, list):
        raise ValueError('invalid candidate, reports or age')
    expected = digest(candidate.get('digest'))
    revision = text(candidate.get('policyRevision'))
    required = candidate.get('requiredChecks')
    if not isinstance(required, list) or not required:
        raise ValueError('nonempty required checks needed')
    for kind in required:
        text(kind)
    if len(set(required)) != len(required):
        raise ValueError('duplicate required check')
    seen, ignored, grouped = set(), [], {kind: [] for kind in required}
    for report in reports:
        if not isinstance(report, dict):
            raise ValueError('invalid report')
        identifier = text(report.get('id'))
        if identifier in seen:
            raise ValueError('duplicate report ID')
        seen.add(identifier)
        kind = text(report.get('check'))
        result = report.get('passed')
        if result is not None and type(result) is not bool:
            raise ValueError('passed must be bool or None')
        at = report.get('checkedAt')
        if at is not None:
            number(at)
        mismatch = []
        if digest(report.get('digest')) != expected:
            mismatch.append('digest')
        if text(report.get('policyRevision')) != revision:
            mismatch.append('policyRevision')
        if kind not in grouped:
            mismatch.append('check-not-required')
        if mismatch:
            ignored.append(dict(id=identifier, reasons=mismatch))
        else:
            grouped[kind].append(report)
    checks = []
    for kind in sorted(required):
        passed, failed, unresolved = [], [], []
        for report in grouped[kind]:
            at = report.get('checkedAt')
            if at is None or at > now or now - at > max_age or report.get('passed') is None:
                unresolved.append(report['id'])
            elif report['passed']:
                passed.append(report['id'])
            else:
                failed.append(report['id'])
        status = 'failed' if failed else ('unresolved' if unresolved or not passed else 'satisfied')
        checks.append(dict(check=kind, status=status, passes=sorted(passed),
                           failures=sorted(failed), unresolved=sorted(unresolved)))
    return dict(criteriaSatisfied=all(c['status'] == 'satisfied' for c in checks),
                checks=checks, ignored=sorted(ignored, key=lambda r: r['id']),
                signaturesVerified=False, artifactScanned=False, productionAuthorized=False)


def main():
    a, b = 'sha256:' + 'a' * 64, 'sha256:' + 'b' * 64
    candidate = dict(digest=a, policyRevision='r8', requiredChecks=['scan', 'integration'])
    scan = dict(id='scan-1', digest=a, policyRevision='r8', check='scan', passed=True, checkedAt=100)
    integration = dict(scan, id='integration-1', check='integration')
    fixtures = []

    def case(name, reports, expected, **kwargs):
        before = deepcopy((candidate, reports))
        result = assess(candidate, reports, **kwargs)
        assert result['criteriaSatisfied'] is expected, name
        assert (candidate, reports) == before
        fixtures.append(dict(id=name, **result))
        return result

    case('matching-evidence', [scan, integration], True)
    case('different-digest', [dict(scan, digest=b), integration], False)
    case('different-policy', [dict(scan, policyRevision='r7'), integration], False)
    case('pending-scan', [dict(scan, passed=None), integration], False)
    case('missing-check', [integration], False)
    case('stale-report', [dict(scan, checkedAt=89), integration], False)
    case('freshness-boundary', [dict(scan, checkedAt=90), integration], True)
    case('future-report', [dict(scan, checkedAt=101), integration], False)
    case('known-failure-and-unknown', [dict(scan, passed=False), dict(integration, passed=None)], False)
    case('conflicting-current-reports', [scan, dict(scan, id='scan-2', passed=False), integration], False)
    case('irrelevant-report', [scan, integration, dict(scan, id='scan-old-artifact', digest=b, passed=False)], True)
    case('missing-timestamp', [dict(scan, checkedAt=None), integration], False)
    combinations = 0
    for scan_result, integration_result, timestamp, matching_digest, matching_policy in product(
            (False, True, None), (False, True, None), (89, 90, 100, 101), (False, True), (False, True)):
        s = dict(scan, passed=scan_result, checkedAt=timestamp, digest=a if matching_digest else b,
                 policyRevision='r8' if matching_policy else 'r7')
        i = dict(integration, passed=integration_result)
        result = assess(candidate, [s, i])
        expected = scan_result is True and integration_result is True and timestamp in (90, 100) and matching_digest and matching_policy
        assert result['criteriaSatisfied'] == expected
        if integration_result is False:
            assert result['checks'][0]['status'] == 'failed'
        combinations += 1
    reports = [scan, integration, dict(scan, id='scan-2', passed=False)]
    expected = assess(candidate, reports)
    permutation_count = 0
    for order in permutations(reports):
        assert assess(candidate, list(order)) == expected
        permutation_count += 1
    invalid = [
        (dict(candidate, digest='latest'), [scan], {}),
        (dict(candidate, policyRevision=''), [scan], {}),
        (dict(candidate, requiredChecks=[]), [scan], {}),
        (dict(candidate, requiredChecks=['scan', 'scan']), [scan], {}),
        (dict(candidate, requiredChecks='scan'), [scan], {}),
        (candidate, [scan, scan], {}),
        (candidate, [dict(scan, passed=1)], {}),
        (candidate, [dict(scan, passed='true')], {}),
        (candidate, [dict(scan, checkedAt=True)], {}),
        (candidate, [dict(scan, checkedAt=float('nan'))], {}),
        (candidate, [dict(scan, digest='sha256:bad')], {}),
        (candidate, [dict(scan, id='')], {}),
        (candidate, [dict(scan, check=None)], {}),
        (candidate, [None], {}),
        (candidate, [scan], dict(max_age=-1)),
        (candidate, [scan], dict(now=float('inf'))),
    ]
    for c, r, kwargs in invalid:
        try:
            assess(c, r, **kwargs)
        except ValueError:
            pass
        else:
            raise AssertionError('invalid input accepted')
    print(json.dumps(dict(labId='pcse-release-evidence', fixtures=fixtures,
                          stateCombinations=combinations, inputPermutations=permutation_count,
                          invalidInputs=len(invalid), inputPreserved=True, orderIndependent=True,
                          network=False, cloudExecuted=False, persistentWrites=False,
                          scriptSha256=sha256(Path(__file__).read_bytes()).hexdigest()), indent=2))


if __name__ == '__main__':
    main()
NA PRÁTICA

A release candidata mudou de digest, o scan está pendente e o sink central não consegue escrever; um dashboard verde não resolve estas três lacunas.

Armadilhas comuns

Confundir tag com digest, scan pendente com ausência de findings, simulação com bloqueio e evento recebido com resposta concluída.

Tópicos relacionados: CI/CD e cadeia de fornecimento de software · Gestão de alterações e passagem a produção · Observabilidade e resposta a incidentes

Leva esta ideia contigo

Cada aprovação deve apontar para o artefacto, critério, âmbito e estado realmente observados.

Criar conta

Referência: Generate and validate build provenance · 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.