mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-13 05:45:52 +01:00
283 lines
7.3 KiB
Markdown
283 lines
7.3 KiB
Markdown
# ACL (Access Control List)
|
|
|
|
Access control at the TCP connection level with IP/CIDR, timezone, and country-based filtering.
|
|
|
|
## Overview
|
|
|
|
The ACL package provides network-level access control by wrapping TCP listeners and validating incoming connections against configurable allow/deny rules. It integrates with MaxMind GeoIP for geographic-based filtering and supports access logging with notification batching.
|
|
|
|
### Primary consumers
|
|
|
|
- `internal/entrypoint` - Wraps the main TCP listener for connection filtering
|
|
- Operators - Configure rules via YAML configuration
|
|
|
|
### Non-goals
|
|
|
|
- HTTP request-level filtering (handled by middleware)
|
|
- Authentication or authorization (see `internal/auth`)
|
|
- VPN or tunnel integration
|
|
|
|
### Stability
|
|
|
|
Stable internal package. The public API is the `Config` struct and its methods.
|
|
|
|
## Public API
|
|
|
|
### Exported types
|
|
|
|
```go
|
|
type Config struct {
|
|
Default string // "allow" or "deny" (default: "allow")
|
|
AllowLocal *bool // Allow private/loopback IPs (default: true)
|
|
Allow Matchers // Allow rules
|
|
Deny Matchers // Deny rules
|
|
Log *accesslog.ACLLoggerConfig // Access logging configuration
|
|
|
|
Notify struct {
|
|
To []string // Notification providers
|
|
Interval time.Duration // Notification frequency (default: 1m)
|
|
IncludeAllowed *bool // Include allowed in notifications (default: false)
|
|
}
|
|
}
|
|
```
|
|
|
|
```go
|
|
type Matcher struct {
|
|
match MatcherFunc
|
|
}
|
|
```
|
|
|
|
```go
|
|
type Matchers []Matcher
|
|
```
|
|
|
|
### Exported functions and methods
|
|
|
|
```go
|
|
func (c *Config) Validate() gperr.Error
|
|
```
|
|
|
|
Validates configuration and sets defaults. Must be called before `Start`.
|
|
|
|
```go
|
|
func (c *Config) Start(parent task.Parent) gperr.Error
|
|
```
|
|
|
|
Initializes the ACL, starts the logger and notification goroutines.
|
|
|
|
```go
|
|
func (c *Config) IPAllowed(ip net.IP) bool
|
|
```
|
|
|
|
Returns true if the IP is allowed based on configured rules. Performs caching and GeoIP lookup if needed.
|
|
|
|
```go
|
|
func (c *Config) WrapTCP(lis net.Listener) net.Listener
|
|
```
|
|
|
|
Wraps a `net.Listener` to filter connections by IP.
|
|
|
|
```go
|
|
func (matcher *Matcher) Parse(s string) error
|
|
```
|
|
|
|
Parses a matcher string in the format `{type}:{value}`. Supported types: `ip`, `cidr`, `tz`, `country`.
|
|
|
|
## Architecture
|
|
|
|
### Core components
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[TCP Listener] --> B[TCPListener Wrapper]
|
|
B --> C{IP Allowed?}
|
|
C -->|Yes| D[Accept Connection]
|
|
C -->|No| E[Close Connection]
|
|
|
|
F[Config] --> G[Validate]
|
|
G --> H[Start]
|
|
H --> I[Matcher Evaluation]
|
|
I --> C
|
|
|
|
J[MaxMind] -.-> K[IP Lookup]
|
|
K -.-> I
|
|
|
|
L[Access Logger] -.-> M[Log & Notify]
|
|
M -.-> B
|
|
```
|
|
|
|
### Connection filtering flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant TCPListener
|
|
participant Config
|
|
participant MaxMind
|
|
participant Logger
|
|
|
|
Client->>TCPListener: Connection Request
|
|
TCPListener->>Config: IPAllowed(clientIP)
|
|
|
|
alt Loopback IP
|
|
Config-->>TCPListener: true
|
|
else Private IP (allow_local)
|
|
Config-->>TCPListener: true
|
|
else Cached Result
|
|
Config-->>TCPListener: Cached Result
|
|
else Evaluate Allow Rules
|
|
Config->>Config: Check Allow list
|
|
alt Matches
|
|
Config->>Config: Cache true
|
|
Config-->>TCPListener: Allowed
|
|
else Evaluate Deny Rules
|
|
Config->>Config: Check Deny list
|
|
alt Matches
|
|
Config->>Config: Cache false
|
|
Config-->>TCPListener: Denied
|
|
else Default Action
|
|
Config->>MaxMind: Lookup GeoIP
|
|
MaxMind-->>Config: IPInfo
|
|
Config->>Config: Apply default rule
|
|
Config->>Config: Cache result
|
|
Config-->>TCPListener: Result
|
|
end
|
|
end
|
|
end
|
|
|
|
alt Logging enabled
|
|
Config->>Logger: Log access attempt
|
|
end
|
|
```
|
|
|
|
### Matcher types
|
|
|
|
| Type | Format | Example |
|
|
| -------- | ----------------- | --------------------- |
|
|
| IP | `ip:address` | `ip:192.168.1.1` |
|
|
| CIDR | `cidr:network` | `cidr:192.168.0.0/16` |
|
|
| TimeZone | `tz:timezone` | `tz:Asia/Shanghai` |
|
|
| Country | `country:ISOCode` | `country:GB` |
|
|
|
|
## Configuration Surface
|
|
|
|
### Config sources
|
|
|
|
Configuration is loaded from `config/config.yml` under the `acl` key.
|
|
|
|
### Schema
|
|
|
|
```yaml
|
|
acl:
|
|
default: "allow" # "allow" or "deny"
|
|
allow_local: true # Allow private/loopback IPs
|
|
log:
|
|
log_allowed: false # Log allowed connections
|
|
notify:
|
|
to: ["gotify"] # Notification providers
|
|
interval: "1m" # Notification interval
|
|
include_allowed: false # Include allowed in notifications
|
|
```
|
|
|
|
### Hot-reloading
|
|
|
|
Configuration requires restart. The ACL does not support dynamic rule updates.
|
|
|
|
## Dependency and Integration Map
|
|
|
|
### Internal dependencies
|
|
|
|
- `internal/maxmind` - IP geolocation lookup
|
|
- `internal/logging/accesslog` - Access logging
|
|
- `internal/notif` - Notifications
|
|
- `internal/task/task.go` - Lifetime management
|
|
|
|
### Integration points
|
|
|
|
```go
|
|
// Entrypoint uses ACL to wrap the TCP listener
|
|
aclListener := config.ACL.WrapTCP(listener)
|
|
http.Server.Serve(aclListener, entrypoint)
|
|
```
|
|
|
|
## Observability
|
|
|
|
### Logs
|
|
|
|
- `ACL started` - Configuration summary on start
|
|
- `log_notify_loop` - Access attempts (allowed/denied)
|
|
|
|
Log levels: `Info` for startup, `Debug` for client closure.
|
|
|
|
### Metrics
|
|
|
|
No metrics are currently exposed.
|
|
|
|
## Security Considerations
|
|
|
|
- Loopback and private IPs are always allowed unless explicitly denied
|
|
- Cache TTL is 1 minute to limit memory usage
|
|
- Notification channel has a buffer of 100 to prevent blocking
|
|
- Failed connections are immediately closed without response
|
|
|
|
## Failure Modes and Recovery
|
|
|
|
| Failure | Behavior | Recovery |
|
|
| --------------------------------- | ------------------------------------- | --------------------------------------------- |
|
|
| Invalid matcher syntax | Validation fails on startup | Fix configuration syntax |
|
|
| MaxMind database unavailable | GeoIP lookups return unknown location | Default action applies; cache hit still works |
|
|
| Notification provider unavailable | Notification dropped | Error logged, continues operation |
|
|
| Cache full | No eviction, uses Go map | No action needed |
|
|
|
|
## Usage Examples
|
|
|
|
### Basic configuration
|
|
|
|
```go
|
|
aclConfig := &acl.Config{
|
|
Default: "allow",
|
|
AllowLocal: ptr(true),
|
|
Allow: acl.Matchers{
|
|
{match: matchIP(net.ParseIP("192.168.1.0/24"))},
|
|
},
|
|
Deny: acl.Matchers{
|
|
{match: matchISOCode("CN")},
|
|
},
|
|
}
|
|
if err := aclConfig.Validate(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if err := aclConfig.Start(parent); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
```
|
|
|
|
### Wrapping a TCP listener
|
|
|
|
```go
|
|
listener, err := net.Listen("tcp", ":443")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Wrap with ACL
|
|
aclListener := aclConfig.WrapTCP(listener)
|
|
|
|
// Use with HTTP server
|
|
server := &http.Server{}
|
|
server.Serve(aclListener)
|
|
```
|
|
|
|
### Creating custom matchers
|
|
|
|
```go
|
|
matcher := &acl.Matcher{}
|
|
err := matcher.Parse("country:US")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Use the matcher
|
|
allowed := matcher.match(ipInfo)
|
|
```
|