Last active
April 3, 2025 12:29
-
-
Save sinuscosinustan/311a17dccdb7d5c2f9b80cc9be179242 to your computer and use it in GitHub Desktop.
Small webserver for easy firewalling for Satisfactory 1.1 Experimental ("workaround" the ReliableMessaging crash)
This file contains hidden or 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
--- | |
services: | |
firewall-web: | |
build: firewall-shit/ | |
container_name: firewall-server | |
restart: unless-stopped | |
labels: | |
- "traefik.enable=true" | |
- "traefik.http.middlewares.fwshit-strip.stripprefix.prefixes=/fwshit" | |
- "traefik.http.routers.fwshit.rule=Host(`MYDOMAIN.LOCAL`) && PathRegexp(`^/(fwshit)`)" | |
- "traefik.http.routers.fwshit.entrypoints=websecure" | |
- "traefik.http.routers.fwshit.tls.certresolver=myresolver" | |
- "traefik.http.routers.fwshit.middlewares=fwshit-strip" | |
- "traefik.http.services.fwshit.loadbalancer.server.port=8080" | |
network_mode: host | |
cap_add: | |
- NET_ADMIN | |
- SYS_ADMIN |
This file contains hidden or 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
FROM golang:1.24-alpine as builder | |
WORKDIR /app | |
COPY . . | |
RUN go build -o server | |
FROM alpine:latest | |
RUN apk add --no-cache iptables ip6tables util-linux iptables-legacy | |
WORKDIR /app | |
COPY --from=builder /app/server /server | |
CMD ["/server"] |
This file contains hidden or 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
package main | |
import ( | |
"fmt" | |
"html/template" | |
"log" | |
"net" | |
"net/http" | |
"os/exec" | |
"strings" | |
"net/netip" | |
) | |
const listenPort = "8080" | |
const ipv4Chain = "FIREWALL-WEB" | |
const ipv6Chain = "FIREWALL-WEB6" | |
var ports = []string{"8888/tcp", "7777/tcp", "7777/udp", "27777/tcp"} | |
var tmpl = ` | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Firewall Access</title> | |
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> | |
<style> | |
body { | |
background-color: #f8f9fa; | |
} | |
.container { | |
max-width: 600px; | |
margin-top: 10vh; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container text-center"> | |
<h1 class="mb-4">Request Firewall Access</h1> | |
<p>This will add your IPv4 and IPv6 address (if present) to the firewall on ports 8888 and 7777.</p> | |
<form method="POST"> | |
<input type="hidden" name="v4" id="v4"> | |
<input type="hidden" name="v6" id="v6"> | |
<button type="submit" class="btn btn-primary btn-lg">Allow My IP</button> | |
</form> | |
{{if .Message}} | |
<div class="alert alert-info mt-4" role="alert"> | |
{{.Message}} | |
</div> | |
{{end}} | |
</div> | |
<script> | |
async function getIPs() { | |
try { | |
const v4 = await fetch('https://ipv4.icanhazip.com').then(r => r.text()); | |
document.getElementById('v4').value = v4.trim(); | |
} catch {} | |
try { | |
const v6 = await fetch('https://ipv6.icanhazip.com').then(r => r.text()); | |
document.getElementById('v6').value = v6.trim(); | |
} catch {} | |
} | |
getIPs(); | |
</script> | |
</body> | |
</html> | |
` | |
type PageData struct { | |
Message template.HTML | |
} | |
func main() { | |
ensureChainsExist() | |
http.HandleFunc("/", handler) | |
log.Printf("Listening on :%s...", listenPort) | |
log.Fatal(http.ListenAndServe(":"+listenPort, nil)) | |
} | |
func handler(w http.ResponseWriter, r *http.Request) { | |
log.Printf("%s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) | |
if r.Method == http.MethodGet { | |
tmplParsed := template.Must(template.New("page").Funcs(template.FuncMap{"safeHTML": func(s string) template.HTML { return template.HTML(s) }}).Parse(tmpl)) | |
tmplParsed.Execute(w, nil) | |
return | |
} | |
r.ParseForm() | |
ips := []string{} | |
if v4 := strings.TrimSpace(r.FormValue("v4")); net.ParseIP(v4) != nil { | |
ips = append(ips, v4) | |
} | |
if v6 := strings.TrimSpace(r.FormValue("v6")); net.ParseIP(v6) != nil { | |
ips = append(ips, v6) | |
} | |
if len(ips) == 0 { | |
http.Error(w, "No valid IP provided", http.StatusBadRequest) | |
return | |
} | |
var messages []string | |
for _, ip := range ips { | |
log.Printf("Processing IP: %s", ip) | |
for _, portProto := range ports { | |
msg := allowInFirewall(ip, portProto) | |
log.Println(msg) | |
messages = append(messages, template.HTMLEscapeString(msg)) | |
} | |
} | |
tmplParsed := template.Must(template.New("page").Funcs(template.FuncMap{"safeHTML": func(s string) template.HTML { return template.HTML(s) }}).Parse(tmpl)) | |
tmplParsed.Execute(w, PageData{Message: template.HTML(strings.Join(messages, "<br>"))}) | |
} | |
func ensureChainsExist() { | |
exec.Command("iptables-legacy", "-N", ipv4Chain).Run() | |
exec.Command("ip6tables-legacy", "-N", ipv6Chain).Run() | |
exec.Command("iptables-legacy", "-C", "INPUT", "-j", ipv4Chain).Run() | |
exec.Command("iptables-legacy", "-I", "INPUT", "-j", ipv4Chain).Run() | |
exec.Command("ip6tables-legacy", "-C", "INPUT", "-j", ipv6Chain).Run() | |
exec.Command("ip6tables-legacy", "-I", "INPUT", "-j", ipv6Chain).Run() | |
for _, portProto := range ports { | |
parts := strings.Split(portProto, "/") | |
port := parts[0] | |
proto := parts[1] | |
if err := exec.Command("iptables-legacy", "-C", ipv4Chain, "-p", proto, "--dport", port, "-j", "DROP").Run(); err != nil { | |
exec.Command("iptables-legacy", "-A", ipv4Chain, "-p", proto, "--dport", port, "-j", "DROP").Run() | |
} | |
if err := exec.Command("ip6tables-legacy", "-C", ipv6Chain, "-p", proto, "--dport", port, "-j", "DROP").Run(); err != nil { | |
exec.Command("ip6tables-legacy", "-A", ipv6Chain, "-p", proto, "--dport", port, "-j", "DROP").Run() | |
} | |
} | |
} | |
func allowInFirewall(ip string, portProto string) string { | |
parsed := net.ParseIP(ip) | |
if parsed == nil { | |
return fmt.Sprintf("Invalid IP: %s", ip) | |
} | |
parts := strings.Split(portProto, "/") | |
port := parts[0] | |
proto := parts[1] | |
var ( | |
checkCmd *exec.Cmd | |
addCmd *exec.Cmd | |
ruleDesc string | |
) | |
if strings.Contains(ip, ":") { | |
addr, err := netip.ParseAddr(ip) | |
if err != nil || !addr.Is6() { | |
return fmt.Sprintf("Invalid IPv6 address: %s", ip) | |
} | |
prefix, err := addr.Prefix(64) | |
if err != nil { | |
return fmt.Sprintf("Error while getting /64 prefix: %s", err) | |
} | |
checkCmd = exec.Command("ip6tables-legacy", "-C", ipv6Chain, "-p", proto, "-s", prefix.String(), "--dport", port, "-j", "ACCEPT") | |
addCmd = exec.Command("ip6tables-legacy", "-I", ipv6Chain, "1", "-p", proto, "-s", prefix.String(), "--dport", port, "-j", "ACCEPT") | |
ruleDesc = fmt.Sprintf("%s", prefix) | |
} else { | |
checkCmd = exec.Command("iptables-legacy", "-C", ipv4Chain, "-p", proto, "-s", ip, "--dport", port, "-j", "ACCEPT") | |
addCmd = exec.Command("iptables-legacy", "-I", ipv4Chain, "1", "-p", proto, "-s", ip, "--dport", port, "-j", "ACCEPT") | |
ruleDesc = fmt.Sprintf("%s", ip) | |
} | |
if err := checkCmd.Run(); err == nil { | |
return fmt.Sprintf("🔁 %s is already whitelisted for port %s/%s", ruleDesc, port, proto) | |
} | |
if err := addCmd.Run(); err != nil { | |
return fmt.Sprintf("❌ Failed to allow %s: %v", ruleDesc, err) | |
} | |
return fmt.Sprintf("✅ Allowed %s on port %s/%s", ruleDesc, port, proto) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment