Remove all fabricated candidate names (suffixes and the wordlist). Discovery now only tests names that actually occur in the incoming hostname: the subdomain label and the registrable-domain label (assets.example.com -> assets, example). Nothing is permuted or drawn from a wordlist.
190 lines
5.1 KiB
Go
190 lines
5.1 KiB
Go
// Package config loads all ctcloud configuration from environment variables.
|
|
// There are no configuration files at runtime.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
DatabaseURL string
|
|
DBMaxConns int
|
|
|
|
DNSServers []string
|
|
DNSWorkers int
|
|
DNSTimeout time.Duration
|
|
DNSCacheSize int
|
|
DNSMaxCNAMEdepth int
|
|
DNSPositiveTTL time.Duration
|
|
DNSNegativeTTL time.Duration
|
|
|
|
DedupSize int
|
|
|
|
DirectQueueSize int
|
|
DNSQueueSize int
|
|
DBQueueSize int
|
|
|
|
DBBatchSize int
|
|
DBFlushInterval time.Duration
|
|
|
|
ActiveProbing bool
|
|
ProbeWorkers int
|
|
ProbeTimeout time.Duration
|
|
ProbeQueueSize int
|
|
ProbeDeep bool
|
|
IndexEnabled bool
|
|
IndexMaxObjects int
|
|
|
|
BucketScanEnabled bool
|
|
BucketScanQPS int
|
|
BucketScanWorkers int
|
|
BucketScanQueue int
|
|
BucketScanDO bool
|
|
|
|
MetricsAddr string
|
|
UIUser string
|
|
UIPass string
|
|
LogLevel string
|
|
LogFormat string
|
|
StatsInterval time.Duration
|
|
|
|
LogListSource string
|
|
StartAtEnd bool
|
|
ShutdownTimeout time.Duration
|
|
DedupeTTL time.Duration
|
|
}
|
|
|
|
// DefaultLogListSource is the public CT log list used by certspotter.
|
|
const DefaultLogListSource = "https://www.gstatic.com/ct/log_list/v3/log_list.json"
|
|
|
|
func Load() (*Config, error) {
|
|
c := &Config{
|
|
DatabaseURL: os.Getenv("DATABASE_URL"),
|
|
DBMaxConns: envInt("DB_MAX_CONNS", 8),
|
|
DNSServers: envList("DNS_SERVERS", nil),
|
|
DNSWorkers: envInt("DNS_WORKERS", 64),
|
|
DNSTimeout: envDuration("DNS_TIMEOUT", 5*time.Second),
|
|
DNSCacheSize: envInt("DNS_CACHE_SIZE", 1<<18),
|
|
DedupSize: envInt("DEDUP_SIZE", 1<<18),
|
|
DNSMaxCNAMEdepth: envInt("DNS_MAX_CNAME_DEPTH", 8),
|
|
DNSPositiveTTL: envDuration("DNS_POSITIVE_TTL", 10*time.Minute),
|
|
DNSNegativeTTL: envDuration("DNS_NEGATIVE_TTL", 1*time.Minute),
|
|
DirectQueueSize: envInt("DIRECT_QUEUE_SIZE", 8192),
|
|
DNSQueueSize: envInt("DNS_QUEUE_SIZE", 262144),
|
|
DBQueueSize: envInt("DB_QUEUE_SIZE", 8192),
|
|
DBBatchSize: envInt("DB_BATCH_SIZE", 500),
|
|
DBFlushInterval: envDuration("DB_FLUSH_INTERVAL", 2*time.Second),
|
|
ActiveProbing: envBool("ACTIVE_PROBING", false),
|
|
ProbeWorkers: envInt("PROBE_WORKERS", 8),
|
|
ProbeTimeout: envDuration("PROBE_TIMEOUT", 5*time.Second),
|
|
ProbeQueueSize: envInt("PROBE_QUEUE_SIZE", 1<<16),
|
|
ProbeDeep: envBool("PROBE_DEEP", true),
|
|
IndexEnabled: envBool("INDEX_ENABLED", true),
|
|
IndexMaxObjects: envInt("INDEX_MAX_OBJECTS", 1000),
|
|
BucketScanEnabled: envBool("BUCKET_SCAN_ENABLED", true),
|
|
BucketScanQPS: envInt("BUCKET_SCAN_QPS", 50),
|
|
BucketScanWorkers: envInt("BUCKET_SCAN_WORKERS", 16),
|
|
BucketScanQueue: envInt("BUCKET_SCAN_QUEUE", 65536),
|
|
BucketScanDO: envBool("BUCKET_SCAN_DO", false),
|
|
MetricsAddr: envString("METRICS_ADDR", ":9090"),
|
|
UIUser: envString("UI_USER", ""),
|
|
UIPass: envString("UI_PASS", ""),
|
|
LogLevel: envString("LOG_LEVEL", "info"),
|
|
LogFormat: envString("LOG_FORMAT", "text"),
|
|
StatsInterval: envDuration("STATS_INTERVAL", 60*time.Second),
|
|
LogListSource: envString("LOG_LIST_SOURCE", DefaultLogListSource),
|
|
StartAtEnd: envBool("START_AT_END", false),
|
|
ShutdownTimeout: envDuration("SHUTDOWN_TIMEOUT", 20*time.Second),
|
|
DedupeTTL: envDuration("DEDUP_TTL", time.Hour),
|
|
}
|
|
if err := c.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *Config) validate() error {
|
|
if c.DatabaseURL == "" {
|
|
return fmt.Errorf("DATABASE_URL is required")
|
|
}
|
|
if c.DNSWorkers < 1 {
|
|
return fmt.Errorf("DNS_WORKERS must be >= 1")
|
|
}
|
|
if c.DNSMaxCNAMEdepth < 1 {
|
|
return fmt.Errorf("DNS_MAX_CNAME_DEPTH must be >= 1")
|
|
}
|
|
if c.DirectQueueSize < 1 || c.DNSQueueSize < 1 || c.DBQueueSize < 1 {
|
|
return fmt.Errorf("queue sizes must be >= 1")
|
|
}
|
|
if c.DBBatchSize < 1 {
|
|
return fmt.Errorf("DB_BATCH_SIZE must be >= 1")
|
|
}
|
|
switch c.LogLevel {
|
|
case "debug", "info", "warn", "error":
|
|
default:
|
|
return fmt.Errorf("LOG_LEVEL must be one of debug, info, warn, error")
|
|
}
|
|
switch c.LogFormat {
|
|
case "text", "json":
|
|
default:
|
|
return fmt.Errorf("LOG_FORMAT must be text or json")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func envList(key string, def []string) []string {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return def
|
|
}
|
|
parts := strings.Split(v, ",")
|
|
out := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
if p = strings.TrimSpace(p); p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return def
|
|
}
|
|
return out
|
|
}
|
|
|
|
func envString(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func envInt(key string, def int) int {
|
|
if v := os.Getenv(key); v != "" {
|
|
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
func envBool(key string, def bool) bool {
|
|
if v := os.Getenv(key); v != "" {
|
|
if b, err := strconv.ParseBool(strings.TrimSpace(v)); err == nil {
|
|
return b
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
func envDuration(key string, def time.Duration) time.Duration {
|
|
if v := os.Getenv(key); v != "" {
|
|
if d, err := time.ParseDuration(strings.TrimSpace(v)); err == nil {
|
|
return d
|
|
}
|
|
}
|
|
return def
|
|
}
|