diff --git a/config.example.yaml b/config.example.yaml index 8bddd04..dea03d8 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -9,8 +9,10 @@ listen: ":8080" allow_all: false # CIDR allowlist when allow_all is false. Empty list denies all clients. +# Можно указывать и одиночный IP (будет трактован как /32 или /128), и CIDR. whitelist_cidrs: - "127.0.0.1/32" + - "203.0.113.5" - "::1/128" # Docker bridge (adjust to your environment): # - "172.16.0.0/12" @@ -24,8 +26,18 @@ trusted_proxies: [] servers: - alias: main_srv base_url: http://127.0.0.1:9091 - # Default path prefix on upstream (Telemt Control API uses /v1). path_prefix: /v1 - # Optional: name of environment variable whose value is sent as Authorization - # to this upstream (exact string, Telemt auth_header semantics). # authorization_env: TELEMT_API_AUTH + + # ivx: порт 9091 как у типичного Telemt API; при необходимости — https и другой порт. + - alias: gt1 + base_url: http://gt1.ivx.su:9091 + path_prefix: /v1 + + - alias: gt2 + base_url: http://gt2.ivx.su:9091 + path_prefix: /v1 + + - alias: gt3 + base_url: http://gt3.ivx.su:9091 + path_prefix: /v1 diff --git a/docs/GATEWAY_RUN.md b/docs/GATEWAY_RUN.md index a1db22b..60de1e0 100644 --- a/docs/GATEWAY_RUN.md +++ b/docs/GATEWAY_RUN.md @@ -60,6 +60,7 @@ servers: Правила: - При `allow_all: false` и **пустом** `whitelist_cidrs` доступ будет **закрыт для всех** (кроме `GET /health`). +- В `whitelist_cidrs` и `trusted_proxies` допустимы **CIDR** (`10.0.0.0/8`) и **одиночный IPv4/IPv6** без маски (`87.103.241.8` эквивалентно `87.103.241.8/32`). - `GET /health` на шлюзе **не** проверяется по whitelist — так проще настроить Docker `HEALTHCHECK` и оркестраторы. - Поле `path_prefix` по умолчанию равно `/v1` (префикс Telemt Control API). diff --git a/internal/config/config.go b/internal/config/config.go index 64d6ba5..ed79a3b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -83,18 +83,34 @@ func (c *Config) Validate() error { return fmt.Errorf("at least one server entry is required") } for i, s := range c.WhitelistCIDRs { - if _, err := netip.ParsePrefix(strings.TrimSpace(s)); err != nil { + if _, err := parseCIDROrIP(s); err != nil { return fmt.Errorf("whitelist_cidrs[%d]: %w", i, err) } } for i, s := range c.TrustedProxies { - if _, err := netip.ParsePrefix(strings.TrimSpace(s)); err != nil { + if _, err := parseCIDROrIP(s); err != nil { return fmt.Errorf("trusted_proxies[%d]: %w", i, err) } } return nil } +// parseCIDROrIP accepts a CIDR ("10.0.0.0/8") or a single IP ("87.103.241.8" → /32 or /128). +func parseCIDROrIP(s string) (netip.Prefix, error) { + s = strings.TrimSpace(s) + if s == "" { + return netip.Prefix{}, fmt.Errorf("empty") + } + if p, err := netip.ParsePrefix(s); err == nil { + return p, nil + } + addr, err := netip.ParseAddr(s) + if err != nil { + return netip.Prefix{}, fmt.Errorf("%w (use CIDR like %s/32 for IPv4)", err, s) + } + return addr.Prefix(addr.BitLen()) +} + // Parsed holds compiled CIDR lists and server map. type Parsed struct { Config *Config @@ -108,7 +124,7 @@ type Parsed struct { func (c *Config) Parse() (*Parsed, error) { var wl []netip.Prefix for _, s := range c.WhitelistCIDRs { - p, err := netip.ParsePrefix(strings.TrimSpace(s)) + p, err := parseCIDROrIP(s) if err != nil { return nil, err } @@ -116,7 +132,7 @@ func (c *Config) Parse() (*Parsed, error) { } var tr []netip.Prefix for _, s := range c.TrustedProxies { - p, err := netip.ParsePrefix(strings.TrimSpace(s)) + p, err := parseCIDROrIP(s) if err != nil { return nil, err } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index fb117ca..617d755 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -31,6 +31,26 @@ servers: } } +func TestParseCIDRorIP(t *testing.T) { + for _, tc := range []struct { + in string + want string + }{ + {"87.103.241.8", "87.103.241.8/32"}, + {"87.103.241.8/32", "87.103.241.8/32"}, + {"10.0.0.0/8", "10.0.0.0/8"}, + {"2001:db8::1", "2001:db8::1/128"}, + } { + p, err := parseCIDROrIP(tc.in) + if err != nil { + t.Fatalf("%q: %v", tc.in, err) + } + if p.String() != tc.want { + t.Fatalf("%q: got %s want %s", tc.in, p, tc.want) + } + } +} + func TestValidateDuplicateAlias(t *testing.T) { c := &Config{ Servers: []Server{