@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 365 lines 10.6 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 caddyhttp
16
17 import (
18 "errors"
19 "fmt"
20 "net"
21 "net/http"
22 "net/netip"
23 "strings"
24
25 "cel.dev/cel-go/cel"
26 "cel.dev/cel-go/common/types/ref"
27 "go.uber.org/zap"
28 "go.uber.org/zap/zapcore"
29
30 "github.com/caddyserver/caddy/v2"
31 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
32 "github.com/caddyserver/caddy/v2/internal"
33 )
34
35 // MatchRemoteIP matches requests by the remote IP address,
36 // i.e. the IP address of the direct connection to Caddy.
37 type MatchRemoteIP struct {
38 // The IPs or CIDR ranges to match.
39 Ranges []string `json:"ranges,omitempty"`
40
41 // cidrs and zones vars should aligned always in the same
42 // length and indexes for matching later
43 cidrs []*netip.Prefix
44 zones []string
45 logger *zap.Logger
46 }
47
48 // MatchClientIP matches requests by the client IP address,
49 // i.e. the resolved address, considering trusted proxies.
50 type MatchClientIP struct {
51 // The IPs or CIDR ranges to match.
52 Ranges []string `json:"ranges,omitempty"`
53
54 // cidrs and zones vars should aligned always in the same
55 // length and indexes for matching later
56 cidrs []*netip.Prefix
57 zones []string
58 logger *zap.Logger
59 }
60
61 func init() {
62 caddy.RegisterModule(MatchRemoteIP{})
63 caddy.RegisterModule(MatchClientIP{})
64 }
65
66 // CaddyModule returns the Caddy module information.
67 func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
68 return caddy.ModuleInfo{
69 ID: "http.matchers.remote_ip",
70 New: func() caddy.Module { return new(MatchRemoteIP) },
71 }
72 }
73
74 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
75 func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
76 // iterate to merge multiple matchers into one
77 for d.Next() {
78 for d.NextArg() {
79 if d.Val() == "forwarded" {
80 return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
81 }
82 if d.Val() == "private_ranges" {
83 m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
84 continue
85 }
86 m.Ranges = append(m.Ranges, d.Val())
87 }
88 if d.NextBlock(0) {
89 return d.Err("malformed remote_ip matcher: blocks are not supported")
90 }
91 }
92 return nil
93 }
94
95 // CELLibrary produces options that expose this matcher for use in CEL
96 // expression matchers.
97 //
98 // Example:
99 //
100 // expression remote_ip('192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8')
101 func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
102 return CELMatcherImpl(
103 // name of the macro, this is the function name that users see when writing expressions.
104 "remote_ip",
105 // name of the function that the macro will be rewritten to call.
106 "remote_ip_match_request_list",
107 // internal data type of the MatchPath value.
108 []*cel.Type{cel.ListType(cel.StringType)},
109 // function to convert a constant list of strings to a MatchPath instance.
110 func(data ref.Val) (RequestMatcherWithError, error) {
111 refStringList := stringSliceType
112 strList, err := data.ConvertToNative(refStringList)
113 if err != nil {
114 return nil, err
115 }
116
117 m := MatchRemoteIP{}
118
119 for _, input := range strList.([]string) {
120 if input == "forwarded" {
121 return nil, errors.New("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
122 }
123 m.Ranges = append(m.Ranges, input)
124 }
125
126 err = m.Provision(ctx)
127 return m, err
128 },
129 )
130 }
131
132 // Provision parses m's IP ranges, either from IP or CIDR expressions.
133 func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
134 m.logger = ctx.Logger()
135 cidrs, zones, err := provisionCidrsZonesFromRanges(m.Ranges)
136 if err != nil {
137 return err
138 }
139 m.cidrs = cidrs
140 m.zones = zones
141
142 return nil
143 }
144
145 // Match returns true if r matches m.
146 func (m MatchRemoteIP) Match(r *http.Request) bool {
147 match, err := m.MatchWithError(r)
148 if err != nil {
149 SetVar(r.Context(), MatcherErrorVarKey, err)
150 }
151 return match
152 }
153
154 // MatchWithError returns true if r matches m.
155 func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
156 // if handshake is not finished, we infer 0-RTT that has
157 // not verified remote IP; could be spoofed, so we throw
158 // HTTP 425 status to tell the client to try again after
159 // the handshake is complete
160 if r.TLS != nil && !r.TLS.HandshakeComplete {
161 return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified"))
162 }
163
164 address := r.RemoteAddr
165 clientIP, zoneID, err := parseIPZoneFromString(address)
166 if err != nil {
167 if c := m.logger.Check(zapcore.ErrorLevel, "getting remote "); c != nil {
168 c.Write(zap.Error(err))
169 }
170
171 return false, nil
172 }
173 matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
174 if !matches && !zoneFilter {
175 if c := m.logger.Check(zapcore.DebugLevel, "zone ID from remote IP did not match"); c != nil {
176 c.Write(zap.String("zone", zoneID))
177 }
178 }
179 return matches, nil
180 }
181
182 // CaddyModule returns the Caddy module information.
183 func (MatchClientIP) CaddyModule() caddy.ModuleInfo {
184 return caddy.ModuleInfo{
185 ID: "http.matchers.client_ip",
186 New: func() caddy.Module { return new(MatchClientIP) },
187 }
188 }
189
190 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
191 func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
192 // iterate to merge multiple matchers into one
193 for d.Next() {
194 for d.NextArg() {
195 if d.Val() == "private_ranges" {
196 m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
197 continue
198 }
199 m.Ranges = append(m.Ranges, d.Val())
200 }
201 if d.NextBlock(0) {
202 return d.Err("malformed client_ip matcher: blocks are not supported")
203 }
204 }
205 return nil
206 }
207
208 // CELLibrary produces options that expose this matcher for use in CEL
209 // expression matchers.
210 //
211 // Example:
212 //
213 // expression client_ip('192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8')
214 func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
215 return CELMatcherImpl(
216 // name of the macro, this is the function name that users see when writing expressions.
217 "client_ip",
218 // name of the function that the macro will be rewritten to call.
219 "client_ip_match_request_list",
220 // internal data type of the MatchPath value.
221 []*cel.Type{cel.ListType(cel.StringType)},
222 // function to convert a constant list of strings to a MatchPath instance.
223 func(data ref.Val) (RequestMatcherWithError, error) {
224 refStringList := stringSliceType
225 strList, err := data.ConvertToNative(refStringList)
226 if err != nil {
227 return nil, err
228 }
229
230 m := MatchClientIP{
231 Ranges: strList.([]string),
232 }
233
234 err = m.Provision(ctx)
235 return m, err
236 },
237 )
238 }
239
240 // Provision parses m's IP ranges, either from IP or CIDR expressions.
241 func (m *MatchClientIP) Provision(ctx caddy.Context) error {
242 m.logger = ctx.Logger()
243 cidrs, zones, err := provisionCidrsZonesFromRanges(m.Ranges)
244 if err != nil {
245 return err
246 }
247 m.cidrs = cidrs
248 m.zones = zones
249 return nil
250 }
251
252 // Match returns true if r matches m.
253 func (m MatchClientIP) Match(r *http.Request) bool {
254 match, err := m.MatchWithError(r)
255 if err != nil {
256 SetVar(r.Context(), MatcherErrorVarKey, err)
257 }
258 return match
259 }
260
261 // MatchWithError returns true if r matches m.
262 func (m MatchClientIP) MatchWithError(r *http.Request) (bool, error) {
263 // if handshake is not finished, we infer 0-RTT that has
264 // not verified remote IP; could be spoofed, so we throw
265 // HTTP 425 status to tell the client to try again after
266 // the handshake is complete
267 if r.TLS != nil && !r.TLS.HandshakeComplete {
268 return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified"))
269 }
270
271 address := GetVar(r.Context(), ClientIPVarKey).(string)
272 clientIP, zoneID, err := parseIPZoneFromString(address)
273 if err != nil {
274 m.logger.Error("getting client IP", zap.Error(err))
275 return false, nil
276 }
277 matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
278 if !matches && !zoneFilter {
279 m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID))
280 }
281 return matches, nil
282 }
283
284 func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
285 cidrs := []*netip.Prefix{}
286 zones := []string{}
287 repl := caddy.NewReplacer()
288 for _, str := range ranges {
289 str = repl.ReplaceAll(str, "")
290 // Exclude the zone_id from the IP
291 if strings.Contains(str, "%") {
292 split := strings.Split(str, "%")
293 str = split[0]
294 // write zone identifiers in m.zones for matching later
295 zones = append(zones, split[1])
296 } else {
297 zones = append(zones, "")
298 }
299 if strings.Contains(str, "/") {
300 ipNet, err := netip.ParsePrefix(str)
301 if err != nil {
302 return nil, nil, fmt.Errorf("parsing CIDR expression '%s': %v", str, err)
303 }
304 cidrs = append(cidrs, &ipNet)
305 } else {
306 ipAddr, err := netip.ParseAddr(str)
307 if err != nil {
308 return nil, nil, fmt.Errorf("invalid IP address: '%s': %v", str, err)
309 }
310 ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
311 cidrs = append(cidrs, &ipNew)
312 }
313 }
314 return cidrs, zones, nil
315 }
316
317 func parseIPZoneFromString(address string) (netip.Addr, string, error) {
318 ipStr, _, err := net.SplitHostPort(address)
319 if err != nil {
320 ipStr = address // OK; probably didn't have a port
321 }
322
323 // Some IPv6-Addresses can contain zone identifiers at the end,
324 // which are separated with "%"
325 zoneID := ""
326 if strings.Contains(ipStr, "%") {
327 split := strings.Split(ipStr, "%")
328 ipStr = split[0]
329 zoneID = split[1]
330 }
331
332 ipAddr, err := netip.ParseAddr(ipStr)
333 if err != nil {
334 return netip.IPv4Unspecified(), "", err
335 }
336
337 return ipAddr, zoneID, nil
338 }
339
340 func matchIPByCidrZones(clientIP netip.Addr, zoneID string, cidrs []*netip.Prefix, zones []string) (bool, bool) {
341 zoneFilter := true
342 for i, ipRange := range cidrs {
343 if ipRange.Contains(clientIP) {
344 // Check if there are zone filters assigned and if they match.
345 if zones[i] == "" || zoneID == zones[i] {
346 return true, false
347 }
348 zoneFilter = false
349 }
350 }
351 return false, zoneFilter
352 }
353
354 // Interface guards
355 var (
356 _ RequestMatcherWithError = (*MatchRemoteIP)(nil)
357 _ caddy.Provisioner = (*MatchRemoteIP)(nil)
358 _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
359 _ CELLibraryProducer = (*MatchRemoteIP)(nil)
360
361 _ RequestMatcherWithError = (*MatchClientIP)(nil)
362 _ caddy.Provisioner = (*MatchClientIP)(nil)
363 _ caddyfile.Unmarshaler = (*MatchClientIP)(nil)
364 _ CELLibraryProducer = (*MatchClientIP)(nil)
365 )