gcp

GCP Service Account Last usage auditing using Golang

2022-05-12

Golang sample that shows you how to View recent usage for service accounts and keys

The documentation above is sufficient but i’m writing this short article to show the actual code in golang you can use to parse the response.

The API in context here is the PolicyAnalyzer API which you can use to call the query end point to retrieve the last time a svc account was used.

However, here’s the thing: the GoogleCloudPolicyanalyzerV1Activity response object gives the good stuff back as a googleapi.RawMessage

type GoogleCloudPolicyanalyzerV1Activity struct {
	// Activity: A struct of custom fields to explain the activity.
	Activity googleapi.RawMessage `json:"activity,omitempty"`

	// ActivityType: The type of the activity.
	ActivityType string `json:"activityType,omitempty"`

	// FullResourceName: The full resource name that identifies the
	// resource. For examples of full resource names for Google Cloud
	// services, see
	// https://cloud.google.com/iam/help/troubleshooter/full-resource-names.
	FullResourceName string `json:"fullResourceName,omitempty"`

	// ObservationPeriod: The data observation period to build the activity.
	ObservationPeriod *GoogleCloudPolicyanalyzerV1ObservationPeriod `json:"observationPeriod,omitempty"`

	// ForceSendFields is a list of field names (e.g. "Activity") to
	// unconditionally include in API requests. By default, fields with
	// empty or default values are omitted from API requests. However, any
	// non-pointer, non-interface field appearing in ForceSendFields will be
	// sent to the server regardless of whether the field is empty or not.
	// This may be used to include empty fields in Patch requests.
	ForceSendFields []string `json:"-"`

	// NullFields is a list of field names (e.g. "Activity") to include in
	// API requests with the JSON null value. By default, fields with empty
	// values are omitted from API requests. However, any field with an
	// empty value appearing in NullFields will be sent to the server as
	// null. It is an error if a field in this list has a non-empty value.
	// This may be used to include null fields in Patch requests.
	NullFields []string `json:"-"`
}

how do you parse that? well..by hand and the hard way… and so i wrote this snippet up which you’ll find at the end (yes, it’ would’ve been nice if it returned a easily parsed struct…)


First lets show the basic gcloud usage

Suppose you have a service account with several keys:

$  gcloud iam service-accounts keys list --iam-account=elevate@fabled-ray-104117.iam.gserviceaccount.com
KEY_ID                                    CREATED_AT            EXPIRES_AT            DISABLED
9ad962a3f60ff5b976954e7f17640210649420fd  2022-05-01T14:44:18Z  2022-05-17T14:44:18Z
ea5f19177b3b26cac9bd1f3a9458c7e48223d999  2021-12-23T22:53:47Z  9999-12-31T23:59:59Z
d1f14e907b86dd4f4609fe5bb503e58bad607b0c  2022-05-10T15:49:59Z  2022-05-26T15:49:59Z

To list the overall login for any key, use

 gcloud policy-intelligence query-activity \
   --activity-type=serviceAccountLastAuthentication \
   --project=fabled-ray-104117 \
   --query-filter='activities.full_resource_name="//iam.googleapis.com/projects/fabled-ray-104117/serviceAccounts/elevate@fabled-ray-104117.iam.gserviceaccount.com"'


---
activity:
  lastAuthenticatedTime: '2022-05-10T07:00:00Z'
  serviceAccount:
    fullResourceName: //iam.googleapis.com/projects/fabled-ray-104117/serviceAccounts/elevate@fabled-ray-104117.iam.gserviceaccount.com
    projectNumber: '248066739582'
    serviceAccountId: '117471943676050750091'
activityType: serviceAccountLastAuthentication
fullResourceName: //iam.googleapis.com/projects/fabled-ray-104117/serviceAccounts/elevate@fabled-ray-104117.iam.gserviceaccount.com
observationPeriod:
  endTime: '2022-05-10T07:00:00Z'
  startTime: '2021-12-23T08:00:00Z'

To list the time a specific key was used

 gcloud policy-intelligence query-activity \
   --activity-type=serviceAccountKeyLastAuthentication  \
   --project=fabled-ray-104117 \
   --query-filter='activities.full_resource_name="//iam.googleapis.com/projects/fabled-ray-104117/serviceAccounts/elevate@fabled-ray-104117.iam.gserviceaccount.com/keys/ea5f19177b3b26cac9bd1f3a9458c7e48223d999"'

---
activity:
  lastAuthenticatedTime: '2022-05-10T07:00:00Z'
  serviceAccountKey:
    fullResourceName: //iam.googleapis.com/projects/fabled-ray-104117/serviceAccounts/elevate@fabled-ray-104117.iam.gserviceaccount.com/keys/ea5f19177b3b26cac9bd1f3a9458c7e48223d999
    projectNumber: '248066739582'
    serviceAccountId: '117471943676050750091'
activityType: serviceAccountKeyLastAuthentication
fullResourceName: //iam.googleapis.com/projects/fabled-ray-104117/serviceAccounts/elevate@fabled-ray-104117.iam.gserviceaccount.com/keys/ea5f19177b3b26cac9bd1f3a9458c7e48223d999
observationPeriod:
  endTime: '2022-05-10T07:00:00Z'
  startTime: '2021-12-23T08:00:00Z'

but in my case, i accessed secret manager today so the last log entry i see is for that date/time in question

Audit log get

um…the last login is was at timestamp: "2022-05-10T11:17:10.721846310Z" but the lastAuthenticatedTime shows as 2022-05-10T07:00:00Z

well, thats just the granularity it works at for now. See the docs about this field

lastAuthenticatedTime: A timestamp representing the date at which the most recent authentication event occurred. The time in this timestamp is always T07:00:00Z, regardless of the exact time of the authentication event.

Note: the observationPeriod value denotes when the scan was done so any real time, recent usage won’t be shown. Just wait till it runs and wait at a day’s granularity

one usecase for all this is to just Find unused service accounts

..anyway, the reason i wrote this up was because it wasn’t easy to parse the go struct…so the code you’re after here is


This site supports webmentions. Send me a mention via this form.