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

Identidades, credenciais e evidência de acesso

Revê diretórios, confiança de workloads e caminhos de concessão sem confundir inventário com acesso efetivo.

Começar pelo pedido e pela identidade

Numa revisão de acesso, começa por escrever quem pretende fazer o quê, sobre que recurso e em que contexto. “O operador tem acesso ao projeto” é demasiado vago para orientar uma decisão. Um pedido útil identifica a pessoa ou workload, o recurso completo, a operação e a finalidade aprovada. Acrescenta a hora da observação, a origem dos dados e o responsável pela decisão. Uma falha numa operação não demonstra que todas as outras estão impedidas. Um sucesso também não demonstra que o âmbito concedido é adequado. Considera uma equipa APS fictícia que prepara uma janela de fecho de fundos. Uma pessoa revê resultados, um job produz ficheiros e um administrador mantém a plataforma. Estas três atividades não devem partilhar automaticamente a mesma identidade. Se o job falha, conceder à pessoa um papel mais amplo pode não alterar a identidade que realmente faz o pedido. Identifica o chamador observado antes de propor permissões. Este exemplo não descreve procedimentos internos BNP Paribas. Organiza a investigação em quatro perguntas: como a identidade é estabelecida, que credencial é apresentada, que concessões existem e que limites se aplicam. Regista as dúvidas como dúvidas. A entrega da revisão deve permitir que outro operador repita a análise sem depender de uma conversa privada ou de nomes de apresentação ambíguos.

Sincronizar diretórios com âmbito explícito

GCDS compara entidades dentro do âmbito configurado. Uma conta que desaparece de uma pesquisa LDAP pode ter saído da empresa, mas também pode ter sido excluída por uma alteração acidental do filtro. Não transformes uma ausência na pesquisa numa decisão de negócio sem reconciliação. No exercício, o pedido aprovado contém quatro saídas e a simulação propõe 286 suspensões. A diferença de 282 contas é uma questão a investigar, não uma margem a aceitar. Compara os identificadores afetados com a lista aprovada e com a pesquisa anterior. Quando uma conta não administrativa deve existir apenas no Google, avalia a exclusão do lado Google. Excluir um nome apenas do LDAP não protege uma conta já ausente dessa origem. Quando uma exceção exige manter atributos locais de uma entidade existente nos dois lados, a configuração documentada pode usar exclusões nos dois lados. Atribui um responsável à exceção e define a próxima revisão; uma exclusão sem acompanhamento pode tornar-se um ponto cego no processo de saídas. Na passagem para produção, entrega o âmbito pretendido, o resultado da simulação e uma explicação das diferenças. Uma ligação LDAP bem-sucedida confirma conectividade, não a correção da seleção. Se faltam dados, mantém a alteração pendente até esclarecer o conjunto afetado. O objetivo é conseguir explicar cada alteração proposta antes de a executar.

Proteger administração e recuperação

Separar a conta de administração da conta quotidiana reduz a exposição da identidade privilegiada em tarefas que não precisam dela. Essa separação deve ser acompanhada de autenticação forte, responsáveis identificados e recuperação ensaiada. Uma conta de emergência sem fatores disponíveis na janela de prevenção pode existir no inventário e continuar inutilizável. Uma palavra-passe partilhada por toda a equipa pode facilitar uma intervenção, mas dificulta atribuir a ação a uma pessoa e amplia a exposição. Desenha também os caminhos que não passam pelo IdP principal. Uma máscara de rede na configuração SSO pode encaminhar apenas algumas origens para o IdP; uma via de palavra-passe Google fora desse conjunto precisa de proteção própria. A recuperação de um super admin merece a mesma análise. A afirmação “MFA obrigatório” deve indicar quais identidades e caminhos foram realmente abrangidos, incluindo aqueles usados quando o serviço habitual está indisponível. Num ensaio fictício, pede a um segundo operador autorizado que percorra o procedimento com os responsáveis definidos, sem receber segredos por mensagens improvisadas. Regista o que conseguiu executar, o que faltou e como se encerra o acesso excecional. Depois verifica se uma saída no IdP deixou contas Google órfãs. Antes de reutilizar nomes ou remover dados, coordena direitos, identidade e necessidades de conservação com os responsáveis adequados.

Distinguir credenciais e operações delegadas

Uma service account pode ser a identidade usada por um workload e também um recurso sobre o qual alguém recebe permissões administrativas. Escreve sempre em qual destes sentidos a estás a analisar. O papel Service Account User permite actAs, mas não inclui a emissão direta de access tokens. Quando uma integração só precisa de um ID token OIDC, compara o papel específico com Token Creator e verifica as capacidades adicionais que este último introduz. Atribuir o papel no âmbito mínimo continua a ser uma decisão separada da escolha do papel. Num pipeline fictício, a equipa quer substituir uma chave duradoura por credenciais curtas. O plano precisa de identificar o consumidor, a origem da identidade, a emissão, o destino e a forma de confirmar funcionamento. “Sem chave no repositório” não demonstra que uma chave antiga deixou de funcionar. Uma restrição a novas criações também não revoga automaticamente credenciais já emitidas. Mantém o inventário antigo no plano até teres evidência de migração e retirada. Se uma chave foi exposta, remover o ficheiro não elimina cópias externas: trata a credencial no serviço e investiga o uso. Para uma chave aparentemente inativa, compara a observação com o ciclo do consumidor. Sete dias sem eventos não cobrem um processo trimestral. Usa essa diferença para pedir evidência, em vez de concluir inutilidade ou conceder uma exceção permanente.

Construir confiança sem nomes recicláveis

Workload Identity Federation exige analisar quem pode emitir uma identidade aceite e quais atributos determinam o principal de destino. Um emissor partilhado por vários clientes pode assinar tokens legítimos para todos eles. A assinatura confirma a origem, mas não prova que o tenant corresponde à organização que pretendes autorizar. Define restrições com atributos confiáveis e verifica quem pode alterar o mapeamento. Uma mudança aparentemente pequena nesse ponto pode modificar o conjunto de workloads que alcança o mesmo grant. No caso do pipeline de fecho, o nome do repositório pode ser reutilizado. Um novo recurso com o nome antigo não deve herdar silenciosamente a relação de confiança. Usa identificadores estáveis e não reutilizáveis quando a origem os oferece. Se dois providers no mesmo pool produzem o mesmo subject para entidades diferentes, analisa a colisão; durações curtas não tornam esses subjects distintos. A configuração precisa de um namespace de identidade sem essa ambiguidade. Prepara evidência com uma tabela de quatro tentativas fictícias: tenant esperado e repositório esperado; tenant diferente; repositório diferente; nome antigo associado a outro identificador. Escreve o resultado pretendido antes do ensaio autorizado e conserva a configuração relevante. Não aceites apenas o caso de sucesso: a confiança deve excluir origens que parecem semelhantes mas não satisfazem os critérios. Não coloques tokens completos nem segredos no relatório de aceitação.

Separar grants, fronteiras e hierarquia

Uma revisão deve distinguir uma concessão de operação, uma negação aplicável e a elegibilidade do recurso. PAB não concede permissões. Quando várias PAB se aplicam, um recurso elegível numa delas pode manter-se elegível; não uses uma interseção por intuição. Confirma ainda a versão de aplicação e a cobertura da permissão analisada. Um desenho que depende de bloquear uma operação fora dessa cobertura tem uma lacuna, mesmo que o diagrama da fronteira pareça correto. As condições também não são intercambiáveis entre todos os mecanismos. Uma expressão temporal aceite numa allow policy não deve ser copiada para deny sem verificar as funções suportadas. As condições deny documentadas usam funções de tags de recursos. Na revisão, liga a intenção do controlo à capacidade real do mecanismo, em vez de aprovar uma expressão porque a sintaxe parece familiar. Na Organization Policy, identifica o tipo de constraint e a forma de herança. Uma política booleana explícita do descendente pode substituir a herdada no modelo aplicável. Numa lista configurada para combinar políticas, uma negação explícita prevalece sobre o mesmo valor permitido. Estes são modelos diferentes. Regista organização, pasta, projeto, configuração explícita e defaults relevantes. Usa valores abstratos no exercício para praticar a avaliação; antes de configurar uma constraint real, confirma as regras específicas que lhe são aplicáveis.

Concluir apenas o que a evidência demonstra

Retirar um binding direto é uma ação concreta, mas a conclusão “todo o acesso foi retirado” é mais ampla. Procura caminhos através de grupos, grants herdados e outras identidades autorizadas que estejam dentro do âmbito da revisão. Identifica quando e como cada conjunto foi recolhido. Um inventário incompleto pode revelar um problema real sem ser suficiente para demonstrar ausência de outros problemas. Conserva ambas as ideias no relatório: o caminho encontrado e os limites da pesquisa. Alterações de acesso têm propagação. Grupos encadeados podem demorar mais do que a alteração direta de uma política; não prometas convergência num número fixo de minutos sem suporte. Se uma operação continua a funcionar, regista principal, recurso, operação e hora. Investiga propagação e vias independentes em paralelo, sem concluir imediatamente que a alteração falhou ou que basta esperar. Se há urgência, a contenção deve seguir o processo autorizado do ambiente. Uma entrega útil para APS pode dizer: “binding direto removido; caminho por grupo identificado; política herdada ainda por recolher; validação operacional pendente”. Cada frase tem evidência e responsável. Evita converter um estado verde do script em autorização para produção. A revisão termina quando os critérios acordados estão demonstrados e as lacunas têm tratamento explícito, não quando deixa de haver nomes numa lista parcial.

Praticar com um inventário local

O exercício Python usa apenas dados fictícios incluídos no ficheiro. Executa-o localmente com python3 run.py e lê o JSON produzido. Antes de executar, prevê o resultado do primeiro caso: o grant direto está inativo, mas u pertence a a, a pertence a b e b tem um grant ativo para reviewer/funds. O caminho por grupos deve aparecer como conhecido. Não é uma prova de acesso efetivo, porque o exercício não avalia deny, PAB, expansão de permissões, hierarquia, sessões ou comportamento específico do serviço. Depois compara três estados de uma pertença: true, false e null. True confirma o vínculo nos dados fornecidos; false exclui esse vínculo; null mantém uma possibilidade por confirmar. Quando existe um caminho com informação desconhecida, o resultado deve preservar essa incerteza. Quando existe um ciclo entre grupos, a pesquisa deve terminar. A ordem dos registos não deve mudar a conclusão. Role e resource são comparados como identificadores exatos; o script não infere que um papel inclui outro. O programa verifica oito casos, combinações de estados, permutações e entradas inválidas sem alterar o snapshot. Observa especialmente o inventário incompleto sem caminhos: ausência de resultados não prova revogação. Escreve uma nota de entrega para cada caso, indicando o facto, a limitação e a evidência seguinte. Modifica depois uma pertença nos dados fictícios e justifica a diferença antes de consultar a saída.

"""Offline membership inventory exercise. This is not an IAM policy evaluator."""
import copy
import hashlib
import itertools
import json
from collections import deque
from pathlib import Path


def review(snapshot, principal, role, resource):
    def label(value):
        return isinstance(value, str) and bool(value.strip()) and value == value.strip()

    def state(value):
        return value is None or type(value) is bool

    if not isinstance(snapshot, dict) or set(snapshot) != {"actors", "memberships", "grants", "inventoryComplete"}:
        raise ValueError("Expected actors, memberships, grants and inventoryComplete")
    if type(snapshot["inventoryComplete"]) is not bool:
        raise ValueError("inventoryComplete must be an explicit boolean")
    if not all(label(x) for x in (principal, role, resource)):
        raise ValueError("Query identifiers must be nonempty trimmed strings")
    actors = snapshot["actors"]
    if not isinstance(actors, list) or not all(isinstance(a, dict) and set(a) == {"id", "kind"} and label(a["id"]) and a["kind"] in ("principal", "group") for a in actors):
        raise ValueError("Invalid actors")
    kinds = {a["id"]: a["kind"] for a in actors}
    if len(kinds) != len(actors) or kinds.get(principal) != "principal":
        raise ValueError("Duplicate actors or undeclared query principal")
    edges, grants = snapshot["memberships"], snapshot["grants"]
    if not isinstance(edges, list) or not isinstance(grants, list):
        raise ValueError("Memberships and grants must be lists")
    ids = set()
    for edge in edges:
        if not isinstance(edge, dict) or set(edge) != {"id", "member", "group", "state"} or not label(edge["id"]):
            raise ValueError("Invalid membership record")
        if not isinstance(edge["member"], str) or not isinstance(edge["group"], str) or edge["member"] not in kinds or kinds.get(edge["group"]) != "group" or not state(edge["state"]) or edge["id"] in ids:
            raise ValueError("Invalid membership reference, state or duplicate ID")
        ids.add(edge["id"])
    for grant in grants:
        if not isinstance(grant, dict) or set(grant) != {"id", "principal", "role", "resource", "active"} or not all(label(grant[k]) for k in ("id", "principal", "role", "resource")):
            raise ValueError("Invalid grant record")
        if grant["principal"] not in kinds or not state(grant["active"]) or grant["id"] in ids:
            raise ValueError("Invalid grant reference, state or duplicate ID")
        ids.add(grant["id"])

    def reachable(include_unknown):
        # Sorting makes the chosen shortest witness deterministic for a snapshot.
        found, pending = {principal: []}, deque([principal])
        while pending:
            member = pending.popleft()
            for edge in sorted(edges, key=lambda e: e["id"]):
                if edge["member"] != member or edge["state"] is False or (edge["state"] is None and not include_unknown):
                    continue
                if edge["group"] not in found:
                    found[edge["group"]] = found[member] + [edge["id"]]
                    pending.append(edge["group"])
        return found

    known, possible = reachable(False), reachable(True)
    confirmed, uncertain = [], []
    for grant in sorted(grants, key=lambda g: g["id"]):
        if grant["role"] != role or grant["resource"] != resource or grant["active"] is False:
            continue
        actor = grant["principal"]
        if actor in known and grant["active"] is True:
            confirmed.append({"grant": grant["id"], "membershipPath": known[actor]})
        elif actor in possible:
            uncertain.append({"grant": grant["id"], "membershipPath": possible[actor]})
    return {"knownGrantPaths": confirmed, "possibleButUnconfirmedPaths": uncertain,
            "inventoryComplete": snapshot["inventoryComplete"], "effectiveAccessProven": False,
            "revocationProven": False, "productionAuthorized": False}


def fixture():
    return {"actors": [{"id": "u", "kind": "principal"}, {"id": "a", "kind": "group"}, {"id": "b", "kind": "group"}],
            "memberships": [{"id": "m1", "member": "u", "group": "a", "state": True}, {"id": "m2", "member": "a", "group": "b", "state": True}],
            "grants": [{"id": "direct", "principal": "u", "role": "reviewer", "resource": "funds", "active": False}, {"id": "nested", "principal": "b", "role": "reviewer", "resource": "funds", "active": True}],
            "inventoryComplete": True}


def evidence():
    result, snapshots = [], []
    for name in ("nested-remains", "unknown-membership", "removed-membership", "unknown-grant", "different-resource", "cycle", "incomplete-empty", "direct-and-nested"):
        s = fixture()
        if name == "unknown-membership": s["memberships"][1]["state"] = None
        if name == "removed-membership": s["memberships"][0]["state"] = False
        if name == "unknown-grant": s["grants"][1]["active"] = None
        if name == "different-resource": s["grants"][1]["resource"] = "payments"
        if name == "cycle": s["memberships"].append({"id": "m3", "member": "b", "group": "a", "state": True})
        if name == "incomplete-empty": s["memberships"] = []; s["inventoryComplete"] = False
        if name == "direct-and-nested": s["grants"][0]["active"] = True
        before = copy.deepcopy(s)
        r = review(s, "u", "reviewer", "funds")
        assert s == before
        counts = {"nested-remains": (1, 0), "unknown-membership": (0, 1), "removed-membership": (0, 0), "unknown-grant": (0, 1), "different-resource": (0, 0), "cycle": (1, 0), "incomplete-empty": (0, 0), "direct-and-nested": (2, 0)}
        assert (len(r["knownGrantPaths"]), len(r["possibleButUnconfirmedPaths"])) == counts[name]
        result.append({"id": name, **r}); snapshots.append(s)
    combinations = 0
    for first, second, active in itertools.product((False, None, True), repeat=3):
        s = fixture(); s["memberships"][0]["state"] = first; s["memberships"][1]["state"] = second; s["grants"][1]["active"] = active
        r = review(s, "u", "reviewer", "funds")
        expected_known = all(x is True for x in (first, second, active))
        expected_possible = not any(x is False for x in (first, second, active)) and not expected_known
        assert bool(r["knownGrantPaths"]) == expected_known
        assert bool(r["possibleButUnconfirmedPaths"]) == expected_possible
        combinations += 1
    cycle = snapshots[5]; permutations = 0
    for actors in itertools.permutations(cycle["actors"]):
        for edges in itertools.permutations(cycle["memberships"]):
            for grants in itertools.permutations(cycle["grants"]):
                s = {**cycle, "actors": list(actors), "memberships": list(edges), "grants": list(grants)}
                assert review(s, "u", "reviewer", "funds") == review(cycle, "u", "reviewer", "funds")
                permutations += 1
    invalid = []
    def bad(change):
        s = fixture(); change(s); invalid.append(s)
    bad(lambda s: s.update(inventoryComplete=None))
    bad(lambda s: s.update(inventoryComplete=1))
    bad(lambda s: s["actors"].append(dict(s["actors"][0])))
    bad(lambda s: s["actors"][0].update(kind="group"))
    bad(lambda s: s["memberships"][0].update(state=1))
    bad(lambda s: s["memberships"][0].update(state="true"))
    bad(lambda s: s["memberships"][0].update(member="missing"))
    bad(lambda s: s["memberships"][0].update(group="u"))
    bad(lambda s: s["memberships"][0].update(id="direct"))
    bad(lambda s: s["grants"][1].update(active=0))
    bad(lambda s: s["grants"][1].update(principal="missing"))
    bad(lambda s: s["grants"][1].update(role=" "))
    bad(lambda s: s["grants"][1].update(extra="unexpected"))
    bad(lambda s: s.update(memberships={}))
    for s in invalid:
        try: review(s, "u", "reviewer", "funds")
        except ValueError: pass
        else: raise AssertionError("Invalid input accepted")
    return {"fixtures": result, "stateCombinations": combinations, "recordPermutations": permutations, "invalidInputs": len(invalid), "inputPreserved": True, "orderIndependent": True,
            "network": False, "cloudExecuted": False, "iamPoliciesRead": 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

Um grant direto desapareceu, mas dois grupos ainda ligam o prestador ao grant de revisão de fundos; o snapshot não contém todas as políticas.

Armadilhas comuns

Confundir autenticação com autorização, eliminar apenas o binding direto, tratar nomes recicláveis como identidade estável ou concluir revogação a partir de um inventário parcial.

Tópicos relacionados: Federação de workloads e pipelines · Governance de identidades privilegiadas · Herança e revisão de autorização

Leva esta ideia contigo

Relaciona a conclusão com o pedido concreto e com a cobertura real da evidência; mantém caminhos desconhecidos visíveis até serem investigados.

Criar conta

Referência: Policy types · 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.