Using Cloud Asset Inventory feeds to monitor changes in permissions of storage buckets (part 2)

In the last article we learned how to create a Cloud Asset Inventory (CAI) feed to monitor changes to a Cloud Storage Bucket.

The feed we configured will notify us for every IAM change in any bucket. However, there might be some cases where we are interested only on specific changes.

In this article we’ll learn how to use conditions to monitor changes with specific parameters.

We will explore how to get notified for two specific conditions:

  1. When any role is granted to the special entity allUsers.
  2. When the role roles/storage.objectViewer is granted to the special entity allUsers.

Common Expression Language

To use specific conditions in CAI feeds, we use the Common Expression Language or CEL.

CEL is an open-source non-Turing complete language that implements common semantics for expression evaluation. It is tailored to express attribute-based logic expressions. For a more in-depth exploration of CEL, see the CEL spec and its language definition.

When CEL is used in CAI, we have a single variable called temporal_asset which contains the change applied to the asset in TemporalAsset format.

If the CEL expression evaluates to true, the TemporalAsset will be sent as a notification to the configured Pub/Sub topic.

Detecting a storage bucket creation

The temporal_asset variable contains an element called prior_asset_state which can be used to inspect the asset state before the change is applied. We can compare it to the value google.cloud.asset.v1.TemporalAsset.PriorAssetState.DOES_NOT_EXIST to detect if the asset is new (that is, the prior_asset_state is DOES NOT EXIST). Other values for prior_asset_state are available here.

Such expresion can be constructed like this:

temporal_asset.prior_asset_state == google.cloud.asset.v1.TemporalAsset.PriorAssetState.DOES_NOT_EXIST

To modify our feed to include this expression we use the gcloud asset feeds update with the --condition-expression flag:

gcloud asset feeds update gs-feed --project=$PROJECT_ID \
--condition-expression="temporal_asset.prior_asset_state == google.cloud.asset.v1.TemporalAsset.PriorAssetState.DOES_NOT_EXIST"

Detecting roles granted to a user

All the specification of the modified asset as available in temporal_asset in the the asset field, which in itself its an Asset structure.

The set of permissions is availabe under temporal_asset.asset.iam_policy.bindings as an array of Binding structures.

Detecting when any role is granted to a specific user

Let’s start with an sceario when we want to know if any role has been granted to allUsers.

Because the bindings are a structure, we cannot use a simple expression to compare: we need to look for any binding tha matches our criteria.

CEL offers a few macros to compare arrays:

  • a.all(x, p): each member or array a is iterated by x and expression p is evaluated. If p results True for ALL members of a, the macro returns True.
  • a.exists(x, p): each member or array a is iterated by x and expression p is evaluated. If p results True for ANY members of a, the macro returns True.
  • a.exists_one(x, p): each member or array a is iterated by x and expression p is evaluated. If p results True for exactly 1 member of a, the macro returns True.

First, we need to iterate the temporal_asset.asset.iam_policy.bindings array:

temporal_asset.asset.iam_policy.bindings.exists(b, EXPRESSION)

The Binding structure defines the item member as an array, so this one needs to be iterated too. For each binding b, we need to know if any item of the b.members array is allUsers:

b.members.exists(x, x == "allUsers")

Finally, we combine both elements in a single expression:

temporal_asset.asset.iam_policy.bindings(b, b.members.exists(x, x == "allUsers"))

Now, we can update the feed to use the condition expression we just built:

gcloud asset feeds update gs-feed --project=$PROJECT_ID \
--condition-expression="temporal_asset.asset.iam_policy.bindings(b, b.members.exists(x, x == "allUsers"))"

Detecting when a specific role is granted to a specific user

Finally, let’s see how we can detect when a specific role, let’s say roles/storage.objectViewer is granted to allUsers.

In this case, two criteria must match in the same policy binding b:

b.members.exists(m, m == "allUsers") && b.role == "roles/storage.objectViewer"

We include that expression in the exists() function applied to the bindings array, and we get:

temporal_asset.asset.iam_policy.bindings.exists(b, b.members.exists(m, m == "allUsers") && b.role == "roles/storage.objectViewer")

Now we can update the feed with the new conditional expression:

gcloud asset feeds update gs-feed --project=$PROJECT_ID \
--condition-expression="temporal_asset.asset.iam_policy.bindings.exists(b, b.members.exists(m, m == "allUsers") && b.role == "roles/storage.objectViewer")

Unexptected scenarios

We stated we would be notified when a specific permission is granted to a specific user, however that’s not exactly how CAI feeds works.

Actually, at this point we have configured the feed to send notification when any change to a bucket resulting in an IAM Policy binding whose member is “allUsers” and whose role is “objectViewer”.

This is important, because the feed will also generate notifications with other changes.

Let’s assume the following scenario:

  1. The role “storage.objectViewer” was granted to “allUsers”. A notification is sent.
  2. Some time later, another role (doen’t matter which one) is granted to another entity in the same bucket. A notification will also be sent, becase the IAM policy already contains the allUsers/storage.objectViewer pair.

It is important to understand the object temporal_asset.asset.iam_policy.bindings will contain the whole policy binding to be set in the object, and not only the changes being applied (also known as a policy delta).

Conclusions

We explored Cloud Asset Inventory feeds, a very useful feature of CAI to monitor changes in our assets.

Examples were given to monitor general changes to a Cloud Storage Bucket, and later we focus on changes to the IAM policy applied to said bucket.