summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/ip_range.go
blob: b1db254752fd4dc20934c33c1b1c97a5c1480d01 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// 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"
)

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" {
			m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
			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
}

// PrivateRangesCIDR returns a list of private CIDR range
// strings, which can be used as a configuration shortcut.
func PrivateRangesCIDR() []string {
	return []string{
		"192.168.0.0/16",
		"172.16.0.0/12",
		"10.0.0.0/8",
		"127.0.0.1/8",
		"fd00::/8",
		"::1",
	}
}

// Interface guards
var (
	_ caddy.Provisioner     = (*StaticIPRange)(nil)
	_ caddyfile.Unmarshaler = (*StaticIPRange)(nil)
	_ IPRangeSource         = (*StaticIPRange)(nil)
)