Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/clean-registry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ jobs:
steps:
- uses: NethServer/ns8-github-actions/.github/actions/delete-image@v1
with:
images: "nethsecurity-controller webssh"
images: "nethsecurity-controller webssh nethsecurity-vpn nethsecurity-api nethsecurity-ui nethsecurity-proxy"
delete_image_token: ${{ secrets.IMAGES_CLEANUP_TOKEN }}
61 changes: 61 additions & 0 deletions .github/workflows/controller-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Controller tests

on:
push:
paths:
- "controller/**"
- ".github/workflows/controller-tests.yml"
pull_request:
paths:
- "controller/**"
- ".github/workflows/controller-tests.yml"
workflow_dispatch:

defaults:
run:
working-directory: controller

jobs:
api:
name: API Tests
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Podman
run: |
sudo apt-get update
sudo apt-get install -y podman oathtool
- name: Start TimescaleDB
run: |
# renovate: datasource=docker depName=docker.io/timescale/timescaledb
podman run --rm -d --name timescaledb -p 5432:5432 -e POSTGRES_PASSWORD=password -e POSTGRES_USER=report docker.io/timescale/timescaledb:2.23.1-pg16
# Wait for DB to be ready
for i in {1..30}; do
podman exec timescaledb pg_isready -U report && break
sleep 1
done
- name: Setup Go
uses: actions/setup-go@v7
with:
go-version-file: controller/api/go.mod
- name: Test with the Go CLI
run: cd api && go test ./... -coverpkg=./... -coverprofile=coverage.out -v

smoke:
name: Smoke Tests
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y podman buildah jq
- name: Create tunsec device
run: |
sudo ip tuntap add dev tunsec mod tun
sudo ip addr add 172.21.0.1/16 dev tunsec
sudo ip link set dev tunsec up
- name: Run smoke test
run: ./test/smoke.sh
38 changes: 0 additions & 38 deletions .github/workflows/update-controller.yml

This file was deleted.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Logs
logs
!controller/api/logs/
*.log
npm-debug.log*
yarn-debug.log*
Expand Down Expand Up @@ -105,3 +106,6 @@ dist

# Tests output
tests/outputs/

# Controller API binary
controller/api/api
45 changes: 35 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,53 @@ This file provides guidance to AI agents when working with code in this reposito

## Repository overview

This is an **NS8 module** (`ns8-nethsecurity-controller`) that packages and configures an instance of [nethsecurity-controller](https://github.com/NethServer/nethsecurity-controller) on a NethServer 8 node. A single controller instance manages a fleet of NethSecurity firewall units over an OpenVPN tunnel, collecting their logs/metrics and exposing a Vue cluster-admin UI.
This is an **NS8 module** (`ns8-nethsecurity-controller`) that packages and configures an instance of nethsecurity-controller on a NethServer 8 node. A single controller instance manages a fleet of NethSecurity firewall units over an OpenVPN tunnel, collecting their logs/metrics and exposing a Vue cluster-admin UI.

The module's containers run as one systemd-managed Podman pod:
- `nethsecurity-api` — Go REST API (source lives in the separate `NethServer/nethsecurity-controller` repo, **not** in this repo; only consumed as a prebuilt image)
- `nethsecurity-vpn` — OpenVPN server (external image)
- `nethsecurity-ui` — lighttpd static UI server (external image; distinct from this repo's own `ui/`)
- `nethsecurity-proxy` — Traefik reverse proxy (external image)
- `nethsecurity-api` — Go REST API (`controller/api/`)
- `nethsecurity-vpn` — OpenVPN server (`controller/vpn/`)
- `nethsecurity-ui` — lighttpd serving the NethSecurity UI built from `NethServer/nethsecurity-ui` (`controller/ui/`; distinct from this repo's own `ui/`)
- `nethsecurity-proxy` — Traefik reverse proxy (`controller/proxy/`)
- `promtail` / `loki` — log shipping and storage
- `prometheus` — metrics scraping
- `timescale` — TimescaleDB for network-traffic/DPI/VPN time-series data
- `grafana` — dashboards over Prometheus + Loki + Timescale
- `webssh` — web SSH client, built locally from upstream `huashengdun/webssh` with a custom template

Only the module glue (`imageroot/`), the cluster-admin frontend (`ui/`), and the `webssh` UI override are implemented in this repo; the API server, VPN, UI-proxy, and other images are pulled prebuilt and pinned in `build-images.sh`.
The module glue (`imageroot/`), the cluster-admin frontend (`ui/`), the `webssh` UI override and the controller services (`controller/`) are implemented in this repo; the other images are pulled prebuilt and pinned in `build-images.sh`.

## Build

`build-images.sh` builds images with **buildah** (no top-level Containerfile — built imperatively via `buildah from/add/config/commit`). It:
1. Builds `webssh` from `python:3.13.14-alpine`, unpacking the upstream `huashengdun/webssh` release and replacing its UI with `webssh/index.html`.
2. Builds the Vue `ui/` app in a `node:24.17.0-slim` builder container (`corepack enable && yarn install --frozen-lockfile && yarn build`), then assembles the main `nethsecurity-controller` image from `scratch` with `imageroot/` → `/imageroot` and `ui/dist` → `/ui`, plus NS8 image labels (`org.nethserver.authorizations`, `org.nethserver.min-core`, `org.nethserver.images`, etc.).
2. Builds `nethsecurity-{vpn,api,ui,proxy}` with `buildah build --target dist` from `controller/<service>/Containerfile`. The module references them with the same `IMAGETAG` as the module image.
3. Builds the Vue `ui/` app in a `node:24.17.0-slim` builder container (`corepack enable && yarn install --frozen-lockfile && yarn build`), then assembles the main `nethsecurity-controller` image from `scratch` with `imageroot/` → `/imageroot` and `ui/dist` → `/ui`, plus NS8 image labels (`org.nethserver.authorizations`, `org.nethserver.min-core`, `org.nethserver.images`, etc.).

Version pins for all external images (`controller_version`, `promtail_image`, `loki_image`, `prometheus_image`, `grafana_image`, `timescale_image`, `webssh_version`) live at the top of `build-images.sh`. `controller_version` is kept in sync automatically both by Renovate (custom regex manager in `renovate.json`) and by the nightly `update-controller.yml` workflow.
Version pins for all external images (`promtail_image`, `loki_image`, `prometheus_image`, `grafana_image`, `timescale_image`, `webssh_version`) live at the top of `build-images.sh`. The nethsecurity-ui version is the `UI_VERSION` ARG in `controller/ui/Containerfile`, bumped by Renovate.

## Controller services (`controller/`)

`api/`, `vpn/`, `proxy/`, `ui/` each hold a Containerfile; `controller.te` is the SELinux policy. `ui-new/` is a Vue 3/Vite UI, not built or tested in CI yet.

Dev environment: `controller/dev.sh start|stop` runs a local pod with all services plus TimescaleDB and writes `api.env`. It needs a `tunsec` device, created once as root:

```bash
sudo ip tuntap add dev tunsec mod tun
sudo ip addr add 172.21.0.1/16 dev tunsec
sudo ip link set dev tunsec up
```

`dev.sh` defaults to images tagged with the current branch (as published by CI); run `IMAGE_TAG=latest ./dev.sh start` for images built locally with `build-images.sh`. `controller/test/smoke.sh` runs `build-images.sh`, starts the pod and checks login, units and health.

Go API tests need TimescaleDB running. Use the image pinned as `timescale_image` in `build-images.sh`:

```bash
podman run --rm -d --name timescaledb -p 5432:5432 -e POSTGRES_PASSWORD=password -e POSTGRES_USER=report <timescale_image>
cd controller/api && go test ./...
podman stop timescaledb
```

Add or update tests for any API change, and keep the README in each service directory up to date. Commit scope for controller changes is the service (`api`, `vpn`, `ui`, `proxy`), e.g. `fix(vpn): resolve authentication handshake failure`.

## UI development (`ui/`)

Expand Down Expand Up @@ -67,8 +92,8 @@ Integration tests use **Robot Framework** driven over SSH against a live NS8 nod

## CI (`.github/workflows/`)

All workflows are thin wrappers around reusable workflows in `NethServer/ns8-github-actions`; there is no dedicated local lint job:
Most workflows are thin wrappers around reusable workflows in `NethServer/ns8-github-actions`; there is no dedicated local lint job:
- `publish-images.yml` — on push, runs `build-images.sh` via `publish-branch.yml@v1`; also runs `module-info.yml@v1` and (on stable/latest releases) `scan-with-trivy.yml@v1`.
- `test-module.yml` — runs the Robot Framework suite after images publish, or manually via `workflow_dispatch`.
- `update-controller.yml` — nightly cron that bumps `controller_version` in `build-images.sh` and opens a PR.
- `controller-tests.yml` — Go API tests and `controller/test/smoke.sh`, on push/PR touching `controller/`.
- `build-apidoc.yml` / `clean-apidoc.yml` — build/clean API docs from `validate-input.json`/`validate-output.json` changes.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ns8-nethsecurity-controller

Setup and start an instance of [nethsecurity-controller](https://github.com/NethServer/nethsecurity-controller).
Setup and start an instance of [nethsecurity-controller](controller).

Each node can host multiple controller instances.

Expand Down Expand Up @@ -81,19 +81,19 @@ The module is composed by the following systemd units:

### API Server

The [api server](https://github.com/NethServer/nethsecurity-controller/tree/master/api) gives NethSecurity the ability to register itself to NS8 (through [`ns-plug`](https://dev.nethsecurity.org/nethsecurity/packages/ns-plug/)) and gives access to the on-demand generated credentials for the VPN.
The [api server](controller/api) gives NethSecurity the ability to register itself to NS8 (through [`ns-plug`](https://dev.nethsecurity.org/nethsecurity/packages/ns-plug/)) and gives access to the on-demand generated credentials for the VPN.

The API also registers the endpoints for the [Traefik Proxy](#proxy-and-ui) that allows direct interaction with the firewall, even if it's not in the same network.

### VPN

The [OpenVPN container](https://github.com/NethServer/nethsecurity-controller/tree/master/vpn) tunnels connection from the NethSecurity to the NS8 through a VPN tunnel, due to [firewall configuration](https://github.com/NethServer/ns8-nethsecurity-controller/blob/main/imageroot/actions/configure-module/20configure#L87) in NS8, no client can be reached from other clients and only client-server communication is allowed.
The [OpenVPN container](controller/vpn) tunnels connection from the NethSecurity to the NS8 through a VPN tunnel, due to [firewall configuration](https://github.com/NethServer/ns8-nethsecurity-controller/blob/main/imageroot/actions/configure-module/20configure#L87) in NS8, no client can be reached from other clients and only client-server communication is allowed.

The module uses the NS8 [TUN feature](https://dev.nethsecurity.org/ns8-core/core/tun/) to create a new network interface and assign it to the VPN container.

### Proxy and UI

The [UI](https://github.com/NethServer/nethsecurity-controller/tree/master/ui) allows the browse of the interface directly off the NethSecurity installation, this is possible due to the [Traefik Proxy](https://github.com/NethServer/nethsecurity-controller/tree/master/proxy) server that redirects the urls to the correct IP inside the VPN.
The [UI](controller/ui) allows the browse of the interface directly off the NethSecurity installation, this is possible due to the [Traefik Proxy](controller/proxy) server that redirects the urls to the correct IP inside the VPN.

### Promtail

Expand Down Expand Up @@ -243,7 +243,7 @@ journalctl _UID=$(id -u nethsecurity-controller1) --grep 'MIGRATION'
### Database maintenance

The database is used to store configuration and metrics.
See [Database design](https://github.com/NethServer/nethsecurity-controller/tree/main/api#database-design) for more details.
See [Database design](controller/api/README.md#database-design) for more details.

#### DPI stats cleanup

Expand Down
19 changes: 14 additions & 5 deletions build-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ images=()
repobase="${REPOBASE:-ghcr.io/nethserver}"
# Configure the image name
reponame="nethsecurity-controller"
controller_version="2.4.1"
promtail_image="docker.io/grafana/promtail:3.6.11"
loki_image="docker.io/grafana/loki:2.9.17"
prometheus_image="docker.io/prom/prometheus:v3.15.0"
Expand Down Expand Up @@ -53,6 +52,16 @@ buildah commit "${webssh}" "${repobase}/webssh"
# Append the image URL to the images array
images+=("${repobase}/webssh")

# Build controller service images
for service in vpn api ui proxy; do
echo "Build nethsecurity-${service} container"
buildah build --layers --target dist \
--file "controller/${service}/Containerfile" \
--tag "${repobase}/nethsecurity-${service}" \
"controller/${service}"
images+=("${repobase}/nethsecurity-${service}")
done

# Create a new empty container image
container=$(buildah from scratch)

Expand All @@ -78,10 +87,10 @@ buildah config --entrypoint=/ \
--label="org.nethserver.min-core=3.20.1" \
--label="org.nethserver.tcp-ports-demand=11" \
--label="org.nethserver.images=\
ghcr.io/nethserver/nethsecurity-vpn:$controller_version \
ghcr.io/nethserver/nethsecurity-api:$controller_version \
ghcr.io/nethserver/nethsecurity-ui:$controller_version \
ghcr.io/nethserver/nethsecurity-proxy:$controller_version \
ghcr.io/nethserver/nethsecurity-vpn:${IMAGETAG:-latest} \
ghcr.io/nethserver/nethsecurity-api:${IMAGETAG:-latest} \
ghcr.io/nethserver/nethsecurity-ui:${IMAGETAG:-latest} \
ghcr.io/nethserver/nethsecurity-proxy:${IMAGETAG:-latest} \
$promtail_image \
$loki_image \
$prometheus_image \
Expand Down
Loading
Loading