Adding connections api

This commit is contained in:
2026-03-11 10:57:40 -04:00
parent e0fe0c4d34
commit 3634f502a1
4 changed files with 167 additions and 2 deletions

View File

@@ -17,7 +17,8 @@ Each portspoof_py instance runs independently and exposes a JSON API. portspoof_
7. [Adding nodes](#adding-nodes) 7. [Adding nodes](#adding-nodes)
8. [Fetch cron](#fetch-cron) 8. [Fetch cron](#fetch-cron)
9. [HTTP trigger endpoint](#http-trigger-endpoint) 9. [HTTP trigger endpoint](#http-trigger-endpoint)
10. [Dashboard](#dashboard) 10. [JSON API](#json-api)
11. [Dashboard](#dashboard)
11. [Upgrading](#upgrading) 11. [Upgrading](#upgrading)
12. [Troubleshooting](#troubleshooting) 12. [Troubleshooting](#troubleshooting)
@@ -56,6 +57,8 @@ portspoof_concentrator/
│ ├── footer.php Shared footer with version number │ ├── footer.php Shared footer with version number
│ ├── functions.php Node CRUD, fetch helpers, run_fetch(), dashboard queries │ ├── functions.php Node CRUD, fetch helpers, run_fetch(), dashboard queries
│ └── style.php Shared CSS (included inline by both pages) │ └── style.php Shared CSS (included inline by both pages)
├── api/
│ └── connections.php JSON API — recent connections (token-protected)
└── cron/ └── cron/
└── fetch.php CLI polling script — run via cron or manually (CLI only) └── fetch.php CLI polling script — run via cron or manually (CLI only)
``` ```
@@ -421,6 +424,68 @@ When the endpoint is disabled: `503 Service Unavailable`.
--- ---
## JSON API
### `GET /api/connections.php`
Returns connections ingested within a configurable lookback window, newest first.
Uses the same `TRIGGER_TOKEN` for authentication as `trigger.php` — set it in `config.php` before use.
#### Parameters
| Parameter | Default | Max | Description |
|---|---|---|---|
| `minutes` | `10` | `1440` | Lookback window in minutes |
| `node_id` | _(all)_ | — | Filter results to a specific node ID |
#### Authentication
```bash
# Authorization header (preferred)
curl -H "Authorization: Bearer your-token" https://yourserver/api/connections.php
# Query string
curl "https://yourserver/api/connections.php?token=your-token"
```
#### Examples
```bash
# Last 10 minutes from all nodes
curl -H "Authorization: Bearer your-token" https://yourserver/api/connections.php
# Last 30 minutes from node 2
curl -H "Authorization: Bearer your-token" "https://yourserver/api/connections.php?minutes=30&node_id=2"
```
#### Response
```json
{
"since": "2026-03-11T14:01:00Z",
"minutes": 10,
"count": 3,
"connections": [
{
"id": 9821,
"occurred_at": "2026-03-11 14:10:42.831204",
"node_id": 1,
"node_name": "honeypot-eu-1",
"src_ip": "198.51.100.42",
"src_port": 54312,
"dst_port": 443,
"banner_hex": "485454502f312e31203230300d0a",
"banner_len": 14
}
]
}
```
`banner_hex` is null when portspoof_py sent no banner. `occurred_at` is in the database timezone (UTC recommended).
---
## Dashboard ## Dashboard
`index.php` auto-refreshes every 30 seconds and shows: `index.php` auto-refreshes every 30 seconds and shows:

69
api/connections.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
/**
* GET /api/connections.php
*
* Returns connections from the last 10 minutes (or ?minutes=N) as JSON.
*
* Authentication: same TRIGGER_TOKEN as trigger.php
* Authorization: Bearer <token>
* or ?token=<token>
*
* Optional query parameters:
* minutes int Lookback window in minutes (default 10, max 1440)
* node_id int Filter to a specific node
*/
require_once __DIR__ . '/../includes/functions.php';
header('Content-Type: application/json');
// ── Auth ──────────────────────────────────────────────────────────────────────
if (TRIGGER_TOKEN === '') {
http_response_code(503);
echo json_encode(['error' => 'API is disabled. Set TRIGGER_TOKEN in config.php.']);
exit;
}
$provided = '';
$auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (str_starts_with($auth_header, 'Bearer ')) {
$provided = substr($auth_header, 7);
}
if ($provided === '' && isset($_REQUEST['token'])) {
$provided = $_REQUEST['token'];
}
if (!hash_equals(TRIGGER_TOKEN, $provided)) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
// ── Parameters ────────────────────────────────────────────────────────────────
$minutes = min(1440, max(1, (int)($_GET['minutes'] ?? 10)));
$node_id = isset($_GET['node_id']) ? (int)$_GET['node_id'] : null;
// ── Query ─────────────────────────────────────────────────────────────────────
$rows = connections_since($minutes, $node_id);
$since = date('Y-m-d\TH:i:s\Z', time() - $minutes * 60);
echo json_encode([
'since' => $since,
'minutes' => $minutes,
'count' => count($rows),
'connections' => array_map(fn($r) => [
'id' => (int)$r['id'],
'occurred_at' => $r['occurred_at'],
'node_id' => (int)$r['node_id'],
'node_name' => $r['node_name'],
'src_ip' => $r['src_ip'],
'src_port' => (int)$r['src_port'],
'dst_port' => (int)$r['dst_port'],
'banner_hex' => $r['banner_hex'],
'banner_len' => (int)$r['banner_len'],
], $rows),
], JSON_PRETTY_PRINT);

View File

@@ -180,6 +180,37 @@ function run_fetch(): array {
return $results; return $results;
} }
// ── API queries ───────────────────────────────────────────────────────────────
/**
* Return connections from the last $minutes minutes, newest first.
* Optionally filtered to a single node.
*/
function connections_since(int $minutes = 10, ?int $node_id = null): array {
$since = date('Y-m-d H:i:s', time() - $minutes * 60);
if ($node_id !== null) {
$s = db()->prepare(
'SELECT c.id, c.occurred_at, c.src_ip, c.src_port, c.dst_port,
c.banner_hex, c.banner_len, n.name AS node_name, n.id AS node_id
FROM connections c JOIN nodes n ON n.id = c.node_id
WHERE c.node_id = ? AND c.occurred_at >= ?
ORDER BY c.occurred_at DESC'
);
$s->execute([$node_id, $since]);
} else {
$s = db()->prepare(
'SELECT c.id, c.occurred_at, c.src_ip, c.src_port, c.dst_port,
c.banner_hex, c.banner_len, n.name AS node_name, n.id AS node_id
FROM connections c JOIN nodes n ON n.id = c.node_id
WHERE c.occurred_at >= ?
ORDER BY c.occurred_at DESC'
);
$s->execute([$since]);
}
return $s->fetchAll();
}
// ── Dashboard stats ─────────────────────────────────────────────────────────── // ── Dashboard stats ───────────────────────────────────────────────────────────
function global_stats(): array { function global_stats(): array {

View File

@@ -1,2 +1,2 @@
<?php <?php
define('APP_VERSION', '2603.1'); define('APP_VERSION', '2603.2');