Recoger registros de gestión de privilegios de endpoints de BeyondTrust
En este documento se explica cómo ingerir registros de BeyondTrust Endpoint Privilege Management (EPM) en Google Security Operations mediante AWS S3. El analizador se centra en transformar los datos de registro JSON sin procesar de BeyondTrust Endpoint en un formato estructurado que se ajuste al modelo de datos unificado (UDM). Primero, inicializa los valores predeterminados de varios campos y, a continuación, analiza la carga útil JSON. Después, asigna campos específicos del registro sin procesar a los campos de UDM correspondientes del objeto event.idm.read_only_udm
.
Antes de empezar
Asegúrate de que cumples los siguientes requisitos previos:
- Instancia de Google SecOps
- Acceso privilegiado a AWS
- Acceso con privilegios a BeyondTrust Endpoint Privilege Management
Configurar la gestión de identidades y accesos de AWS para la ingestión de Google SecOps
- Crea un usuario siguiendo esta guía: Crear un usuario de gestión de identidades y accesos.
- Selecciona el usuario creado.
- Selecciona la pestaña Credenciales de seguridad.
- En la sección Claves de acceso, haz clic en Crear clave de acceso.
- Selecciona Servicio de terceros como Caso práctico.
- Haz clic en Siguiente.
- Opcional: añade una etiqueta de descripción.
- Haz clic en Crear clave de acceso.
- Haz clic en Descargar archivo CSV para guardar la clave de acceso y la clave de acceso secreta para futuras consultas.
- Haz clic en Listo.
- Selecciona la pestaña Permisos.
- En la sección Políticas de permisos, haz clic en Añadir permisos.
- Selecciona Añadir permisos.
- Seleccione Adjuntar políticas directamente.
- Busca y selecciona la política AmazonS3FullAccess.
- Haz clic en Siguiente.
- Haz clic en Añadir permisos.
Configurar BeyondTrust EPM para el acceso a la API
- Inicia sesión en la consola web BeyondTrust Privilege Management como administrador.
- Ve a Configuración del sistema > API REST > Tokens.
- Haz clic en Add Token (Añadir token).
- Proporcione los siguientes detalles de configuración:
- Name (Nombre): escribe
Google SecOps Collector
. - Ámbitos: selecciona Audit:Read y otros ámbitos según sea necesario.
- Name (Nombre): escribe
- Guarda y copia el valor del token (será tu BPT_API_TOKEN).
- Copia la URL base de la API. Normalmente es
https://<your-epm-server>/api/v3
o/api/v2
, según tu versión (la usarás como BPT_API_URL).
Crear un segmento de AWS S3
- Inicia sesión en la consola de administración de AWS.
- Ve a Consola de AWS > Servicios > S3 > Crear un bucket.
- Proporcione los siguientes detalles de configuración:
- Nombre del segmento:
my-beyondtrust-logs
. - Región: [tu elección] > Crear.
- Nombre del segmento:
Crear un rol de gestión de identidades y accesos para EC2
- Inicia sesión en la consola de administración de AWS.
- Ve a AWS Console > Services > IAM > Roles > Create role (Consola de AWS > Servicios > IAM > Roles > Crear rol).
- Proporcione los siguientes detalles de configuración:
- Entidad de confianza: Servicio de AWS > EC2 > Siguiente.
- Adjuntar permiso: AmazonS3FullAccess (o una política con ámbito para tu bucket) > Siguiente.
- Nombre del rol:
EC2-S3-BPT-Writer
> Crear rol.
Opcional: Inicia y configura tu VM de EC2 Collector
- Inicia sesión en la consola de administración de AWS.
- Ve a Servicios.
- En la barra de búsqueda, escribe EC2 y selecciónalo.
- En el panel de control de EC2, haz clic en Instances (Instancias).
- Haz clic en Lanzar instancias.
- Proporcione los siguientes detalles de configuración:
- Nombre: introduce
BPT-Log-Collector
. - AMI: selecciona Ubuntu Server 22.04 LTS.
- Tipo de instancia: t3.micro (o un tipo más grande) y, a continuación, haz clic en Siguiente.
- Red: comprueba que la opción Red esté configurada en tu VPC predeterminada.
- Rol de gestión de identidades y accesos: selecciona el rol de gestión de identidades y accesos EC2-S3-BPT-Writer en el menú.
- Asignar IP pública automáticamente: Habilitar (o asegúrate de que puedes acceder a ella mediante una VPN) > Siguiente.
- Añadir almacenamiento: deje la configuración de almacenamiento predeterminada (8 GiB) y, a continuación, haga clic en Siguiente.
- Selecciona Crear un grupo de seguridad.
- Regla de entrada: haz clic en Añadir regla.
- Tipo: selecciona SSH.
- Puerto: 22.
- Fuente: tu IP
- Haga clic en Revisar y publicar.
- Seleccione o cree un par de claves.
- Haz clic en Descargar par de claves.
- Guarda el archivo PEM descargado. Necesitarás este archivo para conectarte a tu instancia mediante SSH.
- Nombre: introduce
Conéctate a tu máquina virtual (VM) mediante SSH:
chmod 400 ~/Downloads/your-key.pem ssh -i ~/Downloads/your-key.pem ubuntu@<EC2_PUBLIC_IP>
Instalar los requisitos previos del recopilador
Ejecuta el siguiente comando:
# Update OS sudo apt update && sudo apt upgrade -y # Install Python, Git sudo apt install -y python3 python3-venv python3-pip git # Create & activate virtualenv python3 -m venv ~/bpt-venv source ~/bpt-venv/bin/activate # Install libraries pip install requests boto3
Crea un directorio y un archivo de estado:
sudo mkdir -p /var/lib/bpt-collector sudo touch /var/lib/bpt-collector/last_run.txt sudo chown ubuntu:ubuntu /var/lib/bpt-collector/last_run.txt
Inicialízalo (por ejemplo, hace 1 hora):
echo "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" > /var/lib/bpt-collector/last_run.txt
Implementar la secuencia de comandos del recopilador de Armis
Crea una carpeta de proyecto:
mkdir ~/bpt-collector && cd ~/bpt-collector
Exporta las variables de entorno necesarias (por ejemplo, en
~/.bashrc
):export BPT_API_URL="https://<your-subdomain>-services.pm.beyondtrustcloud.com" export BPT_CLIENT_ID="your-client-id" export BPT_CLIENT_SECRET="your-client-secret" export S3_BUCKET="my-bpt-logs" export S3_PREFIX="bpt/" export STATE_FILE="/var/lib/bpt-collector/last_run.txt" export PAGE_SIZE="100"
Crea
collector_bpt.py
e introduce el siguiente código:#!/usr/bin/env python3 import os, sys, json, boto3, requests from datetime import datetime, timezone, timedelta # ── UTILS ───────────────────────────────────────────────────────────────── def must_env(var): val = os.getenv(var) if not val: print(f"ERROR: environment variable {var} is required", file=sys.stderr) sys.exit(1) return val def ensure_state_file(path): d = os.path.dirname(path) if not os.path.isdir(d): os.makedirs(d, exist_ok=True) if not os.path.isfile(path): ts = (datetime.now(timezone.utc) - timedelta(hours=1))\ .strftime("%Y-%m-%dT%H:%M:%SZ") with open(path, "w") as f: f.write(ts) # ── CONFIG ───────────────────────────────────────────────────────────────── BPT_API_URL = must_env("BPT_API_URL") # for example, https://subdomain-services.pm.beyondtrustcloud.com CLIENT_ID = must_env("BPT_CLIENT_ID") CLIENT_SECRET = must_env("BPT_CLIENT_SECRET") S3_BUCKET = must_env("S3_BUCKET") S3_PREFIX = os.getenv("S3_PREFIX", "") # for example, "bpt/" STATE_FILE = os.getenv("STATE_FILE", "/var/lib/bpt-collector/last_run.txt") PAGE_SIZE = int(os.getenv("PAGE_SIZE", "100")) # ── END CONFIG ───────────────────────────────────────────────────────────── ensure_state_file(STATE_FILE) def read_last_run(): with open(STATE_FILE, "r") as f: ts = f.read().strip() return datetime.fromisoformat(ts.replace("Z", "+00:00")) def write_last_run(dt): with open(STATE_FILE, "w") as f: f.write(dt.strftime("%Y-%m-%dT%H:%M:%SZ")) def get_oauth_token(): resp = requests.post( f"{BPT_API_URL}/oauth/connect/token", headers={"Content-Type": "application/x-www-form-urlencoded"}, data={ "grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET } ) resp.raise_for_status() return resp.json()["access_token"] def fetch_events(token, start, end): headers = {"Authorization": f"Bearer {token}"} offset = 0 while True: params = { "startTime": start, "endTime": end, "limit": PAGE_SIZE, "offset": offset } resp = requests.get( f"{BPT_API_URL}/management-api/v1/Audit/Events", headers=headers, params=params ) resp.raise_for_status() events = resp.json().get("events", []) if not events: break for e in events: yield e offset += PAGE_SIZE def upload_to_s3(obj, key): boto3.client("s3").put_object( Bucket=S3_BUCKET, Key=key, Body=json.dumps(obj).encode("utf-8") ) def main(): # 1) determine window start_dt = read_last_run() end_dt = datetime.now(timezone.utc) START = start_dt.strftime("%Y-%m-%dT%H:%M:%SZ") END = end_dt.strftime("%Y-%m-%dT%H:%M:%SZ") print(f"Fetching events from {START} to {END}") # 2) authenticate and fetch token = get_oauth_token() count = 0 for idx, evt in enumerate(fetch_events(token, START, END), start=1): key = f"{S3_PREFIX}{end_dt.strftime('%Y/%m/%d')}/evt_{int(end_dt.timestamp())}_{idx}.json" upload_to_s3(evt, key) count += 1 print(f"Uploaded {count} events") # 3) persist state write_last_run(end_dt) if __name__ == "__main__": main()
Haz que sea ejecutable:
chmod +x collector_bpt.py
Programar diariamente con Cron
Ejecuta el siguiente comando:
crontab -e
Añade la tarea diaria a medianoche UTC:
0 0 * * * cd ~/bpt-collector && source ~/bpt-venv/bin/activate && ./collector_bpt.py >> ~/bpt-collector/bpt.log 2>&1
Configurar feeds
Para configurar un feed, sigue estos pasos:
- Ve a Configuración de SIEM > Feeds.
- Haz clic en Añadir feed.
- En la página siguiente, haga clic en Configurar un solo feed.
- En el campo Nombre del feed, introduzca un nombre para el feed (por ejemplo, Registros de EPM de BeyondTrust).
- Selecciona Amazon S3 V2 como Tipo de fuente.
- Seleccione Beyondtrust Endpoint Privilege Management como Tipo de registro.
- Haz clic en Siguiente.
Especifique los valores de los siguientes parámetros de entrada:
- URI de S3: el URI del contenedor (el formato debe ser
s3://your-log-bucket-name/
). Sustituye lo siguiente:your-log-bucket-name
: el nombre del segmento.
- Opciones de eliminación de la fuente: selecciona la opción de eliminación que prefieras.
- URI de S3: el URI del contenedor (el formato debe ser
Haz clic en Siguiente.
Revise la configuración de la nueva fuente en la pantalla Finalizar y, a continuación, haga clic en Enviar.
Tabla de asignación de UDM
Campo de registro | Asignación de UDM | Lógica |
---|---|---|
agent.id | principal.asset.attribute.labels.value | El valor se toma del campo agent.id del registro sin procesar y se asigna a una etiqueta con la clave agent_id en la matriz principal.asset.attribute.labels de UDM. |
agent.version | principal.asset.attribute.labels.value | El valor se toma del campo agent.version del registro sin procesar y se asigna a una etiqueta con la clave agent_version en la matriz principal.asset.attribute.labels de UDM. |
ecs.version | principal.asset.attribute.labels.value | El valor se toma del campo ecs.version del registro sin procesar y se asigna a una etiqueta con la clave ecs_version en la matriz principal.asset.attribute.labels de UDM. |
event_data.reason | metadata.description | El valor se toma del campo event_data.reason del registro sin procesar y se asigna al campo description del objeto metadata en el modelo de datos unificado. |
event_datas.ActionId | metadata.product_log_id | El valor se toma del campo event_datas.ActionId del registro sin procesar y se asigna al campo product_log_id del objeto metadata en el modelo de datos unificado. |
file.path | principal.file.full_path | El valor se toma del campo file.path del registro sin procesar y se asigna al campo full_path del objeto principal.file en el modelo de datos unificado. |
headers.content_length | additional.fields.value.string_value | El valor se toma del campo headers.content_length del registro sin procesar y se asigna a una etiqueta con la clave content_length en la matriz additional.fields de UDM. |
headers.content_type | additional.fields.value.string_value | El valor se toma del campo headers.content_type del registro sin procesar y se asigna a una etiqueta con la clave content_type en la matriz additional.fields de UDM. |
headers.http_host | additional.fields.value.string_value | El valor se toma del campo headers.http_host del registro sin procesar y se asigna a una etiqueta con la clave http_host en la matriz additional.fields de UDM. |
headers.http_version | network.application_protocol_version | El valor se toma del campo headers.http_version del registro sin procesar y se asigna al campo application_protocol_version del objeto network en el modelo de datos unificado. |
headers.request_method | network.http.method | El valor se toma del campo headers.request_method del registro sin procesar y se asigna al campo method del objeto network.http en el modelo de datos unificado. |
host.hostname | principal.hostname | El valor se toma del campo host.hostname del registro sin procesar y se asigna al campo hostname del objeto principal en el modelo de datos unificado. |
host.hostname | principal.asset.hostname | El valor se toma del campo host.hostname del registro sin procesar y se asigna al campo hostname del objeto principal.asset en el modelo de datos unificado. |
host.ip | principal.asset.ip | El valor se toma del campo host.ip del registro sin procesar y se añade a la matriz ip del objeto principal.asset del UDM. |
host.ip | principal.ip | El valor se toma del campo host.ip del registro sin procesar y se añade a la matriz ip del objeto principal del UDM. |
host.mac | principal.mac | El valor se toma del campo host.mac del registro sin procesar y se añade a la matriz mac del objeto principal del UDM. |
host.os.platform | principal.platform | El valor es MAC si el campo host.os.platform del registro sin procesar es igual a macOS . |
host.os.version | principal.platform_version | El valor se toma del campo host.os.version del registro sin procesar y se asigna al campo platform_version del objeto principal en el modelo de datos unificado. |
labels.related_item_id | metadata.product_log_id | El valor se toma del campo labels.related_item_id del registro sin procesar y se asigna al campo product_log_id del objeto metadata en el modelo de datos unificado. |
process.command_line | principal.process.command_line | El valor se toma del campo process.command_line del registro sin procesar y se asigna al campo command_line del objeto principal.process en el modelo de datos unificado. |
process.name | additional.fields.value.string_value | El valor se toma del campo process.name del registro sin procesar y se asigna a una etiqueta con la clave process_name en la matriz additional.fields de UDM. |
process.parent.name | additional.fields.value.string_value | El valor se toma del campo process.parent.name del registro sin procesar y se asigna a una etiqueta con la clave process_parent_name en la matriz additional.fields de UDM. |
process.parent.pid | principal.process.parent_process.pid | El valor se toma del campo process.parent.pid del registro sin procesar, se convierte en una cadena y se asigna al campo pid del objeto principal.process.parent_process de UDM. |
process.pid | principal.process.pid | El valor se toma del campo process.pid del registro sin procesar, se convierte en una cadena y se asigna al campo pid del objeto principal.process de UDM. |
user.id | principal.user.userid | El valor se toma del campo user.id del registro sin procesar y se asigna al campo userid del objeto principal.user en el modelo de datos unificado. |
nombre.usuario | principal.user.user_display_name | El valor se toma del campo user.name del registro sin procesar y se asigna al campo user_display_name del objeto principal.user en el modelo de datos unificado. |
N/A | metadata.event_timestamp | La marca de tiempo del evento se define como la marca de tiempo de la entrada de registro. |
N/A | metadata.event_type | El tipo de evento se define como GENERIC_EVENT si no se encuentra ningún principal. De lo contrario, se define como STATUS_UPDATE . |
N/A | network.application_protocol | El protocolo de aplicación se define como HTTP si el campo headers.http_version del registro sin procesar contiene HTTP . |
¿Necesitas más ayuda? Recibe respuestas de los miembros de la comunidad y de los profesionales de Google SecOps.