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:
- When any role is granted to the special entity
allUsers
. - When the role
roles/storage.objectViewer
is granted to the special entityallUsers
.
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 arraya
is iterated byx
and expressionp
is evaluated. Ifp
resultsTrue
for ALL members ofa
, the macro returnsTrue
.a.exists(x, p)
: each member or arraya
is iterated byx
and expressionp
is evaluated. Ifp
resultsTrue
for ANY members ofa
, the macro returnsTrue
.a.exists_one(x, p)
: each member or arraya
is iterated byx
and expressionp
is evaluated. Ifp
resultsTrue
for exactly 1 member ofa
, the macro returnsTrue
.
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:
- The role “storage.objectViewer” was granted to “allUsers”. A notification is sent.
- 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.