Skip to content

Trust boundaries

Airframe’s security rests on a few boundaries. Code that nobody has reviewed runs where it can reach nothing, code that the right people approved runs with identities that each fit one job, and controls in Google Cloud itself hold even if every pipeline were bypassed. This page draws those boundaries, says what enforces each one, and marks what is designed but not built. Claims and mechanisms then lists every claim, one by one.

The examples come from a demo organization, a fictional online retailer, whose projects all sit under one folder in Google Cloud.

The platform runs its pipeline jobs on two runners of its own, the untrusted and the trusted, and keeps them apart.

The untrusted runner runs every merge request’s pipeline, and the checks on main that need no identity, such as validation, the policy pack and the application’s tests. It sits in the platform’s continuous integration project, outside the perimeters, in a network of its own with no route to the platform’s networks, and its jobs cannot reach the metadata server, where a machine’s credentials would be. Nothing it runs can obtain a cloud identity either: every identity a job can obtain is bound to a protected branch or tag, and a merge request’s pipeline has neither. So anyone who can push a branch gets a pipeline that holds nothing.

The trusted runner sits inside the platform’s production network and takes only jobs tagged for it, from protected refs. Two kinds of work run there: the executor’s runs, and the pipeline on each repository’s protected main after a merge. It runs only images from the platform’s own registry.

The gate decides when a change may cross from one side to the other. It reads the ownership model from the catalog’s protected main, never from the change it judges, counts no approval by the author or a committer, and gives its own approval bound to the commit it checked. Only then does it start the executor, with a trigger token that only the gate holds, and the executor checks everything again before it holds a credential.

A GitLab approval policy makes the gate’s approval a condition of merging in every repository but security-policies, which holds the approval rules and is the way out when the gate cannot approve. Another policy requires the approval of a named person from the catalog who neither wrote nor committed the change. Today one person runs the platform, and every named approver is one of the maintainer’s own accounts: the mechanism is enforced, but the separation between two people is not yet real. The gate and the executor explains the whole sequence.

  • Before a merge, the executor may use every product’s plan identity, which reads and changes nothing, and every non-production apply identity, for previews. It holds no production apply identity, so nothing that runs before a merge can change production.
  • After a merge, each root’s job on protected main obtains that root’s own apply identity, and no other. The binding names the repository’s path, the environment and the protected ref, so a job of another repository, another environment or an unprotected branch gets nothing.
  • People hold viewer roles, and a product’s team may add a version of one of its production secrets, never read one. Anything more is a grant: level 1, an hour of reading a non-production project, which the requester approves with a reason, or an administrative grant of up to two hours, approved by another member of the platform group, which today means the maintainer’s second account.

Three platform roots, the organization’s policies, the audit configuration and the identity provider, are applied from a laptop under a grant instead, and a daily read-only plan shows any change to them as drift.

untrusted runnertrusted runnera merge request'spipeline: no identitythe gate: theowners approvedthe executor: plan,build, previewafter the merge:protected maineach root's ownapply identitythe floor: organization and deny policies, below every identity

Below every identity sits the floor: controls in Google Cloud that hold even if a pipeline were bypassed, set on the demo organization’s folder by the organization root, and the firewall policy by the network root.

  • Organization policies (enforced). They forbid creating or uploading service account keys, default networks, public buckets and public Cloud SQL addresses, and require Shielded VMs and OS Login. Only the organization’s own principals may appear in an allow policy, only the trusted runner may hold an external address, and resources go only to us-central1 where a service supports the constraint.
  • A deny policy (enforced). It denies everyone key creation, organization policy changes, folder IAM changes and changes to the folder’s log sinks. Its only exceptions are the break-glass group, the bootstrap administrator account and Privileged Access Manager’s own service agent, plus, for folder IAM alone, the identity that configures the grants. So a change to the folder’s access exists only as a recorded, time-boxed grant.
  • A hierarchical firewall policy (enforced). Traffic into and out of the platform’s networks is denied by default; Google’s APIs, load balancer health checks and each runner’s narrow allowance pass, and traffic between the platform’s ranges is decided by rules the network root generates.
  • No owner or editor below the folder (enforced). A custom organization policy constraint refuses a grant of either role from any path, the console included, except on a project still in its factory’s bootstrap window and for Google’s own APIs service agent, which Google grants editor when a project’s APIs are turned on. The project factories remove the owner role Google gives them on each project they create.
  • Three perimeters (designed). VPC Service Controls perimeters for production, non-production and audit exist as dry-run specifications: they log what they would block and block nothing. Enforcing them is one variable, once the platform’s own pipelines run clean under them. The organization root’s daily reads that a perimeter would refuse come from inside the production perimeter, from the trusted runner on the production network’s VPC, because the perimeter’s list of services reachable from that VPC does not include them.

The architecture’s floor goes further than what is built:

  • Binary Authorization for every service (designed). The architecture counts it in the floor and lists it, with the firewall policy, among the deny policy’s permissions; the deny policy covers neither. What is built is opt-in per service: the golden path’s Cloud Run module opts each service into its project’s policy, which in a billed product’s production project admits only an image the platform built and promoted. A service deployed without the module, such as the platform’s telemetry gateway, runs without it.

A daily Policy Analyzer job in the landing-zone repository also fails, and raises an alert, if any person other than the one administrator account, or any group, holds a role at the organization.

Every change can be followed back to the merge request, the approval and the person.

What is logged. Google Cloud’s audit logs record every administrative action, and Data Access logs are on at the folder for the services whose reads matter: Secret Manager, Cloud SQL, Cloud Storage, IAM, Cloud KMS and the token exchange that a pipeline’s identity goes through. GitLab’s own audit events, such as approvals, memberships and token creation, stream to the gate and to the audit receiver, a separate service, and each writes every event as a log line. GitLab does not send an event again when a destination could not receive it, so the bucket below holds each event twice while both are up and misses one only when both are unreachable; an event GitLab only streams is then lost.

Where it goes. One aggregated sink at the folder routes every project’s audit logs, and those GitLab lines, into a bucket in the audit project, encrypted with a key the platform holds. The deny policy refuses any change to the folder’s sinks, so neither a pipeline nor anyone without a grant can reroute the record. It also guards the folder’s IAM policy, which holds the audit configuration, with one more exception: the identity that configures the grants, which acts only from reviewed code. That identity’s condition requires a change to modify at least one grant, so a call that changes only the audit configuration is refused. A call that changes a permitted grant and the audit configuration together still passes; at the folder a rule in Grafana pages on it, and at the organization no sink sees it.

The sink leaves out the entries VPC Service Controls writes in dry run, which record what a perimeter would have refused and not a refusal. Each project’s own log keeps them, and the locked bucket does not hold them for a year.

How long it stays (enforced). A retention policy keeps every object for 365 days, and nobody can delete or overwrite one before then. The policy is locked, which Google makes irreversible: nobody, an administrator under a grant included, can shorten or remove it.

What joins it together. The gate records each verdict with the claims of the token that asked, the executor records each run with its merge request, commit and pipeline, a plan job keeps its plan and the policy verdict as artifacts that only the repository’s developers can read, and each audit log entry names the identity that acted, which answers to one repository, environment and protected ref.

What watches it (enforced). A second, narrow sink at the folder sends the entries a security team would watch for to Grafana, through the production log gateway: a key creation attempt, a deny or organization policy refusal, a refused token, a change to the folder’s sinks, deny policies or audit configuration, a grant, and GitLab’s audit events. Rules page the platform on the first five and open a ticket for a grant or a burst of GitLab changes. An event at the organization itself, such as an organization-level grant, is outside the folder’s sinks.

The architecture also designs an audit job that compares every access against the catalog; it does not exist yet (designed).

Where it lives

In the landing-zone repository:

In the control-plane repository:

MIT license. Copyright Airframe contributors.

This site describes platform release 2026.10.20-1.