Firewall Analytics is the first product in the Cloudflare dashboard to utilize the new GraphQL Analytics API. All Cloudflare dashboard products are built using the same public APIs that we provide to our customers, allowing us to understand the challenges they face when interfacing with our APIs. This parity helps us build and shape our products, most recently the new GraphQL Analytics API that we’re thrilled to release today.
By defining the data we want, along with the response format, our GraphQL Analytics API has enabled us to prototype new functionality and iterate quickly from our beta user feedback. It is helping us deliver more insightful analytics tools within the Cloudflare dashboard to our customers.
Our user research and testing for Firewall Analytics surfaced common use cases in our customers' workflow:
Identifying spikes in firewall activity over time
Understanding the common attributes of threats
Drilling down into granular details of an individual event to identify potential false positives
We can address all of these use cases using our new GraphQL Analytics API.
GraphQL Basics
Before we look into how to address each of these use cases, let's take a look at the format of a GraphQL query and how our schema is structured.
A GraphQL query is comprised of a structured set of fields, for which the server provides corresponding values in its response. The schema defines which fields are available and their type. You can find more information about the GraphQL query syntax and format in the official GraphQL documentation.
To run some GraphQL queries, we recommend downloading a GraphQL client, such as GraphiQL, to explore our schema and run some queries. You can find documentation on getting started with this in our developer docs.
At the top level of the schema is the viewer
field. This represents the top level node of the user running the query. Within this, we can query the zones
field to find zones the current user has access to, providing a filter
argument, with a zoneTag
of the identifier of the zone we'd like narrow down to.
{
viewer {
zones(filter: { zoneTag: "YOUR_ZONE_ID" }) {
# Here is where we'll query our firewall events
}
}
}
Now that we have a query that finds our zone, we can start querying the firewall events which have occurred in that zone, to help solve some of the use cases we’ve identified.
Visualising spikes in firewall activity
It's important for customers to be able to visualise and understand anomalies and spikes in their firewall activity, as these could indicate an attack or be the result of a misconfiguration.
Plotting events in a timeseries chart, by their respective action, provides users with a visual overview of the trend of their firewall events.
Within the zones
field in the query we’ve created earlier, we can query our firewall event aggregates using the firewallEventsAdaptiveGroups
field, providing arguments to limit the count of groups, a filter for the date range we're looking for (combined with any user-entered filters), and a list of fields to order by; in this case, just the datetimeHour
field that we're grouping by.
Within the zones
field in the query we created earlier, we can further query our firewall event aggregates using the firewallEventsAdaptiveGroups
field and providing arguments for:
A
limit
for the count of groupsA
filter
for the date range we're looking for (combined with any user-entered filters)A list of fields to
orderBy
(in this case, just thedatetimeHour
field that we're grouping by).
By adding the dimensions
field, we're querying for groups of firewall events, aggregated by the fields nested within dimensions
. In this case, our query includes the action
and datetimeHour
fields, meaning the response will be groups of firewall events which share the same action, and fall within the same hour. We also add a count
field, to get a numeric count of how many events fall within each group.
query FirewallEventsByTime($zoneTag: string, $filter: FirewallEventsAdaptiveGroupsFilter_InputObject) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
firewallEventsAdaptiveGroups(
limit: 576
filter: $filter
orderBy: [datetimeHour_DESC]
) {
count
dimensions {
action
datetimeHour
}
}
}
}
}
Note - Each of our groups queries require a limit to be set. A firewall event can have one of 8 possible actions, and we are querying over a 72 hour period. At most, we’ll end up with 567 groups, so we can set that as the limit for our query.
This query would return a response in the following format:
{
"viewer": {
"zones": [
{
"firewallEventsAdaptiveGroups": [
{
"count": 5,
"dimensions": {
"action": "jschallenge",
"datetimeHour": "2019-09-12T18:00:00Z"
}
}
...
]
}
]
}
}
We can then take these groups and plot each as a point on a time series chart. Mapping over the firewallEventsAdaptiveGroups
array, we can use the group’s count
property on the y-axis for our chart, then use the nested fields within the dimensions
object, using action
as unique series and the datetimeHour
as the time stamp on the x-axis.
Top Ns
After identifying a spike in activity, our next step is to highlight events with commonality in their attributes. For example, if a certain IP address or individual user agent is causing many firewall events, this could be a sign of an individual attacker, or could be surfacing a false positive.
Similarly to before, we can query aggregate groups of firewall events using the firewallEventsAdaptiveGroups
field. However, in this case, instead of supplying action
and datetimeHour
to the group’s dimensions
, we can add individual fields that we want to find common groups of.
By ordering by descending count, we’ll retrieve groups with the highest commonality first, limiting to the top 5 of each. We can add a single field nested within dimensions
to group by it. For example, adding clientIP
will give five groups with the IP addresses causing the most events.
We can also add a firewallEventsAdaptiveGroups
field with no nested dimensions
. This will create a single group which allows us to find the total count of events matching our filter.
query FirewallEventsTopNs($zoneTag: string, $filter: FirewallEventsAdaptiveGroupsFilter_InputObject) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
topIPs: firewallEventsAdaptiveGroups(
limit: 5
filter: $filter
orderBy: [count_DESC]
) {
count
dimensions {
clientIP
}
}
topUserAgents: firewallEventsAdaptiveGroups(
limit: 5
filter: $filter
orderBy: [count_DESC]
) {
count
dimensions {
userAgent
}
}
total: firewallEventsAdaptiveGroups(
limit: 1
filter: $filter
) {
count
}
}
}
}
Note - we can add the firewallEventsAdaptiveGroups
field multiple times within a single query, each aliased differently. This allows us to fetch multiple different groupings by different fields, or with no groupings at all. In this case, getting a list of top IP addresses, top user agents, and the total events.
We can then reference each of these aliases in the UI, mapping over their respective groups to render each row with its count, and a bar which represents the proportion of total events, showing the proportion of all events each row equates to.
Are these firewall events false positives?
After users have identified spikes, anomalies and common attributes, we wanted to surface more information as to whether these have been caused by malicious traffic, or are false positives.
To do this, we wanted to provide additional context on the events themselves, rather than just counts. We can do this by querying the firewallEventsAdaptive
field for these events.
Our GraphQL schema uses the same filter format for both the aggregate firewallEventsAdaptiveGroups
field and the raw firewallEventsAdaptive
field. This allows us to use the same filters to fetch the individual events which summate to the counts and aggregates in the visualisations above.
query FirewallEventsList($zoneTag: string, $filter: FirewallEventsAdaptiveFilter_InputObject) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
firewallEventsAdaptive(
filter: $filter
limit: 10
orderBy: [datetime_DESC]
) {
action
clientAsn
clientCountryName
clientIP
clientRequestPath
clientRequestQuery
datetime
rayName
source
userAgent
}
}
}
}
Once we have our individual events, we can render all of the individual fields we’ve requested, providing users the additional context on event they need to determine whether this is a false positive or not.
That’s how we used our new GraphQL Analytics API to build Firewall Analytics, helping solve some of our customers most common security workflow use cases. We’re excited to see what you build with it, and the problems you can help tackle.
You can find out how to get started querying our GraphQL Analytics API using GraphiQL in our developer documentation, or learn more about writing GraphQL queries on the official GraphQL Foundation documentation.