Merge commit from fork

Instead of casting lease ID to uint32, fix the TTL() function
to use etcd time-to-live API for determining TTL. Add configurable
min-lease-ttl and max-lease-ttl options to prevent extreme TTL
values. By default, lease records now go through bounds checking
with 30s to 1d as the min/max.

Added unit tests for validation and docs.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
This commit is contained in:
Ville Vesilehto
2025-09-05 03:14:27 +03:00
committed by GitHub
parent 066e51675c
commit e1768a5d27
6 changed files with 333 additions and 15 deletions

View File

@@ -2,7 +2,11 @@ package etcd
import (
"crypto/tls"
"errors"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
@@ -33,7 +37,11 @@ func setup(c *caddy.Controller) error {
func etcdParse(c *caddy.Controller) (*Etcd, error) {
config := dnsserver.GetConfig(c)
etc := Etcd{PathPrefix: "skydns"}
etc := Etcd{
PathPrefix: "skydns",
MinLeaseTTL: defaultLeaseMinTTL,
MaxLeaseTTL: defaultLeaseMaxTTL,
}
var (
tlsConfig *tls.Config
err error
@@ -88,6 +96,24 @@ func etcdParse(c *caddy.Controller) (*Etcd, error) {
return &Etcd{}, c.Errf("credentials requires 2 arguments, username and password")
}
username, password = args[0], args[1]
case "min-lease-ttl":
if !c.NextArg() {
return &Etcd{}, c.ArgErr()
}
minLeaseTTL, err := parseTTL(c.Val())
if err != nil {
return &Etcd{}, c.Errf("invalid min-lease-ttl value: %v", err)
}
etc.MinLeaseTTL = minLeaseTTL
case "max-lease-ttl":
if !c.NextArg() {
return &Etcd{}, c.ArgErr()
}
maxLeaseTTL, err := parseTTL(c.Val())
if err != nil {
return &Etcd{}, c.Errf("invalid max-lease-ttl value: %v", err)
}
etc.MaxLeaseTTL = maxLeaseTTL
default:
if c.Val() != "}" {
return &Etcd{}, c.Errf("unknown property '%s'", c.Val())
@@ -124,3 +150,35 @@ func newEtcdClient(endpoints []string, cc *tls.Config, username, password string
}
const defaultEndpoint = "http://localhost:2379"
// parseTTL parses a TTL value with flexible time units using Go's standard duration parsing.
// Supports formats like: "30", "30s", "5m", "1h", "90s", "2h30m", etc.
func parseTTL(s string) (uint32, error) {
s = strings.TrimSpace(s)
if s == "" {
return 0, nil
}
// Handle plain numbers (assume seconds)
if _, err := strconv.ParseUint(s, 10, 64); err == nil {
// If it's just a number, append "s" for seconds
s += "s"
}
// Use Go's standard time.ParseDuration for robust parsing
duration, err := time.ParseDuration(s)
if err != nil {
return 0, errors.New("invalid TTL format, use format like '30', '30s', '5m', '1h', or '2h30m'")
}
// Convert to seconds and check bounds
seconds := duration.Seconds()
if seconds < 0 {
return 0, errors.New("TTL must be non-negative")
}
if seconds > 4294967295 { // uint32 max value
return 0, errors.New("TTL too large, maximum is 4294967295 seconds")
}
return uint32(seconds), nil
}