@agentsoc/beacon 0.0.1
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/README.md +148 -0
- package/dist/cli.js +74 -0
- package/dist/config.js +37 -0
- package/dist/enrich.js +32 -0
- package/dist/forwarder.js +132 -0
- package/dist/run-beacon.js +17 -0
- package/dist/service-install.js +67 -0
- package/dist/tail.js +71 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# AgentSOC Syslog Beacon CLI
|
|
2
|
+
|
|
3
|
+
Lightweight, background-running log forwarder (beacon) for AgentSOC.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Syslog Beacon CLI allows you to configure, install, and run a lightweight log forwarding daemon that sends system logs to an AgentSOC ingest endpoint.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- [Node.js](https://nodejs.org/) v16 or higher
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### 🚀 Quick Install (One-Line Commands)
|
|
16
|
+
|
|
17
|
+
#### macOS / Linux
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
curl -fsSL https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.sh | bash
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
#### Windows PowerShell
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
irm https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.ps1 | iex
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Standard npm
|
|
30
|
+
|
|
31
|
+
Published as [`@agentsoc/beacon`](https://www.npmjs.com/package/@agentsoc/beacon) (npm requires a lowercase scope; same AgentSOC package).
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g @agentsoc/beacon
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `syslog-beacon` command is also installed as an alias for the same CLI.
|
|
38
|
+
|
|
39
|
+
### Option 2: Run from source with Bun
|
|
40
|
+
|
|
41
|
+
If you have the repository cloned and [Bun](https://bun.sh/) installed:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd apps/connectors/syslog-beacon-cli
|
|
45
|
+
bun install
|
|
46
|
+
bun run src/cli.ts config --url <url> --key <key>
|
|
47
|
+
bun run src/cli.ts run
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Installation Scripts
|
|
51
|
+
|
|
52
|
+
Automated installation scripts are provided for easy setup:
|
|
53
|
+
|
|
54
|
+
- **`install.sh`** - macOS/Linux installer
|
|
55
|
+
- **`install.ps1`** - Windows PowerShell installer
|
|
56
|
+
- **`install.bat`** - Windows batch installer
|
|
57
|
+
|
|
58
|
+
These scripts automatically check for Node.js/npm, install the package, and verify the installation.
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
The CLI provides the following commands:
|
|
63
|
+
|
|
64
|
+
### Configure
|
|
65
|
+
|
|
66
|
+
Configure the AgentSOC connection (saves to a local configuration file):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
beacon config --url <url> --key <key>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Required Parameters:**
|
|
73
|
+
|
|
74
|
+
- `--url, -u <url>`: AgentSOC ingest URL (e.g., `https://api.agentsoc.com/ingest`)
|
|
75
|
+
- `--key, -k <key>`: AgentSOC API Key (e.g., `sk_1234567890abcdef`)
|
|
76
|
+
|
|
77
|
+
**Example:**
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Note:** If you run `bun run src/cli.ts config` without the required parameters, you'll receive an error instructing you to provide the `-u` and `-k` options.
|
|
84
|
+
|
|
85
|
+
### Run Foreground Daemon
|
|
86
|
+
|
|
87
|
+
Run the forwarder daemon in the foreground:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
beacon run [options]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Options:**
|
|
94
|
+
|
|
95
|
+
- `--batch-size <n>`: Set the batch size for log forwarding (default: 25)
|
|
96
|
+
- `--flush-ms <n>`: Set the flush interval in milliseconds (default: 2000)
|
|
97
|
+
|
|
98
|
+
**Example:**
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
beacon run --batch-size 50 --flush-ms 5000
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
_Note: You can also use environment variables `AGENTSOC_API_KEY` and `AGENTSOC_INGEST_URL` instead of running the `config` command._
|
|
105
|
+
|
|
106
|
+
### Install Service
|
|
107
|
+
|
|
108
|
+
Install the background service daemon (systemd on Linux, launchd on macOS):
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
beacon install
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
- Start the daemon using npm script: `npm start` or `bun run start`
|
|
117
|
+
- Install service using npm script: `npm run install-service` or `bun run install-service`
|
|
118
|
+
- Build the distributable: `npm run build:simple` (compiles TypeScript to dist/)
|
|
119
|
+
|
|
120
|
+
## Building & Distribution
|
|
121
|
+
|
|
122
|
+
### Build the Package
|
|
123
|
+
|
|
124
|
+
To build the TypeScript files into the `dist/` folder:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run build:simple
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Share Installation Scripts
|
|
131
|
+
|
|
132
|
+
Users can install the package using one of the provided installation scripts:
|
|
133
|
+
|
|
134
|
+
- Copy the `install.sh` script to your repository (macOS/Linux users)
|
|
135
|
+
- Copy the `install.ps1` script to your repository (Windows PowerShell users)
|
|
136
|
+
- Copy the `install.bat` script to your repository (Windows batch users)
|
|
137
|
+
|
|
138
|
+
Users can then run:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
curl -fsSL https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.sh | bash
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or use npm:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm install -g @agentsoc/beacon
|
|
148
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
4
|
+
import { runBeacon } from "./run-beacon.js";
|
|
5
|
+
import { installService } from "./service-install.js";
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name("beacon")
|
|
9
|
+
.description("AgentSOC Syslog Beacon - Lightweight background log forwarder")
|
|
10
|
+
.version("1.0.0", "-v, --version");
|
|
11
|
+
program
|
|
12
|
+
.command("config")
|
|
13
|
+
.description("Configure the AgentSOC connection")
|
|
14
|
+
.option("-u, --url <url>", "AgentSOC ingest URL")
|
|
15
|
+
.option("-k, --key <key>", "AgentSOC API Key")
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
if (!options.url || !options.key) {
|
|
18
|
+
console.log("\n[beacon] Missing required options.\n");
|
|
19
|
+
console.log("Usage: beacon config --url <url> --key <key>\n");
|
|
20
|
+
console.log("Examples:");
|
|
21
|
+
console.log(" bun run src/cli.ts config -u https://api.agentsoc.com/ingest -k sk_1234567890abcdef");
|
|
22
|
+
console.log(" beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef\n");
|
|
23
|
+
console.log("Options:");
|
|
24
|
+
console.log(" -u, --url <url> AgentSOC ingest URL");
|
|
25
|
+
console.log(" -k, --key <key> AgentSOC API Key\n");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
await saveConfig({
|
|
29
|
+
ingestUrl: options.url,
|
|
30
|
+
apiKey: options.key,
|
|
31
|
+
});
|
|
32
|
+
console.log("[beacon] Config saved.");
|
|
33
|
+
});
|
|
34
|
+
program
|
|
35
|
+
.command("install")
|
|
36
|
+
.description("Install the background service daemon")
|
|
37
|
+
.action(async () => {
|
|
38
|
+
try {
|
|
39
|
+
await installService();
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error("[beacon] Install failed:", err);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
program
|
|
47
|
+
.command("run")
|
|
48
|
+
.description("Run the forwarder daemon (foreground)")
|
|
49
|
+
.option("--batch-size <n>", "Batch size", "25")
|
|
50
|
+
.option("--flush-ms <n>", "Flush interval in ms", "2000")
|
|
51
|
+
.action(async (options) => {
|
|
52
|
+
const config = await loadConfig();
|
|
53
|
+
const apiKey = process.env.AGENTSOC_API_KEY || config.apiKey;
|
|
54
|
+
const ingestUrl = process.env.AGENTSOC_INGEST_URL || config.ingestUrl;
|
|
55
|
+
if (!apiKey || !ingestUrl) {
|
|
56
|
+
console.error("[beacon] Missing apiKey or ingestUrl. Run `beacon config` or set ENV vars.");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
console.log("[beacon] Starting in foreground...");
|
|
60
|
+
const beacon = await runBeacon({
|
|
61
|
+
apiKey,
|
|
62
|
+
ingestUrl,
|
|
63
|
+
batchSize: parseInt(options.batchSize, 10),
|
|
64
|
+
flushMs: parseInt(options.flushMs, 10),
|
|
65
|
+
});
|
|
66
|
+
const stop = async () => {
|
|
67
|
+
console.log("\n[beacon] Stopping...");
|
|
68
|
+
await beacon.stop();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
};
|
|
71
|
+
process.on("SIGINT", stop);
|
|
72
|
+
process.on("SIGTERM", stop);
|
|
73
|
+
});
|
|
74
|
+
program.parse(process.argv);
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
export function getConfigDir() {
|
|
5
|
+
const home = os.homedir();
|
|
6
|
+
if (process.platform === "win32") {
|
|
7
|
+
return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "agentsoc-beacon");
|
|
8
|
+
}
|
|
9
|
+
if (process.platform === "darwin") {
|
|
10
|
+
return path.join(home, "Library", "Application Support", "agentsoc-beacon");
|
|
11
|
+
}
|
|
12
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
13
|
+
return path.join(xdg, "agentsoc-beacon");
|
|
14
|
+
}
|
|
15
|
+
export function getConfigFile() {
|
|
16
|
+
return path.join(getConfigDir(), "config.json");
|
|
17
|
+
}
|
|
18
|
+
export async function loadConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const file = getConfigFile();
|
|
21
|
+
const data = await fs.readFile(file, "utf8");
|
|
22
|
+
return JSON.parse(data);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function saveConfig(config) {
|
|
29
|
+
const dir = getConfigDir();
|
|
30
|
+
try {
|
|
31
|
+
await fs.mkdir(dir, { recursive: true });
|
|
32
|
+
await fs.writeFile(getConfigFile(), JSON.stringify(config, null, 2), "utf8");
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error(`[syslog-beacon] Failed to save config to ${getConfigFile()}:`, err);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/enrich.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
4
|
+
export async function getHostInfo() {
|
|
5
|
+
const config = await loadConfig();
|
|
6
|
+
let agentId = config.agentId;
|
|
7
|
+
if (!agentId) {
|
|
8
|
+
agentId = crypto.randomUUID();
|
|
9
|
+
config.agentId = agentId;
|
|
10
|
+
await saveConfig(config);
|
|
11
|
+
}
|
|
12
|
+
const hostname = os.hostname();
|
|
13
|
+
const osPlatform = os.platform();
|
|
14
|
+
let ip = "127.0.0.1";
|
|
15
|
+
let mac = "00:00:00:00:00:00";
|
|
16
|
+
const interfaces = os.networkInterfaces();
|
|
17
|
+
for (const name of Object.keys(interfaces)) {
|
|
18
|
+
const iface = interfaces[name];
|
|
19
|
+
if (!iface)
|
|
20
|
+
continue;
|
|
21
|
+
for (const info of iface) {
|
|
22
|
+
if (!info.internal && info.family === "IPv4") {
|
|
23
|
+
ip = info.address;
|
|
24
|
+
mac = info.mac;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (ip !== "127.0.0.1")
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
return { agentId, hostname, ip, osPlatform, mac };
|
|
32
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
function parseSyslogPri(line) {
|
|
2
|
+
// Default mapping if line does not start with <PRI>
|
|
3
|
+
let severity = 5; // notice (maps to 'low' severity in our logic)
|
|
4
|
+
let facility = 1; // user-level messages
|
|
5
|
+
const match = line.match(/^<(\d+)>/);
|
|
6
|
+
if (match) {
|
|
7
|
+
const pri = parseInt(match[1], 10);
|
|
8
|
+
facility = Math.floor(pri / 8); // pri >> 3
|
|
9
|
+
severity = pri & 7; // pri % 8
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
// If testing on macOS via log stream, the raw line might not contain <PRI>.
|
|
13
|
+
// Try to guess from keywords just for testing convenience:
|
|
14
|
+
const l = line.toLowerCase();
|
|
15
|
+
if (l.includes("critical") || l.includes("fatal") || l.includes("panic")) {
|
|
16
|
+
severity = 2; // crit
|
|
17
|
+
}
|
|
18
|
+
else if (l.includes("high") ||
|
|
19
|
+
l.includes("error") ||
|
|
20
|
+
l.includes("failed")) {
|
|
21
|
+
severity = 3; // err
|
|
22
|
+
}
|
|
23
|
+
else if (l.includes("medium") || l.includes("warn")) {
|
|
24
|
+
severity = 4; // warning
|
|
25
|
+
}
|
|
26
|
+
else if (l.includes("info")) {
|
|
27
|
+
severity = 6; // info
|
|
28
|
+
}
|
|
29
|
+
else if (l.includes("low") || l.includes("notice")) {
|
|
30
|
+
severity = 5; // notice
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
let severityStr = "info";
|
|
34
|
+
if (severity <= 2)
|
|
35
|
+
severityStr = "critical";
|
|
36
|
+
else if (severity === 3)
|
|
37
|
+
severityStr = "high";
|
|
38
|
+
else if (severity === 4)
|
|
39
|
+
severityStr = "medium";
|
|
40
|
+
else if (severity === 5)
|
|
41
|
+
severityStr = "low";
|
|
42
|
+
return { severityStr, facility };
|
|
43
|
+
}
|
|
44
|
+
function extractProcInfo(line) {
|
|
45
|
+
// Try to extract the program name from syslog format: hostname[pid]: message or hostname program: message
|
|
46
|
+
const match = line.match(/^\S+\s+(?:\S+\s+)?(\w+)(?:\[\d+\])?:\s*(.*)$/);
|
|
47
|
+
if (match) {
|
|
48
|
+
return { app: match[1], message: match[2] };
|
|
49
|
+
}
|
|
50
|
+
// Fall back to the whole line as message
|
|
51
|
+
return { message: line };
|
|
52
|
+
}
|
|
53
|
+
export class Forwarder {
|
|
54
|
+
opts;
|
|
55
|
+
buffer = [];
|
|
56
|
+
timer = null;
|
|
57
|
+
batchSize;
|
|
58
|
+
flushMs;
|
|
59
|
+
constructor(opts) {
|
|
60
|
+
this.opts = opts;
|
|
61
|
+
this.batchSize = opts.batchSize || 25;
|
|
62
|
+
this.flushMs = opts.flushMs || 2000;
|
|
63
|
+
}
|
|
64
|
+
pushLog(line) {
|
|
65
|
+
const { severityStr, facility } = parseSyslogPri(line);
|
|
66
|
+
const { app, message } = extractProcInfo(line);
|
|
67
|
+
// Build syslog event matching the ingest API schema
|
|
68
|
+
const event = {
|
|
69
|
+
full_log: message,
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
hostname: this.opts.host.hostname,
|
|
72
|
+
facility,
|
|
73
|
+
severity: severityStr,
|
|
74
|
+
id: `${this.opts.host.agentId}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
75
|
+
agent: {
|
|
76
|
+
id: this.opts.host.agentId,
|
|
77
|
+
name: this.opts.host.hostname,
|
|
78
|
+
ip: this.opts.host.ip,
|
|
79
|
+
mac: this.opts.host.mac,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
if (app) {
|
|
83
|
+
event.app = app;
|
|
84
|
+
event.process = {
|
|
85
|
+
name: app,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
this.buffer.push(event);
|
|
89
|
+
if (this.buffer.length >= this.batchSize) {
|
|
90
|
+
this.flush();
|
|
91
|
+
}
|
|
92
|
+
else if (!this.timer) {
|
|
93
|
+
this.timer = setTimeout(() => this.flush(), this.flushMs);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async flush() {
|
|
97
|
+
if (this.timer) {
|
|
98
|
+
clearTimeout(this.timer);
|
|
99
|
+
this.timer = null;
|
|
100
|
+
}
|
|
101
|
+
if (this.buffer.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
const payload = this.buffer;
|
|
104
|
+
this.buffer = [];
|
|
105
|
+
console.log(`[syslog-beacon] Pushing ${payload.length} logs to API...`);
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(this.opts.ingestUrl, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
Authorization: `Bearer ${this.opts.apiKey}`,
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(payload),
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
const text = await res.text();
|
|
117
|
+
console.error(`[syslog-beacon] Failed to push logs: HTTP ${res.status} ${text}`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(`[syslog-beacon] Successfully pushed ${payload.length} logs`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error(`[syslog-beacon] Ingest error:`, err);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async stop() {
|
|
128
|
+
if (this.buffer.length > 0) {
|
|
129
|
+
await this.flush();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getHostInfo } from './enrich.js';
|
|
2
|
+
import { Forwarder } from './forwarder.js';
|
|
3
|
+
import { SyslogSource } from './tail.js';
|
|
4
|
+
export async function runBeacon(opts) {
|
|
5
|
+
const host = await getHostInfo();
|
|
6
|
+
const forwarder = new Forwarder({ ...opts, host });
|
|
7
|
+
const source = new SyslogSource((line) => {
|
|
8
|
+
forwarder.pushLog(line);
|
|
9
|
+
});
|
|
10
|
+
source.start();
|
|
11
|
+
return {
|
|
12
|
+
stop: async () => {
|
|
13
|
+
source.stop();
|
|
14
|
+
await forwarder.stop();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
export async function installService() {
|
|
6
|
+
const config = await loadConfig();
|
|
7
|
+
if (!config.apiKey || !config.ingestUrl) {
|
|
8
|
+
throw new Error('Config missing apiKey or ingestUrl. Please configure first.');
|
|
9
|
+
}
|
|
10
|
+
const isBun = process.execPath.endsWith('bun') || process.execPath.endsWith('bun.exe');
|
|
11
|
+
const runner = isBun ? process.execPath : 'node';
|
|
12
|
+
const cliPath = path.resolve(process.argv[1]);
|
|
13
|
+
const command = `${runner} ${cliPath} run`;
|
|
14
|
+
if (process.platform === 'linux') {
|
|
15
|
+
const serviceUnit = `[Unit]
|
|
16
|
+
Description=AgentSOC Syslog Beacon
|
|
17
|
+
After=network.target
|
|
18
|
+
|
|
19
|
+
[Service]
|
|
20
|
+
Type=simple
|
|
21
|
+
ExecStart=${command}
|
|
22
|
+
Restart=always
|
|
23
|
+
RestartSec=10
|
|
24
|
+
Environment=NODE_ENV=production
|
|
25
|
+
|
|
26
|
+
[Install]
|
|
27
|
+
WantedBy=multi-user.target
|
|
28
|
+
`;
|
|
29
|
+
const dest = '/etc/systemd/system/syslog-beacon.service';
|
|
30
|
+
await fs.writeFile(dest, serviceUnit, 'utf8');
|
|
31
|
+
execSync('systemctl daemon-reload');
|
|
32
|
+
execSync('systemctl enable syslog-beacon');
|
|
33
|
+
execSync('systemctl start syslog-beacon');
|
|
34
|
+
console.log('[syslog-beacon] Installed systemd service');
|
|
35
|
+
}
|
|
36
|
+
else if (process.platform === 'darwin') {
|
|
37
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
38
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
39
|
+
<plist version="1.0">
|
|
40
|
+
<dict>
|
|
41
|
+
<key>Label</key>
|
|
42
|
+
<string>com.agentsoc.syslog-beacon</string>
|
|
43
|
+
<key>ProgramArguments</key>
|
|
44
|
+
<array>
|
|
45
|
+
<string>${runner}</string>
|
|
46
|
+
<string>${cliPath}</string>
|
|
47
|
+
<string>run</string>
|
|
48
|
+
</array>
|
|
49
|
+
<key>RunAtLoad</key>
|
|
50
|
+
<true/>
|
|
51
|
+
<key>KeepAlive</key>
|
|
52
|
+
<true/>
|
|
53
|
+
</dict>
|
|
54
|
+
</plist>
|
|
55
|
+
`;
|
|
56
|
+
const dest = '/Library/LaunchDaemons/com.agentsoc.syslog-beacon.plist';
|
|
57
|
+
await fs.writeFile(dest, plist, 'utf8');
|
|
58
|
+
execSync(`launchctl load -w ${dest}`);
|
|
59
|
+
console.log('[syslog-beacon] Installed launchd service');
|
|
60
|
+
}
|
|
61
|
+
else if (process.platform === 'win32') {
|
|
62
|
+
console.log('[syslog-beacon] Windows service installation must be done via NSSM or WinSW. Skipping automatic install.');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
throw new Error(`Platform ${process.platform} is not supported for automatic service installation.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
package/dist/tail.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import dgram from "node:dgram";
|
|
3
|
+
export class SyslogSource {
|
|
4
|
+
onLog;
|
|
5
|
+
tailProc;
|
|
6
|
+
udpServer;
|
|
7
|
+
constructor(onLog) {
|
|
8
|
+
this.onLog = onLog;
|
|
9
|
+
}
|
|
10
|
+
start() {
|
|
11
|
+
if (process.platform === "linux") {
|
|
12
|
+
this.tailProc = spawn("tail", [
|
|
13
|
+
"-F",
|
|
14
|
+
"/var/log/syslog",
|
|
15
|
+
"/var/log/auth.log",
|
|
16
|
+
]);
|
|
17
|
+
this.tailProc.stdout?.on("data", (data) => {
|
|
18
|
+
const lines = data.toString().split("\n");
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
if (line.trim())
|
|
21
|
+
this.onLog(line);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
this.tailProc.stderr?.on("data", (data) => {
|
|
25
|
+
console.error(`[syslog-tail] ${data.toString()}`);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (process.platform === "darwin") {
|
|
29
|
+
this.tailProc = spawn("log", [
|
|
30
|
+
"stream",
|
|
31
|
+
"--predicate",
|
|
32
|
+
'process == "sshd" or process == "sudo" or process == "login" or process == "logger"',
|
|
33
|
+
"--style",
|
|
34
|
+
"syslog",
|
|
35
|
+
]);
|
|
36
|
+
console.log("[syslog-tail] macOS log stream started");
|
|
37
|
+
this.tailProc.stdout?.on("data", (data) => {
|
|
38
|
+
const lines = data.toString().split("\n");
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (line.trim())
|
|
41
|
+
this.onLog(line);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
this.tailProc.stderr?.on("data", (data) => {
|
|
45
|
+
console.error(`[syslog-tail] ${data.toString()}`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Windows or other - start a UDP server
|
|
50
|
+
this.udpServer = dgram.createSocket("udp4");
|
|
51
|
+
this.udpServer.on("message", (msg) => {
|
|
52
|
+
this.onLog(msg.toString("utf8").trim());
|
|
53
|
+
});
|
|
54
|
+
this.udpServer.on("error", (err) => {
|
|
55
|
+
console.error(`[syslog-udp] error:\n${err.stack}`);
|
|
56
|
+
this.udpServer?.close();
|
|
57
|
+
});
|
|
58
|
+
this.udpServer.bind(5140, () => {
|
|
59
|
+
console.log("[syslog-udp] listening on UDP 5140");
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
stop() {
|
|
64
|
+
if (this.tailProc) {
|
|
65
|
+
this.tailProc.kill();
|
|
66
|
+
}
|
|
67
|
+
if (this.udpServer) {
|
|
68
|
+
this.udpServer.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentsoc/beacon",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Lightweight, background-running log forwarder (beacon) for AgentSOC",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"beacon": "./dist/cli.js",
|
|
8
|
+
"syslog-beacon": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/cli.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "bun run --cwd ../../.. bun build --target bun --entry-points ./apps/connectors/syslog-beacon-cli/src/cli.ts --outdir ./apps/connectors/syslog-beacon-cli/dist --minify",
|
|
16
|
+
"build:simple": "tsc",
|
|
17
|
+
"start": "bun run src/cli.ts run",
|
|
18
|
+
"install-service": "bun run src/cli.ts install"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "^11.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/bun": "latest",
|
|
25
|
+
"typescript": "^5.6.0"
|
|
26
|
+
}
|
|
27
|
+
}
|