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

Revogação, sessões e decisões de recuperação

Reconstrói uma cronologia de identidade, distingue âmbitos de revogação e fundamenta contenção e recuperação com evidência.

Começar pela identidade e pelo relógio

Às 09:12, o serviço de reconciliação de um banco fictício apresenta leituras fora do padrão. Às 09:16, o responsável por segurança confirma que uma credencial saiu do ambiente autorizado. O gestor de incidente tem de coordenar contenção, investigação e continuidade do batch. Este exercício não descreve procedimentos internos BNP Paribas. O primeiro registo útil identifica quem atua, qual a credencial, que recurso foi acedido, quando ocorreu a observação e qual a origem da evidência. “O acesso foi revogado” é demasiado amplo se a ação apenas removeu um ficheiro num portátil. Constrói uma cronologia com três colunas: facto observado, ação executada e resultado ainda por confirmar. Uma resposta HTTP de sucesso a uma alteração administrativa prova a aceitação dessa operação; não é uma medição de todos os caminhos de acesso. Não registes tokens completos, palavras-passe ou chaves privadas no ticket. Usa identificadores que permitam correlacionar os registos sem redistribuir o segredo. A equipa de plataforma confirma os workloads afetados; segurança avalia a contenção; operação mantém a previsão de conclusão do batch; o PM comunica impacto, incerteza e a próxima atualização. Antes de escolher comandos, distingue conta de utilizador gerida, identidade Workforce, service account e origem externa de um workload. A mesma expressão “terminar sessão” pode corresponder a efeitos diferentes. Define também o âmbito temporal: a alteração realizada às 09:20 não apaga automaticamente o que aconteceu às 09:10.

Conter utilizadores sem confundir suspensão e eliminação

O processo de saída de um colaborador e a resposta a uma conta comprometida podem usar partes do mesmo inventário, mas precisam de decisões próprias. Numa conta Workspace gerida, a suspensão repõe cookies de início de sessão e tokens OAuth. Ainda é necessário investigar ações anteriores e configurações suspeitas antes de devolver acesso. Se a pessoa usava uma identidade externa com grants diretos em recursos, fechar o registo no diretório interno não demonstra remoção dessas autorizações. Regista o principal exato e verifica os âmbitos em que recebeu acesso. Num segundo caso fictício, um prestador termina o contrato à sexta-feira. O projeto principal removeu o binding, mas uma equipa de dados mantém um grant num recurso. O handover deve listar o âmbito já tratado e o que falta confirmar, com um responsável por cada ação. Não transformes o sucesso de uma alteração num certificado de revogação completa. A investigação pode continuar mesmo quando já existe contenção suficiente para reduzir o risco imediato. Eliminar um subject Workforce também pode afetar os dados que ele possui exclusivamente. A janela de recuperação não significa que os dados permanecem acessíveis durante todo esse período. Antes de usar eliminação numa necessidade temporária, identifica propriedade, preservação e condições de recuperação. Uma decisão consciente de eliminação é diferente de usar o comando como atalho para terminar uma sessão.

Separar a chave dos tokens que já foram emitidos

A equipa elimina uma chave às 10:05 e marca a ação como concluída. Um token emitido às 10:00 continua dentro do seu prazo. São dois objetos diferentes: impedir o uso futuro da chave não invalida por si os tokens já emitidos. O plano de resposta deve identificar também caminhos de impersonation e quem consegue emitir novas credenciais. Se um principal indevido mantém Token Creator, a rotação da chave não encerrou esse caminho. No caso do batch, desativar a service account pode afetar tanto a exportação como a reconciliação que partilham a identidade. Essa consequência deve estar no registo de decisão, com impacto de negócio e alternativa de recuperação. Não se deve comunicar “apenas o processo malicioso foi bloqueado” sem um controlo que tenha efetivamente esse âmbito. Uma identidade alternativa, se aprovada, exige os seus próprios privilégios mínimos, testes e critérios de aceitação. A urgência não transforma uma proposta em autorização. Define a reativação como uma decisão separada da contenção. A equipa precisa de conhecer o último momento em que a emissão indevida era possível, o prazo aplicável às credenciais e a evidência que sustenta esses limites. Uma política de duração alargada impede usar uma hora como máximo universal. No handover, escreve a condição pendente e o responsável por a confirmar; evita uma hora de recuperação baseada apenas no momento em que o alerta desapareceu.

Identificar a credencial que a aplicação realmente usa

Um operador revoga a configuração local do CLI e vê a lista de contas vazia. Entretanto, um serviço noutro servidor continua a aceder a dados. A observação não é contraditória se os dois processos usarem origens diferentes. Para service accounts e certas contas externas, gcloud auth revoke tem um efeito local e não elimina a chave ou a credencial de origem no sistema que a emitiu. Regista exatamente a configuração removida e a máquina onde a ação ocorreu. Application Default Credentials também exige inventário da origem efetiva. A revogação das ADC criadas por application-default login não abrange automaticamente um ficheiro selecionado por variável de ambiente nem a identidade técnica associada a uma VM. Num exercício guiado, pede ao formando uma tabela com processo, host, principal e origem de credenciais. Só depois associa cada ação ao âmbito que pode tratar. Não é necessário imprimir segredos para identificar a origem; o caminho do ficheiro e a identidade esperada podem ser tratados com os cuidados de acesso adequados. No fluxo humano, uma reposição de palavra-passe pode interromper sincronização de aplicações de email com determinados âmbitos OAuth. Isso não demonstra que qualquer outro mecanismo de autenticação foi tratado, nem que dados já descarregados foram apagados. A conclusão do incidente deve unir evidência por caminho de acesso, incluindo exceções e lacunas, em vez de depender do sucesso de um único comando.

Ligar grupos temporários, sessões e prova de autenticação

A pessoa entra numa sessão Workforce às 14:00 e ativa um grupo JIT às 14:10. Num desenho sem SCIM, a sessão existente pode continuar com as claims anteriores. O problema não se resolve automaticamente acrescentando papéis ao projeto. Primeiro compara a hora da autenticação com a hora da alteração de grupo e testa uma nova autenticação. No sentido inverso, uma pertença expirada no IdP pode continuar representada numa sessão antiga; o prazo JIT não deve ser prometido como instante garantido de perda de acesso sem analisar esse mecanismo. A duração da sessão deve fazer parte da aceitação do desenho de acesso temporário. Num workshop, apresenta duas linhas temporais e pede à equipa que identifique o intervalo entre a intenção administrativa e a atualização observável da identidade. Se o desenho usa SCIM, acrescenta a evidência de aprovisionamento e de saúde da sincronização. Não a substituas por uma suposição de atualização instantânea. A forma de atualização deve estar explícita no cenário. A expiração de uma sessão IAP pode levar a autenticação silenciosa quando a sessão IdP continua ativa. Não ver uma caixa de palavra-passe não demonstra ausência de um fluxo de autenticação. Para investigar, relaciona eventos e identificadores. Os eventos Workforce de login e troca de tokens pertencem à recolha Data Access do STS; confirma a configuração antes de interpretar uma pesquisa vazia como ausência de atividade.

Interpretar um diagnóstico incompleto sem inventar certeza

Uma ferramenta de diagnóstico tem entradas, permissões e limites de suporte. Se o investigador não pode ver uma política ou confirmar pertença a grupos, Unknown não significa que o utilizador está impedido de aceder. Antes de alterar grants, verifica que a consulta descreve o principal, recurso e permissão realmente envolvidos. Repetir a consulta com a identidade de um administrador e aplicar o resultado a outra pessoa é uma troca do objeto de análise. O diagnóstico de políticas Pub/Sub requer atenção ao âmbito suportado: rever o projeto não substitui a revisão da política allow no próprio recurso. De forma semelhante, a rejeição de um tipo de principal não suportado não é uma decisão de autorização negativa. No caso das PAB, a organização do conjunto de principais pode diferir da organização do recurso alvo. Regista onde falta visibilidade e quem pode fornecer a evidência. A funcionalidade de diagnóstico PAB consultada está marcada Preview; isso deve estar explícito quando influencia a escolha de ferramenta. Num caso guiado, o utilizador recebe acesso durante uma janela e o investigador obtém Unknown por falta de leitura dos grupos. Uma boa próxima ação é obter a evidência em falta pelo processo autorizado, mantendo a contenção necessária. Conceder Owner ao utilizador para tornar o resultado verde modifica o risco sem esclarecer a causa. O relatório deve distinguir uma decisão real observada, uma simulação com âmbito definido e uma avaliação incompleta.

Preservar identidade e rever mudanças de hierarquia

A conta técnica apagada e uma conta nova com o mesmo email não são a mesma identidade. O identificador numérico distingue-as e os grants antigos não passam para a nova por terem um nome igual. Quando a necessidade é recuperar a identidade original, verifica a janela e as condições de undelete antes de ocupar o nome com outra conta. Numa revisão de recuperação, pede evidência do identificador, dos bindings relevantes e das dependências do workload. O êxito de criar um nome não é prova de restauro funcional. A hierarquia introduz outra fonte de diferenças pouco visíveis. Um projeto movido para uma pasta pode manter a política local e ganhar ou perder acesso herdado. Num caso fictício, uma equipa de reporting passa a conseguir ler um recurso porque o projeto entrou numa pasta com grants mais amplos. A revisão limitada ao IAM local não deteta essa mudança. Compara origem e destino, incluindo controlos aplicáveis, acesso esperado e testes negativos. Analyze Move ajuda a identificar avisos e bloqueios, mas a existência de um relatório não demonstra todas as condições do destino. Uma secção de firewall que falhou por falta de permissão fica por avaliar. O comité precisa de uma lista de verificações concluídas, lacunas e responsáveis, além da janela de execução. Se houver adiamento, regista a evidência concreta que falta para voltar a decidir.

Praticar com uma cronologia declarada e assumir os limites

O exercício Python usa minutos inteiros num relógio fictício de incidente. Recebe principal, momento atual, último instante declarado de emissão possível, duração máxima declarada e inventário de tokens. Não lê tokens reais, não valida assinaturas, não consulta IAM e não autoriza recuperação em produção. Os valores de entrada são afirmações fornecidas para análise, não factos que o programa conseguiu verificar. Assim, uma saída correta do código não demonstra contenção do ambiente real. Começa pelo exemplo com emissão terminada no minuto 40, duração máxima 60 e observação no minuto 95. O horizonte declarado é 100, logo faltam cinco minutos nesse modelo. Depois altera o momento para 100 e observa a fronteira: um token cujo prazo termina nesse instante já não aparece entre os conhecidos ainda dentro da validade. Retira a duração máxima ou a hora de fim de emissão e verifica que o resultado passa a desconhecido, em vez de assumir um prazo favorável. Acrescenta agora um token emitido depois do fim declarado ou com duração superior ao limite fornecido. O exercício conserva a contradição e deixa de tratar esse limite como confiável. Um token de outro principal é identificado separadamente. Inventário incompleto continua explícito mesmo quando o horizonte calculado passou. No debrief, explica o que o cálculo demonstra sob as premissas, que evidência falta no incidente e quem pode aprovar a recuperação. Essa separação entre cálculo, evidência e decisão é o resultado da aula.

"""Offline worksheet for declared credential timing; no cloud authorization decisions.
Times are whole minutes on a fictional incident clock. Input claims are not verified.
"""
import copy
import hashlib
import itertools
import json
from pathlib import Path


def integer(value, name, minimum=0):
    if type(value) is not int or value < minimum:
        raise ValueError(name + ' must be an integer >= ' + str(minimum))
    return value


def label(value, name):
    if not isinstance(value, str) or not value.strip():
        raise ValueError(name + ' must be nonempty text')
    return value


def evaluate(snapshot):
    if not isinstance(snapshot, dict):
        raise ValueError('snapshot must be an object')
    principal = label(snapshot.get('principal'), 'principal')
    now = integer(snapshot.get('now'), 'now')
    stopped = snapshot.get('issuanceStoppedAt')
    maximum = snapshot.get('maximumLifetime')
    if stopped is not None:
        integer(stopped, 'issuanceStoppedAt')
        if stopped > now:
            raise ValueError('issuance stop cannot be in the future')
    if maximum is not None:
        integer(maximum, 'maximumLifetime', 1)
    complete = snapshot.get('inventoryComplete')
    if type(complete) is not bool:
        raise ValueError('inventoryComplete must be boolean')
    tokens = snapshot.get('tokens')
    if not isinstance(tokens, list):
        raise ValueError('tokens must be a list')
    ids = set()
    selected = []
    ignored = []
    for token in tokens:
        if not isinstance(token, dict):
            raise ValueError('token must be an object')
        tid = label(token.get('id'), 'id')
        if tid in ids:
            raise ValueError('duplicate token id')
        ids.add(tid)
        owner = label(token.get('principal'), 'token principal')
        issued = integer(token.get('issuedAt'), 'issuedAt')
        expires = integer(token.get('expiresAt'), 'expiresAt')
        if issued > now or expires <= issued:
            raise ValueError('invalid token interval')
        (selected if owner == principal else ignored).append(token)
    contradictions = []
    for token in selected:
        if stopped is not None and token['issuedAt'] > stopped:
            contradictions.append(token['id'] + ':issued-after-declared-stop')
        if maximum is not None and token['expiresAt'] - token['issuedAt'] > maximum:
            contradictions.append(token['id'] + ':exceeds-declared-lifetime')
    # Issuance at the stop minute is included conservatively in the upper bound.
    horizon = stopped + maximum if stopped is not None and maximum is not None else None
    elapsed = None if horizon is None or contradictions else now >= horizon
    return {
        'principal': principal,
        'knownUnexpiredTokens': sorted(t['id'] for t in selected if now < t['expiresAt']),
        'ignoredOtherPrincipal': sorted(t['id'] for t in ignored),
        'declaredHorizon': horizon,
        'remainingUnderDeclaredBounds': None if horizon is None or contradictions else max(0, horizon - now),
        'elapsedUnderDeclaredBounds': elapsed,
        'contradictions': sorted(contradictions),
        'inventoryComplete': complete,
        'inventoryCoverageUnproven': not complete,
        'credentialValidityVerified': False,
        'actualAccessEvaluated': False,
        'productionRecoveryAuthorized': False,
    }


def evidence():
    base = {'principal': 'batch-A', 'now': 100, 'issuanceStoppedAt': 40,
            'maximumLifetime': 60, 'inventoryComplete': True,
            'tokens': [{'id': 't1', 'principal': 'batch-A', 'issuedAt': 39, 'expiresAt': 99}]}
    def variant(**changes):
        return dict(copy.deepcopy(base), **changes)
    samples = {
        'window-elapsed': variant(),
        'still-waiting': variant(now=95),
        'unknown-lifetime': variant(maximumLifetime=None),
        'unknown-stop': variant(issuanceStoppedAt=None),
        'incomplete-inventory': variant(inventoryComplete=False),
        'issuance-contradiction': variant(tokens=[{'id': 'late', 'principal': 'batch-A', 'issuedAt': 45, 'expiresAt': 70}]),
        'lifetime-contradiction': variant(tokens=[{'id': 'long', 'principal': 'batch-A', 'issuedAt': 30, 'expiresAt': 110}]),
        'other-principal': variant(tokens=base['tokens'] + [{'id': 'other', 'principal': 'batch-B', 'issuedAt': 90, 'expiresAt': 200}]),
        'expiry-boundary': variant(tokens=[{'id': 'edge', 'principal': 'batch-A', 'issuedAt': 40, 'expiresAt': 100}]),
        'no-tokens-unknown-stop': variant(tokens=[], issuanceStoppedAt=None),
        'two-contradictions': variant(tokens=[{'id': 'conflict', 'principal': 'batch-A', 'issuedAt': 50, 'expiresAt': 150}]),
    }
    results = {name: evaluate(value) for name, value in samples.items()}
    assert results['window-elapsed']['elapsedUnderDeclaredBounds'] is True
    assert results['still-waiting']['knownUnexpiredTokens'] == ['t1']
    assert results['still-waiting']['remainingUnderDeclaredBounds'] == 5
    assert results['unknown-lifetime']['declaredHorizon'] is None
    assert results['unknown-stop']['elapsedUnderDeclaredBounds'] is None
    assert results['incomplete-inventory']['inventoryCoverageUnproven'] is True
    assert results['incomplete-inventory']['elapsedUnderDeclaredBounds'] is True
    assert results['issuance-contradiction']['elapsedUnderDeclaredBounds'] is None
    assert results['lifetime-contradiction']['knownUnexpiredTokens'] == ['long']
    assert results['lifetime-contradiction']['remainingUnderDeclaredBounds'] is None
    assert results['other-principal']['ignoredOtherPrincipal'] == ['other']
    assert results['expiry-boundary']['knownUnexpiredTokens'] == []
    assert results['no-tokens-unknown-stop']['elapsedUnderDeclaredBounds'] is None
    assert len(results['two-contradictions']['contradictions']) == 2
    combos = 0
    for stopped, lifetime, now in itertools.product([None, 0, 20], [None, 30, 60], [60, 100, 130]):
        x = variant(tokens=[], issuanceStoppedAt=stopped, maximumLifetime=lifetime, now=now)
        before = copy.deepcopy(x)
        r = evaluate(x)
        assert x == before
        if stopped is None or lifetime is None:
            assert r['elapsedUnderDeclaredBounds'] is None
        elif stopped == 20 and lifetime == 60 and now == 60:
            assert r['elapsedUnderDeclaredBounds'] is False
            assert r['remainingUnderDeclaredBounds'] == 20
        else:
            assert r['elapsedUnderDeclaredBounds'] is True
        assert r['productionRecoveryAuthorized'] is False
        combos += 1
    tokens = [base['tokens'][0], {'id': 'late', 'principal': 'batch-A', 'issuedAt': 45, 'expiresAt': 70},
              {'id': 'other', 'principal': 'batch-B', 'issuedAt': 90, 'expiresAt': 200}]
    expected = evaluate(variant(tokens=tokens))
    permutations = 0
    for perm in itertools.permutations(tokens):
        x = variant(tokens=list(perm)); before = copy.deepcopy(x)
        assert evaluate(x) == expected
        assert x == before
        permutations += 1
    bad = [None, [], variant(principal=''), variant(now=True), variant(now=-1),
           variant(issuanceStoppedAt=101), variant(issuanceStoppedAt='40'),
           variant(maximumLifetime=0), variant(maximumLifetime=True),
           variant(inventoryComplete=1), variant(tokens=None), variant(tokens=[{}]),
           variant(tokens=[base['tokens'][0], base['tokens'][0]]),
           variant(tokens=[{'id':'x','principal':'batch-A','issuedAt':101,'expiresAt':130}]),
           variant(tokens=[{'id':'x','principal':'batch-A','issuedAt':30,'expiresAt':30}]),
           variant(tokens=[{'id':'x','principal':'','issuedAt':30,'expiresAt':50}]),
           variant(tokens=[{'id':'x','principal':'batch-A','issuedAt':30,'expiresAt':True}])]
    for x in bad:
        try:
            evaluate(x)
        except ValueError:
            pass
        else:
            raise AssertionError('invalid input accepted')
    return {'scriptSha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
            'fixtures': [{'id': name, **r} for name, r in results.items()],
            'timingCombinations': combos, 'inputPermutations': permutations,
            'invalidInputs': len(bad), 'inputPreserved': True, 'orderIndependent': True,
            'network': False, 'cloudExecuted': False, 'persistentWrites': False,
            'scope': 'Fictional declared timing worksheet; no token parsing, signature verification, policy evaluation or real revocation.'}


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

No relógio fictício, emissão terminada em 40 e duração máxima 60 produzem horizonte 100. No minuto 95 faltam cinco minutos sob essas premissas; um token emitido em 45 contradiz o fim declarado e impede confiar no cálculo.

Armadilhas comuns

Tratar limpeza local como revogação global; chave eliminada como token expirado; Unknown como deny; nome recriado como identidade restaurada; relatório parcial como aprovação de mudança.

Tópicos relacionados: Identidades, credenciais e evidência de acesso · Segurança operacional e evidência de release · Governação, âmbito e evidência de controlos

Leva esta ideia contigo

Relaciona identidade, credencial, âmbito e tempo. A ação administrativa, a evidência do efeito e a aprovação de recuperação são registos diferentes, que devem concordar antes de fechar o incidente.

Criar conta

Referência: Respond to compromised Google Cloud credentials · 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.