Skip to content

Commit bfb9635

Browse files
committed
add dns/sshd configuration handling for lighthouse nodes
1 parent 7cc7b94 commit bfb9635

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ This project uses [Semantic Versioning](https://semver.org/) - MAJOR.MINOR.PATCH
44

55
# Changelog
66

7+
## 1.0.1 (2026-03-14)
8+
9+
- Added configuration handling for the built in lighthouse DNS server
10+
- Added configuration handling for the built in lighthouse SSHD server
11+
712
## 1.0.0 (2026-03-05)
813

914
- Initial release of saltext-nebula

docs/topics/pillar-configuration.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,56 @@ base:
174174
- nebula.databases
175175
```
176176

177+
## Lighthouse DNS
178+
179+
Lighthouses can serve DNS for the Nebula network, allowing hosts to resolve
180+
each other by name. Configure `serve_dns` and the `dns` bind address under
181+
the host entry:
182+
183+
```yaml
184+
nebula:
185+
hosts:
186+
lighthouse01:
187+
ip: "10.10.10.1/24"
188+
is_lighthouse: true
189+
serve_dns: true
190+
dns:
191+
host: "0.0.0.0" # bind to all interfaces; use nebula IP to restrict to overlay only
192+
port: 53 # use a non-privileged port like 5353 if not running as root
193+
```
194+
195+
`serve_dns` is silently ignored for non-lighthouse hosts. Remember to open
196+
the chosen port in the host's inbound firewall rules and in your OS-level
197+
firewall (iptables, nftables, ufw, etc.).
198+
199+
## Nebula SSH Server
200+
201+
Nebula includes a built-in SSH server for management access that operates
202+
independently of the OS SSH daemon. It is disabled by default and only emitted
203+
in the generated config when `enabled: true` is set:
204+
205+
```yaml
206+
nebula:
207+
hosts:
208+
myhost:
209+
ip: "10.10.10.50/24"
210+
sshd:
211+
enabled: true
212+
listen: "127.0.0.1:22" # address:port for the nebula sshd to bind
213+
host_key: "/etc/nebula/ssh_host_ed25519_key" # omit to use this default
214+
authorized_users:
215+
- user: alice
216+
keys:
217+
- 'ssh-ed25519 AAAA...'
218+
# Optional: also accept nebula CA-signed SSH certificates
219+
authorized_nebula_certificate_authorities:
220+
- 'ssh-ed25519 AAAA...'
221+
```
222+
223+
When `host_key` is omitted it defaults to `<config_dir>/ssh_host_ed25519_key`.
224+
The key file must be generated separately (e.g. `ssh-keygen -t ed25519 -f
225+
/etc/nebula/ssh_host_ed25519_key -N ""`).
226+
177227
## Advanced Configuration Options
178228

179229
### Unsafe Routes

src/saltext/nebula/modules/nebula.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,16 @@ def build_config(minion_id=None):
624624
if host_config.get("calculated_remotes"):
625625
lh_config["calculated_remotes"] = host_config["calculated_remotes"]
626626

627+
# serve_dns / dns: lighthouse-only
628+
if is_lighthouse and host_config.get("serve_dns"):
629+
lh_config["serve_dns"] = True
630+
dns_cfg = host_config.get("dns", {})
631+
if dns_cfg:
632+
lh_config["dns"] = {
633+
"host": dns_cfg.get("host", "0.0.0.0"),
634+
"port": dns_cfg.get("port", 53),
635+
}
636+
627637
config["lighthouse"] = lh_config
628638

629639
# --- Listen ---
@@ -658,6 +668,25 @@ def build_config(minion_id=None):
658668
"timestamp_format": "2006-01-02T15:04:05Z07:00",
659669
}
660670

671+
# --- SSHD (optional, host-level) ---
672+
sshd_cfg = host_config.get("sshd", {})
673+
if sshd_cfg.get("enabled"):
674+
sshd = {
675+
"enabled": True,
676+
"listen": sshd_cfg.get("listen", "127.0.0.1:22"),
677+
"host_key": sshd_cfg.get(
678+
"host_key",
679+
os.path.join(paths["config_dir"], "ssh_host_ed25519_key"),
680+
),
681+
}
682+
if sshd_cfg.get("authorized_users"):
683+
sshd["authorized_users"] = sshd_cfg["authorized_users"]
684+
if sshd_cfg.get("authorized_nebula_certificate_authorities"):
685+
sshd["authorized_nebula_certificate_authorities"] = sshd_cfg[
686+
"authorized_nebula_certificate_authorities"
687+
]
688+
config["sshd"] = sshd
689+
661690
# --- Firewall ---
662691
# Common defaults
663692
common_fw = nebula_pillar.get("firewall", {})

tests/unit/test_modules_nebula.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Unit tests for saltext.nebula.modules.nebula
33
"""
44

5+
import os
56
from datetime import datetime
67
from datetime import timedelta
78
from unittest.mock import MagicMock
@@ -413,6 +414,117 @@ def test_static_host_map(self):
413414
assert "172.25.0.1" in config["static_host_map"]
414415
assert config["static_host_map"]["172.25.0.1"] == ["1.2.3.4:4242"]
415416

417+
def test_lighthouse_serve_dns(self):
418+
"""Lighthouse with serve_dns emits serve_dns and dns block inside lighthouse config."""
419+
nebula_mod.__pillar__["nebula"]["hosts"]["testhost"]["is_lighthouse"] = True
420+
nebula_mod.__pillar__["nebula"]["hosts"]["testhost"]["serve_dns"] = True
421+
nebula_mod.__pillar__["nebula"]["hosts"]["testhost"]["dns"] = {
422+
"host": "172.25.0.2",
423+
"port": 5353,
424+
}
425+
with patch.object(
426+
nebula_mod,
427+
"detect_paths",
428+
return_value={
429+
"ca_file": "/etc/nebula/ca.crt",
430+
"cert_file": "/etc/nebula/testhost.crt",
431+
"key_file": "/etc/nebula/testhost.key",
432+
"config_dir": "/etc/nebula",
433+
},
434+
):
435+
config = nebula_mod.build_config()
436+
437+
assert config["lighthouse"]["serve_dns"] is True
438+
assert config["lighthouse"]["dns"]["host"] == "172.25.0.2"
439+
assert config["lighthouse"]["dns"]["port"] == 5353
440+
441+
def test_serve_dns_omitted_on_non_lighthouse(self):
442+
"""serve_dns is not emitted for regular nodes even if set in pillar."""
443+
nebula_mod.__pillar__["nebula"]["hosts"]["testhost"]["serve_dns"] = True
444+
with patch.object(
445+
nebula_mod,
446+
"detect_paths",
447+
return_value={
448+
"ca_file": "/etc/nebula/ca.crt",
449+
"cert_file": "/etc/nebula/testhost.crt",
450+
"key_file": "/etc/nebula/testhost.key",
451+
"config_dir": "/etc/nebula",
452+
},
453+
):
454+
config = nebula_mod.build_config()
455+
456+
assert "serve_dns" not in config["lighthouse"]
457+
assert "dns" not in config["lighthouse"]
458+
459+
def test_sshd_enabled(self):
460+
"""sshd block is built correctly from pillar."""
461+
nebula_mod.__pillar__["nebula"]["hosts"]["testhost"]["sshd"] = {
462+
"enabled": True,
463+
"listen": "127.0.0.1:20022",
464+
"host_key": "/etc/nebula/testhost_host",
465+
"authorized_users": [{"user": "alice", "keys": ["ssh-ed25519 AAAA..."]}],
466+
}
467+
with patch.object(
468+
nebula_mod,
469+
"detect_paths",
470+
return_value={
471+
"ca_file": "/etc/nebula/ca.crt",
472+
"cert_file": "/etc/nebula/testhost.crt",
473+
"key_file": "/etc/nebula/testhost.key",
474+
"config_dir": "/etc/nebula",
475+
},
476+
):
477+
config = nebula_mod.build_config()
478+
479+
assert "sshd" in config
480+
assert config["sshd"]["enabled"] is True
481+
assert config["sshd"]["listen"] == "127.0.0.1:20022"
482+
assert config["sshd"]["host_key"] == "/etc/nebula/testhost_host"
483+
assert config["sshd"]["authorized_users"][0]["user"] == "alice"
484+
485+
@pytest.mark.parametrize(
486+
"config_dir",
487+
[
488+
"/etc/nebula",
489+
"C:\\ProgramData\\Nebula",
490+
],
491+
)
492+
def test_sshd_default_host_key(self, config_dir):
493+
"""sshd host_key defaults to <config_dir>/ssh_host_ed25519_key on both Unix and Windows."""
494+
nebula_mod.__pillar__["nebula"]["hosts"]["testhost"]["sshd"] = {
495+
"enabled": True,
496+
"listen": "127.0.0.1:22",
497+
}
498+
with patch.object(
499+
nebula_mod,
500+
"detect_paths",
501+
return_value={
502+
"ca_file": f"{config_dir}/ca.crt",
503+
"cert_file": f"{config_dir}/testhost.crt",
504+
"key_file": f"{config_dir}/testhost.key",
505+
"config_dir": config_dir,
506+
},
507+
):
508+
config = nebula_mod.build_config()
509+
510+
assert config["sshd"]["host_key"] == os.path.join(config_dir, "ssh_host_ed25519_key")
511+
512+
def test_sshd_absent_when_not_configured(self):
513+
"""sshd key is not emitted when not in pillar."""
514+
with patch.object(
515+
nebula_mod,
516+
"detect_paths",
517+
return_value={
518+
"ca_file": "/etc/nebula/ca.crt",
519+
"cert_file": "/etc/nebula/testhost.crt",
520+
"key_file": "/etc/nebula/testhost.key",
521+
"config_dir": "/etc/nebula",
522+
},
523+
):
524+
config = nebula_mod.build_config()
525+
526+
assert "sshd" not in config
527+
416528

417529
# ---------------------------------------------------------------------------
418530
# backup_config / rollback_config

0 commit comments

Comments
 (0)