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

Comunicação privada e evidência de perímetro

Diagnostica DNS, inspeção, políticas e caminhos privados com observações delimitadas e critérios de aceitação.

Desenhar o percurso de uma chamada

Um incidente de comunicação começa com um pedido concreto. Identifica o processo que inicia a ligação, a origem de rede, o nome solicitado, o endereço devolvido, o protocolo e o destino final. Num serviço APS fictício, “a API não funciona” pode significar que o nome não resolve, que TCP não estabelece, que o handshake TLS falha ou que a aplicação devolve uma recusa. Estes resultados exigem evidência diferente. Não acrescentes permissões à identidade antes de demonstrar que o pedido chegou à camada onde essas permissões são avaliadas. Constrói uma tabela com uma linha por salto e por decisão: resolução, encaminhamento, firewall, terminação TLS, controlo de identidade e autorização da operação. Regista também quem observa cada resultado. O portátil de um operador pode usar outro resolvedor, outra origem e outro caminho do que o job em produção. Um teste no portátil não substitui o teste do chamador afetado. Associa a cada observação hora, origem e âmbito, sem copiar credenciais para o ticket. O exemplo de fecho de fundos desta aula é fictício e não descreve procedimentos internos BNP Paribas. A decisão de alteração deve relacionar uma hipótese com uma observação que a pode confirmar ou refutar. Se só tens um timeout, preserva essa limitação: ainda não sabes qual das várias dependências falhou. Define a próxima recolha e o responsável antes de alterar várias camadas ao mesmo tempo.

Resolver o nome no âmbito correto

Uma zona privada tem redes autorizadas a consultar os seus registos. O papel IAM de um operador para ler a configuração DNS não concede automaticamente essa visibilidade a uma VPC. Mantém separadas as perguntas “quem pode administrar os registos?” e “de onde podem ser resolvidos?”. Num projeto com ambientes de análise e produção, uma zona corretamente visível no primeiro pode continuar invisível no segundo. A correção deve respeitar a separação pretendida e o mecanismo de resolução usado pelo cliente. Quando há zonas sobrepostas, verifica o sufixo mais específico. No exercício, uma zona filha existe, mas o registo foi publicado apenas na zona pai. A consulta seleciona a zona filha e pode receber NXDOMAIN; não assumas uma procura automática na zona menos específica ou pública. Para diagnosticar, regista o nome completo, o resolvedor efetivamente usado e a zona que corresponde ao pedido. Evita interpretar qualquer NXDOMAIN como prova de que o serviço de aplicação está desligado. DNSSEC trata autenticidade e integridade de dados DNS; não cifra consultas. Se o requisito menciona confidencialidade de nomes, identifica separadamente o transporte e o âmbito de exposição. Antes da alteração, pede a um colega que explique qual propriedade cada controlo demonstra. A entrega deve permitir verificar nomes permitidos e nomes que devem permanecer invisíveis, em vez de limitar a aceitação a uma única consulta bem-sucedida.

Distinguir encaminhamento DNS e efeitos de cache

Uma consulta encaminhada pode ter uma origem diferente da VM que iniciou a resolução. Para um destino DNS on-premises do tipo tratado na documentação, observa a origem 35.199.192.0/19 e verifica o retorno pelo caminho apropriado. Se a VM consulta diretamente o servidor e recebe resposta, isso demonstra esse fluxo direto. Não demonstra que a resposta à consulta encaminhada consegue regressar à origem do serviço Cloud DNS. Compara os dois fluxos antes de atribuir a falha ao conteúdo da zona. O mesmo cuidado aplica-se ao tempo. Reduzir um TTL autoritativo não encurta retroativamente a validade de respostas já em cache. Uma alteração às 09:20 pode coexistir com um valor obtido às 09:00 com uma hora de validade. Planeia a redução com antecedência e inclui na aceitação a diferença entre resposta autoritativa e resposta observada pelos clientes. Não prometas convergência universal a partir de uma única consulta local. Num exercício de passagem de turno, prepara três colunas: observação, explicação possível e evidência em falta. “O autoritativo devolve o novo endereço” e “o job usa ainda o antigo” podem ser simultaneamente verdadeiros. A próxima ação pode ser identificar a cache relevante e o momento de obtenção, em vez de desfazer imediatamente a mudança. Uma reversão também tem efeitos de cache; documenta como vais reconhecer recuperação sem confundir clientes com estados diferentes.

Verificar a cobertura real da inspeção TLS

A inspeção TLS não é uma propriedade universal de qualquer tráfego que atravessa uma VPC. Cloud NGFW precisa dos recursos, associações e políticas adequados ao caminho e à funcionalidade. Os CA pools e políticas de inspeção têm âmbito regional; preparar uma região não completa automaticamente outra. Desenha onde ocorre a inspeção e quem mantém cada dependência. Inclui a verificação de certificados e da confiança dos clientes no plano de aceitação, sem transformar desativação de validação numa solução permanente para falhas. A documentação consultada exclui a inspeção de egress serverless, incluindo os caminhos por Direct VPC egress e por Serverless VPC Access connectors. Uma alteração entre esses dois caminhos não elimina a limitação. Antes de prometer um controlo ao sponsor, confirma origem do tráfego, protocolos, âmbito regional e suporte atual. Se a necessidade não cabe no mecanismo, revê a arquitetura ou a combinação de controlos; uma caixa num diagrama não demonstra inspeção. Num ensaio fictício, separa disponibilidade e cobertura: a chamada pode funcionar enquanto o tráfego pretendido não é inspecionado, ou falhar devido a uma dependência da inspeção. Define previamente observações para cada hipótese e um critério de reversão aprovado. Evita alterar simultaneamente política de rede, certificados e versão da aplicação sem capacidade de atribuir o resultado. No handover, deixa claros os workloads abrangidos e as exclusões conhecidas.

Controlar entradas e saídas web

Secure Web Proxy controla tráfego web de saída no caminho configurado para o usar. Criar o proxy não prova que um cliente deixa de usar uma ligação direta ainda permitida. Para cada workload, verifica configuração, encaminhamento e caminhos alternativos. No lado de entrada, a aplicação deve validar o JWT IAP, incluindo a audience esperada para o recurso. Uma assinatura válida destinada a outro backend não satisfaz esse requisito. Não substituas essa validação por um email que parece corporativo. Cloud Armor permite afinar regras WAF, incluindo exclusões de campos para assinaturas relevantes. Se o valor legítimo de report_note causa um falso positivo, delimita a correção e preserva inspeção fora desse âmbito. Confirma o que a assinatura inspeciona: uma exclusão de parâmetro não equivale a excluir qualquer análise do corpo inteiro. Regista pedidos legítimos que devem passar e pedidos de controlo que devem continuar a ser recusados, usando dados fictícios aprovados para ensaio. Na revisão de release, enumera entradas atuais, antigas e administrativas. Um caminho principal bem protegido não demonstra proteção dos restantes. A retirada de uma entrada antiga precisa de evidência de inacessibilidade e de revisão dos consumidores que dependiam dela. Se não observaste uma entrada, regista desconhecido. Não trates ausência de logs como prova automática de ausência de tráfego, nem declares toda a aplicação protegida com base apenas no domínio usado pelos utilizadores habituais.

Avaliar regras de perímetro como condições

VPC Service Controls acrescenta decisões sobre chamadas a serviços protegidos; não substitui IAM. Escreve o cliente, a origem, o recurso e o método antes de rever a exceção. Dentro de uma regra ingress, origem e identidade exigidas têm de corresponder. Entre várias regras ingress, satisfazer uma alternativa pode bastar. Por isso, acrescentar uma regra mais estreita não restringe automaticamente uma regra ampla que permanece ativa. A mudança precisa de tratar o conjunto efetivo e as dependências legítimas. Quando a chamada envolve perímetros diferentes, as políticas de todos os perímetros envolvidos têm de permitir o pedido. Ingress e egress não são definidos apenas pelo sentido dos bytes devolvidos. Uma leitura pode exigir analisar a origem do cliente, o recurso externo e as políticas dos dois lados. Delimita a troca aprovada sem usar uma exceção universal como resposta a uma falha de integração. Mantém separados os serviços restringidos na fronteira e os VPC accessible services. A documentação atual permite políticas de perímetro distintas para VPCs num mesmo host project. Avalia essa capacidade antes de afirmar que separar projetos é obrigatório. Na firewall, lê também a ação exata: goto_next prossegue na próxima etapa de avaliação e não significa allow. Em peering, distingue network tags de regras VPC de secure Tags de políticas de firewall; nomes semelhantes não garantem o mesmo âmbito de identificação.

Aceitar conectividade privada com evidência

Private Service Connect oferece acesso orientado a serviços e usa tradução de endereços, podendo evitar a coordenação dos intervalos internos de produtor e consumidor. Isso não equivale a unir redes completas. Para um consumidor iniciar chamadas a um serviço publicado, avalia endpoints e os tipos suportados. Para o produtor iniciar novas ligações à rede do consumidor, avalia interfaces e network attachments. Respostas num fluxo já iniciado não provam capacidade de iniciar outro na direção inversa. Lê o estado do endpoint com cuidado. Pending pode refletir aceitação em falta ou limites de ligação e não desaparece necessariamente com o tempo. Accepted confirma a aceitação de configuração, mas não garante fluxo útil. A aceitação de serviço precisa ainda de observar a chamada real e as dependências relevantes. Coordena com o produtor o âmbito autorizado em vez de mudar repetidamente as credenciais HTTP do cliente para resolver uma ligação ainda não estabelecida. Private Google Access é configurado por subnet. Ao migrar um template, revê essa propriedade no destino e os requisitos de DNS, rotas e firewall. O endpoint restricted.googleapis.com limita os serviços disponíveis; uma API não suportada precisa de revisão do desenho. Finalmente, o nome default internet gateway não prova trânsito pela Internet pública para o caminho documentado de APIs Google. Avalia o destino e o comportamento suportado, preservando uma explicação que a equipa de produção consiga verificar.

Comparar cobertura sem esconder desconhecidos

O exercício local define um requisito fictício: cada caminho acessível precisa de TLS, controlo de identidade e WAF. Não é uma regra universal para todos os serviços. Cada observação usa true, false ou null. True declara evidência positiva fornecida, false declara ausência ou inacessibilidade conforme o campo e null conserva informação desconhecida. O programa não recolhe essa evidência nem verifica se quem a forneceu está correto. A completude do inventário é igualmente uma declaração que precisaria de justificação fora do exercício. Antes de executar python3 run.py, prevê o caso com front protegido e legacy acessível sem WAF. Deve aparecer uma lacuna conhecida. Se admin tem acessibilidade desconhecida, deve permanecer por esclarecer; não desaparece por front estar coberto. Uma falha conhecida continua visível mesmo quando outro controlo no mesmo caminho é desconhecido. Se todos os caminhos estão inacessíveis, o programa não declara cobertura útil de serviço. Isto evita confundir um sistema desligado com um serviço aceite. Os nove casos incluídos verificam inventário incompleto, ausência de caminhos, falhas e incerteza. O programa percorre 162 combinações de estados, seis permutações e 14 entradas inválidas. Não chama APIs, não envia pacotes e não valida JWT, certificados, regras de firewall ou políticas reais. Escreve uma nota de entrega com o facto encontrado, a limitação da evidência e a próxima recolha necessária. Nenhum resultado autoriza uma alteração de produção.

"""Review declared control coverage of fictional paths; no network operations."""
import copy
import hashlib
import itertools
import json
from pathlib import Path

REQUIRED = ("tls", "identity", "waf")


def assess(snapshot):
    if not isinstance(snapshot, dict) or set(snapshot) != {"inventoryComplete", "paths"}:
        raise ValueError("Expected inventoryComplete and paths")
    if type(snapshot["inventoryComplete"]) is not bool or not isinstance(snapshot["paths"], list):
        raise ValueError("Explicit boolean completeness and path list required")
    ids, results = set(), []
    for p in snapshot["paths"]:
        if not isinstance(p, dict) or set(p) != {"id", "reachable", "controls"}:
            raise ValueError("Invalid path shape")
        if not isinstance(p["id"], str) or not p["id"].strip() or p["id"] != p["id"].strip() or p["id"] in ids:
            raise ValueError("Invalid or duplicate path ID")
        if not isinstance(p["controls"], dict) or set(p["controls"]) != set(REQUIRED):
            raise ValueError("Each required control needs an explicit state")
        states = [p["reachable"], *p["controls"].values()]
        if any(x is not None and type(x) is not bool for x in states):
            raise ValueError("States must be true, false or null")
        ids.add(p["id"])
        missing = sorted(k for k in REQUIRED if p["controls"][k] is False)
        unknown = sorted(k for k in REQUIRED if p["controls"][k] is None)
        if p["reachable"] is False:
            status = "unreachable-in-snapshot"
        elif p["reachable"] is None:
            status = "unresolved"
        elif missing:
            status = "known-gap"
        elif unknown:
            status = "unresolved"
        else:
            status = "covered-in-snapshot"
        results.append({"id": p["id"], "status": status,
                        "missingControls": missing, "unknownControls": unknown,
                        "reachabilityUnknown": p["reachable"] is None})
    results.sort(key=lambda x: x["id"])
    gaps = [p["id"] for p in results if p["status"] == "known-gap"]
    unresolved = [p["id"] for p in results if p["status"] == "unresolved"]
    covered = [p["id"] for p in results if p["status"] == "covered-in-snapshot"]
    return {"paths": results, "knownGaps": gaps, "unresolvedPaths": unresolved,
            "inventoryComplete": snapshot["inventoryComplete"],
            "coverageWithinDeclaredSnapshot": bool(covered) and not gaps and not unresolved and snapshot["inventoryComplete"],
            "serviceHealthProven": False, "securityProven": False, "productionAuthorized": False}


def path(name, reachable=True, tls=True, identity=True, waf=True):
    return {"id": name, "reachable": reachable, "controls": {"tls": tls, "identity": identity, "waf": waf}}


def evidence():
    fixtures = [
        ("declared-covered", {"inventoryComplete": True, "paths": [path("front")]}, ([], [], True)),
        ("alternate-unprotected", {"inventoryComplete": True, "paths": [path("front"), path("legacy", identity=False, waf=False)]}, (["legacy"], [], False)),
        ("alternate-unreachable", {"inventoryComplete": True, "paths": [path("front"), path("legacy", reachable=False, identity=False, waf=False)]}, ([], [], True)),
        ("alternate-reachability-unknown", {"inventoryComplete": True, "paths": [path("front"), path("legacy", reachable=None, identity=False)]}, ([], ["legacy"], False)),
        ("missing-evidence", {"inventoryComplete": True, "paths": [path("front", waf=None)]}, ([], ["front"], False)),
        ("gap-and-unknown", {"inventoryComplete": True, "paths": [path("front", identity=False, waf=None)]}, (["front"], [], False)),
        ("incomplete-inventory", {"inventoryComplete": False, "paths": [path("front")]}, ([], [], False)),
        ("empty-inventory", {"inventoryComplete": True, "paths": []}, ([], [], False)),
        ("all-unreachable", {"inventoryComplete": True, "paths": [path("front", reachable=False)]}, ([], [], False)),
    ]
    output = []
    for name, snapshot, expected in fixtures:
        original = copy.deepcopy(snapshot); result = assess(snapshot)
        assert snapshot == original
        assert (result["knownGaps"], result["unresolvedPaths"], result["coverageWithinDeclaredSnapshot"]) == expected
        output.append({"id": name, **result})
    combinations = 0
    for complete in (False, True):
        for reach, tls, identity, waf in itertools.product((False, None, True), repeat=4):
            p = path("x", reach, tls, identity, waf)
            result = assess({"inventoryComplete": complete, "paths": [p]})
            controls = (tls, identity, waf)
            known_gap = reach is True and any(x is False for x in controls)
            uncertain = reach is None or (reach is True and not any(x is False for x in controls) and any(x is None for x in controls))
            assert bool(result["knownGaps"]) == known_gap
            assert bool(result["unresolvedPaths"]) == uncertain
            assert result["coverageWithinDeclaredSnapshot"] == (complete and reach is True and all(x is True for x in controls))
            assert result["paths"][0]["unknownControls"] == sorted(k for k in REQUIRED if p["controls"][k] is None)
            combinations += 1
    mixed = {"inventoryComplete": True, "paths": [path("good"), path("bad", tls=False), path("unknown", waf=None)]}
    permutations = 0
    for order in itertools.permutations(mixed["paths"]):
        assert assess({**mixed, "paths": list(order)}) == assess(mixed)
        permutations += 1
    invalid = []
    def bad(change):
        snapshot = {"inventoryComplete": True, "paths": [path("x")]}; change(snapshot); invalid.append(snapshot)
    bad(lambda s: s.update(inventoryComplete=None))
    bad(lambda s: s.update(inventoryComplete=1))
    bad(lambda s: s.update(paths={}))
    bad(lambda s: s.update(extra=True))
    bad(lambda s: s["paths"].append(path("x")))
    bad(lambda s: s["paths"][0].update(id=" x"))
    bad(lambda s: s["paths"][0].update(id=""))
    bad(lambda s: s["paths"][0].update(reachable=1))
    bad(lambda s: s["paths"][0].update(reachable="false"))
    bad(lambda s: s["paths"][0].update(controls=[]))
    bad(lambda s: s["paths"][0]["controls"].pop("waf"))
    bad(lambda s: s["paths"][0]["controls"].update(extra=True))
    bad(lambda s: s["paths"][0]["controls"].update(tls=0))
    bad(lambda s: s["paths"][0]["controls"].update(identity="unknown"))
    for snapshot in invalid:
        try: assess(snapshot)
        except ValueError: pass
        else: raise AssertionError("Accepted invalid input")
    return {"fixtures": output, "stateCombinations": combinations, "pathPermutations": 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(evidence(), ensure_ascii=False, indent=2))
NA PRÁTICA

O job resolve um nome antigo, um endpoint ainda está Pending e o caminho legado não tem o controlo exigido; cada facto precisa de tratamento próprio.

Armadilhas comuns

Confundir DNS com conectividade, assinatura com audience válida, nova regra restrita com remoção de uma exceção ampla e Accepted com serviço operacional.

Tópicos relacionados: Identidades e autorização de workloads · Diagnóstico de DNS e TLS · Aceitação e passagem a produção

Leva esta ideia contigo

Um controlo só sustenta a conclusão no caminho e âmbito demonstrados; falhas conhecidas e informação desconhecida devem continuar visíveis.

Criar conta

Referência: Configure Private Google Access · 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.