crowdstrike

package
v1.86.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: BSD-3-Clause Imports: 24 Imported by: 0

README

CrowdStrike Adapter

Entity External IDs

The CrowdStrike adapter supports two API mechanisms: GraphQL (Identity Protection) and REST (Endpoint Protection). The Entity External ID determines which mechanism and endpoint is used.

GraphQL Entities (Identity Protection)

These entities query the CrowdStrike Identity Protection GraphQL API:

{baseURL}/identity-protection/combined/graphql/{apiVersion}
Entity External ID Unique ID Attribute Order By Description
user entityId RISK_SCORE Identity Protection users
incident incidentId END_TIME Identity Protection incidents
endpoint entityId RISK_SCORE Identity Protection endpoints
How GraphQL Queries Work

Each entity external ID has a dedicated QueryBuilder that generates a specific GraphQL query. The adapter uses the machinebox/graphql client to POST queries to the Identity Protection endpoint.

Query dispatch (GetQueryBuilder in query.go):

  • user -> UserQueryBuilder
  • incident -> IncidentQueryBuilder
  • endpoint -> EndpointQueryBuilder
User Query

Queries the entities field with types: [USER]:

{
  entities(
    archived: false
    enabled: true
    types: [USER]
    sortKey: RISK_SCORE
    sortOrder: DESCENDING
    first: 100
    after: "cursor..."
  ) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      ... on UserEntity {
        entityId
        primaryDisplayName
        emailAddresses
        riskScore
        riskScoreSeverity
        mostRecentActivity
        accounts {
          ... on ActiveDirectoryAccountDescriptor {
            domain
            samAccountName
            upn
            enabled
            ...
          }
        }
        riskFactors {
          score
          severity
          type
        }
      }
    }
  }
}

Key fields returned: entityId, primaryDisplayName, emailAddresses, riskScore, riskScoreSeverity, impactScore, mostRecentActivity, inactive, stale, watched, shared, nested accounts (Active Directory details), and riskFactors.

Incident Query

Queries the incidents field:

{
  incidents(
    first: 100
    sortKey: END_TIME
    sortOrder: DESCENDING
    after: "cursor..."
  ) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      incidentId
      severity
      type
      startTime
      endTime
      lifeCycleStage
      compromisedEntities { entityId, primaryDisplayName, riskScore, ... }
      alertEvents { alertId, alertType, eventSeverity, entities {...}, ... }
    }
  }
}

Key fields returned: incidentId, severity, type, startTime, endTime, lifeCycleStage, nested compromisedEntities (entities involved), and alertEvents (alerts with their own nested entities).

Endpoint Query

Queries the entities field with types: [ENDPOINT]:

{
  entities(
    archived: false
    enabled: true
    types: [ENDPOINT]
    sortKey: RISK_SCORE
    sortOrder: DESCENDING
    first: 100
    after: "cursor..."
  ) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      ... on EndpointEntity {
        entityId
        hostName
        lastIpAddress
        riskScore
        ztaScore
        agentId
        accounts {
          ... on ActiveDirectoryAccountDescriptor { ... }
        }
        riskFactors { score, severity, type }
      }
    }
  }
}

Key fields returned: entityId, hostName, lastIpAddress, riskScore, ztaScore, agentId, agentVersion, cid, unmanaged, staticIpAddresses, nested accounts and riskFactors.

GraphQL Pagination

All GraphQL entities use cursor-based pagination via the pageInfo object:

  • hasNextPage - indicates whether more results exist
  • endCursor - opaque cursor string passed as the after parameter in the next request

The cursor is base64-encoded and stored as a CompositeCursor between pages.

GraphQL Response Parsing

The response is parsed based on the entity external ID:

  • user and endpoint -> reads from data.entities.nodes
  • incident -> reads from data.incidents.nodes
Configuration Parameters

GraphQL entities support these config options:

  • archived (bool) - include archived entities (applies to user and endpoint)
  • enabled (bool) - filter to enabled entities only (applies to user and endpoint)
  • apiVersion - API version for the endpoint URL path
REST Entities (Endpoint Protection)

These entities query CrowdStrike REST APIs:

Entity External ID List Endpoint Get Endpoint Description
endpoint_protection_device GET devices/queries/devices-scroll/v1 POST devices/entities/devices/v2 Falcon endpoint devices
endpoint_protection_incident GET incidents/queries/incidents/v1 POST incidents/entities/incidents/GET/v1 Falcon incidents
endpoint_protection_alert (combined API) POST alerts/combined/alerts/v1 Falcon alerts

How REST Entities Work

  • endpoint_protection_device and endpoint_protection_incident use a two-step pattern: first list resource IDs (GET), then fetch full objects by ID (POST with {"ids": [...]}).
  • endpoint_protection_alert uses a single combined API with pagination via after cursor in the POST body.

Adding New Entity External IDs

Requires code changes. Both GraphQL and REST entities are hardcoded with specific query builders and endpoint mappings.

Adding a GraphQL entity:
  1. Add a constant in datasource.go (e.g., Group string = "group").
  2. Add the entity to ValidGraphQLEntityExternalIDs in datasource.go with its unique ID attribute and order-by field.
  3. Create a new QueryBuilder struct and Build method in query.go that generates the GraphQL query string.
  4. Add a case in GetQueryBuilder to dispatch to the new builder.
  5. If the response field differs from entities, update parseGraphQLResponse in datasource_graphql.go.
  6. Add tests.
Adding a REST entity:
  1. Add a constant in datasource.go.
  2. Add the entity to ValidRESTEntityExternalIDs in datasource.go specifying cursor type and whether it uses the list-then-get pattern.
  3. Add an EndpointInfo entry in EntityExternalIDToEndpoint in endpoint.go with the list and/or get endpoint paths.
  4. If the entity needs special request body handling (like alerts), add logic in datasource_rest.go.
  5. Add tests.

Documentation

Overview

TODO: The contents of this file are unused at the moment. Do not remove. We need it for future improvements.

Index

Constants

View Source
const (
	User             string = "user"
	Incident         string = "incident"
	Endpoint         string = "endpoint"
	Device           string = "endpoint_protection_device"
	EndpointIncident string = "endpoint_protection_incident"
	Alerts           string = "endpoint_protection_alert"
)
View Source
const (
	// MaxPageSize is the maximum page size allowed in a GetPage request.
	MaxPageSize = 1000
)

Variables

View Source
var (
	// ValidGraphQLEntityExternalIDs is a map of valid external IDs of entities that can be queried.
	// The map value is the Entity struct which contains the unique ID attribute.
	ValidGraphQLEntityExternalIDs = map[string]Entity{
		User: {
			UniqueIDAttrExternalID: "entityId",
			OrderByAttribute:       "RISK_SCORE",
		},
		Incident: {
			UniqueIDAttrExternalID: "incidentId",
			OrderByAttribute:       "END_TIME",
		},
		Endpoint: {
			UniqueIDAttrExternalID: "entityId",
			OrderByAttribute:       "RISK_SCORE",
		},
	}

	ValidRESTEntityExternalIDs = map[string]Entity{
		Device:           {},
		EndpointIncident: {UseIntCursor: true},
		Alerts:           {},
	}
)
View Source
var (
	EntityExternalIDToEndpoint = map[string]EndpointInfo{
		Device: {
			ListEndpoint: "devices/queries/devices-scroll/v1",
			GetEndpoint:  "devices/entities/devices/v2",
		},
		EndpointIncident: {
			ListEndpoint: "incidents/queries/incidents/v1",
			GetEndpoint:  "incidents/entities/incidents/GET/v1",
		},

		Alerts: {
			GetEndpoint: "alerts/combined/alerts/v1",
		},
	}
)
View Source
var (
	SupportedAPIVersions = map[string]struct{}{
		"v1": {},
	}
)

Functions

func ConstructRESTEndpoint

func ConstructRESTEndpoint(request *Request, path string) (*string, *framework.Error)

func GetAttributePath

func GetAttributePath(input string) []string

func GetPageInfoAfter

func GetPageInfoAfter(pageInfo *PageInfo, n int) *string

This returns the PageInfo struct of the n deep layer, where 0 is the outermost layer. If n > number of layers or the pageInfo is nil, the function returns nil. If n < 0, the function returns the PageInfo of the outermost layer.

func NewAdapter

func NewAdapter(client Client) framework.Adapter[Config]

NewAdapter instantiates a new Adapter.

func ParseError

func ParseError(errors []ErrorItem) *framework.Error

func SetAfterParameter

func SetAfterParameter(value *string) string

Types

type Adapter

type Adapter struct {
	// Client provides access to the datasource.
	Client Client
}

Adapter implements the framework.Adapter interface to query pages of objects from datasources.

func (*Adapter) GetPage

func (a *Adapter) GetPage(ctx context.Context, request *framework.Request[Config]) framework.Response

GetPage is called by SGNL's ingestion service to query a page of objects from a datasource.

func (*Adapter) RequestPageFromDatasource

func (a *Adapter) RequestPageFromDatasource(
	ctx context.Context, request *framework.Request[Config],
) framework.Response

RequestPageFromDatasource requests a page of objects from a datasource.

func (*Adapter) ValidateGetPageRequest

func (a *Adapter) ValidateGetPageRequest(ctx context.Context, request *framework.Request[Config]) *framework.Error

ValidateGetPageRequest validates the fields of the GetPage Request.

type AlertsMeta added in v1.50.0

type AlertsMeta struct {
	Pagination AlertsPagination `json:"pagination"`
}

type AlertsPagination added in v1.50.0

type AlertsPagination struct {
	After string `json:"after"`
	Total int    `json:"total"`
}

type AlertsRequestBody added in v1.50.0

type AlertsRequestBody struct {
	Limit  int     `json:"limit"`
	After  *string `json:"after,omitempty"`
	Filter *string `json:"filter,omitempty"`
	Sort   *string `json:"sort,omitempty"`
}

type AlertsResponse added in v1.50.0

type AlertsResponse struct {
	Meta      AlertsMeta       `json:"meta"`
	Resources []map[string]any `json:"resources"`
	Errors    []ErrorItem      `json:"errors"`
}

type AttributeNode

type AttributeNode struct {
	Name         string
	Children     map[string]*AttributeNode
	IsFragment   bool
	FragmentType *FragmentType
}

AttributeNode stores the metadata required to build the inner part of the query for an entity.

func AttributeQueryBuilder

func AttributeQueryBuilder(
	entityConfig *framework.EntityConfig,
	rootName string,
) (*AttributeNode, *framework.Error)

func (*AttributeNode) AddChild

func (node *AttributeNode) AddChild(path []string, isFragment bool, fragmentType *FragmentType) *AttributeNode

AddChild adds a child to the current node and returns the child node.

func (*AttributeNode) BuildQuery

func (node *AttributeNode) BuildQuery() string

type Client

type Client interface {
	// GetPage returns a page of JSON objects from the datasource for the
	// requested entity.
	// Returns a (possibly empty) list of JSON objects, each object being
	// unmarshaled into a map by Golang's JSON unmarshaler.
	GetPage(ctx context.Context, request *Request) (*Response, *framework.Error)
}

Client is a client that allows querying the datasource which contains JSON objects.

func NewClient

func NewClient(client *http.Client) Client

NewClient returns a Client to query the datasource.

type Config

type Config struct {
	// Common configuration
	*config.CommonConfig

	APIVersion string            `json:"apiVersion,omitempty"`
	Archived   bool              `json:"archived,omitempty"`
	Enabled    bool              `json:"enabled,omitempty"`
	Filters    map[string]string `json:"filters,omitempty"`
}

Example Config:

{
   "apiVersion": "v1",
   "archived": false,
   "enabled": true,
   "filters": {
       "endpoint_protection_device": "platform:'Windows'"
   }
}

Config is the optional configuration passed in each GetPage calls to the adapter.

func (*Config) Validate

func (c *Config) Validate(_ context.Context) error

Validate ensures that a Config received in a GetPage call is valid.

type Datasource

type Datasource struct {
	Client *http.Client
}

Datasource directly implements a Client interface to allow querying an external datasource.

func (*Datasource) GetPage

func (d *Datasource) GetPage(ctx context.Context, request *Request) (*Response, *framework.Error)

type DatasourceResponse

type DatasourceResponse struct {
	Entities  ResponseItems `json:"entities"`
	Incidents ResponseItems `json:"incidents"`
}

type DetailedResourceRequestBody

type DetailedResourceRequestBody struct {
	Identifiers []string `json:"ids"`
}

type DetailedResourceResponse

type DetailedResourceResponse struct {
	Meta      MetaFields       `json:"meta"`
	Resources []map[string]any `json:"resources"`
	Errors    []ErrorItem      `json:"errors"`
}

type EndpointInfo

type EndpointInfo struct {
	ListEndpoint string
	GetEndpoint  string
}

The REST APIs of CrowdStrike are two level. A list endpoint is used to list entity IDs. A get endpoint is used to get detailed metadata of a specific entity. If a REST API has only one endpoint, it is considered as a get endpoint.

type EndpointQueryBuilder

type EndpointQueryBuilder struct {
	Archived bool
	Enabled  bool
	PageSize int64
	First    int64
}

func (*EndpointQueryBuilder) Build

func (b *EndpointQueryBuilder) Build(request *Request) (string, *framework.Error)

type Entity

type Entity struct {
	// UniqueIDAttrExternalID is the external ID of the entity's uniqueId attribute.
	UniqueIDAttrExternalID string

	// OrderByAttribute is the attribute to order the results by.
	OrderByAttribute string

	// UseIntCursor
	UseIntCursor bool
}

Entity contains entity specific information, such as the entity's unique ID attribute.

type ErrorItem

type ErrorItem struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type FragmentType

type FragmentType struct {
	Parent     string
	Name       string
	Attributes []string
}

type IncidentQueryBuilder

type IncidentQueryBuilder struct {
	First    int64
	PageSize int64
}

func (*IncidentQueryBuilder) Build

func (b *IncidentQueryBuilder) Build(request *Request) (string, *framework.Error)

type ListResourceResponse

type ListResourceResponse struct {
	Meta      MetaFields  `json:"meta"`
	Resources []string    `json:"resources"`
	Errors    []ErrorItem `json:"errors"`
}

type ListScrollResourceResponse

type ListScrollResourceResponse struct {
	Meta      ScrollMetaFields `json:"meta"`
	Resources []string         `json:"resources"`
	Errors    []ErrorItem      `json:"errors"`
}

type MetaFields

type MetaFields struct {
	PaginationInfo PaginationInfo `json:"pagination"`
}

type PageInfo

type PageInfo struct {
	HasNextPage bool   `json:"hasNextPage"`
	EndCursor   string `json:"endCursor"`
	// InnerPageInfo represents the paging details for the next nested level. (1 layer deeper)
	InnerPageInfo *PageInfo
}

func DecodePageInfo

func DecodePageInfo(cursor *string) (*PageInfo, *framework.Error)

type PaginationInfo

type PaginationInfo struct {
	Offset int `json:"offset"`
	Limit  int `json:"limit"`
	Total  int `json:"total"`
}

type QueryBuilder

type QueryBuilder interface {
	Build(*Request) (string, *framework.Error)
}

QueryBuilder is an interface that defines the method for building a query. Each entity has its own builder struct that contains the query parameters required to retrieve the entity.

func GetQueryBuilder

func GetQueryBuilder(request *Request, _ *PageInfo) (QueryBuilder, *framework.Error)

type Request

type Request struct {
	// BaseURL is the Base URL of the datasource to query.
	BaseURL string

	// Token is the Authorization token to use to authentication with the datasource.
	Token string

	// PageSize is the maximum number of objects to return from the entity.
	PageSize int64

	// EntityExternalID is the external ID of the entity.
	// The external ID should match the API's resource name.
	EntityExternalID string

	// A Falcon Query Language filter applicable for REST API based entities.
	// See more at https://falconpy.io/Usage/Falcon-Query-Language.html#operators
	Filter *string

	// GraphQLCursor identifies the first object of the page to return, as returned by
	// the last request for the entity. This field is used to paginate entities from GraphQL APIs.
	// Optional. If not set, return the first page for this entity.
	GraphQLCursor *pagination.CompositeCursor[string]

	// RESTCursor identifies the first object of the page to return, as returned by
	// the last request for the entity. This field is used to paginate entities from REST APIs.
	// Optional. If not set, return the first page for this entity.
	RESTCursor *pagination.CompositeCursor[string]

	// EntityConfig contains entity metadata and a list of attributes to request along with the current request.
	EntityConfig *framework.EntityConfig

	// Ordered is a boolean that indicates whether the results should be ordered.
	Ordered bool

	Config *Config

	// RequestTimeoutSeconds is the timeout duration for requests made to datasources.
	// This should be set to the number of seconds to wait before timing out.
	RequestTimeoutSeconds int
}

Request is a request to the datasource.

type Response

type Response struct {
	// StatusCode is an HTTP status code.
	StatusCode int

	// RetryAfterHeader is the Retry-After response HTTP header, if set.
	RetryAfterHeader string

	// Objects is the list of parsed entity objects returned from the datasource.	// May be empty.
	Objects []map[string]any

	// NextGraphQLCursor is the cursor that identifies the first object of the next
	// page for GraphQL APIs.
	// May be empty.
	NextGraphQLCursor *pagination.CompositeCursor[string]

	// NextRESTCursor is the cursor that identifies the first object of the next
	// page for REST APIs.
	// May be empty.
	NextRESTCursor *pagination.CompositeCursor[string]
}

type ResponseItems

type ResponseItems struct {
	Nodes    []map[string]interface{} `json:"nodes"`
	PageInfo PageInfo                 `json:"pageInfo"`
}

type ScrollMetaFields

type ScrollMetaFields struct {
	PaginationInfo ScrollPaginationInfo `json:"pagination"`
}

type ScrollPaginationInfo

type ScrollPaginationInfo struct {
	Offset string `json:"offset"`
	Limit  int    `json:"limit"`
	Total  int    `json:"total"`
}

"devices/queries/devices-scroll/v1" endpoint has a string offset.

type UserQueryBuilder

type UserQueryBuilder struct {
	Archived bool
	Enabled  bool
	PageSize int64
	First    int64
}

func (*UserQueryBuilder) Build

func (b *UserQueryBuilder) Build(request *Request) (string, *framework.Error)

Directories

Path Synopsis
dev

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL