Skip to content

Commit

Permalink
fix: update swagger issues and update generation flow
Browse files Browse the repository at this point in the history
Fixes CrowdStrike#33
Adds block execute to definition. This should be finally fixed upstream
as well.

Fixes CrowdStrike#35
Updates endpoint response to be of type Object. This one is interesting
because it seems like it should actually be returning a binary object
but instead it returns either a CSV string or JSON object depending on
the type of scheduled report that was chosen. When this is updated
upstream we will treat this as a binary download such as downloading a
sensor.

This also fixes dealing with text/* returns such as csv. Now we just
return it as a string instead of failing.

The Makefile has been updated to make use of the transformation.jq file
to ensure our swagger spec file has fixes needed while we wait for
upstream support.
  • Loading branch information
carlosmmatos committed Nov 15, 2024
1 parent ce3d7fe commit 8587c5e
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 2 deletions.
222 changes: 222 additions & 0 deletions .openapi-generator/transformation.jq
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# Fix the file downloads: invalid swagger for file downloads
.definitions."domain.DownloadItem"."type"="string"
| .definitions."domain.DownloadItem"."format"="binary"
| .paths."/intel/entities/report-files/v1"."get"."responses"."200"."schema"={"$ref": "#/definitions/domain.DownloadItem"}
| .paths."/intel/entities/rules-latest-files/v1"."get"."responses"."200"."schema"={"$ref": "#/definitions/domain.DownloadItem"}
| .paths."/intel/entities/rules-files/v1"."get"."responses"."200"."schema"={"$ref": "#/definitions/domain.DownloadItem"}
| .paths."/real-time-response/entities/extracted-file-contents/v1"."get"."responses"."200"."schema"={"$ref": "#/definitions/domain.DownloadItem"}
# Fix overflow on json number (more than 63 bits are neede hold this field)
| .definitions."domain.APIEvaluationLogicItemV1".properties.id."x-go-type"={type: "Number", import: {package: "encoding/json"}, hints: {noValidation: true}}
# Rename msaspec.Error back to msa.APIError. These are two names for the same type.
| walk(
if type == "object" and has("$ref") and ."$ref" == "#/definitions/msaspec.Error" then ."$ref" = "#/definitions/msa.APIError" else . end
| if type == "object" and has("$ref") and ."$ref" == "#/definitions/msaspec.MetaInfo" then ."$ref" = "#/definitions/msa.MetaInfo" else . end
)
| del(.definitions."msaspec.Error")
# Rename msaspec.Paging to msa.Paging. These are two names for the same type.
| walk(
if type == "object" and has("$ref") and ."$ref" == "#/definitions/msaspec.Paging" then ."$ref" = "#/definitions/msa.Paging" else . end
)
| del(.definitions."msaspec.Paging")
| .definitions."domain.RuleMetaInfo".properties.pagination."$ref" = "#/definitions/msa.Paging"
| .definitions."domain.MsaMetaInfo".properties.pagination."$ref" = "#/definitions/msa.Paging"
# Rename msaspec.MetaInfo to msa.MetaInfo. These are two names for the same type.
| del(.definitions."msaspec.MetaInfo")

# Misc fixes
| .paths."/intel/entities/rules-latest-files/v1".get.parameters |= . + [{type: "string", description: "Download Only if changed since", name: "If-Modified-Since", "in": "header"}]
| .paths."/intel/entities/rules-latest-files/v1".get.responses."304" = {description: "Not Modified"}
| .paths."/oauth2/token".post.responses."201".headers["X-CS-Region"] = {type: "string"}
| .paths."/policy/queries/sensor-update-kernels/{distinct_field}/v1" = .paths."/policy/queries/sensor-update-kernels/{distinct-field}/v1"
| del(.paths."/policy/queries/sensor-update-kernels/{distinct-field}/v1")
| .paths."/policy/queries/sensor-update-kernels/{distinct_field}/v1".get.parameters[0].name = "distinct_field"

# IOA Rule Groups Combined API has incorrect swagger response object: list of ids instead of list of objects
| .paths."/ioarules/queries/rule-groups-full/v1".get.responses."200" = .paths."/ioarules/entities/rule-groups/v1".get.responses."200"
| .paths."/policy/entities/ioa-exclusions/v1".post.responses."201" = .paths."/policy/entities/ioa-exclusions/v1".post.responses."200"
| del(.paths."/policy/entities/ioa-exclusions/v1".post.responses."200")

# Add response code "202" to "/devices/entities/devices/tags/v1" endpoint
| .paths."/devices/entities/devices/tags/v1".patch.responses."202" = .paths."/devices/entities/devices/tags/v1".patch.responses."200"


# CGP should be Gcp
| .paths."/cloud-connect-gcp/entities/account/v1".get.operationId = "GetD4CGcpAccount"
| .paths."/cloud-connect-gcp/entities/account/v1".post.operationId = "CreateD4CGcpAccount"
| .paths."/cloud-connect-gcp/entities/user-scripts/v1".get.operationId = "GetD4CGcpUserScripts"

# Rename spotlight-vulnerabilities and spotlight-evaluation-logic collections back to vulnerabilities & vulnerabilities-evaluation-logic
# looks like spotlight is staying to reverting it again... keeping this code incase it can be used some other time.
# | walk(
# if type == "object" and .tags and (.tags | index("spotlight-vulnerabilities")) then
# .tags |= map(gsub("spotlight-vulnerabilities"; "vulnerabilities"))
# elif type == "object" and .tags and (.tags | index("spotlight-evaluation-logic")) then
# .tags |= map(gsub("spotlight-evaluation-logic"; "vulnerabilities-evaluation-logic"))
# else
# .
# end
# )

# Revert msaspec.QueryResponse back to msa.QueryResponse for falconcomplete-dashboard
| if .paths."/falcon-complete-dashboards/queries/alerts/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/alerts/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end
| if .paths."/falcon-complete-dashboards/queries/devicecount-collections/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/devicecount-collections/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end
| if .paths."/falcon-complete-dashboards/queries/allowlist/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/allowlist/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end
| if .paths."/falcon-complete-dashboards/queries/blocklist/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/blocklist/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end
| if .paths."/falcon-complete-dashboards/queries/detects/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/detects/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end
| if .paths."/falcon-complete-dashboards/queries/escalations/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/escalations/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end
| if .paths."/falcon-complete-dashboards/queries/incidents/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/incidents/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end
| if .paths."/falcon-complete-dashboards/queries/remediations/v1".get.responses."200".schema."$ref" = "#/definitions/msaspec.QueryResponse" then .paths."/falcon-complete-dashboards/queries/remediations/v1".get.responses."200".schema |= {"$ref": "#/definitions/msa.QueryResponse"} else . end

# Revert changes.GetChangesResponse back to public.GetChangesResponse for filevantage
| if .paths."/filevantage/entities/changes/v2".get.responses."200".schema."$ref" = "#/definitions/changes.GetChangesResponse" then
.paths."/filevantage/entities/changes/v2".get.responses."200".schema = {"$ref": "#/definitions/public.GetChangesResponse"}
|.definitions."public.GetChangesResponse" = .definitions."changes.GetChangesResponse"
|del(.definitions."changes.GetChangesResponse") else . end

# Make message-center use consistent return type
| if .paths."/message-center/aggregates/cases/GET/v1".post.responses."403".schema."$ref" = "#/definitions/msa.ReplyMetaOnly" then
.paths."/message-center/aggregates/cases/GET/v1".post.responses."403".schema = {"$ref": "#/definitions/msaspec.ResponseFields"}
else . end

# Custom Storage "custom-type" rename
| .definitions."CustomStorageObjectKeys" = .definitions."CustomType_1255839303"
| del(.definitions."CustomType_1255839303")
| if .paths."/customobjects/v1/collections/{collection_name}/objects".get.responses."200".schema."$ref" = "#/definitions/CustomType_1255839303" then
.paths."/customobjects/v1/collections/{collection_name}/objects".get.responses."200".schema = {"$ref": "#/definitions/CustomStorageObjectKeys"} else . end

| .definitions."CustomStorageResponse" = .definitions."CustomType_3191042536"
| del(.definitions."CustomType_3191042536")
| if .paths."/customobjects/v1/collections/{collection_name}/objects".post.responses."200".schema."$ref" = "#/definitions/CustomType_3191042536" then
.paths."/customobjects/v1/collections/{collection_name}/objects".post.responses."200".schema = {"$ref": "#/definitions/CustomStorageResponse"} else . end
| if .paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}".put.responses."200".schema."$ref" = "#/definitions/CustomType_3191042536" then
.paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}".put.responses."200".schema = {"$ref": "#/definitions/CustomStorageResponse"} else . end
| if .paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}".delete.responses."200".schema."$ref" = "#/definitions/CustomType_3191042536" then
.paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}".delete.responses."200".schema = {"$ref": "#/definitions/CustomStorageResponse"} else . end
| if .paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}/metadata".get.responses."200".schema."$ref" = "#/definitions/CustomType_3191042536" then
.paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}/metadata".get.responses."200".schema = {"$ref": "#/definitions/CustomStorageResponse"} else . end

# # Better operationId for workflows collection
# | .paths."/workflows/entities/execute/v1".post.operationId = "Execute"
# | .paths."/workflows/entities/execution-actions/v1".post.operationId = "ExecutionAction"
# | .paths."/workflows/entities/execution-results/v1".get.operationId = "ExecutionResults"
# | .paths."/workflows/system-definitions/deprovision/v1".post.operationId = "Deprovision"
# | .paths."/workflows/system-definitions/promote/v1".post.operationId = "Promote"
# | .paths."/workflows/system-definitions/provision/v1".post.operationId = "Provision"

# # Better operationId for foundry-logscale collection
# | .paths."/loggingapi/combined/repos/v1".get.operationId = "ListRepos"
# | .paths."/loggingapi/entities/data-ingestion/ingest/v1".post.operationId = "IngestData"
# | .paths."/loggingapi/entities/saved-searches/execute-dynamic/v1".post.operationId = "ExecuteDynamic"
# | .paths."/loggingapi/entities/saved-searches/execute/v1".get.operationId = "GetSearchResults"
# | .paths."/loggingapi/entities/saved-searches/execute/v1".post.operationId = "Execute"
# | .paths."/loggingapi/entities/saved-searches/ingest/v1".post.operationId = "Populate"
# | .paths."/loggingapi/entities/saved-searches/job-results-download/v1".get.operationId = "DownloadResults"
# | .paths."/loggingapi/entities/views/v1".get.operationId = "ListViews"

# # Better operationId for custom-storage collection
# | .paths."/customobjects/v1/collections/{collection_name}/objects".get.operationId = "list"
# | .paths."/customobjects/v1/collections/{collection_name}/objects".post.operationId = "search"
# | .paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}".get.operationId = "get"
# | .paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}".put.operationId = "upload"
# | .paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}".delete.operationId = "delete"
# | .paths."/customobjects/v1/collections/{collection_name}/objects/{object_key}/metadata".get.operationId = "metadata"

# # Better operationId for unidentified-containers collection
# | .paths."/container-security/aggregates/unidentified-containers/count-by-date/v1".get.operationId = "CountByDateRange"
# | .paths."/container-security/aggregates/unidentified-containers/count/v1".get.operationId = "Count"
# | .paths."/container-security/combined/unidentified-containers/v1".get.operationId = "Search"

# # Better operationId for snapshots-registration collection
# | .paths."/snapshots/entities/accounts/v1".post.operationId = "Register"

# # Better operationId for scheduled-reports collection
# | .paths."/reports/entities/scheduled-reports/execution/v1".post.operationId = "Execute"
# | .paths."/reports/entities/scheduled-reports/v1".get.operationId = "QueryById"
# | .paths."/reports/queries/scheduled-reports/v1".get.operationId = "Query"

# # Better operationId for alerts collection
# | .paths."/alerts/queries/alerts/v2".get.operationId = "QueryV2"
# | .paths."/alerts/entities/alerts/v3".patch.operationId = "UpdateV3"
# | .paths."/alerts/aggregates/alerts/v2".post.operationId = "GetAggregateV2"
# | .paths."/alerts/entities/alerts/v2".post.operationId = "GetV2"

# # Better operationId for kubernetes-protection collection
# | .paths."/container-security/combined/clusters/v1".get.operationId = "ClusterCombined"
# | .paths."/container-security/aggregates/clusters/count/v1".get.operationId = "ClusterCount"
# | .paths."/container-security/aggregates/enrichment/clusters/entities/v1".get.operationId = "ClusterEnrichment"
# | .paths."/container-security/aggregates/clusters/count-by-date/v1".get.operationId = "ClustersByDateRangeCount"
# | .paths."/container-security/aggregates/clusters/count-by-kubernetes-version/v1".get.operationId = "ClustersByKubernetesVersionCount"
# | .paths."/container-security/aggregates/clusters/count-by-status/v1".get.operationId = "ClustersByStatusCount"
# | .paths."/container-security/combined/containers/v1".get.operationId = "ContainerCombined"
# | .paths."/container-security/aggregates/containers/count/v1".get.operationId = "ContainerCount"
# | .paths."/container-security/aggregates/containers/count-by-registry/v1".get.operationId = "ContainerCountByRegistry"
# | .paths."/container-security/aggregates/enrichment/containers/entities/v1".get.operationId = "ContainerEnrichment"
# | .paths."/container-security/aggregates/containers/image-detections-count-by-date/v1".get.operationId = "ContainerImageDetectionsCountByDate"
# | .paths."/container-security/aggregates/images/most-used/v1".get.operationId = "ContainerImagesByMostUsed"
# | .paths."/container-security/aggregates/containers/images-by-state/v1".get.operationId = "ContainerImagesByState"
# | .paths."/container-security/aggregates/containers/vulnerability-count-by-severity/v1".get.operationId = "ContainerVulnerabilitiesBySeverityCount"
# | .paths."/container-security/aggregates/containers/count-by-date/v1".get.operationId = "ContainersByDateRangeCount"
# | .paths."/container-security/aggregates/containers/sensor-coverage/v1".get.operationId = "ContainersSensorCoverage"
# | .paths."/container-security/combined/deployments/v1".get.operationId = "DeploymentCombined"
# | .paths."/container-security/aggregates/deployments/count/v1".get.operationId = "DeploymentCount"
# | .paths."/container-security/aggregates/enrichment/deployments/entities/v1".get.operationId = "DeploymentEnrichment"
# | .paths."/container-security/aggregates/deployments/count-by-date/v1".get.operationId = "DeploymentsByDateRangeCount"
# | .paths."/container-security/aggregates/images/count-by-distinct/v1".get.operationId = "DistinctContainerImageCount"
# | .paths."/container-security/aggregates/kubernetes-ioms/count-by-date/v1".get.operationId = "KubernetesIomByDateRange"
# | .paths."/container-security/aggregates/kubernetes-ioms/count/v1".get.operationId = "KubernetesIomCount"
# | .paths."/container-security/entities/kubernetes-ioms/v1".get.operationId = "KubernetesIomEntities"
# | .paths."/container-security/combined/nodes/v1".get.operationId = "NodeCombined"
# | .paths."/container-security/aggregates/enrichment/nodes/entities/v1".get.operationId = "NodeEnrichment"
# | .paths."/container-security/aggregates/nodes/count-by-cloud/v1".get.operationId = "NodesByCloudCount"
# | .paths."/container-security/aggregates/nodes/count-by-container-engine-version/v1".get.operationId = "NodesByContainerEngineVersionCount"
# | .paths."/container-security/aggregates/nodes/count-by-date/v1".get.operationId = "NodesByDateRangeCount"
# | .paths."/container-security/combined/pods/v1".get.operationId = "PodCombined"
# | .paths."/container-security/aggregates/pods/count/v1".get.operationId = "PodCount"
# | .paths."/container-security/aggregates/enrichment/pods/entities/v1".get.operationId = "PodEnrichment"
# | .paths."/container-security/aggregates/pods/count-by-date/v1".get.operationId = "PodsByDateRangeCount"
# | .paths."/container-security/combined/container-images/v1".get.operationId = "RunningContainerImages"
# | .paths."/container-security/aggregates/containers/count-vulnerable-images/v1".get.operationId = "VulnerableContainerImageCount"
# | .paths."/container-security/combined/kubernetes-ioms/v1".get.operationId = "KubernetesIomEntitiesCombined"
# | .paths."/container-security/queries/kubernetes-ioms/v1".get.operationId = "QueryKubernetesIoms"

# Allow an empty string be passed to assignment_rule
| .definitions."host_groups.UpdateGroupReqV1".properties.assignment_rule += {"x-nullable": true}

# Allow expiration to be nullable
| .definitions."api.IndicatorCreateReqV1".properties.expiration += {"x-nullable": true}

# 202 is a valid response for /real-time-response/entities/scripts/v1 patch
| .paths."/real-time-response/entities/scripts/v1".patch.responses."202" = .paths."/real-time-response/entities/scripts/v1".patch.responses."200"
| del(.paths."/real-time-response/entities/scripts/v1".patch.responses."200")

# 200 is a valid response from /real-time-response/entities/queued-sessions/command/v1
| .paths."/real-time-response/entities/queued-sessions/command/v1".delete.responses."200" = .paths."/real-time-response/entities/put-files/v1".delete.responses."200"

# last_seen and first_seen should be a string
| .definitions."models.Container".properties.first_seen.type = "string"
| .definitions."models.Container".properties.last_seen.type = "string"

# device_control.USBClassExceptionsResponse and device_control.USBClassExceptionsReqV1 enum should have BLOCK_EXECUTE in it
| .definitions."device_control.USBClassExceptionsResponse".properties.action.enum += ["BLOCK_EXECUTE"]
| .definitions."device_control.USBClassExceptionsReqV1".properties.action.enum += ["BLOCK_EXECUTE"]

# apidomain.SavedSearchExecuteRequestV1 has parameters listed twice
| del(.definitions."apidomain.SavedSearchExecuteRequestV1".properties.parameters)

# add text/csv to produces for /reports/entities/report-executions-download/v1
| .paths."/reports/entities/report-executions-download/v1".get.produces += ["text/csv"]

# Update the summary and description for /reports/entities/report-executions-download/v1
| .paths."/reports/entities/report-executions-download/v1".get.summary = "Get report entity download. Returns either a JSON object or a CSV string."

# Update /reports/entities/report-executions-download/v1 to be of type Object for now. In the future it will get changed
# to a binary download, so we can treat it the same as sensor download. See below:
# JIRA: https://jira.cs.sys/browse/PLATFORMPG-787321
# | .paths."/reports/entities/report-executions-download/v1".get.responses."200".schema = {
# "$ref": "#/definitions/domain.DownloadItem"
# }
| .paths."/reports/entities/report-executions-download/v1".get.responses."200".schema = {
"type": "object",
"description": "The response can be either a JSON object or a string containing CSV data. When the response is JSON, it will be a structured object. When the response is CSV, it will be returned as a raw string."
}

Loading

0 comments on commit 8587c5e

Please sign in to comment.