Appendix D

Security Checklist

Scope. Every security control this book teaches, merged across all thirty-seven chapters, deduplicated, and re-sorted by the resource it applies to. Each row carries a verification command and the section that explains it. Prerequisites. None. This appendix is a lookup artifact and is meant to be opened out of order. Verified against. Google Cloud console and API surface as of 2026-09, Cloud SDK 583.0.0; every verification command extracted from its source chapter and re-resolved with --help; see sources at end.

Every chapter ends with a checklist ordered by topic, because that is how the chapter was ordered. That is the wrong order for the only moments anyone reads a checklist — an audit, a review, a handover — when the question is what do I check on this resource, not what did Chapter 12 say.

This appendix is that reordering. The 518 rows written across the thirty-seven chapter checklists were extracted mechanically, merged where the same control appeared in several chapters, and cut where a control was specific to the one chapter that raised it. 190 rows survive. The verification commands are copied from the chapters, not retyped, because a retyped command is a command that can be subtly wrong; where several chapters verified the same control differently, the most specific command survived.

Read the § column as the authority: the row is a reminder, and the section is the explanation.

D.1 Organization §

The organization node and the policy that every project below it inherits. These are the controls that cannot be retrofitted cheaply: an estate without an organization resource has nowhere to put any of them.

ControlWhy it mattersHow to verify§
Organization resource exists and owns all projectsWithout an org node there is no place to enforce inherited policygcloud organizations list; Console → IAM & Admin → Settings§1.7
Domain-restricted sharing enforcedBlocks IAM grants to identities outside your organizationgcloud org-policies describe iam.allowedPolicyMemberDomains --organization=ORG_ID --effective; Console → IAM & Admin → Organization policies§2.1
Default org-wide Project Creator / Billing Creator grants removedOtherwise any employee can create ungoverned, funded projectsgcloud organizations get-iam-policy ORG_ID --format="value(bindings.role,bindings.members)"; Console → IAM & Admin → IAM (org scope)§2.5
Super admins separated from Organization AdministratorsPrevents one identity from holding both control planesAdmin console → Account → Admin roles; gcloud organizations get-iam-policy ORG_ID§2.4
Essential Contacts set for the Security categoryGoogle's compromise and suspension notices must reach someonegcloud essential-contacts list --organization=ORG_ID; Console → IAM & Admin → Essential Contacts§2.14
Default network suppressed on new projectsThe default VPC ships permissive rules including SSH from 0.0.0.0/0gcloud compute networks list --project=PROJECT_ID --format="value(name)"; org policy constraints/compute.skipDefaultNetworkCreation (§2.30)§1.12
Automatic role grants to default service accounts disabledOtherwise default SAs receive a basic role granting broad project writegcloud org-policies describe iam.automaticIamGrantsForDefaultServiceAccounts --organization=ORG_ID --effective§1.10
Resource locations constrainedData residency must be enforced, not requestedgcloud org-policies describe constraints/gcp.resourceLocations --organization=ORG_ID --effective§1.6
Every new org policy went dry-run before enforcementEnforcing untested constraints causes outages and rollback pressuregcloud org-policies describe CONSTRAINT --organization=ORG_ID --format="value(dryRunSpec)"§2.30
roles/orgpolicy.policyAdmin granted only at the organizationA project-scoped org policy admin can relax inherited guardrailsgcloud asset search-all-iam-policies --scope="organizations/ORG_ID" --query="policy:roles/orgpolicy.policyAdmin"§2.34
Automatic baseline status determinedSeven constraints may already be enforced, or nonegcloud org-policies list --organization=ORG_ID --show-unset§31.1
Baseline stored as code and applied by pipelineA hand-applied baseline cannot be diffed or reviewedgoogle_org_policy_policy resources in the repository§31.1
Service usage restricted to an allowlistPrevents unreviewed services from holding datagcloud org-policies describe gcp.restrictServiceUsage --organization=ORG_ID --effective§2.30
Shared VPC host projects constrained to an allowlistStops workloads attaching to an unapproved networkgcloud org-policies describe compute.restrictSharedVpcHostProjects --organization=ORG_ID --effective§2.30
Effective policy checked at the resourceAn override below the root is invisible at the rootgcloud org-policies describe CONSTRAINT --project=PROJECT_ID --effective§31.1

D.2 Projects §

Project-level hygiene — the boundary, its lifecycle, and the metadata that makes it governable. A project is the unit of blast radius, so most of what follows is about knowing which one you are looking at and who can create another.

ControlWhy it mattersHow to verify§
Every project carries the mandatory label setAttribution, detection scoping, and ownership all key on labelsgcloud asset search-all-resources --scope="organizations/ORG_ID" --asset-types="cloudresourcemanager.googleapis.com/Project" --query="NOT labels.data-class:*"§2.6
Environment tag bound at the environment foldersTags, unlike labels, drive IAM conditions and org policygcloud resource-manager tags bindings list --parent=//cloudresourcemanager.googleapis.com/projects/PROJECT_NUMBER§2.13
Production projects have deletion protectionDeletion has only a 30-day window and no support override afterwardgcloud projects describe PROJECT_ID --format="value(lifecycleState)"; Terraform deletion_policy = "PREVENT"§2.29
Budget alerts routed to securitySpend anomalies are early compromise indicatorsgcloud billing budgets list --billing-account=BILLING_ACCOUNT_ID; Console → Billing → Budgets & alerts§1.24
Perimeter membership is created by the project factoryMembership is per project and is never inheritedTerraform: the project module adds the project number to the perimeter§20.2
Public assets isolated to their own folderMakes "what is public" enumerableFolder structure and its policy overrides§31.7

D.3 IAM §

Identity and access. The largest single source of real findings in the book, and the section an auditor opens first. Basic roles, service account keys, and public principals account for most of it.

ControlWhy it mattersHow to verify§
No legacy basic role grants outside the sandbox folderThey are unbounded and cannot be narrowed with conditionsgcloud asset search-all-iam-policies --scope=organizations/ORG_ID --query='policy:(owner OR editor)'§3.10
No allUsers or allAuthenticatedUsers bindingsBoth make a resource effectively public and cannot carry conditionsgcloud asset search-all-iam-policies --scope="organizations/ORG_ID" --query="policy:(allUsers OR allAuthenticatedUsers)"§3.32
Human roles bound to groups, not usersUser bindings survive departures and team movesgcloud asset search-all-iam-policies --scope="organizations/ORG_ID" --query="policy:\"user:\""§3.3
No user-managed service account keysNon-expiring bearer credentials with no context bindinggcloud org-policies describe iam.managed.disableServiceAccountKeyCreation --organization=ORG_ID --effective§3.25
Deny policy protecting production project deletionOnly deny subtracts; org policy cannot express thisgcloud iam policies list --attachment-point="cloudresourcemanager.googleapis.com/organizations/ORG_ID" --kind=denypolicies§3.17
Org-node bindings match an approved allowlistOrg grants apply to every current and future resourcegcloud organizations get-iam-policy ORG_ID --format="table(bindings.role,bindings.members)"§3.8
Separation-of-duties pairs have no overlapping principalA single principal completing a sensitive action end to endgcloud asset analyze-iam-policy --organization=ORG_ID --permissions=PERMISSION --analyze-service-account-impersonation§3.22
No impersonation path from low-privilege to high-privilege accountsChains grant privileges nothing else revealssame command, run per privileged account§3.5
Pipeline identity federated, not keyed, and impersonating per-environment accountsThe CI identity is the highest-value target in the orggcloud iam service-accounts get-iam-policy SA_EMAIL --format=json; Console → IAM & Admin → Workload Identity Federation§3.34
Runtime service accounts hold no setIamPolicy on anythingAny setIamPolicy is a self-escalation pathgcloud iam roles describe ROLE --format="value(includedPermissions)" for each granted role§3.9
PAM entitlements exist for every privileged role, with approvers distinct from requestersStanding privilege is the risk JIT removesgcloud pam entitlements list --organization=ORG_ID --location=global§3.27
Access Approval enabled for all supported servicesConverts "Google can access our data" into a signed, per-case recordgcloud access-approval settings get --organization=ORG_ID; Console → Security → Access Approval§3.29
IAM recommendations reviewed on a scheduleUnused privilege is the cheapest privilege to removegcloud recommender recommendations list --project=PROJECT_ID --location=global --recommender=google.iam.policy.Recommender§3.31
Every workload identity pool provider has an attribute conditionWithout one, the provider trusts every identity the issuer will ever sign forgcloud iam workload-identity-pools providers describe PROVIDER_ID --workload-identity-pool=POOL_ID --location=global --format="value(attributeCondition)"; Console → IAM & Admin → Workload Identity Federation§4.4
No role bound to a whole pool (.../POOL_ID/*)Grants access to every identity the provider acceptsgcloud asset search-all-iam-policies --scope="organizations/ORG_ID" --query="policy:workloadIdentityPools"§4.3
No instance uses the Compute Engine default service accountA shared, historically over-privileged identity is not on the audit trail for any workloadgcloud asset search-all-iam-policies --scope=organizations/ORG_ID --query='policy:compute@developer'§8.29
One service account per workloadA shared account destroys attributionAsset inventory joined to workload list§35.4
roles/iam.serviceAccountUser granted narrowlyOtherwise anyone who can deploy can run as any accountgcloud iam service-accounts get-iam-policy SA§35.4
Break-glass path exists and is testedThe first execution should not be during an incidentgcloud pam entitlements list --location=global --organization=ORG_ID§34.1
Credential lifetime constraint checkedIt decides whether the residual window is 1 hour or 12gcloud org-policies describe iam.allowServiceAccountCredentialLifetimeExtension --organization=ORG_ID --effective§34.5

D.4 Networking §

Network reachability and its evidence. Firewall policy, private access, perimeters, and the logs that show whether any of it holds.

ControlWhy it mattersHow to verify§
All production networks are custom subnet modeAuto mode adds subnets for new regions without reviewgcloud compute networks describe NETWORK --format="value(autoCreateSubnetworks)"§5.2
Egress deny-default rule exists on every production VPCThe implied egress rule is allow, so exfiltration is open by defaultgcloud compute firewall-rules list --filter="direction=EGRESS AND denied:*" --format="table(name,priority,destinationRanges.list())"§5.13
No firewall rule allows 22 or 3389 from 0.0.0.0/0Direct administrative exposure to the internetgcloud compute firewall-rules list --filter="allowed.ports:22 AND sourceRanges:0.0.0.0/0"§5.13
Hierarchical policy allows IAP 35.235.240.0/20 and health checks 130.211.0.0/22, 35.191.0.0/16These paths must work regardless of project configurationgcloud compute firewall-policies describe FIREWALL_POLICY_ID --organization=ORG_ID§5.14
Firewall targets use secure tags or service accounts, not network tagsNetwork tags are ungoverned strings any VM editor can addgcloud compute firewall-rules list --format="table(name,targetTags.list(),targetServiceAccounts.list())"§5.16
Private Google Access enabled on every workload subnetVMs without external IPs otherwise cannot reach Google APIsgcloud compute networks subnets list --filter="privateIpGoogleAccess=false" --format="table(name,region,network)"§5.17
restricted.googleapis.com used inside VPC Service Controls perimetersIt refuses APIs outside perimeter support, closing an exfiltration pathinspect the private googleapis.com zone records with gcloud dns record-sets list§5.17
Cloud NAT scoped to named subnets with reserved static IPsA VPC-wide NAT grants future subnets egress by defaultgcloud compute routers nats describe NAT --router=ROUTER --region=REGION --format="value(sourceSubnetworkIpRangesToNat,natIps)"§5.20
VPC Flow Logs enabled on production subnetsThe only record of who talked to whomgcloud compute networks subnets list --filter="enableFlowLogs=false"§5.4
DNS query logging enabled on private zones and DNS policyHighest-signal evidence of command-and-control attemptsgcloud dns managed-zones describe ZONE --format="value(cloudLoggingConfig.enableLogging)"§5.22
DNSSEC enabled on public zones and CAA records presentDNS control is certificate-issuance controlgcloud dns managed-zones describe ZONE --format="value(dnssecConfig.state)"§5.25
roles/compute.networkUser granted on subnets, never at the host projectA project-level grant covers every future subnetgcloud projects get-iam-policy HOST_PROJECT --format=json and per-subnet gcloud compute networks subnets get-iam-policy§5.28
No custom 0.0.0.0/0 route in production without reviewA route is a complete, quiet traffic redirectgcloud compute routes list --filter="destRange=0.0.0.0/0" --format="table(name,network,nextHopIlb,nextHopInstance,priority)"§5.9
No public DNS record points at a deleted resourceDangling records enable subdomain takeoverreconcile gcloud dns record-sets list --zone=ZONE against live resources§5.25
HA VPN uses both gateway interfaces to two peer devices or two peer interfacesA single active interface carries no availability SLAgcloud compute vpn-tunnels list --format="table(name,vpnGateway,vpnGatewayInterface,peerExternalGateway,status)"; Console → Hybrid Connectivity → VPN§6.3
BGP sessions have MD5 authentication configuredPrevents session hijack by anything reaching the link-local addressgcloud compute routers describe ROUTER --region=REGION --format="value(bgpPeers[].md5AuthenticationKeyName)" (the key value itself is never returned)§6.8
Firewall Insights reviewed for shadowed and unused rulesA shadowed rule is a control that does not existgcloud recommender insights list --insight-type=google.compute.firewall.Insight --location=global§6.20
Backends have no external IPsAn external IP bypasses every load balancer controlgcloud compute instances list --filter="networkInterfaces[].accessConfigs[].natIP:*"; org policy constraints/compute.managed.vmExternalIpAccess§7.19
Backend ingress permits only 130.211.0.0/22 and 35.191.0.0/16 (or the proxy-only subnet)Anything wider is a direct path around the load balancergcloud compute firewall-rules list --format="table(name,sourceRanges.list(),targetServiceAccounts.list())"§7.20
Cloud Armor policy attached to every internet-facing backend serviceAn unattached policy protects nothing and warns no onegcloud compute backend-services describe BES --global --format="value(securityPolicy)"; Console → Network Security → Cloud Armor§7.13
WAF rules were run in --preview before enforcementEnforcing untuned CRS signatures blocks legitimate trafficgcloud compute security-policies describe POLICY --format="value(rules[].preview)"§7.14
SSL policy attached to every target HTTPS proxy, minimum TLS 1.2Without one the load balancer negotiates the COMPATIBLE profilegcloud compute target-https-proxies describe PROXY --global --format="value(sslPolicy)"§7.10
Load balancer logging enabled with a non-zero sample rateThe only record of what reached the application and what Armor didgcloud compute backend-services describe BES --global --format="value(logConfig.enable,logConfig.sampleRate)"§7.3
IAP-protected applications verify the signed JWTAn alternate path would otherwise accept a forged identitycode review; confirm issuer and audience checks§7.17
A perimeter exists around every project holding regulated dataIAM alone cannot stop an authorized copy outgcloud access-context-manager perimeters list --policy=POLICY_ID§20.1
Restricted services include compute, KMS, and Secret ManagerRestricting only storage leaves a VM-mediated path opengcloud access-context-manager perimeters describe sp-prod-data --policy=POLICY_ID§20.3
No egress rule names resources: ["*"]That is an unrestricted exfiltration permissionReview the egress policy file in version control§20.6
A dry-run configuration is maintained ahead of the enforced oneMakes perimeter change routine rather than exceptionalgcloud access-context-manager perimeters dry-run describe sp-prod-data --policy=POLICY_ID§20.8
Perimeter enforced, not dry-runA dry-run perimeter is §33.20's theaterPerimeter status§33.20

D.5 Compute §

Compute Engine instances: boot integrity, login path, disks, and the metadata surface.

ControlWhy it mattersHow to verify§
constraints/compute.requireShieldedVm enforcedBlocks any instance without Secure Boot, vTPM, and Integrity Monitoringgcloud org-policies describe constraints/compute.requireShieldedVm --organization=123456789012§8.16
constraints/compute.requireOsLogin enforcedPrevents metadata SSH keys from bypassing IAM-governed accessgcloud org-policies describe compute.managed.requireOsLogin --organization=ORG_ID --effective§8.20
constraints/compute.vmExternalIpAccess denies by defaultRemoves the internet as a reachable path to workload VMsgcloud compute instances list --project=PROJECT_ID --filter="networkInterfaces[].accessConfigs[].natIP:*"§8.22
constraints/compute.trustedImageProjects restrictedForces every instance through the golden-image pipelinegcloud org-policies describe constraints/compute.trustedImageProjects --organization=123456789012§8.35
constraints/compute.disableSerialPortAccess enforcedCloses the interactive serial console as an OS Login bypassgcloud org-policies describe constraints/compute.disableSerialPortAccess --organization=123456789012§8.35
Boot disks and images use customer-managed keys where requiredMeets data-classification requirements without depending on Google-managed key rotationgcloud compute instances describe NAME --zone=ZONE --format='value(disks.diskEncryptionKey)'§8.8
OS Config agent enrolled fleet-wideEnsures patch management and vulnerability signal cover every instancegcloud compute os-config inventories list --location=ZONE returns the full fleet§8.32
Local accounts removed where OS Login appliesA local account with a key is a credential outside IAMansible all -m ansible.builtin.command -a 'getent passwd'§28.10
Every managed instance group and Cloud Run service has a maximumAutoscaling converts an availability attack into a cost attackgcloud run services describe SERVICE --region=REGION --format="value(spec.template.metadata.annotations)"§7.16
Instance metadata scanned for credentialsIt is invisible without a full read maskgcloud asset search-all-resources --asset-types='compute.googleapis.com/Instance' --read-mask='*'§33.6

D.6 GKE §

GKE clusters and the workloads inside them. The recurring split is between the cluster as a Google Cloud resource and the Kubernetes objects it hosts — both need checking.

ControlWhy it mattersHow to verify§
Private nodes enabled on every clusterRemoves external IP exposure on node VMsgcloud container clusters describe CLUSTER --region=REGION --format="value(privateClusterConfig.enablePrivateNodes)"; constraints/container.managed.enablePrivateNodes§9.9
DNS-based control plane endpoint with authorized networks enforcedCurrent recommended model; closes the public-endpoint pathgcloud container clusters describe CLUSTER --region=REGION --format="value(controlPlaneEndpointsConfig)"§9.4
GKE_METADATA set on every node poolA legacy-mode pool exposes the node identity to every pod on itgcloud container clusters describe CLUSTER --region=REGION --format="value(workloadIdentityConfig.workloadPool)"§9.11
Shielded Nodes with Secure Boot on every node poolVerifies node authenticity and boot integritygcloud container node-pools describe POOL ... and read config.shieldedInstanceConfig§9.31
Node service account is sa-gke-node-<cluster>, never default Compute Engine SABounds blast radius of a compromised nodegcloud container node-pools describe POOL --cluster=CLUSTER --region=REGION --format="value(config.serviceAccount)"; constraints/container.managed.disallowDefaultComputeServiceAccount§9.11
Pod Security Admission at restricted on every namespace without a documented exceptionBlocks root, host namespaces, and unconfined capabilities by defaultkubectl get ns -o custom-columns=NAME:.metadata.name,ENFORCE:.metadata.labels.pod-security\.kubernetes\.io/enforce§9.14
Default-deny NetworkPolicy per namespaceWithout one, every pod reaches every other podkubectl get networkpolicy -A; Dataplane V2 enabled makes enforcement always-on§9.15
Application-layer Secret encryption with a Cloud KMS keyProtects Secret contents in an etcd snapshotgcloud container clusters describe CLUSTER --region=REGION --format="value(databaseEncryption)"§9.17
Node auto-upgrade and a release channel setUnpatched nodes accumulate known CVEsgcloud container clusters describe CLUSTER --region=REGION --format="value(releaseChannel)"§9.34
hostNetwork denied by admission policy in Workload Identity clustersHost-networked pods bypass Workload Identity and get the node identityPolicy Controller / admission policy review; Console → Kubernetes Engine → Security Posture§4.12

D.7 Cloud Run §

Cloud Run services and jobs. Ingress, invoker authorization, and the identity the revision runs as.

ControlWhy it mattersHow to verify§
No allUsers binding on roles/run.invoker outside a service explicitly meant to be publicRemoves authentication for the entire servicegcloud run services get-iam-policy SERVICE --region=REGION§10.12
constraints/run.managed.requireInvokerIam applied where no service should ever be publicPrevents allUsers and --no-invoker-iam-check estate-widegcloud org-policies describe constraints/run.managed.requireInvokerIam --project=PROJECT§10.14
Ingress restricted to internal-and-cloud-load-balancing for every load-balanced serviceOtherwise the *.run.app URL bypasses the load balancer and Cloud Armor entirelygcloud run services describe SERVICE --region=REGION --format="value(spec.template.metadata.annotations)"; test the default URL§10.18
Dedicated sa-run-<service> account per service, never the default compute service accountLimits blast radius of a compromised container to that service's own permissionsgcloud run services describe SERVICE --region=REGION --format="value(spec.template.spec.serviceAccountName)"§10.18
constraints/run.allowedIngress restricts all where not justifiedPrevents a service owner from widening ingress without platform reviewgcloud org-policies describe constraints/run.allowedIngress --project=PROJECT§10.11
Cloud SQL connections use roles/cloudsql.client scoped to the service account, not a broader IAM grantLimits which identities can open a proxied database connectiongcloud projects get-iam-policy PROJECT --flatten="bindings[].members" --filter="bindings.role:roles/cloudsql.client"§10.23
Scheduler and other automation accounts hold only roles/run.jobsExecutor on the specific job, not roles/run.developerA compromised trigger credential cannot alter the job it triggersgcloud projects get-iam-policy PROJECT --flatten="bindings[].members" --filter="bindings.role:roles/run.jobsExecutor"§10.28

D.8 Databases §

Cloud SQL, AlloyDB, Spanner, and BigQuery. Connection path, authentication, and encryption at rest.

ControlWhy it mattersHow to verify§
No Cloud SQL instance has a public IP without a documented exceptionPublic IP is the largest single exposure a managed database can havegcloud sql instances list --format="table(name,settings.ipConfiguration.ipv4Enabled)"§12.13
constraints/sql.restrictPublicIp (or the managed variant) enforced at project or orgBackstops the private-networking default against a one-off creategcloud org-policies describe constraints/sql.restrictPublicIp --project=PROJECT_ID --effective (§2.30)§2.30
IAM database authentication enabled for every PostgreSQL/MySQL instanceRemoves the shared password from the connection pathgcloud sql instances describe INSTANCE --format="value(settings.databaseFlags)" for cloudsql.iam_authentication=on§12.7
--ssl-mode=ENCRYPTED_ONLY or stricter on every instanceCloses unencrypted direct-protocol connections that bypass the proxygcloud sql instances describe INSTANCE --format="value(settings.settingsVersion,ipConfiguration.sslMode)"§12.18
CMEK configured at creation for instances holding regulated dataDefault encryption is adequate but not independently auditable or revocablegcloud sql instances describe INSTANCE --format="value(diskEncryptionConfiguration)" (§12.14, §14.8)§12.14
cloudsql.enable_pgaudit on for PostgreSQL instances holding sensitive dataBuilt-in logging flags are coarser and can log password-bearing statementsgcloud sql instances describe INSTANCE --format="value(settings.databaseFlags)"§12.2
--deletion-protection set on every production instancePrevents an accidental or malicious single-command deletiongcloud sql instances describe INSTANCE --format="value(settings.deletionProtectionEnabled)"§12.13
Cloud SQL Auth Proxy or a Language Connector used for every out-of-VPC connectionRemoves reliance on network allowlists and long-lived passwordsReview application configuration and --connector-enforcement on the instance§12.17
BigQuery consumers granted views, not tablesThe view's SQL is the boundarybq show --format=prettyjson DATASET§32.6
BigQuery datasets carry a default table expirationData that no longer exists cannot be exfiltratedbq show --format=prettyjson DATASET§32.6

D.9 Storage §

Cloud Storage buckets and objects: public reachability, access uniformity, retention, and encryption.

ControlWhy it mattersHow to verify§
constraints/storage.uniformBucketLevelAccess enforced org-wideRemoves ACLs as a parallel, unaudited access pathgcloud org-policies describe constraints/storage.uniformBucketLevelAccess --organization=123456789012 --effective§11.9
constraints/storage.publicAccessPrevention enforced org-wideBlocks any accidental public binding regardless of mechanismgcloud org-policies describe constraints/storage.publicAccessPrevention --organization=123456789012 --effective§11.10
No bucket relies on legacy bucket/object rolesThey bypass the IAM condition and audit modelgcloud storage buckets get-iam-policy gs://BUCKET --format="value(bindings.role)" and check for legacy§11.20
Signed URL duration matches task duration, not the 7-day maximumA long-lived signed URL is a long-lived bearer credentialreview application code issuing gcloud storage sign-url / client-library equivalents§11.11
Signing uses service account impersonation, never a downloaded keyRemoves a static credential capable of signing arbitrarilygcloud iam service-accounts keys list --iam-account=SA_EMAIL --managed-by=user returns nothing§11.11
Quarantine bucket is distinct from the bucket applications read fromPrevents unscanned content from reaching production consumersreview bucket inventory against the upload pipeline design (§11.22)§11.22
Soft delete duration meets or exceeds the organization floorProtects against accidental overwrite or deletiongcloud storage buckets describe gs://BUCKET --format="value(softDeletePolicy)"§11.17
Retention policy locked (Bucket Lock) on every compliance-archival bucketAn unlocked policy can be shortened or removed by an administratorgcloud storage buckets describe gs://BUCKET --format="value(retentionPolicy)"§11.16

D.10 Secrets §

Secret Manager and the paths that route around it. The controls that matter are per-secret IAM, rotation, and whether access is logged.

ControlWhy it mattersHow to verify§
Secret-level IAM bindings, not project-level secretAccessorProject-level access reaches every secret, present and futuregcloud secrets get-iam-policy SECRET_ID per secret vs. gcloud projects get-iam-policy§13.4
Rotation topics have a deployed, subscribed handler--rotation-period alone never changes the credentialconfirm a Pub/Sub subscription and recent handler invocations for each topic§13.5
Workloads pin a version number, not latestlatest moves at access time and hides which credential is livegcloud run services describe SERVICE --region=REGION --format="value(spec.template.spec.containers[].env)"§13.8
Data Access DATA_READ logging enabled for Secret ManagerAccessSecretVersion is unrecorded otherwisegcloud projects get-iam-policy PROJECT_ID --format=json | grep -A2 secretmanager§13.12
Version destroy TTL set on secrets holding production credentialsGives an investigation window before an irreversible destroygcloud secrets describe SECRET_ID --format='value(versionDestroyTtl)'§13.2
CMEK'd secrets use user-managed replicationautomatic replication cannot pair with a customer-managed keygcloud secrets describe SECRET_ID --format=json§13.11
Pre-commit and CI secret scanning both activeThe hook catches locally; CI catches what the hook missedpipeline configuration review; hook install in onboarding docs§13.14
Any secret found in Git history is rotated, not just deletedA committed secret is compromised regardless of history rewritingincident log entry for the rotation, dated before any history rewrite§13.14
No credential appears in a log line or a query stringLogs have wider readership than the credential storeLog query for Bearer and for key-shaped strings§21.13

D.11 Encryption §

Customer-managed encryption keys, their provenance, their protection level, and the destruction delay that makes a mistake recoverable.

ControlWhy it mattersHow to verify§
Key rings and keys live only in rc-saas-shared-sec-01, never a workload projectEnforces independent administrative control (§2.21, §14.8)gcloud kms keyrings list --location=us-central1 --project=rc-saas-shared-sec-01§2.21
No global key ring backs data-at-rest CMEKA global dependency for a regional workload with no locality benefitgcloud kms keyrings list --location=global --project=rc-saas-shared-sec-01§14.2
Service agents hold only roles/cloudkms.cryptoKeyEncrypterDecrypter, never roles/cloudkms.adminSeparates use from administrationgcloud kms keys get-iam-policy KEY --keyring=RING --location=REGION§14.1
constraints/gcp.restrictNonCmekServices set for regulated data servicesBlocks Google-managed-key-only resource creationgcloud org-policies describe constraints/gcp.restrictNonCmekServices --organization=123456789012 --effective§14.8
constraints/gcp.restrictCmekCryptoKeyProjects allows only the security projectPrevents CMEK keys sourced from an unapproved projectgcloud org-policies describe gcp.restrictCmekCryptoKeyProjects --organization=ORG_ID§14.8
constraints/cloudkms.minimumDestroyScheduledDuration floors the destroy windowStops an effectively-immediate key destructionsame command against that constraint§14.18
Per-tenant keys exist wherever crypto-shredding is a designed erasure pathA shared key makes destruction all-or-nothingreview key-to-tenant mapping against §14.13's separation axes§14.13
Old key versions disabled or destroyed once no ciphertext depends on themUnused enabled versions are both an attack surface and a costgcloud kms keys versions list --key=KEY --keyring=RING --location=REGION§14.12

D.12 Logging §

Audit and platform logs: what is collected, where it lands, how long it survives, and who can read the sensitive parts.

ControlWhy it mattersHow to verify§
Aggregated org-level log sink to a separate logging projectLogs must survive deletion of the audited projectgcloud logging sinks describe org-audit-sink --organization=ORG_ID§1.20
Sink writer identities granted on every destinationAn ungranted sink routes nothing and reports no errorgcloud logging sinks describe SINK --format="value(writerIdentity)"§17.9
Data Access audit logs enabled for data-bearing servicesAdmin Activity alone cannot answer "who read this"gcloud projects get-iam-policy PROJECT_ID --format="value(auditConfigs)"; Console → IAM & Admin → Audit Logs§1.8
constraints/iam.disableAuditLoggingExemption enforcedAn exempted principal's reads are invisiblegcloud org-policies describe constraints/iam.disableAuditLoggingExemption --organization=ORG_ID§17.3
constraints/gcp.disableCloudLogging never allowedDisables the entire trail for a projectgcloud org-policies describe constraints/gcp.disableCloudLogging --organization=ORG_ID§17.10
Audit bucket retention set and lockedLocking prevents shortening retention or deleting the bucketgcloud logging buckets describe lb-audit-7y --location=us-central1 --project=rc-saas-shared-log-01§17.14
Second copy in Cloud Storage with Bucket LockThe only copy outside the logging project's blast radiusgcloud storage buckets describe gs://rc-saas-shared-log-archive-01§17.12
No workload team holds roles/logging.configWriter at the organizationSink creation is exfiltration; sink deletion is evidence destructiongcloud organizations get-iam-policy ORG_ID --flatten="bindings[].members"§17.12
Log views scoped per audienceCentral storage must not mean universal readgcloud logging views list --bucket=lb-audit-7y --location=us-central1§17.8
Asset export scheduled at least monthlyCAI history covers only 35 daysEvidence bucket object listing by date§29.4

D.13 Monitoring §

Detection and alerting. Security Command Center, notification routing, and the coverage checks that show a control is actually running.

ControlWhy it mattersHow to verify§
SCC activated at the organizationProject-scoped activation produces no attack paths and no cross-project correlationgcloud scc manage services list --organization=organizations/ORG_ID; Security Command Center → Settings§16.6
Threat detectors enabled on productionMisconfiguration scanning without threat detection sees nothing happening nowgcloud scc manage services describe event-threat-detection --organization=organizations/ORG_ID§16.6
Notification config exists and is filteredAn unfiltered stream is unusable and expensive; no stream means no alertinggcloud scc notifications list --organization=ORG_ID§16.14
BigQuery export running alongside Pub/SubPub/Sub retention is bounded; the export is the durable copygcloud scc bqexports list --organization=ORG_ID§16.14
Mute configs are time-boundedA permanent static mute hides both the finding and the failed controlgcloud scc muteconfigs list --organization=ORG_ID§16.3
No workload service account holds an SCC roleFindings are read by pipelines, not applicationsgcloud organizations get-iam-policy ORG_ID --flatten="bindings[].members"§16.1
Detection service enablement is alerted onDisabling a detector produces no findingAlert on gcloud scc manage services update in Admin Activity§34.2
Security alerts exist for the §18.15 event listThese are the events that indicate a control was removedgcloud monitoring policies list --project=rc-saas-shared-sec-01§18.15
Security alerts are built on Admin Activity logsData Access-based alerts can be silenced by editing an audit configgcloud logging metrics describe METRIC --project=rc-saas-shared-sec-01§18.15
Two independent notification channels for critical pagesPagerDuty, Slack, webhooks, and mobile share one failure domaingcloud monitoring policies describe POLICY_ID§18.17
Log sink deletion alerted onLocking the bucket does not protect the sinkMonitoring → Alerting, policy list§17.20

D.14 CI/CD §

The pipeline as a production system: its identity, its state, its artifacts, and the approval that separates a plan from an apply.

ControlWhy it mattersHow to verify§
Default branch requires a pull request and blocks direct pushEvery other control here is advisory without itBranch protection settings; attempt a direct push§22.6
Tags protected and deploy sources are protected referencesA tag-triggered deploy routes around branch protectionTag protection rules; pipeline source configuration§22.6
Fork pull requests build without credentialsOtherwise a stranger's code runs with your permissionsgcloud builds triggers describe TRIGGER --region=REGION§23.2
Signed commits required where production artifacts are builtAuthorship survives repository account compromiseHost verification status on recent commits§22.8
Every trigger sets --service-accountThe default identity is shared and over-grantedgcloud builds triggers list --region=REGION --format="table(name,serviceAccount)"§23.2
Build service accounts are per pipelineA shared account holds the union of every pipeline's grantsIAM policy review in the CI/CD project§23.6
No build account holds deploy permissionsBuild and deploy identities must differgcloud asset search-all-iam-policies --query='policy:cloudbuild.gserviceaccount.com'§25.13
constraints/cloudbuild.disableCreateDefaultServiceAccount enforcedRemoves the shared default before it accumulates grantsgcloud org-policies describe constraints/cloudbuild.disableCreateDefaultServiceAccount --organization=ORG_ID§23.6
Production pools set --no-public-egressThe only constraint on exfiltration from a build stepgcloud builds worker-pools describe POOL --region=REGION§23.5
Builder images pinned by digestA tag executes whatever the publisher pushed todayGrep cloudbuild.yaml for name: values without @sha256:§23.3
Build config comes from the repository, not --inline-configInline configs are edited without reviewgcloud builds triggers describe TRIGGER --region=REGION§23.15
Images referenced downstream by digestA tag can be repointed after verificationgcloud deploy releases describe RELEASE --delivery-pipeline=PIPELINE --region=REGION§23.10
Separate repositories per environmentPromotion is IAM-enforced only if the repositories differgcloud artifacts repositories list --location=us-central1§24.1
IAM granted at repository scope, not projectA project grant erases the environment boundarygcloud artifacts repositories get-iam-policy REPO --location=REGION§24.4
Production repository has --immutable-tagsPrevents a verified tag being repointedgcloud artifacts repositories describe REPO --location=REGION --format="value(dockerConfig.immutableTags)"§24.5
Vulnerability scanning enabled on every repositoryContinuous re-evaluation is the only current viewRepository vulnerabilityScanningConfig§24.6
Production cleanup policy has an explicit Keep for release tagsAge-based deletion otherwise removes a running releasegcloud artifacts repositories describe REPO --location=REGION§24.12
Every production target sets requireApproval: trueThe field defaults to falsegcloud deploy targets describe prod --region=REGION§25.4
Deploy identity holds no Artifact Registry writeDeployment must not be able to publishgcloud artifacts repositories get-iam-policy prod-docker --location=REGION§25.13
Binary Authorization enforced, not dry-run, in productionDry-run logs and admitsgcloud container clusters describe CLUSTER --region=REGION --format="value(binaryAuthorization.evaluationMode)"§25.10
Attesting identity is not the build identityOtherwise the attestation proves only that a build rangcloud kms keys get-iam-policy KEY --keyring=RING --location=REGION§25.11
State bucket has uniform bucket-level access and public access preventionRemoves per-object ACLs and makes §33.1 impossible heregcloud storage buckets describe gs://BUCKET§33.1
State readable only by the pipeline accountThe one control that actually prevents disclosuregcloud storage buckets get-iam-policy gs://BUCKET§26.14
Apply consumes the approved plan artifactOtherwise the approval refers to a different changePipeline definition: apply tfplan, no -auto-approve§26.24
Provider pinned ~> 8.0 and lock file committedReproducible provider binary; a supply chain control.terraform.lock.hcl present and in the diff§26.1
Drift detection runs nightly and alerts on exit code 2Undetected drift is an unrecorded change or an attackerScheduled job and its alert policy (§18.15)§18.15

D.15 Disaster Recovery §

Backup, restore, and the tests that distinguish a backup from a hope.

ControlWhy it mattersHow to verify§
Automated backups and point-in-time recovery enabled with adequate retentionBounds data-loss exposure from both operator error and attackgcloud sql instances describe INSTANCE --format="value(settings.backupConfiguration)"§12.10
Backup coverage reconciled against the asset inventoryFinds unprotected production dataAsset export minus backup plan associations§29.9
A dated restore test existsAn untested restore is a plan, not a capabilityThe restore test record and its verification step§29.9
Backup vault has enforced retentionWithout it a vault is a bucket with a nicer namegcloud backup-dr backup-vaults describe VAULT --location=REGION§29.9
Backups separated from the source's project and keyA compromised project must not reach its own backupsgcloud backup-dr backup-vaults list --location=REGION§33.19
Backups reach back further than detection latencyYou must restore from before the compromise beganBackup retention versus measured detection latency§34.14
Rollback tested on a scheduleAn untested rollback is a plan, not a capabilityRollout history shows a rehearsed rollback§25.9
Previous production artifacts retainedA cleanup policy can delete the rollback targetgcloud artifacts docker images list for the prior release tag§25.9
GKE Backup plan scope reviewed after every namespace changeA stale scope silently stops covering new namespacesreview the google_gke_backup_backup_plan.backup_config state against the cluster's current namespace list§9.40
Runbook snapshots before stoppingStopping destroys memory and serial outputRunbook step order§34.7
State bucket has object versioningThe only recovery from a corrupt state writegcloud storage buckets describe gs://BUCKET --format='value(versioning)'§26.14

Sources §

Every row in this appendix is merged from the Security Checklist of the chapter named in its § column, and each of those chapters carries its own sources. This appendix introduces no claim of its own and therefore cites no external page; the extraction method and the 518-to-190 merge are recorded in .build/verify/appD.md.