-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f730c61
Showing
14 changed files
with
1,840 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.idea/ | ||
.vscode/ | ||
cmd/dynafire/dynafire |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
dynafire - real-time threat detection for any Linux system | ||
= | ||
|
||
[Turris Sentinel](https://view.sentinel.turris.cz/?period=1w) is a real-time threat detection & attack prevention system from | ||
the creators of the [Turris](https://www.turris.com/en/) series of open-source routers, however this service is normally only available via the router interface. | ||
This makes it impractical to use the real-time data provided by Turris Sentinel on a VPS for example, which you cannot easily put behind a Turris router hardware. | ||
|
||
`dynafire` is a lightweight Linux daemon that lets any Linux system running the industry standard `firewalld` firewall update its firewall rules in real-time based on Sentinel data. | ||
|
||
Installation via package managers | ||
- | ||
TODO | ||
|
||
Manual installation | ||
- | ||
Because `dynafire` ships as a single binary, it is easy to install it manually on practically any `systemd`-based distro. | ||
|
||
Before proceeding please ensure that both `NetworkManager` and `firewalld` are installed and running: | ||
|
||
```shell | ||
$ sudo systemctl check NetworkManager | ||
active | ||
|
||
$ sudo systemctl check firewalld | ||
active | ||
``` | ||
|
||
Download the binary: | ||
|
||
TODO | ||
|
||
Ensure the binary is executable: | ||
|
||
`$ chmod +x dynafire` | ||
|
||
Copy the binary to your `$PATH`: | ||
|
||
`$ sudo cp dynafire /usr/bin/` | ||
|
||
|
||
Next, download the `systemd` service definition file: | ||
|
||
TODO | ||
|
||
Building from source | ||
- | ||
|
||
TODO | ||
|
||
Configuration | ||
- | ||
|
||
The `dynafire` configuration file is created upon first launch under `/etc/dynafire/config.json`. | ||
By default, it has the following values: | ||
|
||
```json | ||
{ | ||
"log_level": "INFO", | ||
"zone_target_policy": "ACCEPT" | ||
} | ||
``` | ||
|
||
The `log_level` can be set to `DEBUG` (most verbose), `INFO` and `ERROR` (least verbose). | ||
|
||
By default, the `dynafire` firewalld zone is set to `ACCEPT` every packet that is NOT on the Turris Sentinel blacklist, so as not to accidentally block legitimate traffic. | ||
However, you can make this stricter by changing the `zone_target_policy` to i.e. `REJECT` or `DROP`, see [firewalld zone options](https://firewalld.org/documentation/zone/options.html) for details. | ||
|
||
Contributing | ||
- | ||
Bug reports and pull requests are welcome. Do not hesitate to open a PR / file an issue or a feature request. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log/slog" | ||
"os" | ||
"sync" | ||
|
||
"github.com/MatejLach/dynafire/firewall/firewalld" | ||
"github.com/MatejLach/dynafire/provider/turris" | ||
) | ||
|
||
func main() { | ||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil))) | ||
|
||
fwc, err := firewalld.New() | ||
if err != nil { | ||
slog.Error("Initialization failed; host system pre-requisites not met", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
tc, err := turris.NewClient(turris.Url, turris.Port) | ||
if err != nil { | ||
slog.Error("Unable to initialize Turris dynafire client", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
err = tc.Connect() | ||
if err != nil { | ||
slog.Error("Unable to connect to Turris firewall update server", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
wg := sync.WaitGroup{} | ||
sem := make(chan struct{}, 1) | ||
|
||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
tc.RequestMessages(context.Background()) | ||
}() | ||
|
||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
for listMsg := range tc.ListChan { | ||
slog.Info(fmt.Sprintf("adding %d IPs to the blacklist", len(listMsg.Blacklist))) | ||
sem <- struct{}{} | ||
|
||
err = fwc.ResetFirewallRules() | ||
if err != nil { | ||
slog.Error("unable to clear old IP blacklist", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
err = fwc.BlockIPList(listMsg.Blacklist) | ||
if err != nil { | ||
slog.Error("unable to initialize IP blacklist", "details", err) | ||
os.Exit(1) | ||
} | ||
slog.Info("Starting to process delta updates...") | ||
<-sem | ||
} | ||
}() | ||
|
||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
|
||
for deltaMsg := range tc.DeltaChan { | ||
sem <- struct{}{} | ||
|
||
// 'positive' operation adds an IP to the blacklist | ||
// 'negative' removes an existing IP from the blacklist | ||
switch deltaMsg.Operation { | ||
case "positive": | ||
err = fwc.BlockIP(deltaMsg.IP) | ||
if err != nil { | ||
slog.Error("unable to blacklist IP", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
slog.Debug("blacklisting", "IP", deltaMsg.IP.String()) | ||
case "negative": | ||
err = fwc.UnblockIP(deltaMsg.IP) | ||
if err != nil { | ||
slog.Error("unable to whitelist IP", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
slog.Debug("whitelisting", "IP", deltaMsg.IP.String()) | ||
} | ||
<-sem | ||
} | ||
}() | ||
|
||
wg.Wait() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[Unit] | ||
Description=dynafire - real-time threat detection for any Linux system | ||
After=network.target | ||
|
||
[Install] | ||
WantedBy=multi-user.target | ||
|
||
[Service] | ||
User=root | ||
Type=simple | ||
ExecStart="/usr/bin/dynafire" | ||
TimeoutStopSec=20 | ||
KillMode=process | ||
Restart=on-failure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package firewall | ||
|
||
import "net" | ||
|
||
type Blocker interface { | ||
BlockIP(address net.IP) error | ||
BlockIPList(blacklist []net.IP) error | ||
UnblockIP(address net.IP) error | ||
ResetFirewallRules() error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package firewalld | ||
|
||
import ( | ||
"encoding/json" | ||
"log/slog" | ||
"os" | ||
"strings" | ||
) | ||
|
||
type Config struct { | ||
LogLevel string `json:"log_level"` | ||
ZoneTargetPolicy string `json:"zone_target_policy"` | ||
} | ||
|
||
func initConfig() { | ||
slog.Info("No config.json found, creating new config...") | ||
config := Config{ | ||
LogLevel: "INFO", | ||
ZoneTargetPolicy: "ACCEPT", | ||
} | ||
|
||
jsonBytes, err := json.MarshalIndent(config, "", " ") | ||
if err != nil { | ||
slog.Error("unable to parse config file", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
err = os.MkdirAll("/etc/dynafire", 0775) | ||
if err != nil { | ||
slog.Error("unable to make config directory", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
err = os.WriteFile("/etc/dynafire/config.json", jsonBytes, 0775) | ||
if err != nil { | ||
slog.Error("unable to save default config file", "details", err) | ||
os.Exit(1) | ||
} | ||
|
||
slog.Info("New config.json created, feel free to modify the defaults, then restart for your changes to take effect.") | ||
} | ||
|
||
func parseConfig() (Config, error) { | ||
var config Config | ||
cfgData, err := os.ReadFile("/etc/dynafire/config.json") | ||
if err != nil { | ||
return Config{}, err | ||
} | ||
|
||
err = json.Unmarshal(cfgData, &config) | ||
if err != nil { | ||
return Config{}, err | ||
} | ||
|
||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: parseLogLevel(config.LogLevel)}))) | ||
|
||
return config, nil | ||
} | ||
|
||
func configExists() bool { | ||
if _, err := os.Stat("/etc/dynafire/config.json"); os.IsNotExist(err) { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func parseLogLevel(level string) slog.Level { | ||
switch strings.ToUpper(level) { | ||
case "DEBUG": | ||
return slog.LevelDebug | ||
case "INFO": | ||
return slog.LevelInfo | ||
case "ERROR": | ||
return slog.LevelError | ||
default: | ||
return slog.LevelInfo | ||
} | ||
} |
Oops, something went wrong.