@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 320 lines 10.2 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package reverseproxy
16
17 import (
18 "encoding/json"
19 "fmt"
20 "net/http"
21 "strconv"
22 "strings"
23
24 "github.com/spf13/cobra"
25 "go.uber.org/zap"
26
27 caddycmd "github.com/caddyserver/caddy/v2/cmd"
28
29 "github.com/caddyserver/caddy/v2"
30 "github.com/caddyserver/caddy/v2/caddyconfig"
31 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
32 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
33 "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
34 "github.com/caddyserver/caddy/v2/modules/caddytls"
35 )
36
37 func init() {
38 caddycmd.RegisterCommand(caddycmd.Command{
39 Name: "reverse-proxy",
40 Usage: `[--from <addr>] [--to <addr>] [--change-host-header] [--insecure] [--internal-certs] [--disable-redirects] [--header-up "Field: value"] [--header-down "Field: value"] [--access-log] [--debug]`,
41 Short: "A quick and production-ready reverse proxy",
42 Long: `
43 A simple but production-ready reverse proxy. Useful for quick deployments,
44 demos, and development.
45
46 Simply shuttles HTTP(S) traffic from the --from address to the --to address.
47 Multiple --to addresses may be specified by repeating the flag.
48
49 Unless otherwise specified in the addresses, the --from address will be
50 assumed to be HTTPS if a hostname is given, and the --to address will be
51 assumed to be HTTP.
52
53 If the --from address has a host or IP, Caddy will attempt to serve the
54 proxy over HTTPS with a certificate (unless overridden by the HTTP scheme
55 or port).
56
57 If serving HTTPS:
58 --disable-redirects can be used to avoid binding to the HTTP port.
59 --internal-certs can be used to force issuance certs using the internal
60 CA instead of attempting to issue a public certificate.
61
62 For proxying:
63 --header-up can be used to set a request header to send to the upstream.
64 --header-down can be used to set a response header to send back to the client.
65 --change-host-header sets the Host header on the request to the address
66 of the upstream, instead of defaulting to the incoming Host header.
67 This is a shortcut for --header-up "Host: {http.reverse_proxy.upstream.hostport}".
68 --insecure disables TLS verification with the upstream. WARNING: THIS
69 DISABLES SECURITY BY NOT VERIFYING THE UPSTREAM'S CERTIFICATE.
70 `,
71 CobraFunc: func(cmd *cobra.Command) {
72 cmd.Flags().StringP("from", "f", "localhost", "Address on which to receive traffic")
73 cmd.Flags().StringSliceP("to", "t", []string{}, "Upstream address(es) to which traffic should be sent")
74 cmd.Flags().BoolP("change-host-header", "c", false, "Set upstream Host header to address of upstream")
75 cmd.Flags().BoolP("insecure", "", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)")
76 cmd.Flags().BoolP("disable-redirects", "r", false, "Disable HTTP->HTTPS redirects")
77 cmd.Flags().BoolP("internal-certs", "i", false, "Use internal CA for issuing certs")
78 cmd.Flags().StringArrayP("header-up", "H", []string{}, "Set a request header to send to the upstream (format: \"Field: value\")")
79 cmd.Flags().StringArrayP("header-down", "d", []string{}, "Set a response header to send back to the client (format: \"Field: value\")")
80 cmd.Flags().BoolP("access-log", "", false, "Enable the access log")
81 cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
82 cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdReverseProxy)
83 },
84 })
85 }
86
87 func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
88 caddy.TrapSignals()
89
90 from := fs.String("from")
91 changeHost := fs.Bool("change-host-header")
92 insecure := fs.Bool("insecure")
93 disableRedir := fs.Bool("disable-redirects")
94 internalCerts := fs.Bool("internal-certs")
95 accessLog := fs.Bool("access-log")
96 debug := fs.Bool("debug")
97
98 httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
99 httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
100
101 to, err := fs.GetStringSlice("to")
102 if err != nil {
103 return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid to flag: %v", err)
104 }
105 if len(to) == 0 {
106 return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required")
107 }
108
109 // set up the downstream address; assume missing information from given parts
110 fromAddr, err := httpcaddyfile.ParseAddress(from)
111 if err != nil {
112 return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid downstream address %s: %v", from, err)
113 }
114 if fromAddr.Path != "" {
115 return caddy.ExitCodeFailedStartup, fmt.Errorf("paths are not allowed: %s", from)
116 }
117 if fromAddr.Scheme == "" {
118 if fromAddr.Port == httpPort || fromAddr.Host == "" {
119 fromAddr.Scheme = "http"
120 } else {
121 fromAddr.Scheme = "https"
122 }
123 }
124 if fromAddr.Port == "" {
125 switch fromAddr.Scheme {
126 case "http":
127 fromAddr.Port = httpPort
128 case "https":
129 fromAddr.Port = httpsPort
130 }
131 }
132
133 // set up the upstream address; assume missing information from given parts
134 // mixing schemes isn't supported, so use first defined (if available)
135 toAddresses := make([]string, len(to))
136 var toScheme string
137 for i, toLoc := range to {
138 addr, err := parseUpstreamDialAddress(toLoc)
139 if err != nil {
140 return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toLoc, err)
141 }
142 if addr.scheme != "" && toScheme == "" {
143 toScheme = addr.scheme
144 }
145 toAddresses[i] = addr.dialAddr()
146 }
147
148 // proceed to build the handler and server
149 ht := HTTPTransport{}
150 if toScheme == "https" {
151 ht.TLS = new(TLSConfig)
152 if insecure {
153 ht.TLS.InsecureSkipVerify = true
154 }
155 }
156
157 upstreamPool := UpstreamPool{}
158 for _, toAddr := range toAddresses {
159 parsedAddr, err := caddy.ParseNetworkAddress(toAddr)
160 if err != nil {
161 return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toAddr, err)
162 }
163
164 if parsedAddr.StartPort == 0 && parsedAddr.EndPort == 0 {
165 // unix networks don't have ports
166 upstreamPool = append(upstreamPool, &Upstream{
167 Dial: toAddr,
168 })
169 } else {
170 // expand a port range into multiple upstreams
171 for i := parsedAddr.StartPort; i <= parsedAddr.EndPort; i++ {
172 upstreamPool = append(upstreamPool, &Upstream{
173 Dial: caddy.JoinNetworkAddress("", parsedAddr.Host, fmt.Sprint(i)),
174 })
175 }
176 }
177 }
178
179 handler := Handler{
180 TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
181 Upstreams: upstreamPool,
182 }
183
184 // set up header_up
185 headerUp, err := fs.GetStringArray("header-up")
186 if err != nil {
187 return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err)
188 }
189 if len(headerUp) > 0 {
190 reqHdr := make(http.Header)
191 for i, h := range headerUp {
192 key, val, found := strings.Cut(h, ":")
193 key, val = strings.TrimSpace(key), strings.TrimSpace(val)
194 if !found || key == "" || val == "" {
195 return caddy.ExitCodeFailedStartup, fmt.Errorf("header-up %d: invalid format \"%s\" (expecting \"Field: value\")", i, h)
196 }
197 reqHdr.Set(key, val)
198 }
199 handler.Headers = &headers.Handler{
200 Request: &headers.HeaderOps{
201 Set: reqHdr,
202 },
203 }
204 }
205
206 // set up header_down
207 headerDown, err := fs.GetStringArray("header-down")
208 if err != nil {
209 return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err)
210 }
211 if len(headerDown) > 0 {
212 respHdr := make(http.Header)
213 for i, h := range headerDown {
214 key, val, found := strings.Cut(h, ":")
215 key, val = strings.TrimSpace(key), strings.TrimSpace(val)
216 if !found || key == "" || val == "" {
217 return caddy.ExitCodeFailedStartup, fmt.Errorf("header-down %d: invalid format \"%s\" (expecting \"Field: value\")", i, h)
218 }
219 respHdr.Set(key, val)
220 }
221 if handler.Headers == nil {
222 handler.Headers = &headers.Handler{}
223 }
224 handler.Headers.Response = &headers.RespHeaderOps{
225 HeaderOps: &headers.HeaderOps{
226 Set: respHdr,
227 },
228 }
229 }
230
231 if changeHost {
232 if handler.Headers == nil {
233 handler.Headers = new(headers.Handler)
234 }
235 if handler.Headers.Request == nil {
236 handler.Headers.Request = new(headers.HeaderOps)
237 }
238 if handler.Headers.Request.Set == nil {
239 handler.Headers.Request.Set = http.Header{}
240 }
241 handler.Headers.Request.Set.Set("Host", "{http.reverse_proxy.upstream.hostport}")
242 }
243
244 route := caddyhttp.Route{
245 HandlersRaw: []json.RawMessage{
246 caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", nil),
247 },
248 }
249 if fromAddr.Host != "" {
250 route.MatcherSetsRaw = []caddy.ModuleMap{
251 {
252 "host": caddyconfig.JSON(caddyhttp.MatchHost{fromAddr.Host}, nil),
253 },
254 }
255 }
256
257 server := &caddyhttp.Server{
258 Routes: caddyhttp.RouteList{route},
259 Listen: []string{":" + fromAddr.Port},
260 }
261 if accessLog {
262 server.Logs = &caddyhttp.ServerLogConfig{}
263 }
264
265 if fromAddr.Scheme == "http" {
266 server.AutoHTTPS = &caddyhttp.AutoHTTPSConfig{Disabled: true}
267 } else if disableRedir {
268 server.AutoHTTPS = &caddyhttp.AutoHTTPSConfig{DisableRedir: true}
269 }
270
271 httpApp := caddyhttp.App{
272 Servers: map[string]*caddyhttp.Server{"proxy": server},
273 }
274
275 appsRaw := caddy.ModuleMap{
276 "http": caddyconfig.JSON(httpApp, nil),
277 }
278 if internalCerts && fromAddr.Host != "" {
279 tlsApp := caddytls.TLS{
280 Automation: &caddytls.AutomationConfig{
281 Policies: []*caddytls.AutomationPolicy{{
282 SubjectsRaw: []string{fromAddr.Host},
283 IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
284 }},
285 },
286 }
287 appsRaw["tls"] = caddyconfig.JSON(tlsApp, nil)
288 }
289
290 var false bool
291 cfg := &caddy.Config{
292 Admin: &caddy.AdminConfig{
293 Disabled: true,
294 Config: &caddy.ConfigSettings{
295 Persist: &false,
296 },
297 },
298 AppsRaw: appsRaw,
299 }
300
301 if debug {
302 cfg.Logging = &caddy.Logging{
303 Logs: map[string]*caddy.CustomLog{
304 "default": {BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()}},
305 },
306 }
307 }
308
309 err = caddy.Run(cfg)
310 if err != nil {
311 return caddy.ExitCodeFailedStartup, err
312 }
313
314 caddy.Log().Info("caddy proxying", zap.String("from", fromAddr.String()), zap.Strings("to", toAddresses))
315 if len(toAddresses) > 1 {
316 caddy.Log().Info("using default load balancing policy", zap.String("policy", "random"))
317 }
318
319 select {}
320 }