Collect Akamai Cloud Monitor logs
This document explains how to ingest Akamai Cloud Monitor (Load Balancer, Traffic Shaper, ADC) logs to Google Security Operations using AWS S3. Akamai pushes JSON events to your HTTPS endpoint; an API Gateway + Lambdareceiver writes the events to S3 (JSONL, gz). The parser transforms the JSON logs into UDM. It extracts fields from the JSON payload, performs data type conversions, renames fields to match the UDM schema, and handles specific logic for custom fields and URL construction. It also incorporates error handling and conditional logic based on field presence.
Before you begin
Make sure you have the following prerequisites:
- Google SecOps instance
- Privileged access to Akamai Control Center and Property Manager
- Privileged access to AWS*(S3, IAM, Lambda, API Gateway)
Configure AWS S3 bucket and IAM for Google SecOps
- Create Amazon S3 bucketfollowing this user guide: Creating a bucket
- Save bucket Nameand Regionfor future reference (for example,
akamai-cloud-monitor
). - Create a user following this user guide: Creating an IAM user .
- Select the created User.
- Select the Security credentialstab.
- Click Create Access Keyin the Access Keyssection.
- Select Third-party serviceas the Use case.
- Click Next.
- Optional: add a description tag.
- Click Create access key.
- Click Download CSV fileto save the Access Keyand Secret Access Keyfor later use.
- Click Done.
- Select the Permissionstab.
- Click Add permissionsin the Permissions policiessection.
- Select Add permissions.
- Select Attach policies directly
- Search for and select the AmazonS3FullAccesspolicy.
- Click Next.
- Click Add permissions.
Configure the IAM policy and role for S3 uploads (Lambda)
- In the AWS Console, go to IAM > Policies > Create policy > JSONand paste the policy below.
-
JSON Policy (replace
akamai-cloud-monitor
with your S3 bucket name):{ "Version" : "2012-10-17" , "Statement" : [ { "Sid" : "AllowPutAkamaiObjects" , "Effect" : "Allow" , "Action" : [ "s3:PutObject" ], "Resource" : "arn:aws:s3:::akamai-cloud-monitor/*" } ] }
-
Click Next > Create policy.
-
Go to IAM > Roles > Create role > AWS service > Lambda.
-
Attach the JSON policy.
-
Name the role
WriteAkamaiCMToS3Role
and click Create role.
Create the Lambda function
Setting | Value |
---|---|
Name | akamai_cloud_monitor_to_s3
|
Runtime | Python 3.13 |
Architecture | x86_64 |
Execution role | WriteAkamaiCMToS3Role
|
-
After the function is created, open the Codetab, delete the stub and enter the following code (
akamai_cloud_monitor_to_s3.py
):#!/usr/bin/env python3 # Lambda: Receive Akamai Cloud Monitor POST, write JSONL (gz) to S3 import os , json , gzip , io , uuid , base64 , datetime as dt import boto3 S3_BUCKET = os . environ [ "S3_BUCKET_NAME" ] S3_PREFIX = os . environ . get ( "S3_PREFIX" , "akamai/cloud-monitor/json/" ) . strip ( "/" ) + "/" INGEST_TOKEN = os . environ . get ( "INGEST_TOKEN" ) # optional shared secret in URL query (?token=...) s3 = boto3 . client ( "s3" ) def _write_jsonl_gz ( objs : list ) - > str : key = f " { dt . datetime . utcnow () : %Y/%m/%d } /akamai-cloud-monitor- { uuid . uuid4 () } .json.gz" buf = io . BytesIO () with gzip . GzipFile ( fileobj = buf , mode = "w" ) as gz : for o in objs : gz . write (( json . dumps ( o , separators = ( "," , ":" )) + "n" ) . encode ()) buf . seek ( 0 ) s3 . upload_fileobj ( buf , S3_BUCKET , f " { S3_PREFIX }{ key } " , ExtraArgs = { "ContentType" : "application/json" , "ContentEncoding" : "gzip" , }, ) return f "s3:// { S3_BUCKET } / { S3_PREFIX }{ key } " def _parse_records_from_event ( event ) - > list : # HTTP API (Lambda proxy) event: body is a JSON string body = event . get ( "body" ) or "" if event . get ( "isBase64Encoded" ): body = base64 . b64decode ( body ) . decode ( "utf-8" , "replace" ) try : data = json . loads ( body ) except Exception : # accept line-delimited JSON as pass-through try : return [ json . loads ( line ) for line in body . splitlines () if line . strip ()] except Exception : return [] if isinstance ( data , list ): return data if isinstance ( data , dict ): return [ data ] return [] def lambda_handler ( event , context = None ): # Optional shared-secret verification via query parameter (?token=...) if INGEST_TOKEN : qs = event . get ( "queryStringParameters" ) or {} token = qs . get ( "token" ) if token != INGEST_TOKEN : return { "statusCode" : 403 , "body" : "forbidden" } records = _parse_records_from_event ( event ) if not records : return { "statusCode" : 204 , "body" : "no content" } key = _write_jsonl_gz ( records ) return { "statusCode" : 200 , "headers" : { "Content-Type" : "application/json" }, "body" : json . dumps ({ "ok" : True , "s3_key" : key , "count" : len ( records )}), }
-
Go to Configuration > Environment variables > Edit.
-
Click Add new environment variableand set the following values:
Environment variables
Key Example S3_BUCKET_NAME
akamai-cloud-monitor
S3_PREFIX
akamai/cloud-monitor/json/
INGEST_TOKEN
random-shared-secret
-
Go to Configuration > General configuration.
-
Click Editand set Timeoutto 5 minutes (300 seconds).
-
Click Save.
Create Amazon API Gateway (HTTPS endpoint for Akamai)
- In the AWS Console, go to API Gateway > Create API.
- Select HTTP API > Build.
- Provide the following configuration details:
- Integrations: Choose Lambdaand select
akamai_cloud_monitor_to_s3
. - Routes: Add ANY
/{proxy+}
or create a specific route (for example, POST/akamai/cloud-monitor
). - Stages: Create or use $default.
- Integrations: Choose Lambdaand select
- Deploy the API and copy the Invoke URL(for example,
https://abc123.execute-api.<region>.amazonaws.com
).
Configure Akamai Cloud Monitor to push logs
- In Akamai Control Center, open your Propertyin Property Manager.
- Click Add Rule > choose Cloud Management.
- Add Cloud Monitor Instrumentationand select required Datasets.
- Add Cloud Monitor Data Delivery.
- Delivery Hostname: enter your API Gateway Invoke URL(for example,
abc123.execute-api.<region>.amazonaws.com
). - Delivery URL Path: your route plusan optional query token, for example:
/akamai/cloud-monitor?token=<INGEST_TOKEN>
.
- Delivery Hostname: enter your API Gateway Invoke URL(for example,
- Saveand Activatethe property version.
Configure a feed in Google SecOps to ingest Akamai Cloud Monitor (S3 JSON)
- Go to SIEM Settings > Feeds.
- Click Add New Feed.
- In the Feed namefield, enter a name for the feed (for example,
Akamai Cloud Monitor — S3
). - Select Amazon S3 V2as the Source type.
- Select Akamai Cloud Monitoras the Log type.
- Click Next.
- Specify values for the following input parameters:
- S3 URI:
s3://akamai-cloud-monitor/akamai/cloud-monitor/json/
- Source deletion options: Whether to delete files and/or directories after transferring.
- Maximum File Age: Includes files modified in the last number of days. Default is 180 days.
- Access Key ID: A 20-character alphanumeric account access key (e.g., AKIAIOSFODNN7EXAMPLE).
- Secret Access Key: A 40-character alphanumeric account secret access key (e.g., wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY).
- Asset namespace:
akamai.cloud_monitor
- Ingestion labels: Labels are added to all the events from this feed (for example,
source=akamai_cloud_monitor
,format=json
).
- S3 URI:
- Click Next.
- Review your new feed configuration in the Finalizescreen, and then click Submit.
UDM Mapping Table
Log Field | UDM Mapping | Logic |
---|---|---|
accLang
|
network.http.user_agent
|
Directly mapped if not "-" or empty string. |
city
|
principal.location.city
|
Directly mapped if not "-" or empty string. |
cliIP
|
principal.ip
|
Directly mapped if not empty string. |
country
|
principal.location.country_or_region
|
Directly mapped if not "-" or empty string. |
cp
|
additional.fields
|
Mapped as a key-value pair with key "cp". |
customField
|
about.ip
, about.labels
, src.ip
|
Parsed as key-value pairs. Special handling for "eIp" and "pIp" to map to src.ip
and about.ip
respectively. Other keys are mapped as labels within about
. |
errorCode
|
security_result.summary
, security_result.severity
|
If present, sets security_result.severity
to "ERROR" and maps the value to security_result.summary
. |
geo.city
|
principal.location.city
|
Directly mapped if city
is "-" or empty string. |
geo.country
|
principal.location.country_or_region
|
Directly mapped if country
is "-" or empty string. |
geo.lat
|
principal.location.region_latitude
|
Directly mapped, converted to float. |
geo.long
|
principal.location.region_longitude
|
Directly mapped, converted to float. |
geo.region
|
principal.location.state
|
Directly mapped. |
id
|
metadata.product_log_id
|
Directly mapped if not empty string. |
message.cliIP
|
principal.ip
|
Directly mapped if cliIP
is empty string. |
message.fwdHost
|
principal.hostname
|
Directly mapped. |
message.reqHost
|
target.hostname
, target.url
|
Used to construct target.url
and extract target.hostname
. |
message.reqLen
|
network.sent_bytes
|
Directly mapped, converted to unsigned integer if totalBytes
is empty or "-". |
message.reqMethod
|
network.http.method
|
Directly mapped if reqMethod
is empty string. |
message.reqPath
|
target.url
|
Appended to target.url
. |
message.reqPort
|
target.port
|
Directly mapped, converted to integer if reqPort
is empty string. |
message.respLen
|
network.received_bytes
|
Directly mapped, converted to unsigned integer. |
message.sslVer
|
network.tls.version
|
Directly mapped. |
message.status
|
network.http.response_code
|
Directly mapped, converted to integer if statusCode
is empty or "-". |
message.UA
|
network.http.user_agent
|
Directly mapped if UA
is "-" or empty string. |
network.asnum
|
additional.fields
|
Mapped as a key-value pair with key "asnum". |
network.edgeIP
|
intermediary.ip
|
Directly mapped. |
network.network
|
additional.fields
|
Mapped as a key-value pair with key "network". |
network.networkType
|
additional.fields
|
Mapped as a key-value pair with key "networkType". |
proto
|
network.application_protocol
|
Used to determine network.application_protocol
. |
queryStr
|
target.url
|
Appended to target.url
if not "-" or empty string. |
referer
|
network.http.referral_url
, about.hostname
|
Directly mapped if not "-". Extracted hostname is mapped to about.hostname
. |
reqHost
|
target.hostname
, target.url
|
Used to construct target.url
and extract target.hostname
. |
reqId
|
metadata.product_log_id
, network.session_id
|
Directly mapped if id
is empty string. Also mapped to network.session_id
. |
reqMethod
|
network.http.method
|
Directly mapped if not empty string. |
reqPath
|
target.url
|
Appended to target.url
if not "-". |
reqPort
|
target.port
|
Directly mapped, converted to integer. |
reqTimeSec
|
metadata.event_timestamp
, timestamp
|
Used to set event timestamp. |
start
|
metadata.event_timestamp
, timestamp
|
Used to set event timestamp if reqTimeSec
is empty string. |
statusCode
|
network.http.response_code
|
Directly mapped, converted to integer if not "-" or empty string. |
tlsVersion
|
network.tls.version
|
Directly mapped. |
totalBytes
|
network.sent_bytes
|
Directly mapped, converted to unsigned integer if not empty or "-". |
type
|
metadata.product_event_type
|
Directly mapped. |
UA
|
network.http.user_agent
|
Directly mapped if not "-" or empty string. |
version
|
metadata.product_version
|
Directly mapped. |
xForwardedFor
|
principal.ip
|
Directly mapped if not "-" or empty string. |
(Parser Logic)
|
metadata.vendor_name
|
Set to "Akamai". |
(Parser Logic)
|
metadata.product_name
|
Set to "DataStream". |
(Parser Logic)
|
metadata.event_type
|
Set to "NETWORK_HTTP". |
(Parser Logic)
|
metadata.product_version
|
Set to "2" if version
is empty string. |
(Parser Logic)
|
metadata.log_type
|
Set to "AKAMAI_CLOUD_MONITOR". |
(Parser Logic)
|
network.application_protocol
|
Determined from proto
or message.proto
. Set to "HTTPS" if either contains "HTTPS" (case-insensitive), "HTTP" otherwise. |
(Parser Logic)
|
security_result.severity
|
Set to "INFORMATIONAL" if errorCode
is "-" or empty string. |
(Parser Logic)
|
target.url
|
Constructed from protocol
, reqHost
(or message.reqHost
), reqPath
(or message.reqPath
), and queryStr
. |
Need more help? Get answers from Community members and Google SecOps professionals.