NebularStack
Website

Lunar - Quickstart

This guide takes you from a fresh install to a working RADIUS exchange three ways, in increasing realism:

  1. Testing mode: lunar answers by itself, no backend. Prove the install with radtest in one minute.

  2. With the backend simulator: run lunar_backend_sim in verbose mode next to lunar and watch the full request flow: config fetch, decode, decision.

  3. Against your own backend: the endpoints you implement to make it real.

Installation itself is covered in Lunar's INSTALL.rst in the root of the download. This guide picks up once lunar is installed.

If you're in a real hurry you can always look at our GitHub minimalistic examples: https://github.com/interstellio/examples/tree/master/lunar/radius-backend, PHP, .NET and Java. Start from the one closest to your stack.

Quick Concepts

Before the walk-throughs, two ideas make everything below obvious:

Virtual RADIUS servers. Lunar hosts one or more virtuals. Each virtual is a RADIUS server on its own IPv4 address that binds exactly three UDP ports, one per RADIUS function:

  • auth (Access-Request), default 1812.

  • acct (Accounting-Request), default 1813.

  • CoA / Disconnect (RFC 5176), default 3799.

Each virtual has a set of clients, the NAS devices allowed to talk to it, each matched by source IP and carrying a shared secret.

The backend is the brain. Lunar holds no config or policy of its own. It fetches its virtuals from your backend API over HTTP, and it POSTs every RADIUS request it receives to that backend for a decision. Lunar also runs a small HTTP API of its own for configuration and health probes. So there are two separate HTTP directions: lunar -> backend (config + decisions) and you -> lunar (management).

Testing mode is the one exception: it wires up a built-in virtual that answers locally, so you can test lunar with no backend at all.

1. Testing mode: prove the install

Enable testing mode in lunar.yaml and restart:

services:
  subscriber: true
protocol:
  radius:
    testing: true

Lunar starts a built-in TEST virtual on 127.0.0.1 with a client-test client and shared secret testing123, and mirrors authentication back so a request succeeds without any backend. Test it with radtest (see Tools for all options):

$ /opt/lunar/bin/radtest --username test --password test 127.0.0.1 testing123
...
radtest: Success

A run ending in radtest: Success after an Access-Accept confirms the install, the listener and the codec are all healthy. Never use testing mode in production.

2. See it work with the backend simulator

Testing mode proves lunar runs, but hides the backend conversation. To see that conversation (the config fetch, the decoded packet lunar posts, and the reply) run the backend simulator in verbose mode next to lunar. This is the best way to understand how a real backend integration behaves.

Turn testing mode off for this (so lunar talks to the simulator instead of answering itself), and point lunar's subscriber backend endpoint at the simulator (127.0.0.1:5555 by default).

Step 1: start the simulator in verbose mode

$ /opt/lunar/bin/lunar_backend_sim -V
Listening on 127.0.0.1:5555 (workers=512, auth_sleep=0ms, verbose=on)

In -V mode it pretty-prints every HTTP request lunar makes and every reply it sends back. It serves one fixed virtual (TEST / client-test / testing123, auth 1812 / acct 1813 / CoA 3799) and always accepts auth, mirroring the request attributes onto the reply. See Tools for the full behavior.

Step 2: start lunar

Start (or restart) lunar with the subscriber service enabled and its backend endpoint pointing at the simulator. As lunar comes up it performs its config fetch: it calls the backend for its virtuals:

GET /v1/lunar/<server_id>/virtuals

The simulator returns the one TEST virtual. Lunar binds that virtual's three UDP ports and is now serving. In the simulator's verbose output you will see this GET and the virtual JSON it returned. This is exactly the same fetch lunar does against a real backend. The simulator just always answers with the test virtual.

Step 3: send a request and watch the flow

Fire an Access-Request at the virtual:

$ /opt/lunar/bin/radtest --username alice --password secret 127.0.0.1 testing123

Now watch the simulator's verbose log. Lunar decoded the UDP packet and POSTed it to the backend auth route as JSON:

POST /v1/lunar/<server_id>/auth/TEST
{
  "code": "access-request",
  "attributes": {
    "user-name": {"values": ["alice"]},
    "user-password": {"values": ["secret"]},
    "nas-ip-address": {"values": ["127.0.0.1"]}
  }
}

The simulator replies access-accept, mirroring the attributes back:

{
  "code": "access-accept",
  "attributes": {
    "user-name": {"values": ["alice"]}
  }
}

Lunar builds the Access-Accept on the RADIUS wire back to radtest, which prints radtest: Success. You have now seen the whole path end to end: UDP in -> decode -> backend POST -> decision -> encode -> UDP out.

Step 4: try accounting and a disconnect

The same flow drives the other functions. An accounting Start POSTs to .../acct/TEST and gets an accounting-response. An outbound Disconnect is a single API call to lunar that relays a disconnect-request to the NAS (see the coapod route below and in the RADIUS Integration guide). Watch each in the simulator's verbose log to see the request/response shapes.

3. Against your own backend

To make it real you replace the simulator with your backend. Your backend implements the outbound calls lunar makes (lunar -> backend), and calls lunar's management API when it needs to change or inspect config (backend -> lunar). The RADIUS Integration guide specifies all of these precisely. Here is the shortlist.

Endpoints your backend must serve (lunar -> backend)

These are the calls lunar originates. Implement them and lunar works. Each route's full request and response spec is shown inline below it.

Return this server's virtuals (with their clients). Lunar calls this on startup and on reload. This is the config fetch you saw the simulator answer.

GET
/v1/lunar/{server_id}/virtuals
(backend)

Fetch All Virtuals for This Server.
Service
backend

Return one virtual's current config (used when a single virtual is reloaded).

GET
/v1/lunar/{server_id}/virtual/{virtual_id}
(backend)

Fetch One Virtual's Config.
Service
backend

Decide an Access-Request (or Status-Server). Return access-accept / access-reject with any reply attributes.

POST
/v1/lunar/{server_id}/auth/{virtual_id}
(backend)

Authentication Decision (auth).
Service
backend

Receive an accounting record. Return accounting-response (a simple acknowledgment).

POST
/v1/lunar/{server_id}/acct/{virtual_id}
(backend)

Accounting Record (acct).
Service
backend

Decide an inbound CoA / Disconnect from a NAS.

POST
/v1/lunar/{server_id}/coa/{virtual_id}
(backend)

Inbound CoA / Disconnect Decision (coa).
Service
backend

Receive lunar's remote log lines (accept and store/forward).

POST
/v1/lunar/{server_id}/log
(backend)

Remote Logging.
Service
backend

Health probe, answer 200 when your backend is ready.

GET
/v1/lunar/ping
(backend)

Health Ping.
Service
backend

The request and response bodies use lunar's packet/attribute JSON, a top-level code and an attributes object keyed by attribute name. That format, including data types, enums and credential handling, is documented once in The JSON request format, and the emitted (lower-case) form is shown throughout Kafka when using Kafka event streaming.

Endpoints on lunar you can call (backend -> lunar)

These are the calls you make on lunar, all under /v1 and gated by the x-secret header. Each route's full spec is shown inline below it.

Lunar's health and vitals (also checks the backend).

GET
/v1/status
(lunar)

Instance Health and Vitals.
Service
lunar

List the virtuals lunar currently serves.

GET
/v1/subscriber/radius/virtuals
(lunar)

List Running Virtuals.
Service
lunar

Fetch one virtual's live record.

GET
/v1/subscriber/radius/virtual/{virtual_id}
(lunar)

Get One Virtual.
Service
lunar

List a virtual's clients.

GET
/v1/subscriber/radius/virtual/{virtual_id}/clients
(lunar)

List a Virtual's Clients.
Service
lunar

Fetch one client's live record.

GET
/v1/subscriber/radius/virtual/{virtual_id}/client/{client_id}
(lunar)

Get One Client.
Service
lunar

(Re)load a virtual. Lunar re-fetches that virtual from your backend and applies it in place, with no restart and no disruption to live traffic.

POST
/v1/subscriber/radius/virtual/{virtual_id}
(lunar)

Update or Start a Virtual.
Service
lunar

Stop a virtual.

DELETE
/v1/subscriber/radius/virtual/{virtual_id}
(lunar)

Stop a Virtual.
Service
lunar

Relay a CoA / Disconnect to a NAS in one call, returning the decoded ACK / NAK (see the Overview and the RADIUS Integration guide).

POST
/v1/subscriber/radius/coapod
(lunar)

Relay a CoA / Disconnect to a NAS.
Service
lunar

Run a packet through the backend without touching the RADIUS wire. Returns the backend's reply verbatim. Ideal for integration testing.

POST
/v1/subscriber/radius/virtual/{virtual_id}/client/{client_id}/test
(lunar)

Test a Request Against the Backend.
Service
lunar

A full-path liveness probe that builds a Status-Server, sends it through the whole engine and back, and returns 200 {"status": "ok"} when the entire chain is alive.

GET
/v1/subscriber/radius/{virtual_id}/ping
(lunar)

Virtual Liveness Probe.
Service
lunar

The typical lifecycle: you create a virtual and its clients in your backend, then call POST /v1/subscriber/radius/virtual/<virtual_id> on lunar so it fetches and starts that virtual. From then on lunar serves the wire and posts every decision to your backend.

Where to go next

  • RADIUS Integration is the complete integration reference: every route, the full packet/attribute JSON format, credential handling and the exact outbound calls. Read this to build your backend.

  • Authentication explains how PAP / CHAP / MS-CHAP reach your backend, and the single-call design behind it.

  • Dictionaries covers the attribute dictionaries and how to extend them.

  • Kafka covers optional authentication / accounting event streaming.

  • Tools covers radtest and lunar_backend_sim in full.

Example backend implementations in several languages live in the Interstellio examples repository on GitHub at: https://github.com/interstellio/examples/tree/master/lunar/radius-backend, covering Python (Flask, Falcon, Falcon-async, FastAPI), Go, Node.js (Express), PHP, .NET and Java.