mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-26 11:21:49 +01:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
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() error
|
|
```
|
|
|
|
Validates configuration and sets defaults. Must be called before `Start`.
|
|
|
|
```go
|
|
func (c *Config) Start(parent task.Parent) 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)
|
|
```
|