In this blog we present you with a terraform template and demo to help you understand how to configure Principal Access Boundaries on Google Cloud.
Principal Access Boundaries are organization-level policies that restrict what resources a principal (user, service account, or group) can access, regardless of the IAM permissions granted to them. This makes them a perfect tool to enforce data sharing agreements within a Google Cloud organization.
The Challenge
Imagine you have multiple business units in your organization. Each has business unit is supported by a data engineering team, which use data pipelines to manage their resources on Google Cloud. Their data pipeline service accounts have full IAM administrator privileges on their project, allowing them to create any role binding. However, from an organizational standpoint, it is not allowed to share data with business units without a data sharing contract. This is where Principal Access Boundaries come in.
The organization
The following diagram illustrates the folder hierarchy and resources of our fictive organization:
Organization
└── business-units (folder)
├── BU-001 (folder)
│ └── com-xebia-bu-001 (project)
│ └── data-pipeline (service-account)
│ └── com-xebia-bu-001-data (bucket)
├── BU-002 (folder)
│ └── com-xebia-bu-002 (project)
│ └── data-pipeline (service-account)
│ └── com-xebia-bu-002-data (bucket)
└── BU-003 (folder)
└── com-xebia-bu-003 (project)
└── data-pipeline (service-account)
└── com-xebia-bu-003-data (bucket)
Each business unit has an individual folder (BU-001, BU-002, BU-003), under which you will find all projects for that unit. In this example we created three projects:com-xebia-bu-001,com-xebia-bu-002, com-xebia-bu-003, one for each respective business unit.
over-permissive storage role bindings
To demonstrate the principal access boundaries, we have granted the role roles/storage.objectAdmin to all service accounts on each bucket in each business unit.
bindings:
- role: 'roles/storage.objectAdmin'
members:
-'data-pipeline@com-xebia-bu-001'
-'data-pipeline@com-xebia-bu-002'
-'data-pipeline@com-xebia-bu-003'
This clearly violates our policy on data sharing, as this allows each service account to read the index.html document in each bucket.
Data sharing contracts
As described earlier, business units need to have a data sharing contract in place to access data from other business units. These contracts are shown below:
data_sharing_contracts = {
"BU-001" : ["BU-001"], # only access resources
"BU-002" : ["BU-001", "BU-002"], # own and BU-001 resources
"BU-003" : ["BU-001", "BU-002", "BU-003"] # all BU resources
}
These contracts can be translated into principal access boundaries.
Principal Access Boundaries
The data sharing contracts from the previous paragraph can be enforced by Principal Access Boundary policies, as shown below:
policies:
- policy_id: bu-001
display_name: data sharing contract enforcement for BU-001
details:
rules:
- effect: ALLOW
resources:
- //cloudresourcemanager.googleapis.com/folders/<folder BU-001>
- policy_id: bu-002
display_name: data sharing contract enforcement for BU-002
details:
rules:
- effect: ALLOW
resources:
- //cloudresourcemanager.googleapis.com/folders/<folder BU-001>
- //cloudresourcemanager.googleapis.com/folders/<folder BU-002>
- policy_id: bu-003
display_name: data sharing contract enforcement for BU-003
details:
rules:
- effect: ALLOW
resources:
- //cloudresourcemanager.googleapis.com/folders/<folder BU-001>
- //cloudresourcemanager.googleapis.com/folders/<folder BU-002>
- //cloudresourcemanager.googleapis.com/folders/<folder BU-003>
As you can see, each policy lists all the resources that are included in the boundary. We have removed some properties and folder ids for clarity.
Enforcing Principal Access Boundaries
To enforce the principal access boundary, we have to bind them to a principal set. In our case, the policies are bound to the pincipal set of the corresponding business unit folder, as shown below:
bindings:
- policy_binding_id: bu-001
policy_kind: PRINCIPAL_ACCESS_BOUNDARY
display_name: data sharing contract enforcement for BU-001
policy: .../principalAccessBoundaryPolicies/bu-001
folder: <folder BU-002>
target:
- principal_set: //cloudresourcemanager.googleapis.com/folders/<folder BU-001>
- policy_binding_id: bu-002
policy_kind: PRINCIPAL_ACCESS_BOUNDARY
display_name: data sharing contract enforcement for BU-002
policy: .../principalAccessBoundaryPolicies/bu-002
folder: <folder BU-002>
target:
- principal_set: //cloudresourcemanager.googleapis.com/folders/<folder BU-002>
- policy_binding_id: bu-003
policy_kind: PRINCIPAL_ACCESS_BOUNDARY
display_name: data sharing contract enforcement for BU-003
policy: .../principalAccessBoundaryPolicies/bu-003
folder: <folder BU-003>
target:
- principal_set: //cloudresourcemanager.googleapis.com/folders/<folder BU-003>
Binding the policy to the folder, enforces the boundary to all security principals defined in the projects under the business unit folders: whether it is a service account, workload identity subjector workforce identity subject.
Switching service accounts
To show the principal access boundaries in action, the terraform template also creates a gcloud configuration script (config-gloud.sh), which allows for easy switching of service account. It creates three gcloud configurations:
bu-001- Impersonates BU-001 service accountbu-002- Impersonates BU-002 service accountbu-003- Impersonates BU-003 service account
The script configures the configuration as follows:
gcloud config set account $(gcloud config get account)
gcloud config set core/project com-xebia-bu-001
gcloud config set auth/impersonate_service_account data-pipeline@...
To start the demo, read and apply the terraform template principal-access-boundaries.tf type:
./config-gcloud.sh
Principal Access Boundaries in action
So, let try to access our resources from our own bucket in bu-001:
gcloud config configuration activate bu-001
gcloud storage cat gs://com-xebia-bu-001-data/index.html
<HTML>....</HTML>
It shows the content as expected. Now lets try to access one of the other BU buckets:
gcloud storage cat gs://com-xebia-bu-002-data/index.html
**ERROR:** (gcloud.storage.cat) HTTPError 403: data-pipeline@com-xebia-bu-001.iam.gserviceaccount.com does not have storage.objects.get access to the Google Cloud Storage object. Operations on resource are denied due to an IAM Principal Access Boundary Policy.
As you can see the access was denied and it also states that it was caused by the principal access boundary. This IAM policy troubleshooter, visualizes it as shown in the following image:

It clearly indicates that it is prohibited by the principal access boundary even though the principal does have the IAM permission!
Conclusion
When combining Principal Access Boundaries with a proper folder hierarchy, you can use the policies to enforce defined data sharing agreements between business units, even when an IAM binding are in place.
Image by Ramon Perucho from Pixabay
Written by

Mark van Holsteijn
Mark van Holsteijn is a senior software systems architect at Xebia Cloud-native solutions. He is passionate about removing waste in the software delivery process and keeping things clear and simple.
Our Ideas
Explore More Blogs
Contact



