Skip to content

Commit 0fc9304

Browse files
authored
Merge pull request #5 from sproutmaster/v1.0.1
V1.0.1
2 parents 885176f + 4cfdfc4 commit 0fc9304

File tree

4 files changed

+323
-126
lines changed

4 files changed

+323
-126
lines changed

.traefik.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import: github.com/sproutmaster/TraefikIPRules
66
summary: "Plugin to manage IP allow/deny rules"
77

88
testData:
9-
denyList:
9+
deny:
1010
- "192.168.1.0/24" # Deny a subnet
1111
- "10.0.0.1" # Deny a specific IP
12-
allowList:
12+
allow:
1313
- "192.168.2.0/24" # Allow a subnet
1414
- "10.0.0.2" # Allow a specific IP
15+
precedence: "deny"

README.md

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Traefik IP Rules
22

3-
IPRules is a middleware plugin which accepts IP addresses or IP address ranges, and accepts or blocks requests
4-
originating from those IPs.
3+
IPRules is a middleware plugin which accepts or blocks requests originating from those IPs based on an IP address, range
4+
or subnet.
55

66
## How to use (Kubernetes CRD)
77

@@ -11,32 +11,36 @@ originating from those IPs.
1111
# helm-values.yaml
1212
experimental:
1313
plugins:
14-
ipRule:
14+
iprules:
1515
moduleName: "github.com/sproutmaster/TraefikIPRules"
16-
version: "v1.0.0"
16+
version: "v1.0.1"
1717
```
18-
18+
1919
2. Configure Middleware
2020
```yaml
2121
# middleware.yaml
22-
apiVersion: traefik.containo.us/v1alpha1
22+
apiVersion: traefik.io/v1alpha1
2323
kind: Middleware
2424
metadata:
2525
name: ip-filter
2626
spec:
2727
plugin:
28-
ipRule:
29-
denyList:
30-
- "192.168.1.0/24"
31-
allowList:
32-
- "0.0.0.0/0"
28+
iprules:
29+
allow:
30+
- "192.168.1.1" # Single IP
31+
- "10.0.0.0/8" # CIDR range
32+
- "172.16.1.1-172.16.1.255" # IP range
33+
deny:
34+
- "192.168.1.100-192.168.1.200" # Block this IP range
35+
- "10.0.1.0/24" # Block this subnet
36+
precedence: "deny" # deny first
3337
```
34-
38+
3539
3. Reference it in ingressRoute
3640
3741
```yaml
3842
# ingress-route.yaml
39-
apiVersion: traefik.containo.us/v1alpha1
43+
apiVersion: traefik.io/v1alpha1
4044
kind: IngressRoute
4145
metadata:
4246
name: my-ing
@@ -52,4 +56,14 @@ originating from those IPs.
5256
middlewares:
5357
- name: ip-filter
5458
```
55-
59+
60+
## How to use (Docker Labels)
61+
62+
```yaml
63+
labels:
64+
- "traefik.http.middlewares.iprules.plugin.traefik-ip-rules.allow=192.168.1.1"
65+
- "traefik.http.middlewares.iprules.plugin.traefik-ip-rules.allow=10.0.0.0/8"
66+
- "traefik.http.middlewares.iprules.plugin.traefik-ip-rules.allow=172.16.1.1-172.16.1.255"
67+
- "traefik.http.middlewares.iprules.plugin.traefik-ip-rules.deny=192.168.1.100-192.168.1.200"
68+
- "traefik.http.middlewares.iprules.plugin.traefik-ip-rules.precedence=deny"
69+
```

TraefikIPRules.go

Lines changed: 169 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,159 @@ package TraefikIPRules
22

33
import (
44
"context"
5+
"fmt"
56
"net"
67
"net/http"
78
"strings"
89
)
910

1011
type Config struct {
11-
DenyList []string `json:"denyList,omitempty"`
12-
AllowList []string `json:"allowList,omitempty"`
12+
Deny []string `json:"deny,omitempty"`
13+
Allow []string `json:"allow,omitempty"`
14+
Precedence string `json:"precedence,omitempty"` // "allow" or "deny"
15+
}
16+
17+
type ipRange struct {
18+
start net.IP
19+
end net.IP
1320
}
1421

1522
func CreateConfig() *Config {
1623
return &Config{
17-
DenyList: make([]string, 0),
18-
AllowList: make([]string, 0),
24+
Deny: make([]string, 0),
25+
Allow: make([]string, 0),
26+
Precedence: "deny", // Default to deny
1927
}
2028
}
2129

2230
type IPProcessor struct {
23-
next http.Handler
24-
name string
25-
denyCIDRs []*net.IPNet
26-
denyIPs []net.IP
27-
allowCIDRs []*net.IPNet
28-
allowIPs []net.IP
31+
next http.Handler
32+
name string
33+
denyCIDRs []*net.IPNet
34+
denyIPs []net.IP
35+
denyRanges []ipRange
36+
allowCIDRs []*net.IPNet
37+
allowIPs []net.IP
38+
allowRanges []ipRange
39+
precedence string
40+
}
41+
42+
func parseIPRange(ipRangeStr string) (*ipRange, error) {
43+
parts := strings.Split(ipRangeStr, "-")
44+
if len(parts) != 2 {
45+
return nil, fmt.Errorf("invalid IP range format: %s", ipRangeStr)
46+
}
47+
48+
start := net.ParseIP(strings.TrimSpace(parts[0]))
49+
end := net.ParseIP(strings.TrimSpace(parts[1]))
50+
51+
if start == nil || end == nil {
52+
return nil, fmt.Errorf("invalid IP address in range: %s", ipRangeStr)
53+
}
54+
55+
if start.To4() == nil || end.To4() == nil {
56+
return nil, fmt.Errorf("only IPv4 ranges are supported: %s", ipRangeStr)
57+
}
58+
59+
for i := 0; i < len(start.To4()); i++ {
60+
if start.To4()[i] > end.To4()[i] {
61+
return nil, fmt.Errorf("invalid range: start IP must be less than end IP")
62+
}
63+
if start.To4()[i] < end.To4()[i] {
64+
break
65+
}
66+
}
67+
68+
return &ipRange{
69+
start: start.To4(),
70+
end: end.To4(),
71+
}, nil
72+
}
73+
74+
func (r *ipRange) IPRangeContains(ip net.IP) bool {
75+
if ip.To4() == nil {
76+
return false
77+
}
78+
79+
ip = ip.To4()
80+
for i := 0; i < 4; i++ {
81+
if ip[i] < r.start[i] || ip[i] > r.end[i] {
82+
return false
83+
}
84+
if ip[i] > r.start[i] || ip[i] < r.end[i] {
85+
break
86+
}
87+
}
88+
return true
2989
}
3090

3191
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
92+
if config.Precedence != "" && config.Precedence != "allow" && config.Precedence != "deny" {
93+
return nil, fmt.Errorf("invalid precedence value: %s. Must be either 'allow' or 'deny'", config.Precedence)
94+
}
95+
3296
processor := &IPProcessor{
33-
next: next,
34-
name: name,
97+
next: next,
98+
name: name,
99+
precedence: config.Precedence,
35100
}
36101

37102
// Process deny rules
38-
for _, rule := range config.DenyList {
103+
for _, rule := range config.Deny {
104+
// Try parsing as a single IP
39105
if ip := net.ParseIP(rule); ip != nil {
40106
processor.denyIPs = append(processor.denyIPs, ip)
41107
continue
42108
}
43109

110+
// Try parsing as CIDR
44111
_, network, err := net.ParseCIDR(rule)
45-
if err != nil {
46-
return nil, err
112+
if err == nil {
113+
processor.denyCIDRs = append(processor.denyCIDRs, network)
114+
continue
47115
}
48-
processor.denyCIDRs = append(processor.denyCIDRs, network)
116+
117+
// Try parsing as IP range
118+
ipRange, err := parseIPRange(rule)
119+
if err == nil {
120+
processor.denyRanges = append(processor.denyRanges, *ipRange)
121+
continue
122+
}
123+
124+
return nil, fmt.Errorf("invalid IP, CIDR, or range in deny list: %s", rule)
49125
}
50126

51127
// Process allow rules
52-
for _, rule := range config.AllowList {
128+
for _, rule := range config.Allow {
129+
// Try parsing as a single IP
53130
if ip := net.ParseIP(rule); ip != nil {
54131
processor.allowIPs = append(processor.allowIPs, ip)
55132
continue
56133
}
57134

135+
// Try parsing as CIDR
58136
_, network, err := net.ParseCIDR(rule)
59-
if err != nil {
60-
return nil, err
137+
if err == nil {
138+
processor.allowCIDRs = append(processor.allowCIDRs, network)
139+
continue
61140
}
62-
processor.allowCIDRs = append(processor.allowCIDRs, network)
141+
142+
// Try parsing as IP range
143+
ipRange, err := parseIPRange(rule)
144+
if err == nil {
145+
processor.allowRanges = append(processor.allowRanges, *ipRange)
146+
continue
147+
}
148+
149+
return nil, fmt.Errorf("invalid IP, CIDR, or range in allow list: %s", rule)
63150
}
64151

65152
return processor, nil
66153
}
67154

68-
// getIP extracts the client IP from the request
69155
func (p *IPProcessor) getIP(req *http.Request) net.IP {
70-
// First try X-Forwarded-For header
71156
xff := req.Header.Get("X-Forwarded-For")
72157
if xff != "" {
73-
// Get the first IP in the chain
74158
ips := strings.Split(xff, ",")
75159
if len(ips) > 0 {
76160
if ip := net.ParseIP(strings.TrimSpace(ips[0])); ip != nil {
@@ -79,10 +163,8 @@ func (p *IPProcessor) getIP(req *http.Request) net.IP {
79163
}
80164
}
81165

82-
// Fall back to RemoteAddr
83166
host, _, err := net.SplitHostPort(req.RemoteAddr)
84167
if err != nil {
85-
// Try RemoteAddr as-is
86168
if ip := net.ParseIP(req.RemoteAddr); ip != nil {
87169
return ip
88170
}
@@ -92,53 +174,84 @@ func (p *IPProcessor) getIP(req *http.Request) net.IP {
92174
return net.ParseIP(host)
93175
}
94176

95-
func (p *IPProcessor) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
96-
// Get client IP
97-
clientIP := p.getIP(req)
98-
if clientIP == nil {
99-
http.Error(rw, "Invalid IP address", http.StatusForbidden)
100-
return
101-
}
102-
103-
// Check deny rules first
177+
func (p *IPProcessor) checkIPInDenyList(clientIP net.IP) bool {
104178
for _, denyIP := range p.denyIPs {
105179
if denyIP.Equal(clientIP) {
106-
http.Error(rw, "Access denied", http.StatusForbidden)
107-
return
180+
return true
108181
}
109182
}
110183

111184
for _, denyCIDR := range p.denyCIDRs {
112185
if denyCIDR.Contains(clientIP) {
113-
http.Error(rw, "Access denied", http.StatusForbidden)
114-
return
186+
return true
115187
}
116188
}
117189

118-
// If there are allow rules, the IP must match at least one
119-
if len(p.allowIPs) > 0 || len(p.allowCIDRs) > 0 {
120-
allowed := false
190+
for _, denyRange := range p.denyRanges {
191+
if denyRange.IPRangeContains(clientIP) {
192+
return true
193+
}
194+
}
121195

122-
for _, allowIP := range p.allowIPs {
123-
if allowIP.Equal(clientIP) {
124-
allowed = true
125-
break
126-
}
196+
return false
197+
}
198+
199+
func (p *IPProcessor) checkIPInAllowList(clientIP net.IP) bool {
200+
if len(p.allowIPs) == 0 && len(p.allowCIDRs) == 0 && len(p.allowRanges) == 0 {
201+
return false
202+
}
203+
204+
for _, allowIP := range p.allowIPs {
205+
if allowIP.Equal(clientIP) {
206+
return true
127207
}
208+
}
128209

129-
if !allowed {
130-
for _, allowCIDR := range p.allowCIDRs {
131-
if allowCIDR.Contains(clientIP) {
132-
allowed = true
133-
break
134-
}
135-
}
210+
for _, allowCIDR := range p.allowCIDRs {
211+
if allowCIDR.Contains(clientIP) {
212+
return true
213+
}
214+
}
215+
216+
for _, allowRange := range p.allowRanges {
217+
if allowRange.IPRangeContains(clientIP) {
218+
return true
136219
}
220+
}
221+
222+
return false
223+
}
137224

138-
if !allowed {
139-
http.Error(rw, "Access denied", http.StatusForbidden)
140-
return
225+
func (p *IPProcessor) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
226+
clientIP := p.getIP(req)
227+
if clientIP == nil {
228+
http.Error(rw, "Invalid IP address", http.StatusForbidden)
229+
return
230+
}
231+
232+
var allowed bool
233+
234+
if p.precedence == "allow" {
235+
if p.checkIPInAllowList(clientIP) {
236+
allowed = true
237+
} else if p.checkIPInDenyList(clientIP) {
238+
allowed = false
239+
} else {
240+
allowed = false
141241
}
242+
} else {
243+
if p.checkIPInDenyList(clientIP) {
244+
allowed = false
245+
} else if p.checkIPInAllowList(clientIP) {
246+
allowed = true
247+
} else {
248+
allowed = false
249+
}
250+
}
251+
252+
if !allowed {
253+
http.Error(rw, "Access denied", http.StatusForbidden)
254+
return
142255
}
143256

144257
p.next.ServeHTTP(rw, req)

0 commit comments

Comments
 (0)