Skip to content

Conversation

@codypierce
Copy link
Contributor

Proposed commit message

Changes made

  • Initial package boilerplate
  • Readme updated to spec
  • Changelog updated to spec
  • Event data stream for CEL processing from Neon events API
  • Field definitions for events
  • Pipeline processor definitions
  • Sample log included for supported events
  • Detections data stream for CEL processing from Neon detections API
  • Field definitions for detections
  • Sample log included for supported detections
  • Pipeline, policy, and system tests for new data streams

Checklist

  • I have reviewed tips for building integrations and this pull request is aligned with them.
  • I have verified that all data streams collect metrics or logs.
  • I have added an entry to my package's changelog.yml file.
  • I have verified that Kibana version constraints are current according to guidelines.
  • I have verified that any added dashboard complies with Kibana's Dashboard good practices

Author's Checklist

  • [ ]

How to test this PR locally

  1. Run pipeline and system tests

Related issues

  • N/A

Screenshots

@codypierce codypierce requested a review from a team as a code owner October 22, 2025 18:09
@cla-checker-service
Copy link

cla-checker-service bot commented Oct 22, 2025

💚 CLA has been signed

@andrewkroh andrewkroh added documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. New Integration Issue or pull request for creating a new integration package. labels Oct 22, 2025
@narph narph added the Team:Security-Service Integrations Security Service Integrations team [elastic/security-service-integrations] label Oct 27, 2025
@elasticmachine
Copy link

Pinging @elastic/security-service-integrations (Team:Security-Service Integrations)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to add this file, it will be automatically generated.

@@ -0,0 +1,45 @@
{{- generatedHeader }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was in the template and adds the auto generation "Do not edit" in the rendered README.md

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need it.

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.github/CODEOWNERS needs to be updated to include this package with the owner matching the owner in the package's manifest.

Comment on lines 58 to 69
- date:
field: json.detection_timestamp
target_field: neon_cyber.detections.detection_timestamp
tag: date_detection_timestamp
formats:
- ISO8601
- date:
field: json.detection_timestamp
target_field: '@timestamp'
tag: date_timestamp
formats:
- ISO8601
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these we might want to have on_failure handlers? How much can we tolerate the timestamps being absent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added on_failure just in case but these are guaranteed to be there

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should be clearer (your interpretation is entirely reasonable, but not what I meant).

Let's say there is an invalid timestamp so the parse fails, is the document now useless (because it will not have the timestamp) or is it something that we can salvage some information from? If it's the former, we don't need to have an on_failure handler since we can just say definitively that the document had an error and we didn't try to do any further work, but if it's the latter, we can say, "there was a problem with this field, but I kept going and be aware of the error when you look at the document".

It's no doubt unlikely that there would be a corrupted timestamp, but we live in the real world, so it is always possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latter would be correct. Especially for detections we wouldn't want to ignore any information. In that case is the on_failure I added correct, or is there another suggestion?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, if that's the semantics, what you've done is right.

I would say though that the json processor failure would be unrecoverable; if that fails there's no point in doing any further work since we can guarantee that the fields will be absent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, committed a change to remove json on_failure

Comment on lines 52 to 59
- name: enable_request_tracer
type: bool
title: Enable request tracing
default: false
multi: false
required: false
show_user: false
description: The request tracer logs requests and responses to the agent's local file-system for debugging configurations. Enabling this request tracing compromises security and should only be used for debugging. See [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-cel.html#_resource_tracer_filename) for details.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be at the data stream level.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to DS

i.e. scheme://host:port/path
required: true
show_user: true
default: https://api.neoncyber.io/v1/detections
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of user ergonomics, is it the case that the detections endpoint is always at /v1/detections? (similarly for events at /v1/events). If it is, I would suggest hard coding the endpoint path into the CEL program and making the base URL a package level configuration next to the API token field. This would mean that that user only needs to configure it once (assuming that the base URL is almost constant for a user).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved URL to package and hardcoded and removed resource_url

),
},
},
"want_more": false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any time I remove this "want_more" line "elastic_package test system" fails to get a hit on the mock server and I don't know why.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I will take a look and see what is going on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your help! I couldn't figure out how that works since on paper it seem extraneous but the system test definitely prevents it from completing if that single line is removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is identified in the error message that unfortunately only gets shown in the logs

{
    "log.level": "error",
    "@timestamp": "2025-11-05T22:21:36.369Z",
    "log.origin": {
        "function": "github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator.logComponentStateChange",
        "file.name": "coordinator/coordinator.go",
        "file.line": 800
    },
    "message": "Unit state changed cel-default-cel-neon_cyber-50f5cedf-f9ea-481d-af64-5a5da5a63c74 (STARTING->FAILED): failed to check program: failed compilation: ERROR: <input>:12:54: found no matching overload for '_?_:_' applied to '(bool, map(string, list(map(string, string))), map(string, map(string, map(string, string))))'\n |     ).do_request().as(resp, (resp.StatusCode == 200) ?\n | .....................................................^ accessing config",
    "log": {
        "source": "elastic-agent"
    },
    "component": {
        "id": "cel-default",
        "state": "HEALTHY"
    },
    "unit": {
        "id": "cel-default-cel-neon_cyber-50f5cedf-f9ea-481d-af64-5a5da5a63c74",
        "type": "input",
        "state": "FAILED",
        "old_state": "STARTING"
    },
    "ecs.version": "1.6.0"
}

extracting the relevant part

found no matching overload for '_?_:_' applied to '(bool, map(string, list(map(string, string))), map(string, map(string, map(string, string))))'
 |     ).do_request().as(resp, (resp.StatusCode == 200) ?
 | .....................................................^ accessing config

What this is saying is that the ternary has branches that are not the same type, map(string, list(map(string, string))) and map(string, map(string, map(string, string))). They need to agree, and we can achieve this by making them both dyn types so that the compile-time types are the same even though the runtime types are not identical.

This is done by (with some other cosmetic changes):

diff --git a/packages/neon_cyber/data_stream/detections/agent/stream/cel.yml.hbs b/packages/neon_cyber/data_stream/detections/agent/stream/cel.yml.hbs
index ea6c2524d4..efa226430e 100644
--- a/packages/neon_cyber/data_stream/detections/agent/stream/cel.yml.hbs
+++ b/packages/neon_cyber/data_stream/detections/agent/stream/cel.yml.hbs
@@ -34,31 +34,34 @@ program: |-
       ).do_request().as(resp, (resp.StatusCode == 200) ?
         resp.Body.decode_json().as(body,
           {
-            "events": (has(body.data) && body.data.size() > 0) ?
-              body.data.map(e,
-                {
-                  "message": e.encode_json(),
-                }
-              )
-            :
-              []
+            "events": dyn(
+              (has(body.data) && body.data.size() > 0) ?
+                body.data.map(e,
+                  {
+                    "message": e.encode_json(),
+                  }
+                )
+              :
+                []
+            )
           }
         )
       :
         {
-          "events": {
-            "error": {
-              "code": string(resp.StatusCode),
-              "id": string(resp.Status),
-              "message": "GET " + state.url + (
-                (size(resp.Body) != 0) ?
-                  string(resp.Body)
-                :
-                  string(resp.Status) + " (" + string(resp.StatusCode) + ")"
-              ),
-            },
-          },
-          "want_more": false,
+          "events": dyn(
+            {
+              "error": {
+                "code": string(resp.StatusCode),
+                "id": string(resp.Status),
+                "message": "GET " + state.url.trim_right("/") + "/v1/detections: " + (
+                  (size(resp.Body) != 0) ?
+                    string(resp.Body)
+                  :
+                    string(resp.Status) + " (" + string(resp.StatusCode) + ")"
+                ),
+              },
+            }
+          )
         }
       )
     )
diff --git a/packages/neon_cyber/data_stream/events/agent/stream/cel.yml.hbs b/packages/neon_cyber/data_stream/events/agent/stream/cel.yml.hbs
index 40341dcf6b..54cb70b0ac 100644
--- a/packages/neon_cyber/data_stream/events/agent/stream/cel.yml.hbs
+++ b/packages/neon_cyber/data_stream/events/agent/stream/cel.yml.hbs
@@ -34,31 +34,34 @@ program: |-
       ).do_request().as(resp, (resp.StatusCode == 200) ?
         resp.Body.decode_json().as(body,
           {
-            "events": (has(body.data) && body.data.size() > 0) ?
-              body.data.map(e,
-                {
-                  "message": e.encode_json(),
-                }
-              )
-            :
-              []
+            "events": dyn(
+              (has(body.data) && body.data.size() > 0) ?
+                body.data.map(e,
+                  {
+                    "message": e.encode_json(),
+                  }
+                )
+              :
+                []
+            )
           }
         )
       :
         {
-          "events": {
-            "error": {
-              "code": string(resp.StatusCode),
-              "id": string(resp.Status),
-              "message": "GET " + state.url + (
-                (size(resp.Body) != 0) ?
-                  string(resp.Body)
-                :
-                  string(resp.Status) + " (" + string(resp.StatusCode) + ")"
-              ),
-            },
-          },
-          "want_more": false,
+          "events": dyn(
+            {
+              "error": {
+                "code": string(resp.StatusCode),
+                "id": string(resp.Status),
+                "message": "GET " + state.url.trim_right("/") + "/v1/events: " + (
+                  (size(resp.Body) != 0) ?
+                    string(resp.Body)
+                  :
+                    string(resp.Status) + " (" + string(resp.StatusCode) + ")"
+                ),
+              },
+            }
+          )
         }
       )
     )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated to your suggestion and system tests are passing now.

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor changes only, otherwise looking good.

service: neon_cyber
vars:
api_token: xxxx
enable_request_tracer: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be moved to match the new manifest layout. Same in the other data stream.

enable_request_tracer: true
data_stream:
vars:
resource_url: http://{{Hostname}}:{{Port}}/v1/detections
Copy link
Contributor

@efd6 efd6 Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be moved to the package level and changed to url (at the moment the test is reaching out to api.neoncyber.io and failing (correctly) with a 401).

{
    "log.level": "debug",
    "@timestamp": "2025-11-05T22:40:13.138Z",
    "message": "HTTP request",
    "transaction.id": "S6QK57BN7LQHG-1",
    "url.original": "https://api.neoncyber.io/v1/detections",
    "url.scheme": "https",
    "url.path": "/v1/detections",
    "url.domain": "api.neoncyber.io",
    "url.port": "",
    "url.query": "",
    "http.request.method": "GET",
    "http.request.header": {
        "Authorization": [
            "xxxx"
        ],
        "User-Agent": [
            "Elastic-Filebeat/8.19.3 (linux; amd64; 7a508104ea6b6ddae7456dff8b61423e59a73962; 2025-08-26 01:12:39 +0000 UTC)"
        ]
    },
    "user_agent.original": "Elastic-Filebeat/8.19.3 (linux; amd64; 7a508104ea6b6ddae7456dff8b61423e59a73962; 2025-08-26 01:12:39 +0000 UTC)",
    "http.request.body.content": "",
    "http.request.body.truncated": false,
    "http.request.body.bytes": 0,
    "http.request.mime_type": "",
    "ecs.version": "1.6.0"
}
{
    "log.level": "debug",
    "@timestamp": "2025-11-05T22:40:14.281Z",
    "message": "HTTP response",
    "transaction.id": "S6QK57BN7LQHG-1",
    "http.response.status_code": 401,
    "http.response.body.content": "{\"error\":{\"code\":401,\"message\":\"Unauthorized\"},\"data\":null}",
    "http.response.body.truncated": false,
    "http.response.body.bytes": 59,
    "http.response.mime_type": "application/json; charset=utf-8",
    "http.response.header": {
        "Apigw-Requestid": [
            "Tl3MPgXKIAMEJbw="
        ],
        "Content-Length": [
            "59"
        ],
        "Content-Type": [
            "application/json; charset=utf-8"
        ],
        "Date": [
            "Wed, 05 Nov 2025 22:40:14 GMT"
        ],
        "Etag": [
            "W/\"3b-B6EKjFN6TdKYf7gDSWml69abDvE\""
        ],
        "Server": [
            "envoy"
        ],
        "X-Envoy-Upstream-Service-Time": [
            "159"
        ]
    },
    "ecs.version": "1.6.0"
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. My bad.

@efd6
Copy link
Contributor

efd6 commented Nov 5, 2025

/test

@efd6
Copy link
Contributor

efd6 commented Nov 5, 2025

/test

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to run

elastic-package test system -g
elastic-package test pipeline -g
elastic-package test policy -g
elastic-package build

using a v8.18.0 stack (the version that is the minimum specified in the manifest)

field: json.url
target_field: neon_cyber.events.url
ignore_missing: true
tag: rename_events_url
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    - uri_parts:
          field: neon_cyber.events.url
          ignore_missing: true
          tag: uri_parts_events_url

field: json.url
target_field: neon_cyber.detections.url
ignore_missing: true
tag: rename_detection_url
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    - uri_parts:
          field: neon_cyber.detections.url
          ignore_missing: true
          tag: uri_parts_detections_url

@codypierce
Copy link
Contributor Author

codypierce commented Nov 6, 2025

Switched manifest version to match testing config version 8.17.0. Regenerated expected and samples

@efd6
Copy link
Contributor

efd6 commented Nov 6, 2025

/test

@elastic-vault-github-plugin-prod

🚀 Benchmarks report

To see the full report comment with /test benchmark fullreport

@elasticmachine
Copy link

💚 Build Succeeded

History

@efd6
Copy link
Contributor

efd6 commented Nov 6, 2025

Quick query: where were the test sample obtained from? Live API with modification? Constructed from documentation?

@codypierce
Copy link
Contributor Author

Quick query: where were the test sample obtained from? Live API with modification? Constructed from documentation?

Live API for a test account and modifications

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@efd6 efd6 merged commit 1ca737d into elastic:main Nov 6, 2025
7 checks passed
@elastic-vault-github-plugin-prod

Package neon_cyber - 0.1.0 containing this change is available at https://epr.elastic.co/package/neon_cyber/0.1.0/

@andrewkroh andrewkroh added the Integration:neon_cyber Neon Cyber (Partner supported) label Nov 6, 2025
tehbooom pushed a commit to tehbooom/integrations that referenced this pull request Nov 19, 2025
…atform (elastic#15725)

The initial release includes detections and events data streams.

Neon Cyber fields are mapped to their corresponding ECS fields where possible.

Test samples were derived from a live API using a test account.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. Integration:neon_cyber Neon Cyber (Partner supported) New Integration Issue or pull request for creating a new integration package. Team:Security-Service Integrations Security Service Integrations team [elastic/security-service-integrations]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants