List and delete accounts with last-admin and Administrators-group guards, and expose editable network settings via the configuration UI.
136 lines
3.6 KiB
Go
136 lines
3.6 KiB
Go
package auth
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"codeberg.org/SquidSE/ClusterCanvas/service/internal/settings"
|
|
)
|
|
|
|
var hostnamePattern = regexp.MustCompile(`^(?i)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$|^localhost$`)
|
|
|
|
// ValidateHostname checks a public hostname (empty is allowed for local/dev).
|
|
func ValidateHostname(hostname string) error {
|
|
trimmed := strings.TrimSpace(hostname)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
if strings.Contains(trimmed, "/") || strings.Contains(trimmed, ":") || strings.Contains(trimmed, " ") {
|
|
return fmt.Errorf("public_hostname must be a bare DNS name without scheme, port, or path")
|
|
}
|
|
if !hostnamePattern.MatchString(trimmed) {
|
|
return fmt.Errorf("public_hostname is not a valid DNS hostname")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HostMatches reports whether requestHost matches the configured public hostname.
|
|
// An empty / whitespace public hostname means any host is allowed (local/dev).
|
|
func HostMatches(requestHost string, publicHostname string) bool {
|
|
wanted := strings.TrimSpace(publicHostname)
|
|
if wanted == "" {
|
|
return true
|
|
}
|
|
host := strings.TrimSpace(requestHost)
|
|
if parsedHost, _, err := net.SplitHostPort(host); err == nil {
|
|
host = parsedHost
|
|
}
|
|
return strings.EqualFold(host, wanted)
|
|
}
|
|
|
|
// IPAllowed evaluates whitelist/blacklist rules against clientIP.
|
|
func IPAllowed(clientIP string, network settings.NetworkSettings) (bool, string) {
|
|
network = settings.EffectiveNetwork(network)
|
|
parsedIP := net.ParseIP(clientIP)
|
|
if parsedIP == nil {
|
|
return false, "unable to parse client IP"
|
|
}
|
|
|
|
switch network.AccessMode {
|
|
case settings.AccessModeOpen, "":
|
|
return true, ""
|
|
case settings.AccessModeWhitelist:
|
|
if matchesAnyRule(parsedIP, network.Rules) {
|
|
return true, ""
|
|
}
|
|
return false, "client IP is not on the whitelist"
|
|
case settings.AccessModeBlacklist:
|
|
if matchesAnyRule(parsedIP, network.Rules) {
|
|
return false, "client IP matches a blacklist rule"
|
|
}
|
|
return true, ""
|
|
default:
|
|
return false, "invalid access mode"
|
|
}
|
|
}
|
|
|
|
// WouldLockOut estimates whether rules would block clientIP.
|
|
func WouldLockOut(clientIP string, network settings.NetworkSettings) (bool, string) {
|
|
allowed, reason := IPAllowed(clientIP, network)
|
|
if allowed {
|
|
return false, ""
|
|
}
|
|
return true, reason
|
|
}
|
|
|
|
func matchesAnyRule(ip net.IP, rules []string) bool {
|
|
for _, rule := range rules {
|
|
trimmed := strings.TrimSpace(rule)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
if strings.Contains(trimmed, "/") {
|
|
_, network, err := net.ParseCIDR(trimmed)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if network.Contains(ip) {
|
|
return true
|
|
}
|
|
continue
|
|
}
|
|
ruleIP := net.ParseIP(trimmed)
|
|
if ruleIP != nil && ruleIP.Equal(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidateListenAddress checks a bind address (IP or hostname).
|
|
func ValidateListenAddress(address string) error {
|
|
trimmed := strings.TrimSpace(address)
|
|
if trimmed == "" {
|
|
return fmt.Errorf("listen_address is required")
|
|
}
|
|
if trimmed == "0.0.0.0" || trimmed == "::" || trimmed == "localhost" {
|
|
return nil
|
|
}
|
|
if net.ParseIP(trimmed) != nil {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("listen_address must be an IP address or 0.0.0.0")
|
|
}
|
|
|
|
// ValidateAccessRules validates CIDR/IP entries.
|
|
func ValidateAccessRules(rules []string) error {
|
|
for _, rule := range rules {
|
|
trimmed := strings.TrimSpace(rule)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
if strings.Contains(trimmed, "/") {
|
|
if _, _, err := net.ParseCIDR(trimmed); err != nil {
|
|
return fmt.Errorf("invalid CIDR rule %q", trimmed)
|
|
}
|
|
continue
|
|
}
|
|
if net.ParseIP(trimmed) == nil {
|
|
return fmt.Errorf("invalid IP rule %q", trimmed)
|
|
}
|
|
}
|
|
return nil
|
|
}
|