Compare commits

..

12 Commits

Author SHA1 Message Date
mid
5af760f5ee Translated using Weblate (Japanese)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ja/
2025-12-01 10:00:26 +01:00
jokob-sk
fbb4a2f8b4 BE: added /auth endpoint
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 09:24:44 +11:00
jokob-sk
54bce6505b PLG: SNMPDSC Fortinet support #1324
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 09:11:23 +11:00
jokob-sk
6da47cc830 DOCS: migration docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 08:32:22 +11:00
jokob-sk
9cabbf3622 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-12-01 08:03:28 +11:00
jokob-sk
6c28a08bee FE: YYYY-DD-MM timestamp handling #1312
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 08:03:13 +11:00
Sylvain Pichon
86e3decd4e Translated using Weblate (French)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-11-30 08:01:30 +00:00
Safeguard
e14e0bb9e8 Translated using Weblate (Russian)
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-11-30 08:01:28 +00:00
mid
b6023d1373 Translated using Weblate (Japanese)
Currently translated at 88.8% (678 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ja/
2025-11-30 08:01:24 +00:00
Максим Горпиніч
1812cc8ef8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-11-30 08:00:21 +00:00
jokob-sk
5df39f984a BE: docker version github action work #1320
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 12:00:18 +11:00
jokob-sk
d007ed711a BE: docker version github action work #1320
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:58:11 +11:00
12 changed files with 960 additions and 808 deletions

View File

@@ -47,6 +47,12 @@ jobs:
id: get_version
run: echo "version=Dev" >> $GITHUB_OUTPUT
# --- debug output
- name: Debug version
run: |
echo "GITHUB_REF: $GITHUB_REF"
echo "Version: '${{ steps.get_version.outputs.version }}'"
# --- Write the timestamped version to .VERSION file
- name: Create .VERSION file
run: echo "${{ steps.timestamp.outputs.version }}" > .VERSION

View File

@@ -49,9 +49,17 @@ jobs:
id: get_version
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
# --- debug output
- name: Debug version
run: |
echo "GITHUB_REF: $GITHUB_REF"
echo "Version: '${{ steps.get_version.outputs.version }}'"
echo "Version prev: '${{ steps.get_version_prev.outputs.version }}'"
# --- Write version to .VERSION file
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" > .VERSION
run: echo -n "${{ steps.get_version.outputs.version }}" > .VERSION
# --- Generate Docker metadata and tags
- name: Docker meta

View File

@@ -239,29 +239,7 @@ services:
4. Start the container and verify everything works as expected.
5. Stop the container.
6. Perform a one-off migration to the latest `netalertx` image and `20211` user:
> [!NOTE]
> The example below assumes your `/config` and `/db` folders are stored in `local_data_dir`.
> Replace this path with your actual configuration directory. `netalertx` is the container name, which might differ from your setup.
```sh
docker run -it --rm --name netalertx --user "0" \
-v /local_data_dir/config:/data/config \
-v /local_data_dir/db:/data/db \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
ghcr.io/jokob-sk/netalertx:latest
```
...or alternatively execute:
```bash
sudo chown -R 20211:20211 /local_data_dir
sudo chmod -R a+rwx /local_data_dir
```
7. Stop the container
8. Update the `docker-compose.yml` as per example below.
6. Update the `docker-compose.yml` as per example below.
```yaml
services:
@@ -288,5 +266,33 @@ services:
- "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# 🆕 New "tmpfs" section END 🔼
```
7. Perform a one-off migration to the latest `netalertx` image and `20211` user.
9. Start the container and verify everything works as expected.
> [!NOTE]
> The examples below assumes your `/config` and `/db` folders are stored in `local_data_dir`.
> Replace this path with your actual configuration directory. `netalertx` is the container name, which might differ from your setup.
**Automated approach**:
Run the container with the `--user "0"` parameter. Please note, some systems will require the manual approach below.
```sh
docker run -it --rm --name netalertx --user "0" \
-v /local_data_dir/config:/data/config \
-v /local_data_dir/db:/data/db \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
ghcr.io/jokob-sk/netalertx:latest
```
Stop the container and run it as you would normally.
**Manual approach**:
Use the manual approach if the Automated approach fails. Execute the below commands:
```bash
sudo chown -R 20211:20211 /local_data_dir
sudo chmod -R a+rwx /local_data_dir
```
8. Start the container and verify everything works as expected.

View File

@@ -390,12 +390,18 @@ function localizeTimestamp(input) {
}
// 2. European DD/MM/YYYY
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?$/);
if (match) {
let [, d, m, y, t = "00:00:00", tzPart = ""] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
const dNum = parseInt(d, 10);
const mNum = parseInt(m, 10);
if (dNum <= 12 && mNum > 12) {
} else {
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5 ? t + ":00" : t}${tzPart}`;
return formatSafe(iso, tz);
}
}
// 3. US MM/DD/YYYY
match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);

2
front/php/templates/language/fr_fr.json Executable file → Normal file
View File

@@ -311,7 +311,7 @@
"Gen_Filter": "Filtrer",
"Gen_Generate": "Générer",
"Gen_InvalidMac": "Adresse MAC invalide.",
"Gen_Invalid_Value": "",
"Gen_Invalid_Value": "Une valeur invalide a été renseignée",
"Gen_LockedDB": "Erreur - La base de données est peut-être verrouillée - Vérifier avec les outils de dév via F12 -> Console ou essayer plus tard.",
"Gen_NetworkMask": "Masque réseau",
"Gen_Offline": "Hors ligne",

File diff suppressed because it is too large Load Diff

View File

@@ -311,7 +311,7 @@
"Gen_Filter": "Фильтр",
"Gen_Generate": "Генерировать",
"Gen_InvalidMac": "Неверный Mac-адрес.",
"Gen_Invalid_Value": "",
"Gen_Invalid_Value": "Введено некорректное значение",
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
"Gen_NetworkMask": "Маска сети",
"Gen_Offline": "Оффлайн",

2
front/php/templates/language/uk_ua.json Executable file → Normal file
View File

@@ -311,7 +311,7 @@
"Gen_Filter": "Фільтр",
"Gen_Generate": "Генерувати",
"Gen_InvalidMac": "Недійсна Mac-адреса.",
"Gen_Invalid_Value": "",
"Gen_Invalid_Value": "Введено недійсне значення",
"Gen_LockedDB": "ПОМИЛКА БД може бути заблоковано перевірте F12 Інструменти розробника -> Консоль або спробуйте пізніше.",
"Gen_NetworkMask": "Маска мережі",
"Gen_Offline": "Офлайн",

View File

@@ -14,6 +14,14 @@ Specify the following settings in the Settings section of NetAlertX:
If unsure, please check [snmpwalk examples](https://www.comparitech.com/net-admin/snmpwalk-examples-windows-linux/).
Supported output formats:
```
ipNetToMediaPhysAddress[3][192.168.1.9] 6C:6C:6C:6C:6C:b6C1
IP-MIB::ipNetToMediaPhysAddress.17.10.10.3.202 = STRING: f8:81:1a:ef:ef:ef
mib-2.3.1.1.2.15.1.192.168.1.14 "2C F4 32 18 61 43 "
```
### Setup Cisco IOS
Enable IOS SNMP service and restrict to selected (internal) IP/Subnet.

View File

@@ -30,7 +30,7 @@ RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
def main():
mylog('verbose', ['[SNMPDSC] In script '])
mylog('verbose', f"[{pluginName}] In script ")
# init global variables
global snmpWalkCmds
@@ -57,7 +57,7 @@ def main():
commands = [snmpWalkCmds]
for cmd in commands:
mylog('verbose', ['[SNMPDSC] Router snmpwalk command: ', cmd])
mylog('verbose', [f"[{pluginName}] Router snmpwalk command: ", cmd])
# split the string, remove white spaces around each item, and exclude any empty strings
snmpwalkArgs = [arg.strip() for arg in cmd.split(' ') if arg.strip()]
@@ -72,7 +72,7 @@ def main():
timeout=(timeoutSetting)
)
mylog('verbose', ['[SNMPDSC] output: ', output])
mylog('verbose', [f"[{pluginName}] output: ", output])
lines = output.split('\n')
@@ -80,6 +80,8 @@ def main():
tmpSplt = line.split('"')
# Expected Format:
# mib-2.3.1.1.2.15.1.192.168.1.14 "2C F4 32 18 61 43 "
if len(tmpSplt) == 3:
ipStr = tmpSplt[0].split('.')[-4:] # Get the last 4 elements to extract the IP
@@ -89,7 +91,7 @@ def main():
macAddress = ':'.join(macStr)
ipAddress = '.'.join(ipStr)
mylog('verbose', [f'[SNMPDSC] IP: {ipAddress} MAC: {macAddress}'])
mylog('verbose', [f"[{pluginName}] IP: {ipAddress} MAC: {macAddress}"])
plugin_objects.add_object(
primaryId = handleEmpty(macAddress),
@@ -100,8 +102,40 @@ def main():
foreignKey = handleEmpty(macAddress) # Use the primary ID as the foreign key
)
else:
mylog('verbose', ['[SNMPDSC] ipStr does not seem to contain a valid IP:', ipStr])
mylog('verbose', [f"[{pluginName}] ipStr does not seem to contain a valid IP:", ipStr])
# Expected Format:
# IP-MIB::ipNetToMediaPhysAddress.17.10.10.3.202 = STRING: f8:81:1a:ef:ef:ef
elif "ipNetToMediaPhysAddress" in line and "=" in line and "STRING:" in line:
# Split on "=" → ["IP-MIB::ipNetToMediaPhysAddress.xxx.xxx.xxx.xxx ", " STRING: aa:bb:cc:dd:ee:ff"]
left, right = line.split("=", 1)
# Extract the MAC (right side)
macAddress = right.split("STRING:")[-1].strip()
macAddress = normalize_mac(macAddress)
# Extract IP address from the left side
# tail of the OID: last 4 integers = IPv4 address
oid_parts = left.strip().split('.')
ip_parts = oid_parts[-4:]
ipAddress = ".".join(ip_parts)
mylog('verbose', [f"[{pluginName}] (fallback) IP: {ipAddress} MAC: {macAddress}"])
plugin_objects.add_object(
primaryId = handleEmpty(macAddress),
secondaryId = handleEmpty(ipAddress),
watched1 = '(unknown)',
watched2 = handleEmpty(snmpwalkArgs[6]),
extra = handleEmpty(line),
foreignKey = handleEmpty(macAddress)
)
continue
# Expected Format:
# ipNetToMediaPhysAddress[3][192.168.1.9] 6C:6C:6C:6C:6C:b6C1
elif line.startswith('ipNetToMediaPhysAddress'):
# Format: snmpwalk -OXsq output
parts = line.split()
@@ -110,7 +144,7 @@ def main():
ipAddress = parts[0].split('[')[-1][:-1]
macAddress = normalize_mac(parts[1])
mylog('verbose', [f'[SNMPDSC] IP: {ipAddress} MAC: {macAddress}'])
mylog('verbose', [f"[{pluginName}] IP: {ipAddress} MAC: {macAddress}"])
plugin_objects.add_object(
primaryId = handleEmpty(macAddress),
@@ -121,7 +155,7 @@ def main():
foreignKey = handleEmpty(macAddress)
)
mylog('verbose', ['[SNMPDSC] Entries found: ', len(plugin_objects)])
mylog('verbose', [f"[{pluginName}] Entries found: ", len(plugin_objects)])
plugin_objects.write_result_file()

View File

@@ -87,7 +87,8 @@ CORS(
r"/dbquery/*": {"origins": "*"},
r"/messaging/*": {"origins": "*"},
r"/events/*": {"origins": "*"},
r"/logs/*": {"origins": "*"}
r"/logs/*": {"origins": "*"},
r"/auth/*": {"origins": "*"}
},
supports_credentials=True,
allow_headers=["Authorization", "Content-Type"],
@@ -744,6 +745,23 @@ def sync_endpoint():
return jsonify({"success": False, "message": "ERROR: No allowed", "error": "Method Not Allowed"}), 405
# --------------------------
# Auth endpoint
# --------------------------
@app.route("/auth", methods=["GET"])
def check_auth():
if not is_authorized():
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
elif request.method == "GET":
return jsonify({"success": True, "message": "Authentication check successful"}), 200
else:
msg = "[sync endpoint] Method Not Allowed"
write_notification(msg, "alert")
mylog("verbose", [msg])
return jsonify({"success": False, "message": "ERROR: No allowed", "error": "Method Not Allowed"}), 405
# --------------------------
# Background Server Start
# --------------------------

View File

@@ -0,0 +1,66 @@
# tests/test_auth.py
import sys
import os
import pytest
# Register NetAlertX directories
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from helper import get_setting_value # noqa: E402
from api_server.api_server_start import app # noqa: E402
@pytest.fixture(scope="session")
def api_token():
"""Load API token from system settings (same as other tests)."""
return get_setting_value("API_TOKEN")
@pytest.fixture
def client():
"""Flask test client."""
with app.test_client() as client:
yield client
def auth_headers(token):
return {"Authorization": f"Bearer {token}"}
# -------------------------
# AUTH ENDPOINT TESTS
# -------------------------
def test_auth_ok(client, api_token):
"""Valid token should allow access."""
resp = client.get("/auth", headers=auth_headers(api_token))
assert resp.status_code == 200
data = resp.get_json()
assert data is not None
assert data.get("success") is True
assert "successful" in data.get("message", "").lower()
def test_auth_missing_token(client):
"""Missing token should be forbidden."""
resp = client.get("/auth")
assert resp.status_code == 403
data = resp.get_json()
assert data is not None
assert data.get("success") is False
assert "not authorized" in data.get("message", "").lower()
def test_auth_invalid_token(client):
"""Invalid bearer token should be forbidden."""
resp = client.get("/auth", headers=auth_headers("INVALID-TOKEN"))
assert resp.status_code == 403
data = resp.get_json()
assert data is not None
assert data.get("success") is False
assert "not authorized" in data.get("message", "").lower()