Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"

Expand All @@ -33,6 +34,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
log.Printf("Enabled endpoints: %s", strings.Join(cfg.Endpoints.Enabled(), ", "))
log.Printf("Authorization method set to: %s", cfg.Auth.Method)
log.Printf("Starting hetzner-dnsapi-proxy, listening on %s", cfg.ListenAddr)
if err := runServer(cfg.ListenAddr, app.New(cfg)); err != nil {
Expand Down
46 changes: 28 additions & 18 deletions pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,34 @@ func New(cfg *config.Config) http.Handler {
rl := middleware.NewRateLimit(limiter, middleware.RateLimitExceeded)

mux := http.NewServeMux()
mux.Handle("GET /plain/update",
handle(cfg, rl, middleware.BindPlain, authorizer, updater, middleware.StatusOk))
mux.Handle("GET /nic/update", handle(
cfg, middleware.NewRateLimit(limiter, middleware.NicRateLimitExceeded), middleware.BindNicUpdate,
middleware.NicAuth(cfg, lockout), middleware.NicUpdate(updater), middleware.StatusOkNicUpdate,
))
mux.Handle("POST /acmedns/update",
handle(cfg, rl, middleware.BindAcmeDNS, authorizer, updater, middleware.StatusOkAcmeDNS))
mux.Handle("POST /httpreq/present",
handle(cfg, rl, middleware.ContentTypeJSON, middleware.BindHTTPReq, authorizer, updater, middleware.StatusOk))
mux.Handle("POST /httpreq/cleanup",
handle(cfg, rl, middleware.ContentTypeJSON, middleware.BindHTTPReq, authorizer, cleaner, middleware.StatusOk))
mux.Handle("GET /directadmin/CMD_API_SHOW_DOMAINS",
handle(cfg, rl, middleware.NewShowDomainsDirectAdmin(cfg, lockout)))
mux.Handle("GET /directadmin/CMD_API_DOMAIN_POINTER",
handle(cfg, rl, middleware.StatusOk))
mux.Handle("GET /directadmin/CMD_API_DNS_CONTROL",
handle(cfg, rl, middleware.BindDirectAdmin, authorizer, updater, middleware.StatusOkDirectAdmin))
if cfg.Endpoints.Plain {
mux.Handle("GET /plain/update",
handle(cfg, rl, middleware.BindPlain, authorizer, updater, middleware.StatusOk))
}
if cfg.Endpoints.Nic {
mux.Handle("GET /nic/update", handle(
cfg, middleware.NewRateLimit(limiter, middleware.NicRateLimitExceeded), middleware.BindNicUpdate,
middleware.NicAuth(cfg, lockout), middleware.NicUpdate(updater), middleware.StatusOkNicUpdate,
))
}
if cfg.Endpoints.AcmeDNS {
mux.Handle("POST /acmedns/update",
handle(cfg, rl, middleware.BindAcmeDNS, authorizer, updater, middleware.StatusOkAcmeDNS))
}
if cfg.Endpoints.HTTPReq {
mux.Handle("POST /httpreq/present",
handle(cfg, rl, middleware.ContentTypeJSON, middleware.BindHTTPReq, authorizer, updater, middleware.StatusOk))
mux.Handle("POST /httpreq/cleanup",
handle(cfg, rl, middleware.ContentTypeJSON, middleware.BindHTTPReq, authorizer, cleaner, middleware.StatusOk))
}
if cfg.Endpoints.DirectAdmin {
mux.Handle("GET /directadmin/CMD_API_SHOW_DOMAINS",
handle(cfg, rl, middleware.NewShowDomainsDirectAdmin(cfg, lockout)))
mux.Handle("GET /directadmin/CMD_API_DOMAIN_POINTER",
handle(cfg, rl, middleware.StatusOk))
mux.Handle("GET /directadmin/CMD_API_DNS_CONTROL",
handle(cfg, rl, middleware.BindDirectAdmin, authorizer, updater, middleware.StatusOkDirectAdmin))
}

return mux
}
Expand Down
117 changes: 104 additions & 13 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type AllowedDomains map[string][]*net.IPNet

func (out *AllowedDomains) FromString(val string) error {
allowedDomains := AllowedDomains{}
for _, part := range strings.Split(val, ";") {
for part := range strings.SplitSeq(val, ";") {
parts := strings.Split(part, ",")

const expectedParts = 2
Expand All @@ -41,6 +41,7 @@ type Config struct {
Token string `yaml:"token"`
Timeout int `yaml:"timeout"`
Auth Auth `yaml:"auth"`
Endpoints Endpoints `yaml:"endpoints"`
RecordTTL int `yaml:"recordTTL"`
ListenAddr string `yaml:"listenAddr"`
TrustedProxies []string `yaml:"trustedProxies"`
Expand All @@ -50,12 +51,58 @@ type Config struct {
Debug bool `yaml:"debug"`
}

type Endpoints struct {
Plain bool `yaml:"plain"`
Nic bool `yaml:"nic"`
AcmeDNS bool `yaml:"acmedns"`
HTTPReq bool `yaml:"httpreq"`
DirectAdmin bool `yaml:"directadmin"`
}

func (e *Endpoints) Enabled() []string {
var names []string
if e.Plain {
names = append(names, EndpointPlain)
}
if e.Nic {
names = append(names, EndpointNic)
}
if e.AcmeDNS {
names = append(names, EndpointAcmeDNS)
}
if e.HTTPReq {
names = append(names, EndpointHTTPReq)
}
if e.DirectAdmin {
names = append(names, EndpointDirectAdmin)
}
return names
}

func (e *Endpoints) UnmarshalYAML(unmarshal func(any) error) error {
type raw Endpoints
var r raw
if err := unmarshal(&r); err != nil {
return err
}
*e = Endpoints(r)
return nil
}

type Auth struct {
Method string `yaml:"method"`
AllowedDomains AllowedDomains `yaml:"allowedDomains"`
Users []User `yaml:"users"`
}

const (
EndpointPlain = "plain"
EndpointNic = "nic"
EndpointAcmeDNS = "acmedns"
EndpointHTTPReq = "httpreq"
EndpointDirectAdmin = "directadmin"
)

const (
AuthMethodAllowedDomains = "allowedDomains"
AuthMethodUsers = "users"
Expand Down Expand Up @@ -87,6 +134,13 @@ func NewConfig() *Config {
Auth: Auth{
Method: AuthMethodBoth,
},
Endpoints: Endpoints{
Plain: true,
Nic: true,
AcmeDNS: true,
HTTPReq: true,
DirectAdmin: true,
},
RecordTTL: 60,
ListenAddr: ":8081",
RateLimit: RateLimit{
Expand Down Expand Up @@ -140,22 +194,13 @@ func ParseEnv() (*Config, error) {
if err := envBool("DEBUG", &cfg.Debug); err != nil {
return nil, err
}
if err := envFloat("RATE_LIMIT_RPS", &cfg.RateLimit.RPS); err != nil {
if err := envRateLimit(&cfg.RateLimit); err != nil {
return nil, err
}
if err := envInt("RATE_LIMIT_BURST", &cfg.RateLimit.Burst); err != nil {
if err := envLockout(&cfg.Lockout); err != nil {
return nil, err
}
if err := envInt("RATE_LIMIT_IDLE_SECONDS", &cfg.RateLimit.IdleSeconds); err != nil {
return nil, err
}
if err := envInt("LOCKOUT_MAX_ATTEMPTS", &cfg.Lockout.MaxAttempts); err != nil {
return nil, err
}
if err := envInt("LOCKOUT_DURATION_SECONDS", &cfg.Lockout.DurationSeconds); err != nil {
return nil, err
}
if err := envInt("LOCKOUT_WINDOW_SECONDS", &cfg.Lockout.WindowSeconds); err != nil {
if err := envEndpoints(&cfg.Endpoints); err != nil {
return nil, err
}

Expand Down Expand Up @@ -215,6 +260,52 @@ func envBool(key string, dst *bool) error {
return nil
}

func envRateLimit(rl *RateLimit) error {
if err := envFloat("RATE_LIMIT_RPS", &rl.RPS); err != nil {
return err
}
if err := envInt("RATE_LIMIT_BURST", &rl.Burst); err != nil {
return err
}
return envInt("RATE_LIMIT_IDLE_SECONDS", &rl.IdleSeconds)
}

func envLockout(l *Lockout) error {
if err := envInt("LOCKOUT_MAX_ATTEMPTS", &l.MaxAttempts); err != nil {
return err
}
if err := envInt("LOCKOUT_DURATION_SECONDS", &l.DurationSeconds); err != nil {
return err
}
return envInt("LOCKOUT_WINDOW_SECONDS", &l.WindowSeconds)
}

func envEndpoints(endpoints *Endpoints) error {
v, ok := os.LookupEnv("ENDPOINTS")
if !ok {
return nil
}
*endpoints = Endpoints{}
for name := range strings.SplitSeq(v, ",") {
name = strings.TrimSpace(name)
switch name {
case EndpointPlain:
endpoints.Plain = true
case EndpointNic:
endpoints.Nic = true
case EndpointAcmeDNS:
endpoints.AcmeDNS = true
case EndpointHTTPReq:
endpoints.HTTPReq = true
case EndpointDirectAdmin:
endpoints.DirectAdmin = true
default:
return fmt.Errorf("invalid endpoint %q in ENDPOINTS", name)
}
}
return nil
}

func envTrustedProxies(cfg *Config) {
v, ok := os.LookupEnv("TRUSTED_PROXIES")
if !ok {
Expand Down
1 change: 1 addition & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ var _ = Describe("Config", func() {
AllowedDomains: allowedDomains,
Users: users,
},
Endpoints: config.Endpoints{Plain: true, Nic: true, AcmeDNS: true, HTTPReq: true, DirectAdmin: true},
RecordTTL: recordTTL,
ListenAddr: listenAddr,
TrustedProxies: trustedProxies,
Expand Down
2 changes: 2 additions & 0 deletions tests/libserver/libserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func New(url string, ttl int) (server *httptest.Server, token, username, passwor
Domains: []string{"*"},
}},
},
Endpoints: config.Endpoints{Plain: true, Nic: true, AcmeDNS: true, HTTPReq: true, DirectAdmin: true},
RecordTTL: ttl,
RateLimit: config.RateLimit{RPS: 1000, Burst: 1000, IdleSeconds: 600},
Lockout: config.Lockout{MaxAttempts: 1000, DurationSeconds: 3600, WindowSeconds: 900},
Expand All @@ -50,6 +51,7 @@ func NewNoAllowedDomains(url string) *httptest.Server {
Auth: config.Auth{
Method: config.AuthMethodAllowedDomains,
},
Endpoints: config.Endpoints{Plain: true, Nic: true, AcmeDNS: true, HTTPReq: true, DirectAdmin: true},
RateLimit: config.RateLimit{RPS: 1000, Burst: 1000, IdleSeconds: 600},
Lockout: config.Lockout{MaxAttempts: 1000, DurationSeconds: 3600, WindowSeconds: 900},
}
Expand Down
Loading