@marcel91200 / caddy Mirror · read-only / commits / c7efb030
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

reverseproxy: Fix dial placeholders, SRV, active health checks (#3780)

* reverseproxy: Fix dial placeholders, SRV, active health checks Supercedes #3776 Partially reverts or updates #3756, #3693, and #3695 * reverseproxy: add integration tests Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>

Matt Holt committed Oct 13, 2020 at 10:35 UTC c7efb0307d425eb533885e314518c370a06763da
5 files changed +270 -33
caddytest/integration/reverseproxy_test.go
+238
@@ -79,6 +79,244 @@ func TestSRVWithDial(t *testing.T) {
79 `, "json", `upstream: specifying dial address is incompatible with lookup_srv: 0: {\"dial\": \"tcp/address.to.upstream:80\", \"lookup_srv\": \"srv.host.service.consul\"}`)
80 }
81
82 +func TestDialWithPlaceholderUnix(t *testing.T) {
83 +
84 + if runtime.GOOS == "windows" {
85 + t.SkipNow()
86 + }
87 +
88 + f, err := ioutil.TempFile("", "*.sock")
89 + if err != nil {
90 + t.Errorf("failed to create TempFile: %s", err)
91 + return
92 + }
93 + // a hack to get a file name within a valid path to use as socket
94 + socketName := f.Name()
95 + os.Remove(f.Name())
96 +
97 + server := http.Server{
98 + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
99 + w.Write([]byte("Hello, World!"))
100 + }),
101 + }
102 +
103 + unixListener, err := net.Listen("unix", socketName)
104 + if err != nil {
105 + t.Errorf("failed to listen on the socket: %s", err)
106 + return
107 + }
108 + go server.Serve(unixListener)
109 + t.Cleanup(func() {
110 + server.Close()
111 + })
112 + runtime.Gosched() // Allow other goroutines to run
113 +
114 + tester := caddytest.NewTester(t)
115 + tester.InitServer(`
116 + {
117 + "apps": {
118 + "http": {
119 + "servers": {
120 + "srv0": {
121 + "listen": [
122 + ":8080"
123 + ],
124 + "routes": [
125 + {
126 + "handle": [
127 + {
128 + "handler": "reverse_proxy",
129 + "upstreams": [
130 + {
131 + "dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}"
132 + }
133 + ]
134 + }
135 + ]
136 + }
137 + ]
138 + }
139 + }
140 + }
141 + }
142 + }
143 + `, "json")
144 +
145 + req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
146 + if err != nil {
147 + t.Fail()
148 + return
149 + }
150 + req.Header.Set("X-Caddy-Upstream-Dial", socketName)
151 + tester.AssertResponse(req, 200, "Hello, World!")
152 +}
153 +
154 +func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
155 + tester := caddytest.NewTester(t)
156 + tester.InitServer(`
157 + {
158 + "apps": {
159 + "http": {
160 + "servers": {
161 + "srv0": {
162 + "listen": [
163 + ":8080"
164 + ],
165 + "routes": [
166 + {
167 + "match": [
168 + {
169 + "host": [
170 + "localhost"
171 + ]
172 + }
173 + ],
174 + "handle": [
175 + {
176 + "handler": "static_response",
177 + "body": "Hello, World!"
178 + }
179 + ],
180 + "terminal": true
181 + }
182 + ],
183 + "automatic_https": {
184 + "skip": [
185 + "localhost"
186 + ]
187 + }
188 + },
189 + "srv1": {
190 + "listen": [
191 + ":9080"
192 + ],
193 + "routes": [
194 + {
195 + "match": [
196 + {
197 + "host": [
198 + "localhost"
199 + ]
200 + }
201 + ],
202 + "handle": [
203 + {
204 +
205 + "handler": "reverse_proxy",
206 + "upstreams": [
207 + {
208 + "dial": "{http.request.header.X-Caddy-Upstream-Dial}"
209 + }
210 + ]
211 + }
212 + ],
213 + "terminal": true
214 + }
215 + ],
216 + "automatic_https": {
217 + "skip": [
218 + "localhost"
219 + ]
220 + }
221 + }
222 + }
223 + }
224 + }
225 + }
226 + `, "json")
227 +
228 + req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
229 + if err != nil {
230 + t.Fail()
231 + return
232 + }
233 + req.Header.Set("X-Caddy-Upstream-Dial", "localhost:8080")
234 + tester.AssertResponse(req, 200, "Hello, World!")
235 +}
236 +
237 +func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
238 + tester := caddytest.NewTester(t)
239 + tester.InitServer(`
240 + {
241 + "apps": {
242 + "http": {
243 + "servers": {
244 + "srv0": {
245 + "listen": [
246 + ":8080"
247 + ],
248 + "routes": [
249 + {
250 + "match": [
251 + {
252 + "host": [
253 + "localhost"
254 + ]
255 + }
256 + ],
257 + "handle": [
258 + {
259 + "handler": "static_response",
260 + "body": "Hello, World!"
261 + }
262 + ],
263 + "terminal": true
264 + }
265 + ],
266 + "automatic_https": {
267 + "skip": [
268 + "localhost"
269 + ]
270 + }
271 + },
272 + "srv1": {
273 + "listen": [
274 + ":9080"
275 + ],
276 + "routes": [
277 + {
278 + "match": [
279 + {
280 + "host": [
281 + "localhost"
282 + ]
283 + }
284 + ],
285 + "handle": [
286 + {
287 +
288 + "handler": "reverse_proxy",
289 + "upstreams": [
290 + {
291 + "dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:8080"
292 + }
293 + ]
294 + }
295 + ],
296 + "terminal": true
297 + }
298 + ],
299 + "automatic_https": {
300 + "skip": [
301 + "localhost"
302 + ]
303 + }
304 + }
305 + }
306 + }
307 + }
308 + }
309 + `, "json")
310 +
311 + req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
312 + if err != nil {
313 + t.Fail()
314 + return
315 + }
316 + req.Header.Set("X-Caddy-Upstream-Dial", "localhost")
317 + tester.AssertResponse(req, 200, "Hello, World!")
318 +}
319 +
320 func TestSRVWithActiveHealthcheck(t *testing.T) {
321 caddytest.AssertLoadError(t, `
322 {
modules/caddyhttp/reverseproxy/healthchecks.go
+31 -12
@@ -154,23 +154,42 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
154 }
155 }()
156
157 - portStr := strconv.Itoa(upstream.activeHealthCheckPort)
158 - hostAddr := net.JoinHostPort(upstream.networkAddress.Host, portStr)
159 - if upstream.networkAddress.IsUnixNetwork() {
157 + networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true)
158 + if err != nil {
159 + h.HealthChecks.Active.logger.Error("invalid use of placeholders in dial address for active health checks",
160 + zap.String("address", networkAddr),
161 + zap.Error(err),
162 + )
163 + return
164 + }
165 + addr, err := caddy.ParseNetworkAddress(networkAddr)
166 + if err != nil {
167 + h.HealthChecks.Active.logger.Error("bad network address",
168 + zap.String("address", networkAddr),
169 + zap.Error(err),
170 + )
171 + return
172 + }
173 + if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 {
174 + if addr.IsUnixNetwork() {
175 + addr.Network = "tcp" // I guess we just assume TCP since we are using a port??
176 + }
177 + addr.StartPort, addr.EndPort = hcp, hcp
178 + }
179 + if upstream.LookupSRV == "" && addr.PortRangeSize() != 1 {
180 + h.HealthChecks.Active.logger.Error("multiple addresses (upstream must map to only one address)",
181 + zap.String("address", networkAddr),
182 + )
183 + return
184 + }
185 + hostAddr := addr.JoinHostPort(0)
186 + if addr.IsUnixNetwork() {
187 // this will be used as the Host portion of a http.Request URL, and
188 // paths to socket files would produce an error when creating URL,
189 // so use a fake Host value instead; unix sockets are usually local
190 hostAddr = "localhost"
191 }
165 -
166 - dialInfo := DialInfo{
167 - Upstream: upstream,
168 - Network: upstream.networkAddress.Network,
169 - Host: upstream.networkAddress.Host,
170 - Port: portStr,
171 - Address: hostAddr,
172 - }
173 - err := h.doActiveHealthCheck(dialInfo, hostAddr, upstream.Host)
192 + err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: hostAddr}, hostAddr, upstream.Host)
193 if err != nil {
194 h.HealthChecks.Active.logger.Error("active health check failed",
195 zap.String("address", hostAddr),
modules/caddyhttp/reverseproxy/hosts.go
-1
@@ -92,7 +92,6 @@ type Upstream struct {
92 // HeaderAffinity string
93 // IPAffinity string
94
95 - networkAddress caddy.NetworkAddress
95 activeHealthCheckPort int
96 healthCheckPolicy *PassiveHealthChecks
97 cb CircuitBreaker
modules/caddyhttp/reverseproxy/httptransport.go
-3
@@ -182,9 +182,6 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
182 if dialInfo, ok := GetDialInfo(ctx); ok {
183 network = dialInfo.Network
184 address = dialInfo.Address
185 - if dialInfo.Upstream.networkAddress.IsUnixNetwork() {
186 - address = dialInfo.Host
187 - }
185 }
186 conn, err := dialer.DialContext(ctx, network, address)
187 if err != nil {
modules/caddyhttp/reverseproxy/reverseproxy.go
+1 -17
@@ -129,10 +129,8 @@ func (h *Handler) Provision(ctx caddy.Context) error {
129 h.ctx = ctx
130 h.logger = ctx.Logger(h)
131
132 - // get validation out of the way
132 + // verify SRV compatibility
133 for i, v := range h.Upstreams {
134 - // Having LookupSRV non-empty conflicts with 2 other config parameters: active health checks, and upstream dial address.
135 - // Therefore if LookupSRV is empty, then there are no immediately apparent config conflicts, and the iteration can be skipped.
134 if v.LookupSRV == "" {
135 continue
136 }
@@ -219,18 +217,6 @@ func (h *Handler) Provision(ctx caddy.Context) error {
217
218 // set up upstreams
219 for _, upstream := range h.Upstreams {
222 - if upstream.LookupSRV == "" {
223 - addr, err := caddy.ParseNetworkAddress(upstream.Dial)
224 - if err != nil {
225 - return err
226 - }
227 -
228 - if addr.PortRangeSize() != 1 {
229 - return fmt.Errorf("multiple addresses (upstream must map to only one address): %v", addr)
230 - }
231 -
232 - upstream.networkAddress = addr
233 - }
220 // create or get the host representation for this upstream
221 var host Host = new(upstreamHost)
222 existingHost, loaded := hosts.LoadOrStore(upstream.String(), host)
@@ -292,8 +278,6 @@ func (h *Handler) Provision(ctx caddy.Context) error {
278 // then use it, otherwise use the port of upstream.
279 if h.HealthChecks.Active.Port != 0 {
280 upstream.activeHealthCheckPort = h.HealthChecks.Active.Port
295 - } else {
296 - upstream.activeHealthCheckPort = int(upstream.networkAddress.StartPort)
281 }
282 }
283