n8n-nodes-fortisiem 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/credentials/FortiSiemApi.credentials.d.ts +10 -0
- package/dist/credentials/FortiSiemApi.credentials.d.ts.map +1 -0
- package/dist/credentials/FortiSiemApi.credentials.js +67 -0
- package/dist/credentials/FortiSiemApi.credentials.js.map +1 -0
- package/dist/icons/fortisiem.png +0 -0
- package/dist/nodes/FortiSiem/FortiSiem.node.d.ts +6 -0
- package/dist/nodes/FortiSiem/FortiSiem.node.d.ts.map +1 -0
- package/dist/nodes/FortiSiem/FortiSiem.node.js +284 -0
- package/dist/nodes/FortiSiem/FortiSiem.node.js.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/EventDescription.d.ts +4 -0
- package/dist/nodes/FortiSiem/descriptions/EventDescription.d.ts.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/EventDescription.js +68 -0
- package/dist/nodes/FortiSiem/descriptions/EventDescription.js.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/IncidentDescription.d.ts +4 -0
- package/dist/nodes/FortiSiem/descriptions/IncidentDescription.d.ts.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/IncidentDescription.js +275 -0
- package/dist/nodes/FortiSiem/descriptions/IncidentDescription.js.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/OrganizationDescription.d.ts +4 -0
- package/dist/nodes/FortiSiem/descriptions/OrganizationDescription.d.ts.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/OrganizationDescription.js +60 -0
- package/dist/nodes/FortiSiem/descriptions/OrganizationDescription.js.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/WatchlistDescription.d.ts +4 -0
- package/dist/nodes/FortiSiem/descriptions/WatchlistDescription.d.ts.map +1 -0
- package/dist/nodes/FortiSiem/descriptions/WatchlistDescription.js +106 -0
- package/dist/nodes/FortiSiem/descriptions/WatchlistDescription.js.map +1 -0
- package/icons/fortisiem.png +0 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kboykov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# n8n-nodes-fortisiem
|
|
2
|
+
|
|
3
|
+
An [n8n](https://n8n.io/) community node for [FortiSIEM](https://www.fortinet.com/products/siem/fortisiem) — Fortinet's security information and event management (SIEM) platform for real-time monitoring, analytics, and incident management.
|
|
4
|
+
|
|
5
|
+
This node lets you interact with the FortiSIEM REST API directly from your n8n workflows: fetch and update incidents, retrieve triggering events, run analytic event queries, manage monitored organizations, and maintain watchlists.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/n8n-nodes-fortisiem)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://docs.n8n.io/integrations/community-nodes/)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Features](#features)
|
|
16
|
+
- [Prerequisites](#prerequisites)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Credentials](#credentials)
|
|
19
|
+
- [Resources & Operations](#resources--operations)
|
|
20
|
+
- [Incident](#incident)
|
|
21
|
+
- [Event](#event)
|
|
22
|
+
- [Organization](#organization)
|
|
23
|
+
- [Watchlist](#watchlist)
|
|
24
|
+
- [Usage Examples](#usage-examples)
|
|
25
|
+
- [Development](#development)
|
|
26
|
+
- [Publishing to npm](#publishing-to-npm)
|
|
27
|
+
- [Compatibility](#compatibility)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **13 operations** across 4 FortiSIEM resource types
|
|
35
|
+
- HTTP Basic authentication with built-in credential testing
|
|
36
|
+
- Automatic JSON parsing of FortiSIEM's `text/plain` responses
|
|
37
|
+
- Optional "Ignore SSL Issues" toggle for self-signed appliance certificates
|
|
38
|
+
- `continueOnFail` support — process remaining items even when one fails
|
|
39
|
+
- Compatible with n8n's **AI Tool** interface (`usableAsTool: true`)
|
|
40
|
+
- Full TypeScript source with strict mode enabled
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Prerequisites
|
|
45
|
+
|
|
46
|
+
| Requirement | Version |
|
|
47
|
+
|---|---|
|
|
48
|
+
| n8n | ≥ 1.0.0 |
|
|
49
|
+
| FortiSIEM | ≥ 6.x (REST API, `/phoenix/rest`) |
|
|
50
|
+
| Node.js | ≥ 18.x |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
### Via n8n UI (recommended)
|
|
57
|
+
|
|
58
|
+
1. Open n8n → **Settings** → **Community Nodes**
|
|
59
|
+
2. Click **Install**
|
|
60
|
+
3. Enter `n8n-nodes-fortisiem` and confirm
|
|
61
|
+
|
|
62
|
+
### Via npm (self-hosted)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install n8n-nodes-fortisiem
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then restart your n8n instance.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Credentials
|
|
73
|
+
|
|
74
|
+
Create a **FortiSIEM API** credential with the following fields:
|
|
75
|
+
|
|
76
|
+
| Field | Description | Example |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| **Base URL** | Base URL of your FortiSIEM Supervisor (no trailing slash) | `https://fortisiem.example.com` |
|
|
79
|
+
| **Username** | API user as `<organization>/<user>` | `super/admin` |
|
|
80
|
+
| **Password** | Account password | `••••••••` |
|
|
81
|
+
| **Ignore SSL Issues (Insecure)** | Connect even if the TLS certificate fails validation | `true` for self-signed certs |
|
|
82
|
+
|
|
83
|
+
The node authenticates every request using HTTP Basic auth. Click **Test connection** on the credential card to verify connectivity — this calls `GET /phoenix/rest/config/Domain` (list monitored organizations).
|
|
84
|
+
|
|
85
|
+
> **Tip:** Create a dedicated FortiSIEM API user with the minimum required role for your workflows.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Resources & Operations
|
|
90
|
+
|
|
91
|
+
### Incident
|
|
92
|
+
|
|
93
|
+
| Operation | Description | Endpoint |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| **Fetch** | Fetch incidents by filters, time range, paging and ordering | `POST /phoenix/rest/pub/incident` |
|
|
96
|
+
| **Get Many** | Retrieve incidents, optionally filtered by status | `GET /phoenix/rest/pub/incident` |
|
|
97
|
+
| **Get Triggering Events** | Retrieve the events that triggered an incident | `GET /phoenix/rest/pub/incident/triggeringEvents` |
|
|
98
|
+
| **Update** | Update the external ticket state of an incident | `POST /phoenix/rest/pub/incident/update/{incidentId}` |
|
|
99
|
+
|
|
100
|
+
**Fetch** filter options: incident IDs, status, `timeFrom`/`timeTo` (epoch ms), `start`, `size`, `orderBy`, `descending`, and the list of `fields` to return.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### Event
|
|
105
|
+
|
|
106
|
+
| Operation | Description | Endpoint |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| **Query** | Run an analytic event query defined by an XML report | `POST /phoenix/rest/query/` |
|
|
109
|
+
|
|
110
|
+
The **Report XML** field is pre-filled with a sample report definition (top events by count) to get you started.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### Organization
|
|
115
|
+
|
|
116
|
+
| Operation | Description | Endpoint |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| **Get Many** | Retrieve the list of monitored organizations | `GET /phoenix/rest/config/Domain` |
|
|
119
|
+
| **Add** | Add a new organization | `POST /phoenix/rest/organization/add` |
|
|
120
|
+
| **Update** | Update an organization | `GET /phoenix/rest/organization/update` |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Watchlist
|
|
125
|
+
|
|
126
|
+
| Operation | Description | Endpoint |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| **Get Many** | Retrieve all watchlists | `GET /phoenix/rest/watchlist/all` |
|
|
129
|
+
| **Get By Entry** | Get the watchlist containing a specific entry | `GET /phoenix/rest/watchlist/byEntry/{id}` |
|
|
130
|
+
| **Add Entry** | Add an entry to a watchlist | `POST /phoenix/rest/watchlist/addTo` |
|
|
131
|
+
| **Create Group** | Create a new watchlist group | `POST /phoenix/rest/watchlist/save` |
|
|
132
|
+
| **Delete Entry** | Delete one or more watchlist entries | `POST /phoenix/rest/watchlist/entry/delete` |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Usage Examples
|
|
137
|
+
|
|
138
|
+
### Poll for new active incidents and alert
|
|
139
|
+
|
|
140
|
+
1. Add a **Schedule Trigger**
|
|
141
|
+
2. Add a **FortiSIEM** node → resource **Incident** → **Fetch**, set **Time From**/**Time To** to your window and **Status** to `0` (active)
|
|
142
|
+
3. Add an **IF** node to check severity
|
|
143
|
+
4. Add a **Slack** / **Email** node to notify
|
|
144
|
+
|
|
145
|
+
### Sync a FortiSIEM incident to an external ticketing system
|
|
146
|
+
|
|
147
|
+
1. **FortiSIEM** → Incident → **Fetch** the incidents you care about
|
|
148
|
+
2. Create the ticket in your ITSM tool
|
|
149
|
+
3. **FortiSIEM** → Incident → **Update** with the **External Ticket ID** and **External Ticket State** (e.g. `Closed`)
|
|
150
|
+
|
|
151
|
+
### Investigate an incident's root cause
|
|
152
|
+
|
|
153
|
+
1. **FortiSIEM** → Incident → **Get Triggering Events**, expression `{{ $json.incidentId }}` as Incident ID
|
|
154
|
+
2. Inspect the raw events that generated the incident
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
### Requirements
|
|
161
|
+
|
|
162
|
+
- Node.js ≥ 18
|
|
163
|
+
- npm ≥ 9
|
|
164
|
+
|
|
165
|
+
### Setup
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
git clone https://github.com/kboykov/n8n-nodes-fortisiem.git
|
|
169
|
+
cd n8n-nodes-fortisiem
|
|
170
|
+
npm install
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Build
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npm run build
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This runs `tsc` followed by `gulp build:icons` to copy the icon into `dist/`.
|
|
180
|
+
|
|
181
|
+
### Lint & format
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm run lint
|
|
185
|
+
npm run lint:fix # auto-fix where possible
|
|
186
|
+
npm run format # prettier
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Local testing with n8n
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# In this repo
|
|
193
|
+
npm link
|
|
194
|
+
|
|
195
|
+
# In your local n8n installation directory
|
|
196
|
+
npm link n8n-nodes-fortisiem
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Then restart n8n. The FortiSIEM node will appear in the node picker.
|
|
200
|
+
|
|
201
|
+
### Project structure
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
n8n-nodes-fortisiem/
|
|
205
|
+
├── credentials/
|
|
206
|
+
│ └── FortiSiemApi.credentials.ts # Credential definition (Basic auth)
|
|
207
|
+
├── nodes/
|
|
208
|
+
│ └── FortiSiem/
|
|
209
|
+
│ ├── FortiSiem.node.ts # Main node implementation
|
|
210
|
+
│ └── descriptions/
|
|
211
|
+
│ ├── IncidentDescription.ts
|
|
212
|
+
│ ├── EventDescription.ts
|
|
213
|
+
│ ├── OrganizationDescription.ts
|
|
214
|
+
│ └── WatchlistDescription.ts
|
|
215
|
+
├── icons/
|
|
216
|
+
│ └── fortisiem.png
|
|
217
|
+
├── .github/workflows/publish.yml # Automated npm publish on tag
|
|
218
|
+
├── gulpfile.js
|
|
219
|
+
├── package.json
|
|
220
|
+
└── tsconfig.json
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Publishing to npm
|
|
226
|
+
|
|
227
|
+
Releases are published to [npm](https://www.npmjs.com/package/n8n-nodes-fortisiem) automatically by the [`Publish`](.github/workflows/publish.yml) GitHub Actions workflow, which triggers on any pushed tag matching `*.*.*` (e.g. `0.1.0`).
|
|
228
|
+
|
|
229
|
+
The workflow runs `npm ci`, then `npm publish --provenance --access public`, so releases include [npm provenance](https://docs.npmjs.com/generating-provenance-statements) attestation.
|
|
230
|
+
|
|
231
|
+
### One-time setup
|
|
232
|
+
|
|
233
|
+
1. Create an **automation** access token on npmjs.com (Account → Access Tokens → Generate New Token → *Automation*).
|
|
234
|
+
2. In this repository, go to **Settings → Secrets and variables → Actions** and add a secret named **`NPM_TOKEN`** with that token.
|
|
235
|
+
|
|
236
|
+
### Cutting a release
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Bump the version in package.json and create a matching git tag
|
|
240
|
+
npm version patch # or: minor / major
|
|
241
|
+
|
|
242
|
+
# Push the commit and the tag — the tag push triggers the workflow
|
|
243
|
+
git push --follow-tags
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Pushing the tag (for example `v0.1.1` → the workflow matches the `0.1.1` portion) starts the workflow, which builds via the `prepublishOnly` script and publishes to npm.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Compatibility
|
|
251
|
+
|
|
252
|
+
| Component | Status |
|
|
253
|
+
|---|---|
|
|
254
|
+
| n8n Nodes API | v1 |
|
|
255
|
+
| FortiSIEM REST API | `/phoenix/rest` (FortiSIEM ≥ 6.x) |
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
[MIT](LICENSE)
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Contributing
|
|
266
|
+
|
|
267
|
+
Issues and pull requests are welcome. Please open an issue first to discuss any significant changes.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class FortiSiemApi implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
documentationUrl: string;
|
|
6
|
+
properties: INodeProperties[];
|
|
7
|
+
authenticate: IAuthenticateGeneric;
|
|
8
|
+
test: ICredentialTestRequest;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=FortiSiemApi.credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FortiSiemApi.credentials.d.ts","sourceRoot":"","sources":["../../credentials/FortiSiemApi.credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,MAAM,cAAc,CAAC;AAEtB,qBAAa,YAAa,YAAW,eAAe;IACnD,IAAI,SAAkB;IAEtB,WAAW,SAAmB;IAE9B,gBAAgB,SACiG;IAEjH,UAAU,EAAE,eAAe,EAAE,CAsC3B;IAGF,YAAY,EAAE,oBAAoB,CAQhC;IAGF,IAAI,EAAE,sBAAsB,CAO1B;CACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FortiSiemApi = void 0;
|
|
4
|
+
class FortiSiemApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'fortiSiemApi';
|
|
7
|
+
this.displayName = 'FortiSIEM API';
|
|
8
|
+
this.documentationUrl = 'https://docs.fortinet.com/document/fortisiem/6.4.0/integration-api-guide/895612/json-api-incident-integration';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Base URL',
|
|
12
|
+
name: 'baseUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
required: true,
|
|
16
|
+
placeholder: 'https://your-fortisiem-supervisor',
|
|
17
|
+
description: 'Base URL of your FortiSIEM Supervisor, including protocol and (optional) port, without a trailing slash',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'Username',
|
|
21
|
+
name: 'username',
|
|
22
|
+
type: 'string',
|
|
23
|
+
default: '',
|
|
24
|
+
required: true,
|
|
25
|
+
description: 'FortiSIEM API user in the form <organization>/<user> (for the default organization, e.g. super/admin)',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
displayName: 'Password',
|
|
29
|
+
name: 'password',
|
|
30
|
+
type: 'string',
|
|
31
|
+
typeOptions: {
|
|
32
|
+
password: true,
|
|
33
|
+
},
|
|
34
|
+
default: '',
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
displayName: 'Ignore SSL Issues (Insecure)',
|
|
39
|
+
name: 'allowUnauthorizedCerts',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Whether to connect even if SSL certificate validation fails. FortiSIEM appliances often use self-signed certificates.',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
// FortiSIEM's REST API uses HTTP Basic authentication.
|
|
46
|
+
this.authenticate = {
|
|
47
|
+
type: 'generic',
|
|
48
|
+
properties: {
|
|
49
|
+
auth: {
|
|
50
|
+
username: '={{$credentials.username}}',
|
|
51
|
+
password: '={{$credentials.password}}',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
// Validate the credentials by listing monitored organizations.
|
|
56
|
+
this.test = {
|
|
57
|
+
request: {
|
|
58
|
+
method: 'GET',
|
|
59
|
+
baseURL: '={{$credentials.baseUrl}}',
|
|
60
|
+
url: '/phoenix/rest/config/Domain',
|
|
61
|
+
skipSslCertificateValidation: '={{$credentials.allowUnauthorizedCerts}}',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.FortiSiemApi = FortiSiemApi;
|
|
67
|
+
//# sourceMappingURL=FortiSiemApi.credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FortiSiemApi.credentials.js","sourceRoot":"","sources":["../../credentials/FortiSiemApi.credentials.ts"],"names":[],"mappings":";;;AAOA,MAAa,YAAY;IAAzB;QACC,SAAI,GAAG,cAAc,CAAC;QAEtB,gBAAW,GAAG,eAAe,CAAC;QAE9B,qBAAgB,GACf,+GAA+G,CAAC;QAEjH,eAAU,GAAsB;YAC/B;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,mCAAmC;gBAChD,WAAW,EACV,yGAAyG;aAC1G;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,WAAW,EACV,uGAAuG;aACxG;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE;oBACZ,QAAQ,EAAE,IAAI;iBACd;gBACD,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;aACd;YACD;gBACC,WAAW,EAAE,8BAA8B;gBAC3C,IAAI,EAAE,wBAAwB;gBAC9B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,KAAK;gBACd,WAAW,EACV,uHAAuH;aACxH;SACD,CAAC;QAEF,uDAAuD;QACvD,iBAAY,GAAyB;YACpC,IAAI,EAAE,SAAS;YACf,UAAU,EAAE;gBACX,IAAI,EAAE;oBACL,QAAQ,EAAE,4BAA4B;oBACtC,QAAQ,EAAE,4BAA4B;iBACtC;aACD;SACD,CAAC;QAEF,+DAA+D;QAC/D,SAAI,GAA2B;YAC9B,OAAO,EAAE;gBACR,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,2BAA2B;gBACpC,GAAG,EAAE,6BAA6B;gBAClC,4BAA4B,EAAE,0CAA0C;aACxE;SACD,CAAC;IACH,CAAC;CAAA;AApED,oCAoEC"}
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class FortiSiem implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=FortiSiem.node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FortiSiem.node.d.ts","sourceRoot":"","sources":["../../../nodes/FortiSiem/FortiSiem.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,iBAAiB,EAGjB,kBAAkB,EAClB,SAAS,EACT,oBAAoB,EACpB,MAAM,cAAc,CAAC;AA6CtB,qBAAa,SAAU,YAAW,SAAS;IAC1C,WAAW,EAAE,oBAAoB,CAgE/B;IAEI,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;CAuNvE"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FortiSiem = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const EventDescription_1 = require("./descriptions/EventDescription");
|
|
6
|
+
const IncidentDescription_1 = require("./descriptions/IncidentDescription");
|
|
7
|
+
const OrganizationDescription_1 = require("./descriptions/OrganizationDescription");
|
|
8
|
+
const WatchlistDescription_1 = require("./descriptions/WatchlistDescription");
|
|
9
|
+
// FortiSIEM returns most payloads as text/plain even though the body is JSON,
|
|
10
|
+
// so parse it ourselves and fall back to the raw string when it isn't JSON.
|
|
11
|
+
function parseResponse(response) {
|
|
12
|
+
if (typeof response !== 'string') {
|
|
13
|
+
return response;
|
|
14
|
+
}
|
|
15
|
+
const trimmed = response.trim();
|
|
16
|
+
if (trimmed === '') {
|
|
17
|
+
return { success: true };
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(trimmed);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { data: response };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Split a comma-separated string into a trimmed, non-empty list.
|
|
27
|
+
function splitList(value) {
|
|
28
|
+
return value
|
|
29
|
+
.split(',')
|
|
30
|
+
.map((part) => part.trim())
|
|
31
|
+
.filter((part) => part !== '');
|
|
32
|
+
}
|
|
33
|
+
class FortiSiem {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.description = {
|
|
36
|
+
displayName: 'FortiSIEM',
|
|
37
|
+
name: 'fortiSiem',
|
|
38
|
+
icon: 'file:../../icons/fortisiem.png',
|
|
39
|
+
// 'input' is correct for nodes that fetch / mutate data in an external system
|
|
40
|
+
group: ['input'],
|
|
41
|
+
version: 1,
|
|
42
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
43
|
+
description: 'Interact with the FortiSIEM security information and event management platform',
|
|
44
|
+
defaults: {
|
|
45
|
+
name: 'FortiSIEM',
|
|
46
|
+
},
|
|
47
|
+
usableAsTool: true,
|
|
48
|
+
inputs: ['main'],
|
|
49
|
+
outputs: ['main'],
|
|
50
|
+
credentials: [
|
|
51
|
+
{
|
|
52
|
+
name: 'fortiSiemApi',
|
|
53
|
+
required: true,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
properties: [
|
|
57
|
+
{
|
|
58
|
+
displayName: 'Resource',
|
|
59
|
+
name: 'resource',
|
|
60
|
+
type: 'options',
|
|
61
|
+
noDataExpression: true,
|
|
62
|
+
options: [
|
|
63
|
+
{
|
|
64
|
+
name: 'Event',
|
|
65
|
+
value: 'event',
|
|
66
|
+
description: 'Run analytic queries against collected events',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'Incident',
|
|
70
|
+
value: 'incident',
|
|
71
|
+
description: 'Fetch, inspect and update incidents',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'Organization',
|
|
75
|
+
value: 'organization',
|
|
76
|
+
description: 'Manage monitored organizations',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'Watchlist',
|
|
80
|
+
value: 'watchlist',
|
|
81
|
+
description: 'Manage watchlists and their entries',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
default: 'incident',
|
|
85
|
+
},
|
|
86
|
+
// ── Operations (one block per resource) ─────────────────────
|
|
87
|
+
...EventDescription_1.eventOperations,
|
|
88
|
+
...IncidentDescription_1.incidentOperations,
|
|
89
|
+
...OrganizationDescription_1.organizationOperations,
|
|
90
|
+
...WatchlistDescription_1.watchlistOperations,
|
|
91
|
+
// ── Fields (one block per resource) ─────────────────────────
|
|
92
|
+
...EventDescription_1.eventFields,
|
|
93
|
+
...IncidentDescription_1.incidentFields,
|
|
94
|
+
...OrganizationDescription_1.organizationFields,
|
|
95
|
+
...WatchlistDescription_1.watchlistFields,
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async execute() {
|
|
100
|
+
const items = this.getInputData();
|
|
101
|
+
const returnData = [];
|
|
102
|
+
const credentials = (await this.getCredentials('fortiSiemApi'));
|
|
103
|
+
const baseUrl = credentials.baseUrl.replace(/\/$/, '');
|
|
104
|
+
// Typed helper — authentication (Basic auth) is injected via the credential.
|
|
105
|
+
const makeRequest = async (method, path, options = {}) => {
|
|
106
|
+
var _a;
|
|
107
|
+
const requestOptions = {
|
|
108
|
+
method,
|
|
109
|
+
url: `${baseUrl}${path}`,
|
|
110
|
+
headers: {
|
|
111
|
+
Accept: 'application/json',
|
|
112
|
+
...(options.contentType ? { 'Content-Type': options.contentType } : {}),
|
|
113
|
+
},
|
|
114
|
+
qs: options.qs && Object.keys(options.qs).length ? options.qs : undefined,
|
|
115
|
+
body: options.body,
|
|
116
|
+
json: (_a = options.json) !== null && _a !== void 0 ? _a : false,
|
|
117
|
+
skipSslCertificateValidation: credentials.allowUnauthorizedCerts,
|
|
118
|
+
};
|
|
119
|
+
return this.helpers.httpRequestWithAuthentication.call(this, 'fortiSiemApi', requestOptions);
|
|
120
|
+
};
|
|
121
|
+
for (let i = 0; i < items.length; i++) {
|
|
122
|
+
try {
|
|
123
|
+
const resource = this.getNodeParameter('resource', i);
|
|
124
|
+
const operation = this.getNodeParameter('operation', i);
|
|
125
|
+
let responseData;
|
|
126
|
+
// ── INCIDENT ─────────────────────────────────────────────
|
|
127
|
+
if (resource === 'incident') {
|
|
128
|
+
if (operation === 'getMany') {
|
|
129
|
+
const status = this.getNodeParameter('status', i, '');
|
|
130
|
+
const qs = {};
|
|
131
|
+
if (status !== '')
|
|
132
|
+
qs.status = status;
|
|
133
|
+
responseData = parseResponse(await makeRequest('GET', '/phoenix/rest/pub/incident', { qs }));
|
|
134
|
+
}
|
|
135
|
+
else if (operation === 'fetch') {
|
|
136
|
+
const filters = this.getNodeParameter('filterOptions', i, {});
|
|
137
|
+
const body = {};
|
|
138
|
+
const filterClause = {};
|
|
139
|
+
if (filters.incidentId) {
|
|
140
|
+
filterClause.incidentId = splitList(filters.incidentId).map(Number);
|
|
141
|
+
}
|
|
142
|
+
if (filters.status) {
|
|
143
|
+
filterClause.status = splitList(filters.status).map(Number);
|
|
144
|
+
}
|
|
145
|
+
if (Object.keys(filterClause).length)
|
|
146
|
+
body.filters = filterClause;
|
|
147
|
+
if (filters.timeFrom)
|
|
148
|
+
body.timeFrom = filters.timeFrom;
|
|
149
|
+
if (filters.timeTo)
|
|
150
|
+
body.timeTo = filters.timeTo;
|
|
151
|
+
if (filters.start !== undefined)
|
|
152
|
+
body.start = filters.start;
|
|
153
|
+
if (filters.size !== undefined)
|
|
154
|
+
body.size = filters.size;
|
|
155
|
+
if (filters.orderBy)
|
|
156
|
+
body.orderBy = filters.orderBy;
|
|
157
|
+
if (filters.descending !== undefined)
|
|
158
|
+
body.descending = filters.descending;
|
|
159
|
+
if (filters.fields)
|
|
160
|
+
body.fields = splitList(filters.fields);
|
|
161
|
+
responseData = parseResponse(await makeRequest('POST', '/phoenix/rest/pub/incident', {
|
|
162
|
+
body,
|
|
163
|
+
contentType: 'application/json',
|
|
164
|
+
json: true,
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
else if (operation === 'getTriggeringEvents') {
|
|
168
|
+
const incidentId = this.getNodeParameter('incidentId', i);
|
|
169
|
+
const size = this.getNodeParameter('size', i);
|
|
170
|
+
responseData = parseResponse(await makeRequest('GET', '/phoenix/rest/pub/incident/triggeringEvents', {
|
|
171
|
+
qs: { incidentId, size },
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
else if (operation === 'update') {
|
|
175
|
+
const incidentId = this.getNodeParameter('incidentId', i);
|
|
176
|
+
const incidentExtTicketId = this.getNodeParameter('incidentExtTicketId', i);
|
|
177
|
+
const updateFields = this.getNodeParameter('updateFields', i, {});
|
|
178
|
+
const body = { incidentExtTicketId, ...updateFields };
|
|
179
|
+
responseData = parseResponse(await makeRequest('POST', `/phoenix/rest/pub/incident/update/${encodeURIComponent(incidentId)}`, { body, contentType: 'application/json', json: true }));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
|
|
183
|
+
itemIndex: i,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// ── EVENT ────────────────────────────────────────────────
|
|
187
|
+
}
|
|
188
|
+
else if (resource === 'event') {
|
|
189
|
+
if (operation === 'query') {
|
|
190
|
+
const reportXml = this.getNodeParameter('reportXml', i);
|
|
191
|
+
responseData = parseResponse(await makeRequest('POST', '/phoenix/rest/query/', {
|
|
192
|
+
body: reportXml,
|
|
193
|
+
contentType: 'text/xml',
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
|
|
198
|
+
itemIndex: i,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// ── ORGANIZATION ─────────────────────────────────────────
|
|
202
|
+
}
|
|
203
|
+
else if (resource === 'organization') {
|
|
204
|
+
if (operation === 'getMany') {
|
|
205
|
+
responseData = parseResponse(await makeRequest('GET', '/phoenix/rest/config/Domain'));
|
|
206
|
+
}
|
|
207
|
+
else if (operation === 'add') {
|
|
208
|
+
const body = this.getNodeParameter('body', i);
|
|
209
|
+
responseData = parseResponse(await makeRequest('POST', '/phoenix/rest/organization/add', {
|
|
210
|
+
body,
|
|
211
|
+
contentType: 'text/xml',
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
else if (operation === 'update') {
|
|
215
|
+
responseData = parseResponse(await makeRequest('GET', '/phoenix/rest/organization/update'));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
|
|
219
|
+
itemIndex: i,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// ── WATCHLIST ────────────────────────────────────────────
|
|
223
|
+
}
|
|
224
|
+
else if (resource === 'watchlist') {
|
|
225
|
+
if (operation === 'getMany') {
|
|
226
|
+
responseData = parseResponse(await makeRequest('GET', '/phoenix/rest/watchlist/all'));
|
|
227
|
+
}
|
|
228
|
+
else if (operation === 'getByEntry') {
|
|
229
|
+
const entryId = this.getNodeParameter('entryId', i);
|
|
230
|
+
responseData = parseResponse(await makeRequest('GET', `/phoenix/rest/watchlist/byEntry/${encodeURIComponent(entryId)}`));
|
|
231
|
+
}
|
|
232
|
+
else if (operation === 'addEntry') {
|
|
233
|
+
const watchlistId = this.getNodeParameter('watchlistId', i);
|
|
234
|
+
const body = this.getNodeParameter('body', i);
|
|
235
|
+
responseData = parseResponse(await makeRequest('POST', '/phoenix/rest/watchlist/addTo', {
|
|
236
|
+
body,
|
|
237
|
+
qs: { watchlistId },
|
|
238
|
+
contentType: 'application/json',
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
else if (operation === 'createGroup') {
|
|
242
|
+
const body = this.getNodeParameter('body', i);
|
|
243
|
+
responseData = parseResponse(await makeRequest('POST', '/phoenix/rest/watchlist/save', {
|
|
244
|
+
body,
|
|
245
|
+
contentType: 'application/json',
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
else if (operation === 'deleteEntry') {
|
|
249
|
+
const body = this.getNodeParameter('body', i);
|
|
250
|
+
responseData = parseResponse(await makeRequest('POST', '/phoenix/rest/watchlist/entry/delete', {
|
|
251
|
+
body,
|
|
252
|
+
contentType: 'application/json',
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
|
|
257
|
+
itemIndex: i,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown resource: ${resource}`, {
|
|
263
|
+
itemIndex: i,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
|
|
267
|
+
returnData.push(...executionData);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
if (this.continueOnFail()) {
|
|
271
|
+
returnData.push({
|
|
272
|
+
json: { error: error.message },
|
|
273
|
+
pairedItem: { item: i },
|
|
274
|
+
});
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return [returnData];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
exports.FortiSiem = FortiSiem;
|
|
284
|
+
//# sourceMappingURL=FortiSiem.node.js.map
|