github.com/letsencrypt/boulder@v0.20251208.0/observer/probers/dns/dns_conf.go (about) 1 package probers 2 3 import ( 4 "fmt" 5 "net" 6 "net/netip" 7 "slices" 8 "strconv" 9 "strings" 10 11 "github.com/miekg/dns" 12 "github.com/prometheus/client_golang/prometheus" 13 14 "github.com/letsencrypt/boulder/observer/probers" 15 "github.com/letsencrypt/boulder/strictyaml" 16 ) 17 18 var ( 19 validQTypes = map[string]uint16{"A": 1, "TXT": 16, "AAAA": 28, "CAA": 257} 20 ) 21 22 // DNSConf is exported to receive YAML configuration 23 type DNSConf struct { 24 Proto string `yaml:"protocol"` 25 Server string `yaml:"server"` 26 Recurse bool `yaml:"recurse"` 27 QName string `yaml:"query_name"` 28 QType string `yaml:"query_type"` 29 } 30 31 // Kind returns a name that uniquely identifies the `Kind` of `Configurer`. 32 func (c DNSConf) Kind() string { 33 return "DNS" 34 } 35 36 // UnmarshalSettings constructs a DNSConf object from YAML as bytes. 37 func (c DNSConf) UnmarshalSettings(settings []byte) (probers.Configurer, error) { 38 var conf DNSConf 39 err := strictyaml.Unmarshal(settings, &conf) 40 if err != nil { 41 return nil, err 42 } 43 return conf, nil 44 } 45 46 func (c DNSConf) validateServer() error { 47 server := strings.Trim(strings.ToLower(c.Server), " ") 48 // Ensure `server` contains a port. 49 host, port, err := net.SplitHostPort(server) 50 if err != nil || port == "" { 51 return fmt.Errorf( 52 "invalid `server`, %q, could not be split: %s", c.Server, err) 53 } 54 // Ensure `server` port is valid. 55 portNum, err := strconv.Atoi(port) 56 if err != nil { 57 return fmt.Errorf( 58 "invalid `server`, %q, port must be a number", c.Server) 59 } 60 if portNum <= 0 || portNum > 65535 { 61 return fmt.Errorf( 62 "invalid `server`, %q, port number must be one in [1-65535]", c.Server) 63 } 64 // Ensure `server` is a valid FQDN or IP address. 65 _, err = netip.ParseAddr(host) 66 FQDN := dns.IsFqdn(dns.Fqdn(host)) 67 if err != nil && !FQDN { 68 return fmt.Errorf( 69 "invalid `server`, %q, is not an FQDN or IP address", c.Server) 70 } 71 return nil 72 } 73 74 func (c DNSConf) validateProto() error { 75 validProtos := []string{"udp", "tcp"} 76 proto := strings.Trim(strings.ToLower(c.Proto), " ") 77 if slices.Contains(validProtos, proto) { 78 return nil 79 } 80 return fmt.Errorf( 81 "invalid `protocol`, got: %q, expected one in: %s", c.Proto, validProtos) 82 } 83 84 func (c DNSConf) validateQType() error { 85 validQTypes = map[string]uint16{"A": 1, "TXT": 16, "AAAA": 28, "CAA": 257} 86 qtype := strings.Trim(strings.ToUpper(c.QType), " ") 87 q := make([]string, 0, len(validQTypes)) 88 for i := range validQTypes { 89 q = append(q, i) 90 if qtype == i { 91 return nil 92 } 93 } 94 return fmt.Errorf( 95 "invalid `query_type`, got: %q, expected one in %s", c.QType, q) 96 } 97 98 // MakeProber constructs a `DNSProbe` object from the contents of the 99 // bound `DNSConf` object. If the `DNSConf` cannot be validated, an 100 // error appropriate for end-user consumption is returned instead. 101 func (c DNSConf) MakeProber(_ map[string]prometheus.Collector) (probers.Prober, error) { 102 // validate `query_name` 103 if !dns.IsFqdn(dns.Fqdn(c.QName)) { 104 return nil, fmt.Errorf( 105 "invalid `query_name`, %q is not an fqdn", c.QName) 106 } 107 108 // validate `server` 109 err := c.validateServer() 110 if err != nil { 111 return nil, err 112 } 113 114 // validate `protocol` 115 err = c.validateProto() 116 if err != nil { 117 return nil, err 118 } 119 120 // validate `query_type` 121 err = c.validateQType() 122 if err != nil { 123 return nil, err 124 } 125 126 return DNSProbe{ 127 proto: strings.Trim(strings.ToLower(c.Proto), " "), 128 recurse: c.Recurse, 129 qname: c.QName, 130 server: c.Server, 131 qtype: validQTypes[strings.Trim(strings.ToUpper(c.QType), " ")], 132 }, nil 133 } 134 135 // Instrument is a no-op to implement the `Configurer` interface. 136 func (c DNSConf) Instrument() map[string]prometheus.Collector { 137 return nil 138 } 139 140 // init is called at runtime and registers `DNSConf`, a `Prober` 141 // `Configurer` type, as "DNS". 142 func init() { 143 probers.Register(DNSConf{}) 144 }