Draw the path of a call
A communication incident starts with a concrete request. Identify the process initiating the connection, network origin, requested name, returned address, protocol and final destination. In a fictional APS service, “the API does not work” might mean the name does not resolve, TCP does not establish, the TLS handshake fails or the application returns a denial. These outcomes need different evidence. Do not add identity permissions before demonstrating that the request reached the layer where those permissions are evaluated. Build a table with a row for each hop and decision: resolution, routing, firewall, TLS termination, identity control and operation authorization. Also record who observes each result. An operator laptop might use a different resolver, source and path from the production job. A laptop test does not replace a test of the affected caller. Associate each observation with time, origin and scope without copying credentials into the ticket. The funds closing example in this lesson is fictional and does not describe internal BNP Paribas procedures. A change decision should connect a hypothesis to an observation that can confirm or refute it. If all you have is a timeout, preserve that limitation: you do not yet know which dependency failed. Define the next evidence collection and its owner before changing several layers simultaneously.
Resolve the name in the correct scope
A private zone has networks authorized to query its records. An operator IAM role for reading DNS configuration does not automatically grant that visibility to a VPC. Keep “who can administer records?” separate from “where can records be resolved?”. In a project with analytics and production environments, a zone correctly visible to one can remain invisible to the other. Remediation should respect intended separation and the client’s actual resolution mechanism. When zones overlap, check the most-specific suffix. In the exercise, a child zone exists but the record was published only in its parent. The query selects the child and can receive NXDOMAIN; do not assume automatic searching of a less-specific or public zone. For diagnosis, record the full name, actual resolver and zone matching the request. Avoid interpreting every NXDOMAIN as proof that the application service is shut down. DNSSEC addresses authenticity and integrity of DNS data; it does not encrypt queries. If a requirement mentions name confidentiality, identify transport and exposure scope separately. Before changing configuration, ask a colleague to explain which property each control demonstrates. Handover should allow checking both permitted names and names that must remain invisible instead of limiting acceptance to one successful query. Include the expected result for each environment so a later visibility change can be recognized as intentional or unexpected.
Distinguish DNS forwarding and cache effects
A forwarded query can have a different source from the VM that initiated resolution. For the documented on-premises DNS target type, observe the 35.199.192.0/19 source and verify the appropriate return path. If the VM queries the server directly and receives an answer, that demonstrates the direct flow. It does not demonstrate that replies to forwarded queries can return to the Cloud DNS service source. Compare the two flows before assigning fault to zone content. Apply the same care to time. Reducing an authoritative TTL does not retroactively shorten the validity of already-cached answers. A change at 09:20 can coexist with a value retrieved at 09:00 with a one-hour lifetime. Plan reduction in advance and include the distinction between authoritative and client-observed answers in acceptance. Do not promise universal convergence from one local query. For a shift handover exercise, prepare three columns: observation, possible explanation and missing evidence. “The authoritative server returns the new address” and “the job still uses the old address” can both be true. The next action might be to identify the relevant cache and retrieval time rather than immediately undoing the change. Reversal also has cache effects; document how recovery will be recognized without conflating clients in different states. This turns a vague propagation claim into a specific investigation.
Verify actual TLS inspection coverage
TLS inspection is not a universal property of any traffic crossing a VPC. Cloud NGFW needs resources, associations and policies suitable for the path and feature. CA pools and inspection policies have regional scope; preparing one region does not automatically complete another. Draw where inspection occurs and who maintains each dependency. Include certificate and client-trust checks in acceptance planning without turning disabled validation into a permanent solution to failures. The inspected documentation excludes serverless egress inspection, including paths through Direct VPC egress and Serverless VPC Access connectors. Switching between those paths does not remove the limitation. Before promising a control to the sponsor, confirm traffic origin, protocols, regional scope and current support. If the need does not fit the mechanism, revisit architecture or the combination of controls; a box in a diagram does not demonstrate inspection. In a fictional exercise, separate availability from coverage: a call can succeed while the intended traffic is not inspected, or fail because of an inspection dependency. Define observations for each hypothesis and an approved reversal criterion in advance. Avoid changing network policy, certificates and application version simultaneously without being able to attribute the outcome. Handover should make covered workloads and known exclusions clear, with a named owner for resolving each remaining gap before the agreed acceptance decision.
Control web entry and exit paths
Secure Web Proxy controls outbound web traffic on a path configured to use it. Creating the proxy does not prove that a client stops using an independently allowed direct connection. For each workload, check configuration, routing and alternate paths. On ingress, the application should validate the IAP JWT, including the audience expected for the resource. A valid signature intended for another backend does not satisfy that requirement. Do not replace validation with an email that looks corporate. Cloud Armor supports WAF tuning, including field exclusions for relevant signatures. If the legitimate report_note value causes a false positive, bound remediation and preserve inspection outside that scope. Check what the signature inspects: a parameter exclusion does not equal excluding every whole-body inspection. Record legitimate requests that should pass and control requests that should remain denied using fictional data approved for the exercise. At release review, enumerate current, old and administrative entries. One well-protected main path does not demonstrate protection of the others. Retiring an old entry needs evidence of inaccessibility and a review of consumers that depended on it. If an entry was not observed, record unknown. Do not treat missing logs as automatic proof of missing traffic or declare the whole application protected based only on the domain its usual users access. Keep the actual evidence scope visible in the decision.
Evaluate perimeter rules as conditions
VPC Service Controls adds decisions about calls to protected services; it does not replace IAM. Write down client, source, resource and method before reviewing an exception. Within an ingress rule, required source and identity must match. Across several ingress rules, satisfying one alternative can suffice. Adding a narrower rule therefore does not automatically constrain a broad rule that remains active. A change must address the effective set and legitimate dependencies. When a call involves different perimeters, policies in every involved perimeter must allow the request. Ingress and egress are not defined solely by the direction of returned bytes. A read can require examining client origin, external resources and policies on both sides. Bound the approved exchange without using a universal exception as the response to integration failure. Keep services restricted at the boundary separate from VPC accessible services. Current documentation supports different perimeter policies for VPCs within one host project. Evaluate that capability before claiming project separation is mandatory. For firewalls, also read the exact action: goto_next continues at the next evaluation stage and does not mean allow. Across peering, distinguish network tags in VPC rules from secure Tags in firewall policies; similar names do not guarantee the same identification scope. Record those distinctions in the architecture decision so implementation teams can check the intended mechanism.
Accept private connectivity with evidence
Private Service Connect provides service-oriented access and uses address translation, which can avoid coordinating producer and consumer internal address ranges. That does not mean joining entire networks. For a consumer to initiate calls to a published service, evaluate endpoints and supported types. For a producer to initiate new connections into a consumer network, evaluate interfaces and network attachments. Responses on an existing initiated flow do not prove the ability to initiate another in reverse. Read endpoint state carefully. Pending can reflect missing acceptance or connection limits and does not necessarily disappear with time. Accepted confirms configuration acceptance but does not guarantee useful traffic. Service acceptance must still observe the actual call and relevant dependencies. Coordinate authorized scope with the producer instead of repeatedly changing client HTTP credentials to address a connection not yet established. Private Google Access is configured per subnet. When moving a template, review that destination property and DNS, route and firewall requirements. The restricted.googleapis.com endpoint limits available services; an unsupported API requires a design review. Finally, the default internet gateway name does not prove public Internet transit for the documented Google API path. Evaluate destination and supported behavior while retaining an explanation the production team can verify. These checks connect architecture intent with the particular client and service rather than relying on product names alone.
Compare coverage without hiding unknowns
The local exercise defines a fictional requirement: each reachable path needs TLS, identity control and WAF. This is not a universal rule for every service. Each observation uses true, false or null. True declares supplied positive evidence, false declares absence or unreachability depending on the field, and null preserves unknown information. The program neither collects that evidence nor verifies that its supplier is correct. Inventory completeness is also a declaration that would need justification outside the exercise. Before running python3 run.py, predict the case with protected front and reachable legacy lacking WAF. A known gap should appear. If admin reachability is unknown, it should remain unresolved; it does not disappear because front is covered. A known failure remains visible even when another control on the same path is unknown. If every path is unreachable, the program does not declare useful service coverage. This avoids confusing a shut-down system with an accepted service. The nine included cases cover incomplete inventory, missing paths, failures and uncertainty. The program explores 162 state combinations, six permutations and 14 invalid inputs. It calls no APIs, sends no packets and validates neither JWTs, certificates, firewall rules nor real policies. Write a handover note stating the finding, evidence limitation and next required collection. No output authorizes a production change; even a covered snapshot is only a statement about the supplied model.
"""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))
The job resolves an old name, an endpoint remains Pending and a legacy path lacks its required control; each fact needs separate handling.
Common pitfalls
Confusing DNS with connectivity, a signature with a valid audience, a new narrow rule with removal of a broad exception and Accepted with an operational service.
Related topics: Workload identities and authorization · DNS and TLS diagnosis · Acceptance and production handover
A control supports a conclusion only on demonstrated paths and scope; known failures and unknown information must remain visible.
Reference: Configure Private Google Access · Current linked guide; edition date unconfirmed (2026-09-30 inspection)