@@ -5,59 +5,134 @@ package httpx
5
5
6
6
import (
7
7
"context"
8
- "net "
8
+ "fmt "
9
9
"net/http"
10
- "net/http/httptest"
11
10
"net/http/httptrace"
12
11
"net/netip"
13
- "net/url"
14
12
"sync/atomic"
15
13
"testing"
14
+ "time"
15
+
16
+ "code.dny.dev/ssrf"
16
17
17
18
"github.com/hashicorp/go-retryablehttp"
18
19
"github.com/stretchr/testify/assert"
19
20
"github.com/stretchr/testify/require"
20
21
)
21
22
22
- func TestNoPrivateIPs (t * testing.T ) {
23
- ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
24
- _ , _ = w .Write ([]byte ("Hello, world!" ))
25
- }))
26
- t .Cleanup (ts .Close )
27
-
28
- target , err := url .ParseRequestURI (ts .URL )
29
- require .NoError (t , err )
30
-
31
- _ , port , err := net .SplitHostPort (target .Host )
32
- require .NoError (t , err )
33
-
34
- allowedURL := "http://localhost:" + port + "/foobar"
35
- allowedGlob := "http://localhost:" + port + "/glob/*"
36
-
37
- c := NewResilientClient (
38
- ResilientClientWithMaxRetry (1 ),
39
- ResilientClientDisallowInternalIPs (),
40
- ResilientClientAllowInternalIPRequestsTo (allowedURL , allowedGlob ),
41
- )
23
+ func TestPrivateIPs (t * testing.T ) {
24
+ testCases := []struct {
25
+ url string
26
+ disallowInternalIPs bool
27
+ allowedIP bool
28
+ }{
29
+ {
30
+ url : "http://127.0.0.1/foobar" ,
31
+ disallowInternalIPs : true ,
32
+ allowedIP : false ,
33
+ },
34
+ {
35
+ url : "http://localhost/foobar" ,
36
+ disallowInternalIPs : true ,
37
+ allowedIP : false ,
38
+ },
39
+ {
40
+ url : "http://127.0.0.1:56789/test" ,
41
+ disallowInternalIPs : true ,
42
+ allowedIP : false ,
43
+ },
44
+ {
45
+ url : "http://192.168.178.5:56789" ,
46
+ disallowInternalIPs : true ,
47
+ allowedIP : false ,
48
+ },
49
+ {
50
+ url : "http://127.0.0.1:56789/foobar" ,
51
+ disallowInternalIPs : true ,
52
+ allowedIP : true ,
53
+ },
54
+ {
55
+ url : "http://127.0.0.1:56789/glob/bar" ,
56
+ disallowInternalIPs : true ,
57
+ allowedIP : true ,
58
+ },
59
+ {
60
+ url : "http://127.0.0.1:56789/glob/bar/baz" ,
61
+ disallowInternalIPs : true ,
62
+ allowedIP : false ,
63
+ },
64
+ {
65
+ url : "http://127.0.0.1:56789/FOOBAR" ,
66
+ disallowInternalIPs : true ,
67
+ allowedIP : false ,
68
+ },
69
+ {
70
+ url : "http://100.64.1.1:80/private" ,
71
+ disallowInternalIPs : true ,
72
+ allowedIP : true ,
73
+ },
74
+ {
75
+ url : "http://100.64.1.1:80/route" ,
76
+ disallowInternalIPs : true ,
77
+ allowedIP : false ,
78
+ },
79
+ {
80
+ url : "http://198.18.99.99/forbidden" ,
81
+ disallowInternalIPs : true ,
82
+ allowedIP : false ,
83
+ },
84
+ {
85
+ // Even if in the allowed requests, no exceptions can be made.
86
+ url : "http://198.18.99.99/allowed" ,
87
+ disallowInternalIPs : true ,
88
+ allowedIP : false ,
89
+ },
90
+ {
91
+ url : "http://127.0.0.1" ,
92
+ disallowInternalIPs : false ,
93
+ allowedIP : true ,
94
+ },
95
+ {
96
+ url : "http://192.168.178.5" ,
97
+ disallowInternalIPs : false ,
98
+ allowedIP : true ,
99
+ },
100
+ {
101
+ url : "http://127.0.0.1:80/glob/bar" ,
102
+ disallowInternalIPs : false ,
103
+ allowedIP : true ,
104
+ },
105
+ {
106
+ url : "http://100.64.1.1:80/route" ,
107
+ disallowInternalIPs : false ,
108
+ allowedIP : true ,
109
+ },
110
+ }
111
+ for _ , tt := range testCases {
112
+ t .Run (
113
+ fmt .Sprintf ("%s should be allowed %v when disallowed internal IPs is %v" , tt .url , tt .allowedIP , tt .disallowInternalIPs ),
114
+ func (t * testing.T ) {
115
+ options := []ResilientOptions {
116
+ ResilientClientWithMaxRetry (0 ),
117
+ ResilientClientWithConnectionTimeout (50 * time .Millisecond ),
118
+ }
119
+ if tt .disallowInternalIPs {
120
+ options = append (options , ResilientClientDisallowInternalIPs ())
121
+ options = append (options , ResilientClientAllowInternalIPRequestsTo (
122
+ "http://127.0.0.1:56789/foobar" ,
123
+ "http://127.0.0.1:56789/glob/*" ,
124
+ "http://100.64.1.1:80/private" ,
125
+ "http://198.18.99.99/allowed" ))
126
+ }
42
127
43
- for i := 0 ; i < 10 ; i ++ {
44
- for destination , passes := range map [string ]bool {
45
- "http://127.0.0.1:" + port : false ,
46
- "http://localhost:" + port : false ,
47
- "http://192.168.178.5:" + port : false ,
48
- allowedURL : true ,
49
- "http://localhost:" + port + "/glob/bar" : true ,
50
- "http://localhost:" + port + "/glob/bar/baz" : false ,
51
- "http://localhost:" + port + "/FOOBAR" : false ,
52
- } {
53
- _ , err := c .Get (destination )
54
- if ! passes {
55
- require .Errorf (t , err , "dest = %s" , destination )
56
- assert .Containsf (t , err .Error (), "is not a permitted destination" , "dest = %s" , destination )
57
- } else {
58
- require .NoErrorf (t , err , "dest = %s" , destination )
59
- }
60
- }
128
+ c := NewResilientClient (options ... )
129
+ _ , err := c .Get (tt .url )
130
+ if tt .allowedIP {
131
+ assert .NotErrorIs (t , err , ssrf .ErrProhibitedIP )
132
+ } else {
133
+ assert .ErrorIs (t , err , ssrf .ErrProhibitedIP )
134
+ }
135
+ })
61
136
}
62
137
}
63
138
0 commit comments