name: keytos-ezcms
description: A package for Keytos EZCMS / EZID (YubiKey and smart card onboarding) SIEM logs.
testCases:
  - event:
      rawString: '{"SCSerialNumber":"1234567","CertificateSerialNumber":"0A1B2C3D4E5F","Certificate":"MIIE...base64...","Revoked":false,"ExpiryDate":"2027-07-24T00:00:00Z","RequestDate":"2026-07-24T15:04:05.1234567Z","CAName":"Contoso Issuing CA","AgentURL":"https://agent.contoso.com","AuthType":"AzureAD","AuthGuid":"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee","RequesterTenantID":"11111111-2222-3333-4444-555555555555","UserName":"jdoe@contoso.com","DomainID":"domain-01"}'
    assertions:
      - fieldsHaveValues:
          "#Vendor": "keytos"
          "#event.module": "ezcms"
          "#event.kind": "event"
          "#event.dataset": "ezcms"
          "event.category[0]": "iam"
          "event.type[0]": "info"
          "user.name": "jdoe@contoso.com"
          "user.email": "jdoe@contoso.com"
          "x509.serial_number": "0A1B2C3D4E5F"
          "keytos.ca.name": "Contoso Issuing CA"
          "keytos.smartcard.serial": "1234567"
  - event:
      rawString: '{"AlertCode":23,"AlertDescription":"A failed PUK change attempt has been detected, attackers might try to use this API to brute force PUK keys. Please verify smart card owner still has this smart card","MoreInformation":"1234567"}'
    assertions:
      - fieldsHaveValues:
          "#event.module": "ezcms"
          "#event.dataset": "ezcms"
          "event.category[0]": "iam"
          "event.type[0]": "info"
          "event.id": "23"
          "event.reason": "A failed PUK change attempt has been detected, attackers might try to use this API to brute force PUK keys. Please verify smart card owner still has this smart card"
        fieldsNotPresent:
          - "user.name"
  - event:
      rawString: '{"SubscriptionID":"3f2504e0-4f89-41d3-9a0c-0305e82c3301","ActionPerformed":"SubscriptionUpdated","OldSub":"{}","NewSub":"{}","Requester":"admin@contoso.com","DateCreated":"2026-07-24T17:10:00.000Z"}'
    assertions:
      - fieldsHaveValues:
          "#event.module": "ezcms"
          "event.action": "SubscriptionUpdated"
          "user.name": "admin@contoso.com"
          "user.email": "admin@contoso.com"
fieldsToBeRemovedBeforeParsing: []
$schema: https://schemas.humio.com/parser/v0.3.0
script: |-
  // #region PREPARSE
  /************************************************************
  ****** Parse timestamp and log headers
  ****** Extract message field for parsing
  ****** Parse structured data
  ************************************************************/

  // parseJson captures EVERY field EZCMS/EZID emits into Vendor.* automatically.
  parseJson(prefix="Vendor.", excludeEmpty=true, handleNull=discard)
  // EZCMS has several record types, each with its own timestamp field.
  | case {
      Vendor.DateTested = *
        | parseTimestamp(field=Vendor.DateTested);
      Vendor.DateCreated = *
        | parseTimestamp(field=Vendor.DateCreated);
      Vendor.RequestDate = *
        | parseTimestamp(field=Vendor.RequestDate);
      Vendor.DateRequested = *
        | parseTimestamp(field=Vendor.DateRequested);
      // Records like security_alerts carry no timestamp — fall back to ingest
      // time. Do NOT call findTimestamp() here; it errors when none is present.
      *;
  }

  // #endregion

  // #region METADATA
  /************************************************************
  ****** Static Metadata Definitions
  ************************************************************/

  | Parser.version := "1.1.0"
  | Cps.version := "1.2.0"
  | ecs.version := "9.3.0"
  | Vendor := "keytos"
  | event.module := "ezcms"
  | event.kind := "event"

  // #endregion

  // #region NORMALIZATION
  /************************************************************
  ****** Parse unstructured data (i.e. message field)
  ****** Normalize fields to data model
  ************************************************************/

  // Write EZCMS values straight through. The same ECS field is written from each
  // candidate source in turn; a missing source is a no-op, so whichever the record
  // populates wins (record types use different property names for the same concept).
  | event.id := Vendor.AlertCode
  | event.action := coalesce([Vendor.ActionPerformed, Vendor.Reason])
  | event.reason := coalesce([Vendor.AlertDescription, Vendor.Details])
  | message := event.reason
  | event.dataset := "ezcms"

  // CPS requires at least one event.category and event.type. EZCMS manages
  // credentials (smart cards / FIDO keys / accounts).
  | array:append(array="event.category[]", values=["iam"])
  | array:append(array="event.type[]", values=["info"])

  // Identity (record types use different property names for the user)
  | user.name := coalesce([Vendor.UserName, Vendor.UserRequested, Vendor.Requester])
  | case { user.name = /@/ | user.email := lower(user.name); *; }

  // Certificate / smart card
  | x509.serial_number := Vendor.CertificateSerialNumber
  | keytos.ca.name := Vendor.CAName
  | keytos.smartcard.serial := Vendor.SCSerialNumber

  // #endregion

  // #region POST-NORMALIZATION
  /************************************************************
  ****** Post Normalization
  ****** Custom parser logic needed after normalization
  ************************************************************/

  // #endregion
tagFields:
  - Vendor
  - Cps.version
  - ecs.version
  - event.dataset
  - event.kind
  - event.module
  - observer.type
