Tutoriel sur Cloud Audit Logs


Ce tutoriel explique comment écrire, déployer et déclencher une fonction Cloud basée sur les événements avec un déclencheur Cloud Audit Logs.

Cloud Run Functions permet le déclenchement de vos fonctions par les entrées Cloud Audit Logs. De nombreux produits Google Cloud écrivent dans Cloud Audit Logs lorsque des actions importantes se produisent dans le produit. Ces entrées de journal peuvent déclencher l'exécution de Cloud Run Functions en temps réel, ce qui permet aux utilisateurs de les traiter et/ou d'y répondre automatiquement.

Ces journaux sont générés par de nombreux événements différents sur Google Cloud et couvrent la plupart des produits Google Cloud. Ainsi, les déclencheurs Cloud Audit Logs vous permettent de créer des fonctions qui réagissent à la plupart des changements d'état dans Google Cloud.

Ce tutoriel explique comment utiliser les déclencheurs Cloud Audit Logs pour ajouter un libellé aux instances Compute Engine nouvellement créées avec le nom de l'entité (personne ou compte de service) qui les a créées.

Si vous débutez avec Cloud Audit Logs et souhaitez en savoir plus, consultez la documentation correspondante.

Objectifs

  • Écrire une fonction Cloud Run basée sur les événements qui reçoit un événement Cloud Audit Logs lorsqu'une instance de VM Compute Engine est créée.
  • Déclencher la fonction en créant une instance de VM Compute Engine. À ce stade, l'instance sera libellée avec le nom de l'entité (personne ou compte de service) qui l'a créée.

Coûts

Dans ce document, vous utilisez les composants facturables suivants de Google Cloud :

  • Cloud Run functions
  • Cloud Build
  • Pub/Sub
  • Artifact Registry
  • Eventarc
  • Cloud Logging
  • Compute Engine

For details, see Cloud Run functions pricing.

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Avant de commencer

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Cloud Functions, Cloud Run, Cloud Build, Artifact Registry, Eventarc, Logging, Compute Engine, and Pub/Sub APIs.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  6. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the Cloud Functions, Cloud Run, Cloud Build, Artifact Registry, Eventarc, Logging, Compute Engine, and Pub/Sub APIs.

    Enable the APIs

  8. Installez et initialisez le SDK Cloud.
  9. Mettez à jour les composants gcloud :
  10. gcloud components update

    Vous avez besoin d'une invite de commande ? Vous pouvez utiliser Google Cloud Shell. Google Cloud Shell est un environnement de ligne de commande qui inclut le SDK Google Cloud. Vous n'avez donc pas besoin de l'installer. Le SDK Google Cloud est également préinstallé sur les machines virtuelles Google Compute Engine.

  11. Préparez votre environnement de développement.

Prérequis

  1. Ouvrez la page IAM et administration > Journaux d'audit dans la console Google Cloud :

    Accéder à la page IAM et administration > Journaux d'audit

  2. Activez les types de journaux Cloud Audit Logs Admin Read, Data Read et Data Write pour l'API Compute Engine :

    Capture d'écran montrant comment activer les journaux d'audit pour Compute Engine

  3. Vérifiez si le compte de service Compute Engine dispose du rôle Editor. Ce compte de service sera utilisé comme identité de service pour Cloud Run Functions.

    Accéder à la page IAM et administration > IAM

    Recherchez l'entrée PROJECT_NUMBER-compute@developer.gserviceaccount.com dans la table et examinez la colonne Roles. Si la colonne contient Editor, vous pouvez ignorer les étapes suivantes. Sinon, passez aux étapes suivantes et attribuez les rôles nécessaires au compte de service.

  4. Attribuez le rôle eventarc.eventReceiver au compte de service Compute Engine du projet :

    PROJECT_ID=$(gcloud config get-value project)
    PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')
    
    # Allow service account token creation
    gcloud projects add-iam-policy-binding $PROJECT_ID \
     --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
     --role roles/eventarc.eventReceiver
    
  5. Attribuez le rôle run.invoker au compte de service Compute Engine du projet pour que le déclencheur Pub/Sub puisse exécuter la fonction:

    PROJECT_ID=$(gcloud config get-value project)
    PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')
    
    # Allow service account token creation
    gcloud projects add-iam-policy-binding $PROJECT_ID \
     --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
     --role roles/run.invoker
    
  6. Attribuez le rôle compute.instanceAdmin au compte de service Compute Engine du projet afin que le code de la fonction dispose des autorisations nécessaires pour obtenir les instances de VM et définir des libellés sur celles-ci:

    PROJECT_ID=$(gcloud config get-value project)
    PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')
    
    # Allow service account token creation
    gcloud projects add-iam-policy-binding $PROJECT_ID \
     --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
     --role roles/compute.instanceAdmin
    

Préparer l'application

  1. Clonez le dépôt de l'exemple d'application sur votre ordinateur local :

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

  2. Accédez au répertoire contenant l'exemple de code Cloud Run Functions permettant d'accéder à Cloud Audit Logs :

    Node.js

    cd nodejs-docs-samples/functions/v2/autoLabelInstance/

    Python

    cd python-docs-samples/functions/v2/label_gce_instance/

    Go

    cd golang-samples/functions/functionsv2/label_gce_instance/

    Java

    cd java-docs-samples/functions/v2/label-compute-instance/

  3. Consultez l'exemple de code :

    Node.js

    const functions = require('@google-cloud/functions-framework');
    
    const compute = require('@google-cloud/compute');
    const instancesClient = new compute.InstancesClient();
    
    // Register a CloudEvent callback with the Functions Framework that labels
    // newly-created GCE instances with the entity (person or service account)
    // that created them.
    functions.cloudEvent('autoLabelInstance', async cloudEvent => {
      // Extract parameters from the CloudEvent + Cloud Audit Log data
      const payload = cloudEvent.data && cloudEvent.data.protoPayload;
      const authInfo = payload && payload.authenticationInfo;
      let creator = authInfo && authInfo.principalEmail;
    
      // Get relevant VM instance details from the CloudEvent's `subject` property
      // Example value:
      //   compute.googleapis.com/projects/<PROJECT>/zones/<ZONE>/instances/<INSTANCE>
      const params = cloudEvent.subject && cloudEvent.subject.split('/');
    
      // Validate data
      if (!creator || !params || params.length !== 7) {
        throw new Error('Invalid event structure');
      }
    
      // Format the 'creator' parameter to match GCE label validation requirements
      creator = creator.toLowerCase().replace(/\W/g, '_');
    
      // Get the newly-created VM instance's label fingerprint
      // This is required by the Compute Engine API to prevent duplicate labels
      const getInstanceRequest = {
        project: params[2],
        zone: params[4],
        instance: params[6],
      };
      const [instance] = await instancesClient.get(getInstanceRequest);
    
      // Label the instance with its creator
      const setLabelsRequest = Object.assign(
        {
          instancesSetLabelsRequestResource: {
            labels: {creator},
            labelFingerprint: instance.labelFingerprint,
          },
        },
        getInstanceRequest
      );
    
      return instancesClient.setLabels(setLabelsRequest);
    });

    Python

    import re
    
    from google.api_core.exceptions import GoogleAPIError
    from google.cloud import compute_v1
    from google.cloud.compute_v1.types import compute
    
    instances_client = compute_v1.InstancesClient()
    
    
    # CloudEvent function that labels newly-created GCE instances
    # with the entity (user or service account) that created them.
    #
    # @param {object} cloudevent A CloudEvent containing the Cloud Audit Log entry.
    # @param {object} cloudevent.data.protoPayload The Cloud Audit Log entry.
    def label_gce_instance(cloudevent):
        # Extract parameters from the CloudEvent + Cloud Audit Log data
        payload = cloudevent.data.get("protoPayload", dict())
        auth_info = payload.get("authenticationInfo", dict())
        creator = auth_info.get("principalEmail")
    
        # Get relevant VM instance details from the cloudevent's `subject` property
        # Example value:
        #   compute.googleapis.com/projects/<PROJECT_ID>/zones/<ZONE_ID>/instances/<INSTANCE_NAME>
        instance_params = cloudevent["subject"].split("/")
    
        # Validate data
        if not creator or not instance_params or len(instance_params) != 7:
            # This is not something retries will fix, so don't throw an Exception
            # (Thrown exceptions trigger retries *if* you enable retries in GCF.)
            print("ERROR: Invalid `principalEmail` and/or CloudEvent `subject`.")
            return
    
        instance_project = instance_params[2]
        instance_zone = instance_params[4]
        instance_name = instance_params[6]
    
        # Format the 'creator' parameter to match GCE label validation requirements
        creator = re.sub("\\W", "_", creator.lower())
    
        # Get the newly-created VM instance's label fingerprint
        # This is required by the Compute Engine API to prevent duplicate labels
        instance = instances_client.get(
            project=instance_project, zone=instance_zone, instance=instance_name
        )
    
        # Construct API call to label the VM instance with its creator
        request_init = {
            "project": instance_project,
            "zone": instance_zone,
            "instance": instance_name,
        }
        request_init[
            "instances_set_labels_request_resource"
        ] = compute.InstancesSetLabelsRequest(
            label_fingerprint=instance.label_fingerprint, labels={"creator": creator}
        )
        request = compute.SetLabelsInstanceRequest(request_init)
    
        # Perform instance-labeling API call
        try:
            instances_client.set_labels_unary(request)
            print(f"Labelled VM instance {instance_name} with creator: {creator}")
        except GoogleAPIError as e:
            # Swallowing the exception means failed invocations WON'T be retried
            print("Label operation failed", e)
    
            # Uncomment the line below to retry failed invocations.
            # (You'll also have to enable retries in Cloud Functions itself.)
            # raise e
    
        return
    
    

    Go

    
    // Package helloworld provides a set of Cloud Functions samples.
    package helloworld
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"regexp"
    	"strings"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	"cloud.google.com/go/compute/apiv1/computepb"
    	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
    	"github.com/cloudevents/sdk-go/v2/event"
    	"google.golang.org/protobuf/proto"
    )
    
    // AuditLogEntry represents a LogEntry as described at
    // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
    type AuditLogEntry struct {
    	ProtoPayload *AuditLogProtoPayload `json:"protoPayload"`
    }
    
    // AuditLogProtoPayload represents AuditLog within the LogEntry.protoPayload
    // See https://cloud.google.com/logging/docs/reference/audit/auditlog/rest/Shared.Types/AuditLog
    type AuditLogProtoPayload struct {
    	MethodName         string                 `json:"methodName"`
    	ResourceName       string                 `json:"resourceName"`
    	AuthenticationInfo map[string]interface{} `json:"authenticationInfo"`
    }
    
    var client *compute.InstancesClient
    
    func init() {
    	// Create an Instances Client
    	var err error
    	client, err = compute.NewInstancesRESTClient(context.Background())
    	if err != nil {
    		log.Fatalf("Failed to create instances client: %s", err)
    	}
    
    	functions.CloudEvent("label-gce-instance", labelGceInstance)
    }
    
    // Cloud Function that receives GCE instance creation Audit Logs, and adds a
    // `creator` label to the instance.
    func labelGceInstance(ctx context.Context, ev event.Event) error {
    	// Extract parameters from the Cloud Event and Cloud Audit Log data
    	logentry := &AuditLogEntry{}
    	if err := ev.DataAs(logentry); err != nil {
    		err = fmt.Errorf("event.DataAs() : %w", err)
    		log.Printf("Error parsing proto payload: %s", err)
    		return err
    	}
    	payload := logentry.ProtoPayload
    	creator, ok := payload.AuthenticationInfo["principalEmail"]
    	if !ok {
    		err := fmt.Errorf("principalEmail not found in cloud event payload: %v", payload)
    		log.Printf("creator email not found: %s", err)
    		return err
    	}
    
    	// Get relevant VM instance details from the event's `subject` property
    	// Subject format:
    	// compute.googleapis.com/projects/<PROJECT>/zones/<ZONE>/instances/<INSTANCE>
    	paths := strings.Split(ev.Subject(), "/")
    	if len(paths) < 6 {
    		return fmt.Errorf("invalid event subject: %s", ev.Subject())
    	}
    	project := paths[2]
    	zone := paths[4]
    	instance := paths[6]
    
    	// Sanitize the `creator` label value to match GCE label requirements
    	// See https://cloud.google.com/compute/docs/labeling-resources#requirements
    	labelSanitizer := regexp.MustCompile("[^a-z0-9_-]+")
    	creatorstring := labelSanitizer.ReplaceAllString(strings.ToLower(creator.(string)), "_")
    
    	// Get the newly-created VM instance's label fingerprint
    	// This is a requirement of the Compute Engine API and avoids duplicate labels
    	inst, err := client.Get(ctx, &computepb.GetInstanceRequest{
    		Project:  project,
    		Zone:     zone,
    		Instance: instance,
    	})
    	if err != nil {
    		err = fmt.Errorf("could not retrieve GCE instance: %s", err)
    		log.Print(err)
    		return err
    	}
    	if v, ok := inst.Labels["creator"]; ok {
    		// Instance already has a creator label.
    		log.Printf("instance %s already labeled with creator: %s", instance, v)
    		return nil
    	}
    
    	// Add the creator label to the instance
    	op, err := client.SetLabels(ctx, &computepb.SetLabelsInstanceRequest{
    		Project:  project,
    		Zone:     zone,
    		Instance: instance,
    		InstancesSetLabelsRequestResource: &computepb.InstancesSetLabelsRequest{
    			LabelFingerprint: proto.String(inst.GetLabelFingerprint()),
    			Labels: map[string]string{
    				"creator": creatorstring,
    			},
    		},
    	})
    	if err != nil {
    		log.Fatalf("Could not label GCE instance: %s", err)
    	}
    	log.Printf("Creator label added to %s in operation %v", instance, op)
    	return nil
    }
    

    Java

    import com.google.cloud.compute.v1.GetInstanceRequest;
    import com.google.cloud.compute.v1.Instance;
    import com.google.cloud.compute.v1.InstancesClient;
    import com.google.cloud.compute.v1.InstancesSetLabelsRequest;
    import com.google.cloud.compute.v1.SetLabelsInstanceRequest;
    import com.google.cloud.functions.CloudEventsFunction;
    import com.google.gson.Gson;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonSyntaxException;
    import io.cloudevents.CloudEvent;
    import java.nio.charset.StandardCharsets;
    import java.util.logging.Logger;
    
    public class AutoLabelInstance implements CloudEventsFunction {
      private static final Logger logger = Logger.getLogger(AutoLabelInstance.class.getName());
    
      @Override
      public void accept(CloudEvent event) throws Exception {
        // Extract CloudEvent data
        if (event.getData() != null) {
          String cloudEventData = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
    
          // Convert data to JSON
          JsonObject eventData;
          try {
            Gson gson = new Gson();
            eventData = gson.fromJson(cloudEventData, JsonObject.class);
          } catch (JsonSyntaxException error) {
            throw new RuntimeException("CloudEvent data is not valid JSON: " + error.getMessage());
          }
    
          // Extract the Cloud Audit Logging entry from the data's protoPayload
          JsonObject payload = eventData.getAsJsonObject("protoPayload");
          JsonObject auth = payload.getAsJsonObject("authenticationInfo");
    
          // Extract the email address of the authenticated user
          // (or service account on behalf of third party principal) making the request
          String creator = auth.get("principalEmail").getAsString();
          if (creator == null) {
            throw new RuntimeException("`principalEmail` not found in protoPayload.");
          }
          // Format the 'creator' parameter to match GCE label validation requirements
          creator = creator.toLowerCase().replaceAll("\\W", "-");
    
          // Get relevant VM instance details from the CloudEvent `subject` property
          // Example: compute.googleapis.com/projects/<PROJECT>/zones/<ZONE>/instances/<INSTANCE>
          String subject = event.getSubject();
          if (subject == null || subject == "") {
            throw new RuntimeException("Missing CloudEvent `subject`.");
          }
          String[] params = subject.split("/");
    
          // Validate data
          if (params.length < 7) {
            throw new RuntimeException("Can not parse resource from CloudEvent `subject`: " + subject);
          }
          String project = params[2];
          String zone = params[4];
          String instanceName = params[6];
    
          // Instantiate the Compute Instances client
          try (InstancesClient instancesClient = InstancesClient.create()) {
            // Get the newly-created VM instance's label fingerprint
            // This is required by the Compute Engine API to prevent duplicate labels
            GetInstanceRequest getInstanceRequest =
                GetInstanceRequest.newBuilder()
                    .setInstance(instanceName)
                    .setProject(project)
                    .setZone(zone)
                    .build();
            Instance instance = instancesClient.get(getInstanceRequest);
            String fingerPrint = instance.getLabelFingerprint();
    
            // Label the instance with its creator
            SetLabelsInstanceRequest setLabelRequest =
                SetLabelsInstanceRequest.newBuilder()
                    .setInstance(instanceName)
                    .setProject(project)
                    .setZone(zone)
                    .setInstancesSetLabelsRequestResource(
                        InstancesSetLabelsRequest.newBuilder()
                            .putLabels("creator", creator)
                            .setLabelFingerprint(fingerPrint)
                            .build())
                    .build();
    
            instancesClient.setLabelsAsync(setLabelRequest);
            logger.info(
                String.format(
                    "Adding label, \"{'creator': '%s'}\", to instance, \"%s\".",
                    creator, instanceName));
          } catch (Exception error) {
            throw new RuntimeException(
                String.format(
                    "Error trying to label VM instance, %s: %s", instanceName, error.toString()));
          }
        }
      }
    }

Déployer la fonction

Pour déployer la fonction avec un déclencheur Cloud Audit Logs, exécutez la commande suivante dans le répertoire contenant l'exemple de code (ou, dans le cas de Java, le fichier pom.xml) :

Node.js

gcloud functions deploy nodejs-cal-function \
--gen2 \
--runtime=nodejs20 \
--region=REGION \
--source=. \
--entry-point=autoLabelInstance \
--trigger-location=REGION \
--trigger-event-filters="type=google.cloud.audit.log.v1.written" \
--trigger-event-filters="serviceName=compute.googleapis.com" \
--trigger-event-filters="methodName=v1.compute.instances.insert"

Utilisez l'option --runtime pour spécifier l'ID d'exécution d'une version Node.js compatible pour exécuter votre fonction.

Python

gcloud functions deploy python-cal-function \
--gen2 \
--runtime=python312 \
--region=REGION \
--source=. \
--entry-point=label_gce_instance \
--trigger-location=REGION \
--trigger-event-filters="type=google.cloud.audit.log.v1.written" \
--trigger-event-filters="serviceName=compute.googleapis.com" \
--trigger-event-filters="methodName=v1.compute.instances.insert"

Utilisez l'option --runtime pour spécifier l'ID d'exécution d'une version Python compatible pour exécuter votre fonction.

Go

gcloud functions deploy go-cal-function \
--gen2 \
--runtime=go121 \
--region=REGION \
--source=. \
--entry-point=label-gce-instance \
--trigger-location=REGION \
--trigger-event-filters="type=google.cloud.audit.log.v1.written" \
--trigger-event-filters="serviceName=compute.googleapis.com" \
--trigger-event-filters="methodName=v1.compute.instances.insert"

Utilisez l'option --runtime pour spécifier l'ID d'exécution d'une version Go compatible pour exécuter votre fonction.

Java

gcloud functions deploy java-cal-function \
--gen2 \
--runtime=java17 \
--region=REGION \
--source=. \
--entry-point=functions.AutoLabelInstance \
--memory=512MB \
--trigger-location=REGION \
--trigger-event-filters="type=google.cloud.audit.log.v1.written" \
--trigger-event-filters="serviceName=compute.googleapis.com" \
--trigger-event-filters="methodName=v1.compute.instances.insert"

Utilisez l'option --runtime pour spécifier l'ID d'exécution d'une version Java compatible pour exécuter votre fonction.

La commande de déploiement ci-dessus spécifie les paramètres de filtre d'événements suivants qui correspondent à la création de la VM :

  • type : type d'événement Cloud Audit Logs (google.cloud.audit.log.v1.written).
  • serviceName : nom du service Google Cloud ayant généré l'entrée de journal, dans ce cas compute.googleapis.com.
  • methodName : nom de la méthode API qui a généré l'entrée de journal, dans ce cas v1.compute.instances.insert.

Déclencher la fonction

Une fois la fonction déployée, vous pouvez vérifier qu'elle fonctionne :

  1. Créez une instance de VM Compute Engine.

    gcloud compute instances create YOUR_INSTANCE_NAME --zone YOUR_ZONE

    Vous pouvez également accéder à la console Google Cloud, puis cliquer sur Créer une VM.

  2. Exécutez la commande suivante pour vérifier que l'instance a été correctement libellée :

    gcloud compute instances describe YOUR_INSTANCE_NAME \
        --zone YOUR_ZONE \
        --format 'value(labels)'

    Un libellé au format creator=YOURNAMEYOUR_DOMAIN doit s'afficher.

Effectuer un nettoyage

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet mais supprimez les ressources individuelles.

Supprimer le projet

Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.

Pour supprimer le projet :

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Supprimer la fonction

La suppression de fonctions Cloud Run ne supprime pas les ressources stockées dans Cloud Storage.

Pour supprimer la fonction Cloud Run que vous avez créée dans ce tutoriel, exécutez la commande suivante :

Node.js

gcloud functions delete nodejs-cal-function --gen2 --region REGION 

Python

gcloud functions delete python-cal-function --gen2 --region REGION 

Go

gcloud functions delete go-cal-function --gen2 --region REGION 

Java

gcloud functions delete java-cal-function --gen2 --region REGION 

Vous pouvez également supprimer des fonctions Cloud Run à partir de la console Google Cloud.

Supprimer l'instance de VM Compute Engine

Pour supprimer l'instance de VM Compute Engine que vous avez créée dans ce tutoriel, exécutez la commande suivante :

gcloud compute instances delete YOUR_INSTANCE_NAME --zone YOUR_ZONE