Dataclick Olimpo ☀️JuNeDNS Backend Safe, Lightweight and Full DNS Server ideal for single or hosting servers https://www.junedns.com
🌐 Backend for JuNeDNS Server
JuNeDNS is a lightweight DNS server and backend created in Node.js with a fully functionality and easy installation and use. With templates to easily create domain zones.
Eduardo Ruiz <eruiz@dataclick.es>
JuNe / JUst NEeded Philosophy
- Source code using less code as possible So you can understand code and find bugs easier.
- Few and optimized lines is better Elegant design.
- Avoid external dependencies abuse/bloated, and possible third-party bugs Less files size, better and faster to the interpreter.
- Clear and useful documentation with examples and without verbose Get to the point.
- Avoid showing unsolicited popups, notifications or messages in frontend For better User eXperience.
- Simple UI, without many menus/options and with few clicks to get to sites.
- Consequences of having a lot of code (and for simple things): Having to work and search through many files and folders with a lot of wasted time, successive errors due to missing unknown files, madness to move a code to another project, errors due to recursive dependencies difficult to locate, complexity or impossibility to migrate to new versions, unfeasibility to follow the trace with so much code, risk of new errors if the functionality is extended, problems not seen at the first sight, general slowness in the whole development due to excessive and unnecessary code.
1. Install and configure JuNeDNS Server
2. Add tables to JuNeDNS Server database from mysql-backend.sql
Add/Combine with existent MySQL/MariaDB JuNeDNS Server database using mysql-backend.sql
For templates, users and permissions functionality.
File junedns.conf used by JuNeDNS Server and JuNeDNS Backend, be sure backend_*
variables are setted.
...
// For backend
backend_url=http://localhost:9053
backend_cert=
backend_key=
backend_api=false
Changes requires restart JuNeDNS Backend / You need root privileges for installation.
At the very first run it will be created for session token and encrypt users passwords in database backend_token=PRIVATE_KEYPAIR
- 🐧Linux: For security reasons limit file access with
chmod 600 junedns.conf
Use backend_cert / backend_key for SSL (Let´s Encrypt) certificates path if you want HTTPS, and remember to set URL without port backend_url=https://mybackend.tld
Or maybe you prefer to use HTTP and proxy HTTPS with Nginx:
server {
listen 443 ssl;
listen [::]:443 ssl; #http3?
server_name mybackend.tld;
ssl_certificate ACME_PATH/mydomain.tld/fullchain.cer;
ssl_certificate_key ACME_PATH/mydomain.tld/mydomain.tld.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:9053;
}
}
Run JuNeDNS Backend using Node.js with source code or from binary:
Download or clone this repository.
Install dependencies npm install
(JuNe 1 dependence: MySQL).
Requirements node.js and Node Package Manager (NPM).
-
Running from command line (for example to debug) -
node backend.js
ornpm start
-
Running as service
-
🐧Linux:
- Use same JuNeDNS Server config file junedns.conf, you could create a symbolic link
ln -s /etc/junedns/junedns.conf /etc/junedns_backend/junedns.conf
- Create or copy Systemctl service
junedns-backend.service
in folder/etc/systemd/system
or/usr/lib/systemd/system
use ExecStart Source code execution line and adjust path if necessary. - Enable and start service
systemctl enable junedns-backend.service && systemctl start junedns-backend.service
check if runningsystemctl status junedns-backend.service
- Use same JuNeDNS Server config file junedns.conf, you could create a symbolic link
-
🪟 Windows:
- Use same JuNeDNS Server config file junedns.conf and set correctly PATHs, you could create a symbolic link:
mklink "JuNeDNS_PATH\junedns.conf" "JuNeDNS_Backend_PATH\junedns.conf"
- Create service
sc create "JuNeDNS Backend" binPath="NodeJS_PATH\node JuNeDNS_Backend_PATH\backend.js"
start withnet start "JuNeDNS Backend"
- Use same JuNeDNS Server config file junedns.conf and set correctly PATHs, you could create a symbolic link:
-
Download and decompress your version: 🐧Linux (20 Mb), 🪟 Windows (17 Mb) or 🍎MacOS (20 Mb). Create junedns.conf
-
Running from command line (for example to debug with log=3) -
./junedns-backend
orjunedns-backend
-
Running as service
-
🐧Linux:
- Use same JuNeDNS Server path
/etc/junedns
and copy file in it. - Add executable permission
chmod +x /etc/junedns/junedns-backend
- Use same JuNeDNS Server config file junedns.conf
- Create or copy Systemctl service
junedns-backend.service
in folder/etc/systemd/system
or/usr/lib/systemd/system
use ExecStart Binary execution line and adjust path if necessary. - Enable and start service
systemctl enable junedns-backend.service && systemctl start junedns-backend.service
check if runningsystemctl status junedns-backend.service
- Use same JuNeDNS Server path
-
🪟 Windows:
- Use same JuNeDNS Server path
C:\Users\[USER]\AppData\Roaming\JuNeDNS
and copy file in it. - Use same JuNeDNS Server config file junedns.conf in
C:\Users\[USER]\AppData\Roaming\JuNeDNS\junedns.conf
- Create service
sc create "JuNeDNS Backend" binPath="C:\Users\[USER]\AppData\Roaming\JuNeDNS\junedns-backend.exe"
start withnet start "JuNeDNS Backend"
- Use same JuNeDNS Server path
-
🐧Linux: Uncomment the ExecStart line you need for junedns-backend.service
[Unit]
Description=JuNeDNS Backend
After=network.target
[Service]
Type=simple
# Select only 1 ExecStart
#ExecStart=/etc/junedns/junedns-backend #Binary execution
#ExecStart=/usr/bin/node /etc/junedns_backend/backend.js #Source code execution
Restart=always
TimeoutStartSec=0
[Install]
WantedBy=default.target
Create the admin user to login and first steps, using createuser param in command line:
node backend.js createuser USER PASSWORD
- JSON Content-Type requests.
- POST variables in BODY in JSON format.
- You can send
lang
GET parameter for language/login?lang=es
just when changed, token updated. - Returns variables in JSON.
- Token must be in header
x-access-token
except for login, noip or API. - Users/templates management or create/delete domains only if current user
is_admin=1
- Domain and Records changes only if
users.is_admin=1
orpermissions.readonly=0
| Endpoint | Action | Method | POST Variables | Return variables | | --- | --- | :---: | --- | | /login | Login user (all users) | POST | user, passwd | user, types | | /users | Retrieve users | GET | - | users array | | /users | Create user | POST | code, passwd, name, is_admin, domains | - | | /users/:id | Get user | GET | - | - | | /users/:id | Change user | POST | code, passwd, name, is_admin, domains | - | | /users/:id | Delete user | DELETE | - | - | | /templates | Retrieve templates with records count | GET | - | templates array | | /templates | Create template | POST | name, description, is_default | - | | /templates/:id | Get template | GET | - | - | | /templates/:id | Change template | POST | name, description, is_default, records | - | | /templates/:id | Delete template | DELETE | - | - | | /templates/:id/records | Create template record | POST | name, type, content, ttl, prio | - | | /templates/:id/records/:rid | Change template record | POST | name, type, content, ttl, prio | - | | /templates/:id/records/:rid | Delete template record | DELETE | - | - | | /domains | Retrieve domains with records count (all users) | GET | - | domains array (according permissions) | | /domains | Create domain | POST | name, template, users | - | | /domains/:name | Get domain | GET (all users) | - | domain and records | | /domains/:name | Change domain template | POST | template, users | - | | /domains/:name | Delete domain | DELETE | - | - | | /domains/:name/records | Create record | POST | name, type, content, ttl, prio, disabled, no_ip | - | | /domains/:name/records/:rid | Change record | POST | name, type, content, ttl, prio, disabled, no_ip | - | | /domains/:name/records/:rid | Delete record | DELETE | - | - | | /noip/:token | Change No-IP value | GET | - | - | | /api/:apikey/:domain | Create/change record from API | POST | name, type, content | - | | /api/:apikey/:name/:type | Delete record from API | DELETE | - | - |
JuNeDNS No-IP uses /noip/:token endpoint to update IP value. /api/:apikey/... API endpoints for Let´s Encrypt or other service you need.
Required fields are not, for POST variables if default value.
Field | Type | Required | Default | Definition |
---|---|---|---|---|
id | Integer | - | - | User Id |
code | String | ✔ | - | User code |
passwd | String | ✔ | - | User password |
name | String | - | - | User name |
is_admin | Bool | ✔ | 0 | User is admin (for users or full management) |
domains | Array | - | - | For no-admin users, for create/change actions with domains access and if readonly |
domains sample [{"domain_id": 1, "readonly": 0}, {"domain_id": 3, "readonly": 1}] |
Field | Type | Required | Default | Definition |
---|---|---|---|---|
id | Integer | - | - | Domain Id |
name | String | ✔ | - | Domain name in Punycode |
nopunycode | String | - | - | Domain name |
template | Integer | - | 1 | Template to use to create zone |
users | Array | - | - | For no-admin users, for create/change actions with users access and if readonly |
users Only is taken if admin user, sample [{"user_id": 1, "readonly": 0}, {"user_id": 3, "readonly": 1}] |
Field | Type | Required | Default | Definition |
---|---|---|---|---|
id | Integer | - | - | Record Id |
name | String | ✔ | - | Record name in Punycode |
type | String | ✔ | - | DNS type (from login available types) |
content | String | ✔ | - | Record content |
ttl | Integer | 259200 | Record Time To Live / Expiry (seconds) | |
prio | Integer | - | - | Priority only for MX |
disabled | Bool | ✔ | 0 | Record is disabled |
no_ip | String | - | - | No-IP automatized token |
Although domain name is inside record name, for security reasons don´t allow users to change it and show it as read only (like Frontend does), JuNeDNS Server checks Root Zone with record, and JuNeDNS Backend parses this value to avoid spoofing.
Field | Type | Required | Default | Definition |
---|---|---|---|---|
id | Integer | - | - | Template Id |
name | String | ✔ | - | Template name |
description | String | - | - | Template description |
is_default | Bool | ✔ | 0 | Is default selected template for new domains |
records | Array | - | - | Template records |
Field | Type | Required | Default | Definition |
---|---|---|---|---|
id | Integer | - | - | Template record Id |
name | String | ✔ | - | Template record name |
type | String | ✔ | - | Template record type |
content | String | ✔ | - | Template record content |
ttl | Integer | 259200 | Template record Time To Live / Expiry (seconds) | |
prio | Integer | - | - | Template record priority only for MX |
Records in templates using wildcards that are replaced with real values:
Wildcard | Description | Sample |
---|---|---|
%d% | Domain name | mydomain.tld |
%m% | Main domain where NS point to, setted in config main_domain |
nsdomain.tld |
%ip4% | IPv4 setted in config ipv4 |
1.2.3.4 |
%ip6% | IPv6 setted in config ipv6 ignored if not IPv6 |
i:want:an:ipv6:address:so:change:me |
Have a look to some template records as SOA or SPF to check that are correct values (ns1, info...).
Two templates to create domain DNS zones, you can create your owns in database tables:
id | Name | Description | Default |
---|---|---|---|
1 | Default | Normal DNS zone with MX on server | ✔ |
2 | With Google Workspace | DNS zone with MX for Google Workspace | - |
With translated JSON message as message
variable in return BODY, or content response if 200:
| Status | Response | Sample message | | :---: | --- | --- | --- | | 200 | Ok | - | | 201 | Created | Created | | 400 | Bad Request | Already exists | | 401 | Unauthorized | You must login | | 403 | Forbidden | You have not permissions to create domains | | 404 | Not found | Not found mydomain.tld |
Availables for lang
GET parameter.
Help us to translate JuNeDNS in your language 📩 info@junedns.com
| Code | Short code | Language | | :---: | --- | | en-US | en | 🇬🇧 English | | es-ES | es | 🇪🇸 Español | | fr-FR | fr | 🇫🇷 Français | | de-DE | de | 🇩🇪 Deutsch | | it-IT | it | 🇮🇹 Italiano | | pt-PT | pt | 🇵🇹 Português | | zh-CN | ch | 🇨🇳 中文 |
First try ´code´ next ´Short code´ from window.navigator.language (default en-US)
Return all available DNS records, with name and type (str, int8, int16, int32, ipv4, ipv6 or array). Sample:
const types = {
SOA: {primary: 'str', admin: 'str', serial: 'int32', refresh: 'int32', retry: 'int32', expiration: 'int32', minimum: 'int32'},
A: {address: 'ipv4'},
CAA: {flags: [0, 1], tag: ['issue', 'issuewild', 'iodef'], value: 'str'},
...
};
is_admin per user, readonly per user+domain:
Actions | is_admin=1 | is_admin=0, readonly=0 | is_admin=0, readonly=1 | is_admin=0 |
---|---|---|---|---|
Users/templates management | ✅ | ❌ | ❌ | ❌ |
Domains create/delete | ✅ | ❌ | ❌ | ❌ |
Domains list | ✅ | ✅ | ✅ | ❌ |
Records create/change/delete | ✅ | ✅ | ❌ | ❌ |
Records list | ✅ | ✅ | ✅ | ❌ |
With cURL (you don´t need Postman):
- Login:
curl -v -X POST -H "Content-Type: application/json" -d "{\"user\": \"USER\", \"passwd\": \"\"}" http://localhost:9053/login
- Create domain without template:
curl -v -X POST -H "Content-Type: application/json" -H "x-access-token: TOKEN" -d "{\"name\": \"mydomain.tld\", \"template\": 0}" http://localhost:9053/domains
- Delete record 22 from domain mydomain.tld:
curl -v -X DELETE -H "Content-Type: application/json" -H "x-access-token: TOKEN" http://localhost:9053/domains/mydomain.tld/records/22
You can use log
to share parameter with JuNeDNS Server or use backend_log
For security, maximum log size per file will be 50 Mb or truncate.
Line formats in junedns-backend.log
when log=2
(or 3) in junedns.conf
- LGN = Login, NOI = No-IP, API, or ERR = Error.
yyyy-mm-dd hh:mm:ss [LGN|NOI|API|ERR] IP: <ip> message
- Sample of login:
2024-01-01 09:45:05 [LGN] IP: <1.2.3.4> USER user
- Sample of No-IP / API change request (token with 10 first chars):
2024-01-01 09:45:05 [NOI] IP: <1.2.3.4> TOKEN token
2024-01-01 09:45:05 [API] IP: <1.2.3.4> APIKEY apikey
- Error sample if log is not 0 in
junedns.conf
maybeUSER
if login:2024-01-01 09:45:05 [ERR] IP: <1.2.3.4> USER user
2024-01-01 09:45:05 [ERR] IP: <1.2.3.4> API api
- ...or if No-IP request invalid:
2024-01-01 09:45:05 [ERR] IP: <1.2.3.4> NOIP token
User or No-IP token will be truncated to 20 first characters due security.
🐧Linux: Add rotate log functionality and keep in mind that log file will be increase over time.
- Your could change log file path to
/var/log/junedns-backend.log
and changing const flog value in backend.js - Create file
/etc/logrotate.d/junedns-backend
and set correctly JuNeDNS_PATH
JuNeDNS_PATH/junedns-backend.log {
daily
missingok
rotate 4
compress
copytruncate
create 600 root root
}
Get executable compiled in folder dist/ (x64 bits) for: 🐧Linux, 🪟 Windows and 🍎MacOS.
- Rename package.json to package.bak.json
- Then rename package.compile.json to package.json to use Package your Node.js (pkg)
- And run
npm run build
or by platformnpm run build-linux
(or build-win, build-macos)
Using acme.sh to create/renew SSL certificates for HTTPS, and create TXT domain challenge. Different way to create/change record using Backend internal API with cURL, instead of dns_junedns.sh from JuNeDNS Server that uses MySQL/MariaDB client.
- Copy or replace dns_junednsapi.sh to folder
ACME_PATH/dnsapi
- Be sure cURL installed on server
sudo apt install curl
- Change
backend_api=true
injunedns.conf
then restart and you see a newbackend_apikey
value. - Add this API key and backend URL values to
ACME_PATH/account.conf
JUNEDNSAPI_URL='http://localhost:9053'
JUNEDNSAPI_APIKEY='SAME_APIKEY_AS_junedns.conf_backend_apikey'
Then to create a new certificate:
ACME_PATH/acme.sh --issue --dns dns_junednsapi -d "mydomain.tld" --server letsencrypt
Let´s Encrypt is free but only 3 months validity.
Add cron task to automatically renew SSL certificates:
ACME_PATH/acme.sh --home "ACME_PATH" --renew-all --stopRenewOnError --server letsencrypt --cron
Use --server letsencrypt to allow * wildcard domains (default ZeroSSL not supported).
✔️JuNeDNS Backend detects if certificates are renewed (different datetime) and restarts automatically.
If you want to prevent DDoS / brute force attacks you can use Fail2ban, in case of login or No-IP.
- Set
log=2
injunedns.conf
- Create
/etc/fail2ban/jail.d/junednsbackend.conf
[junednsbackend-iptables]
enabled = true
port = dns
filter = junednsbackendfilter
action = iptables[name=junednsbackend, port=9053, protocol=http]
logpath = /etc/junedns/junedns-backend.log
maxretry = 10
- Create
/etc/fail2ban/filter.d/junednsbackendfilter.conf
[Definition]
failregex = ^.+ERR.+IP: <HOST>.+$
ignoreregex =
- Unban IP
fail2ban-client set junednsbackend-iptables unbanip 1.2.3.4
When you create a client ↔ server system, now renamed backend ↔ frontend, you need a link between both for an identification.
This used to be done with a session token saved in a database table along the data as a JSON on the server (I mean, backend), and on the client (I mean, frontend) it´s saved as a cookie.
Now a token is made in a JSON with encrypted data, which is sent in the HTTP header each time by the frontend and verified by the backend.
JSONWebToken, a module of 1170 files, 21 folders and 1,6 MB... is used as standard, included it´s ´shhhhh´ secret. But with JuNe Token the same goal is achieved with a few lines of code.
A private key is generated and stored in junedns.conf
if it was not already created backend_token=
then a JSON is encoded with the data and the expiration of the token, and sent as HTTP header.
Frontend stores it in sessionStorage, and never sends by GET parameters to avoid relogin if back.
🗜 You can see that this code is just a few lines in 930 bytes, a 0,05% from JSONWebToken (that is x1800):
const crypto = require('crypto');
// Generate key pair to export to config
token = crypto.generateKeyPairSync('rsa', {modulusLength: 1024}).privateKey.export({type: 'pkcs1', format: 'der'}).toString('base64');
// Create public and private keys
const pubk = crypto.createPublicKey({key: Buffer.from(token, 'base64'), type: 'pkcs1', format: 'der'});
const prik = crypto.createPrivateKey({key: Buffer.from(token, 'base64'), type: 'pkcs1', format: 'der'});
// Session token variable
let session = {};
// Set header x-access-token an expiry 15 minutes
function setHeader(expiry = 900) {
session._exp = Math.round((new Date()).getTime() / 1000) + expiry;
return crypto.publicEncrypt({key: pubk, padding: crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(JSON.stringify(session))).toString('base64');
}
// Get header if not expired
function getHeader(xhdr) {
session = JSON.parse(crypto.privateDecrypt({key: prik, padding: crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(xhdr, 'base64')).toString('utf-8'));
if(session._exp < Math.round((new Date()).getTime() / 1000))
session = {};
}
** What is safer? ** In database token system, it´s very complicated to discover the token, which would not exist for more than a few hours, and also brute force attacks can be controlled. In JSON token system if the private key is compromised, then it would be easier to perform spoofing.
Included to make this project JuNe BackServer
https://github.com/EduardoRuizM/junedns-server https://github.com/EduardoRuizM/junedns-frontend https://github.com/EduardoRuizM/junedns-noip
Dataclick Olimpo JuNeDNS
- Dataclick.es is a software development company since 2016.
- Olimpo is a whole solution software to manage all domains services such as hosting services and to create Webs in a server.
- JuNe / JUst NEeded Philosophy, available software and development solutions.
- JuNeDNS is a part of Dataclick Olimpo domains management for DNS service, released to Internet community.
- Feel free to use JuNeDNS acording MIT license respecting the brand and image logotype that you can use.
File | Description |
---|---|
backend.js | JuNeDNS Backend main file, just 25 Kb (JuNe Philosophy) |
dns_junedns.sh | Let´s Encrypt API using cURL for acme.sh to create/renew SSL certificates |
junedns.conf | Configuration file for JuNeDNS Server and Backend |
junedns-backend.service | Systemctl service for Backend binary or source code execution |
logo.png | JuNeDNS Backend Logo free to use |
mysql-backend.sql | MySQL/MariaDB database to combine with JuNeDNS Server database |
package.compile.json | package.json file to compile Backend binaries in folder dist/ |
package.json | Original Backend package.json |
texts.js | Texts in languages |
backserver.js | JuNe BackServer for routing and web token |