Add DNS resolver validation context
- commit
- 51f98600e96ddd971ea693eb90143216f7529275
- author
- brice · · 2026-05-01 17:32 UTC
- parent
- ae10fc7
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +30 -4
M internal/adapters/out/acmelego/issuer.go +4 -4
| @@ -123,18 +123,18 @@ func normalizeDNSResolvers(resolvers []string) ([]string, error) { | |||
| 123 | 123 | for i, resolver := range resolvers { | |
| 124 | 124 | trimmed := strings.TrimSpace(resolver) | |
| 125 | 125 | if trimmed == "" { | |
| 126 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | |
| 126 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q", domain.ErrDNSConfigInvalid, i, trimmed) | |
| 127 | 127 | } | |
| 128 | 128 | host, port, err := net.SplitHostPort(trimmed) | |
| 129 | 129 | if err != nil { | |
| 130 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | |
| 130 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: %v", domain.ErrDNSConfigInvalid, i, trimmed, err) | |
| 131 | 131 | } | |
| 132 | 132 | if host == "" { | |
| 133 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | |
| 133 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: host must not be empty", domain.ErrDNSConfigInvalid, i, trimmed) | |
| 134 | 134 | } | |
| 135 | 135 | portNumber, err := strconv.Atoi(port) | |
| 136 | 136 | if err != nil || portNumber < 1 || portNumber > 65535 { | |
| 137 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | |
| 137 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: port must be a number between 1 and 65535", domain.ErrDNSConfigInvalid, i, trimmed) | |
| 138 | 138 | } | |
| 139 | 139 | normalized[i] = trimmed | |
| 140 | 140 | } | |
| @@ -123,18 +123,18 @@ func normalizeDNSResolvers(resolvers []string) ([]string, error) { | |||||
| 123 | for i, resolver := range resolvers { | 123 | for i, resolver := range resolvers { | ||
| 124 | trimmed := strings.TrimSpace(resolver) | 124 | trimmed := strings.TrimSpace(resolver) | ||
| 125 | if trimmed == "" { | 125 | if trimmed == "" { | ||
| 126 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | 126 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q", domain.ErrDNSConfigInvalid, i, trimmed) |
| 127 | } | 127 | } | ||
| 128 | host, port, err := net.SplitHostPort(trimmed) | 128 | host, port, err := net.SplitHostPort(trimmed) | ||
| 129 | if err != nil { | 129 | if err != nil { | ||
| 130 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | 130 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: %v", domain.ErrDNSConfigInvalid, i, trimmed, err) |
| 131 | } | 131 | } | ||
| 132 | if host == "" { | 132 | if host == "" { | ||
| 133 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | 133 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: host must not be empty", domain.ErrDNSConfigInvalid, i, trimmed) |
| 134 | } | 134 | } | ||
| 135 | portNumber, err := strconv.Atoi(port) | 135 | portNumber, err := strconv.Atoi(port) | ||
| 136 | if err != nil || portNumber < 1 || portNumber > 65535 { | 136 | if err != nil || portNumber < 1 || portNumber > 65535 { | ||
| 137 | - | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid) | 137 | + | return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: port must be a number between 1 and 65535", domain.ErrDNSConfigInvalid, i, trimmed) |
| 138 | } | 138 | } | ||
| 139 | normalized[i] = trimmed | 139 | normalized[i] = trimmed | ||
| 140 | } | 140 | } | ||
M internal/adapters/out/acmelego/issuer_test.go +26 -0
| @@ -95,6 +95,8 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) { | |||
| 95 | 95 | require.Error(t, err) | |
| 96 | 96 | assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid) | |
| 97 | 97 | assert.Contains(t, err.Error(), "invalid DNSResolvers entry") | |
| 98 | + | assert.Contains(t, err.Error(), "index 0") | |
| 99 | + | assert.Contains(t, err.Error(), "1.1.1.1:abc") | |
| 98 | 100 | ||
| 99 | 101 | _, err = NewIssuer(Config{ | |
| 100 | 102 | Email: "test@example.com", | |
| @@ -122,6 +124,30 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) { | |||
| 122 | 124 | assert.Equal(t, []string{"1.1.1.1:53", "8.8.8.8:53"}, issuer.cfg.DNSResolvers) | |
| 123 | 125 | } | |
| 124 | 126 | ||
| 127 | + | func TestNormalizeDNSResolversErrorsIncludeEntryContext(t *testing.T) { | |
| 128 | + | tests := []struct { | |
| 129 | + | name string | |
| 130 | + | resolver string | |
| 131 | + | want string | |
| 132 | + | }{ | |
| 133 | + | {name: "empty after trimming", resolver: " \t ", want: "value \"\""}, | |
| 134 | + | {name: "missing port", resolver: "1.1.1.1", want: "value \"1.1.1.1\""}, | |
| 135 | + | {name: "empty host", resolver: ":53", want: "value \":53\""}, | |
| 136 | + | {name: "invalid port", resolver: "1.1.1.1:abc", want: "value \"1.1.1.1:abc\""}, | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | for _, tt := range tests { | |
| 140 | + | t.Run(tt.name, func(t *testing.T) { | |
| 141 | + | _, err := normalizeDNSResolvers([]string{"8.8.8.8:53", tt.resolver}) | |
| 142 | + | require.Error(t, err) | |
| 143 | + | assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid) | |
| 144 | + | assert.Contains(t, err.Error(), "invalid DNSResolvers entry") | |
| 145 | + | assert.Contains(t, err.Error(), "index 1") | |
| 146 | + | assert.Contains(t, err.Error(), tt.want) | |
| 147 | + | }) | |
| 148 | + | } | |
| 149 | + | } | |
| 150 | + | ||
| 125 | 151 | func TestNewIssuerRejectsNonHTTPSURL(t *testing.T) { | |
| 126 | 152 | _, err := NewIssuer(Config{ | |
| 127 | 153 | Email: "test@example.com", | |
| @@ -95,6 +95,8 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) { | |||||
| 95 | require.Error(t, err) | 95 | require.Error(t, err) | ||
| 96 | assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid) | 96 | assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid) | ||
| 97 | assert.Contains(t, err.Error(), "invalid DNSResolvers entry") | 97 | assert.Contains(t, err.Error(), "invalid DNSResolvers entry") | ||
| 98 | + | assert.Contains(t, err.Error(), "index 0") | |||
| 99 | + | assert.Contains(t, err.Error(), "1.1.1.1:abc") | |||
| 98 | 100 | ||||
| 99 | _, err = NewIssuer(Config{ | 101 | _, err = NewIssuer(Config{ | ||
| 100 | Email: "test@example.com", | 102 | Email: "test@example.com", | ||
| @@ -122,6 +124,30 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) { | |||||
| 122 | assert.Equal(t, []string{"1.1.1.1:53", "8.8.8.8:53"}, issuer.cfg.DNSResolvers) | 124 | assert.Equal(t, []string{"1.1.1.1:53", "8.8.8.8:53"}, issuer.cfg.DNSResolvers) | ||
| 123 | } | 125 | } | ||
| 124 | 126 | ||||
| 127 | + | func TestNormalizeDNSResolversErrorsIncludeEntryContext(t *testing.T) { | |||
| 128 | + | tests := []struct { | |||
| 129 | + | name string | |||
| 130 | + | resolver string | |||
| 131 | + | want string | |||
| 132 | + | }{ | |||
| 133 | + | {name: "empty after trimming", resolver: " \t ", want: "value \"\""}, | |||
| 134 | + | {name: "missing port", resolver: "1.1.1.1", want: "value \"1.1.1.1\""}, | |||
| 135 | + | {name: "empty host", resolver: ":53", want: "value \":53\""}, | |||
| 136 | + | {name: "invalid port", resolver: "1.1.1.1:abc", want: "value \"1.1.1.1:abc\""}, | |||
| 137 | + | } | |||
| 138 | + | ||||
| 139 | + | for _, tt := range tests { | |||
| 140 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 141 | + | _, err := normalizeDNSResolvers([]string{"8.8.8.8:53", tt.resolver}) | |||
| 142 | + | require.Error(t, err) | |||
| 143 | + | assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid) | |||
| 144 | + | assert.Contains(t, err.Error(), "invalid DNSResolvers entry") | |||
| 145 | + | assert.Contains(t, err.Error(), "index 1") | |||
| 146 | + | assert.Contains(t, err.Error(), tt.want) | |||
| 147 | + | }) | |||
| 148 | + | } | |||
| 149 | + | } | |||
| 150 | + | ||||
| 125 | func TestNewIssuerRejectsNonHTTPSURL(t *testing.T) { | 151 | func TestNewIssuerRejectsNonHTTPSURL(t *testing.T) { | ||
| 126 | _, err := NewIssuer(Config{ | 152 | _, err := NewIssuer(Config{ | ||
| 127 | Email: "test@example.com", | 153 | Email: "test@example.com", | ||