Skip to content

Commit

Permalink
Add support for allow/deny clients by IP (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
itzg authored Dec 19, 2024
1 parent 513e0b8 commit 7526a70
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 33 deletions.
66 changes: 35 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,65 +11,69 @@ Routes Minecraft client connections to backend servers based upon the requested

```text
-api-binding host:port
The host:port bound for servicing API requests (env API_BINDING)
The host:port bound for servicing API requests (env API_BINDING)
-auto-scale-up
Increase Kubernetes StatefulSet Replicas (only) from 0 to 1 on respective backend servers when accessed (env AUTO_SCALE_UP)
Increase Kubernetes StatefulSet Replicas (only) from 0 to 1 on respective backend servers when accessed (env AUTO_SCALE_UP)
-clients-to-allow value
Zero or more client IP addresses or CIDRs to allow. Takes precedence over deny. (env CLIENTS_TO_ALLOW)
-clients-to-deny value
Zero or more client IP addresses or CIDRs to deny. Ignored if any configured to allow (env CLIENTS_TO_DENY)
-connection-rate-limit int
Max number of connections to allow per second (env CONNECTION_RATE_LIMIT) (default 1)
Max number of connections to allow per second (env CONNECTION_RATE_LIMIT) (default 1)
-cpu-profile string
Enables CPU profiling and writes to given path (env CPU_PROFILE)
Enables CPU profiling and writes to given path (env CPU_PROFILE)
-debug
Enable debug logs (env DEBUG)
Enable debug logs (env DEBUG)
-default string
host:port of a default Minecraft server to use when mapping not found (env DEFAULT)
-docker-socket
Path to Docker socket to use (env DOCKER_SOCKET) (default "unix:///var/run/docker.sock")
host:port of a default Minecraft server to use when mapping not found (env DEFAULT)
-docker-refresh-interval int
Refresh interval in seconds for the Docker Swarm integration (env DOCKER_REFRESH_INTERVAL) (default 15)
Refresh interval in seconds for the Docker integrations (env DOCKER_REFRESH_INTERVAL) (default 15)
-docker-socket string
Path to Docker socket to use (env DOCKER_SOCKET) (default "unix:///var/run/docker.sock")
-docker-timeout int
Timeout configuration in seconds for the Docker Swarm integration (env DOCKER_TIMEOUT)
Timeout configuration in seconds for the Docker integrations (env DOCKER_TIMEOUT)
-in-docker
Use Docker service discovery (env IN_DOCKER)
Use Docker service discovery (env IN_DOCKER)
-in-docker-swarm
Use Docker Swarm service discovery (env IN_DOCKER_SWARM)
Use Docker Swarm service discovery (env IN_DOCKER_SWARM)
-in-kube-cluster
Use in-cluster Kubernetes config (env IN_KUBE_CLUSTER)
Use in-cluster Kubernetes config (env IN_KUBE_CLUSTER)
-kube-config string
The path to a Kubernetes configuration file (env KUBE_CONFIG)
The path to a Kubernetes configuration file (env KUBE_CONFIG)
-mapping value
Comma-separated or repeated mappings of externalHostname=host:port (env MAPPING)
Comma or newline delimited or repeated mappings of externalHostname=host:port (env MAPPING)
-metrics-backend string
Backend to use for metrics exposure/publishing: discard,expvar,influxdb (env METRICS_BACKEND) (default "discard")
Backend to use for metrics exposure/publishing: discard,expvar,influxdb (env METRICS_BACKEND) (default "discard")
-metrics-backend-config-influxdb-addr string
(env METRICS_BACKEND_CONFIG_INFLUXDB_ADDR)
(env METRICS_BACKEND_CONFIG_INFLUXDB_ADDR)
-metrics-backend-config-influxdb-database string
(env METRICS_BACKEND_CONFIG_INFLUXDB_DATABASE)
(env METRICS_BACKEND_CONFIG_INFLUXDB_DATABASE)
-metrics-backend-config-influxdb-interval duration
(env METRICS_BACKEND_CONFIG_INFLUXDB_INTERVAL) (default 1m0s)
(env METRICS_BACKEND_CONFIG_INFLUXDB_INTERVAL) (default 1m0s)
-metrics-backend-config-influxdb-password string
(env METRICS_BACKEND_CONFIG_INFLUXDB_PASSWORD)
(env METRICS_BACKEND_CONFIG_INFLUXDB_PASSWORD)
-metrics-backend-config-influxdb-retention-policy string
(env METRICS_BACKEND_CONFIG_INFLUXDB_RETENTION_POLICY)
(env METRICS_BACKEND_CONFIG_INFLUXDB_RETENTION_POLICY)
-metrics-backend-config-influxdb-tags value
any extra tags to be included with all reported metrics (env METRICS_BACKEND_CONFIG_INFLUXDB_TAGS)
any extra tags to be included with all reported metrics (env METRICS_BACKEND_CONFIG_INFLUXDB_TAGS)
-metrics-backend-config-influxdb-username string
(env METRICS_BACKEND_CONFIG_INFLUXDB_USERNAME)
(env METRICS_BACKEND_CONFIG_INFLUXDB_USERNAME)
-ngrok-token string
If set, an ngrok tunnel will be established. It is HIGHLY recommended to pass as an environment variable. (env NGROK_TOKEN)
If set, an ngrok tunnel will be established. It is HIGHLY recommended to pass as an environment variable. (env NGROK_TOKEN)
-port port
The port bound to listen for Minecraft client connections (env PORT) (default 25565)
The port bound to listen for Minecraft client connections (env PORT) (default 25565)
-receive-proxy-protocol
Receive PROXY protocol from backend servers, by default trusts every proxy header that it receives, combine with -trusted-proxies to specify a list of trusted proxies (env RECEIVE_PROXY_PROTOCOL)
Receive PROXY protocol from backend servers, by default trusts every proxy header that it receives, combine with -trusted-proxies to specify a list of trusted proxies (env RECEIVE_PROXY_PROTOCOL)
-routes-config string
Name or full path to routes config file (env ROUTES_CONFIG)
Name or full path to routes config file (env ROUTES_CONFIG)
-simplify-srv
Simplify fully qualified SRV records for mapping (env SIMPLIFY_SRV)
Simplify fully qualified SRV records for mapping (env SIMPLIFY_SRV)
-trusted-proxies value
Comma delimited list of CIDR notation IP blocks to trust when receiving PROXY protocol (env TRUSTED_PROXIES)
Comma delimited list of CIDR notation IP blocks to trust when receiving PROXY protocol (env TRUSTED_PROXIES)
-use-proxy-protocol
Send PROXY protocol to backend servers (env USE_PROXY_PROTOCOL)
Send PROXY protocol to backend servers (env USE_PROXY_PROTOCOL)
-version
Output version and exit (env VERSION)
Output version and exit (env VERSION)
```


Expand Down
11 changes: 10 additions & 1 deletion cmd/mc-router/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type Config struct {
RoutesConfig string `usage:"Name or full path to routes config file"`
NgrokToken string `usage:"If set, an ngrok tunnel will be established. It is HIGHLY recommended to pass as an environment variable."`

ClientsToAllow []string `usage:"Zero or more client IP addresses or CIDRs to allow. Takes precedence over deny."`
ClientsToDeny []string `usage:"Zero or more client IP addresses or CIDRs to deny. Ignored if any configured to allow"`

SimplifySRV bool `default:"false" usage:"Simplify fully qualified SRV records for mapping"`
}

Expand Down Expand Up @@ -88,6 +91,7 @@ func main() {
if err != nil {
logrus.WithError(err).Fatal("trying to create cpu profile file")
}
//goland:noinspection GoUnhandledErrorResult
defer cpuProfileFile.Close()

logrus.WithField("file", config.CpuProfile).Info("Starting cpu profiling")
Expand Down Expand Up @@ -131,7 +135,12 @@ func main() {
trustedIpNets = append(trustedIpNets, ipNet)
}

connector := server.NewConnector(metricsBuilder.BuildConnectorMetrics(), config.UseProxyProtocol, config.ReceiveProxyProtocol, trustedIpNets)
clientFilter, err := server.NewClientFilter(config.ClientsToAllow, config.ClientsToDeny)
if err != nil {
logrus.WithError(err).Fatal("Unable to create client filter")
}

connector := server.NewConnector(metricsBuilder.BuildConnectorMetrics(), config.UseProxyProtocol, config.ReceiveProxyProtocol, trustedIpNets, clientFilter)
if config.NgrokToken != "" {
connector.UseNgrok(config.NgrokToken)
}
Expand Down
101 changes: 101 additions & 0 deletions server/client_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package server

import (
"github.com/pkg/errors"
"net/netip"
"strings"
)

type addrMatcher struct {
addrs []netip.Addr
prefixes []netip.Prefix
}

func newAddrMatcher(filters []string) (*addrMatcher, error) {
addrs := make([]netip.Addr, 0)
prefixes := make([]netip.Prefix, 0)

if filters != nil {
for _, filter := range filters {
if strings.Contains(filter, "/") {
prefix, err := netip.ParsePrefix(filter)
if err != nil {
return nil, err
}
prefixes = append(prefixes, prefix)
} else {
addr, err := netip.ParseAddr(filter)
if err != nil {
return nil, err
}
addrs = append(addrs, addr)
}
}
}

return &addrMatcher{
addrs: addrs,
prefixes: prefixes,
}, nil
}

func (a *addrMatcher) Match(addr netip.Addr) bool {
for _, a := range a.addrs {

// Before comparison, need to unmap addresses such as
// ::ffff:127.0.0.1
unmapped := addr.Unmap()
if a == unmapped {
return true
}
}
for _, p := range a.prefixes {
if p.Contains(addr) {
return true
}
}
return false
}

func (a *addrMatcher) Empty() bool {
return len(a.addrs) == 0 && len(a.prefixes) == 0
}

// ClientFilter performs allow/deny filtering of client IP addresses
type ClientFilter struct {
allow *addrMatcher
deny *addrMatcher
}

// NewClientFilter provides a mechanism to evaluate client IP addresses and determine if
// they should be allowed access or not.
// The allows and denies can each or both be nil or netip.ParseAddr allowed values.
func NewClientFilter(allows []string, denies []string) (*ClientFilter, error) {
allow, err := newAddrMatcher(allows)
if err != nil {
return nil, errors.Wrap(err, "invalid allow filter")
}
deny, err := newAddrMatcher(denies)
if err != nil {
return nil, errors.Wrap(err, "invalid deny filter")
}
return &ClientFilter{
allow: allow,
deny: deny,
}, nil
}

// Allow determines if the given address is allowed by this filter
// where addrStr is a netip.ParseAddr allowed address
func (f *ClientFilter) Allow(addrPort netip.AddrPort) bool {
if !f.allow.Empty() {
matched := f.allow.Match(addrPort.Addr())
return matched
}
if !f.deny.Empty() {
matched := f.deny.Match(addrPort.Addr())
return !matched
}

return true
}
Loading

0 comments on commit 7526a70

Please sign in to comment.