| 
									
										
										
										
											2023-02-06 14:44:11 -05:00
										 |  |  | // Copyright 2015 Matthew Holt and The Caddy Authors | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  | // you may not use this file except in compliance with the License. | 
					
						
							|  |  |  | // You may obtain a copy of the License at | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | // distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | // See the License for the specific language governing permissions and | 
					
						
							|  |  |  | // limitations under the License. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package caddyhttp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/netip" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/caddyserver/caddy/v2" | 
					
						
							|  |  |  | 	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" | 
					
						
							| 
									
										
										
										
											2024-08-07 20:02:23 +03:00
										 |  |  | 	"github.com/caddyserver/caddy/v2/internal" | 
					
						
							| 
									
										
										
										
											2023-02-06 14:44:11 -05:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							|  |  |  | 	caddy.RegisterModule(StaticIPRange{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IPRangeSource gets a list of IP ranges. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The request is passed as an argument to allow plugin implementations | 
					
						
							|  |  |  | // to have more flexibility. But, a plugin MUST NOT modify the request. | 
					
						
							|  |  |  | // The caller will have read the `r.RemoteAddr` before getting IP ranges. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This should be a very fast function -- instant if possible. | 
					
						
							|  |  |  | // The list of IP ranges should be sourced as soon as possible if loaded | 
					
						
							|  |  |  | // from an external source (i.e. initially loaded during Provisioning), | 
					
						
							|  |  |  | // so that it's ready to be used when requests start getting handled. | 
					
						
							|  |  |  | // A read lock should probably be used to get the cached value if the | 
					
						
							|  |  |  | // ranges can change at runtime (e.g. periodically refreshed). | 
					
						
							|  |  |  | // Using a `caddy.UsagePool` may be a good idea to avoid having refetch | 
					
						
							|  |  |  | // the values when a config reload occurs, which would waste time. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If the list of IP ranges cannot be sourced, then provisioning SHOULD | 
					
						
							|  |  |  | // fail. Getting the IP ranges at runtime MUST NOT fail, because it would | 
					
						
							|  |  |  | // cancel incoming requests. If refreshing the list fails, then the | 
					
						
							|  |  |  | // previous list of IP ranges should continue to be returned so that the | 
					
						
							|  |  |  | // server can continue to operate normally. | 
					
						
							|  |  |  | type IPRangeSource interface { | 
					
						
							|  |  |  | 	GetIPRanges(*http.Request) []netip.Prefix | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // StaticIPRange provides a static range of IP address prefixes (CIDRs). | 
					
						
							|  |  |  | type StaticIPRange struct { | 
					
						
							|  |  |  | 	// A static list of IP ranges (supports CIDR notation). | 
					
						
							|  |  |  | 	Ranges []string `json:"ranges,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Holds the parsed CIDR ranges from Ranges. | 
					
						
							|  |  |  | 	ranges []netip.Prefix | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CaddyModule returns the Caddy module information. | 
					
						
							|  |  |  | func (StaticIPRange) CaddyModule() caddy.ModuleInfo { | 
					
						
							|  |  |  | 	return caddy.ModuleInfo{ | 
					
						
							|  |  |  | 		ID:  "http.ip_sources.static", | 
					
						
							|  |  |  | 		New: func() caddy.Module { return new(StaticIPRange) }, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *StaticIPRange) Provision(ctx caddy.Context) error { | 
					
						
							|  |  |  | 	for _, str := range s.Ranges { | 
					
						
							|  |  |  | 		prefix, err := CIDRExpressionToPrefix(str) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		s.ranges = append(s.ranges, prefix) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *StaticIPRange) GetIPRanges(_ *http.Request) []netip.Prefix { | 
					
						
							|  |  |  | 	return s.ranges | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UnmarshalCaddyfile implements caddyfile.Unmarshaler. | 
					
						
							|  |  |  | func (m *StaticIPRange) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { | 
					
						
							|  |  |  | 	if !d.Next() { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for d.NextArg() { | 
					
						
							|  |  |  | 		if d.Val() == "private_ranges" { | 
					
						
							| 
									
										
										
										
											2024-08-07 20:02:23 +03:00
										 |  |  | 			m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...) | 
					
						
							| 
									
										
										
										
											2023-02-06 14:44:11 -05:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		m.Ranges = append(m.Ranges, d.Val()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CIDRExpressionToPrefix takes a string which could be either a | 
					
						
							|  |  |  | // CIDR expression or a single IP address, and returns a netip.Prefix. | 
					
						
							|  |  |  | func CIDRExpressionToPrefix(expr string) (netip.Prefix, error) { | 
					
						
							|  |  |  | 	// Having a slash means it should be a CIDR expression | 
					
						
							|  |  |  | 	if strings.Contains(expr, "/") { | 
					
						
							|  |  |  | 		prefix, err := netip.ParsePrefix(expr) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return netip.Prefix{}, fmt.Errorf("parsing CIDR expression: '%s': %v", expr, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return prefix, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Otherwise it's likely a single IP address | 
					
						
							|  |  |  | 	parsed, err := netip.ParseAddr(expr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return netip.Prefix{}, fmt.Errorf("invalid IP address: '%s': %v", expr, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	prefix := netip.PrefixFrom(parsed, parsed.BitLen()) | 
					
						
							|  |  |  | 	return prefix, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Interface guards | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	_ caddy.Provisioner     = (*StaticIPRange)(nil) | 
					
						
							|  |  |  | 	_ caddyfile.Unmarshaler = (*StaticIPRange)(nil) | 
					
						
							|  |  |  | 	_ IPRangeSource         = (*StaticIPRange)(nil) | 
					
						
							|  |  |  | ) |