1
16
17 package proxy
18
19 import (
20 "context"
21 "crypto/tls"
22 "crypto/x509"
23 "fmt"
24 "net"
25 "net/http"
26 "net/http/httptest"
27 "net/url"
28 "reflect"
29 "regexp"
30 "testing"
31
32 "github.com/google/go-cmp/cmp"
33 utilnet "k8s.io/apimachinery/pkg/util/net"
34 )
35
36 func TestDialURL(t *testing.T) {
37 roots := x509.NewCertPool()
38 if !roots.AppendCertsFromPEM(localhostCert) {
39 t.Fatal("error setting up localhostCert pool")
40 }
41
42 cert, err := tls.X509KeyPair(localhostCert, localhostKey)
43 if err != nil {
44 t.Fatal(err)
45 }
46 var d net.Dialer
47
48 testcases := map[string]struct {
49 TLSConfig *tls.Config
50 Dial utilnet.DialFunc
51 ExpectError string
52 ExpectProto string
53 }{
54 "insecure": {
55 TLSConfig: &tls.Config{InsecureSkipVerify: true},
56 },
57 "secure, no roots": {
58 TLSConfig: &tls.Config{InsecureSkipVerify: false},
59 ExpectError: "unknown authority|not trusted",
60 },
61 "secure with roots": {
62 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots},
63 },
64 "secure with mismatched server": {
65 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"},
66 ExpectError: "not bogus.com",
67 },
68 "secure with matched server": {
69 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"},
70 },
71
72 "insecure, custom dial": {
73 TLSConfig: &tls.Config{InsecureSkipVerify: true},
74 Dial: d.DialContext,
75 },
76 "secure, no roots, custom dial": {
77 TLSConfig: &tls.Config{InsecureSkipVerify: false},
78 Dial: d.DialContext,
79 ExpectError: "unknown authority|not trusted",
80 },
81 "secure with roots, custom dial": {
82 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots},
83 Dial: d.DialContext,
84 },
85 "secure with mismatched server, custom dial": {
86 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"},
87 Dial: d.DialContext,
88 ExpectError: "not bogus.com",
89 },
90 "secure with matched server, custom dial": {
91 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"},
92 Dial: d.DialContext,
93 },
94 "ensure we use http2 if specified": {
95 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com", NextProtos: []string{"http2"}},
96 Dial: d.DialContext,
97 ExpectProto: "http2",
98 },
99 "ensure we use http/1.1 if unspecified": {
100 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"},
101 Dial: d.DialContext,
102 ExpectProto: "http/1.1",
103 },
104 "ensure we use http/1.1 if available": {
105 TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com", NextProtos: []string{"http2", "http/1.1"}},
106 Dial: d.DialContext,
107 ExpectProto: "http/1.1",
108 },
109 }
110
111 for k, tc := range testcases {
112 func() {
113 ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}))
114 defer ts.Close()
115 ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}, NextProtos: []string{"http2", "http/1.1"}}
116 ts.StartTLS()
117
118
119 tlsConfigCopy := tc.TLSConfig.Clone()
120
121 tlsConfigCopy.Clone()
122 transport := &http.Transport{
123 DialContext: tc.Dial,
124 TLSClientConfig: tlsConfigCopy,
125 }
126
127 extractedDial, err := utilnet.DialerFor(transport)
128 if err != nil {
129 t.Fatal(err)
130 }
131 if fmt.Sprintf("%p", extractedDial) != fmt.Sprintf("%p", tc.Dial) {
132 t.Fatalf("%s: Unexpected dial", k)
133 }
134
135 extractedTLSConfig, err := utilnet.TLSClientConfig(transport)
136 if err != nil {
137 t.Fatal(err)
138 }
139 if extractedTLSConfig == nil {
140 t.Fatalf("%s: Expected tlsConfig", k)
141 }
142
143 u, _ := url.Parse(ts.URL)
144 _, p, _ := net.SplitHostPort(u.Host)
145 u.Host = net.JoinHostPort("127.0.0.1", p)
146 conn, err := DialURL(context.Background(), u, transport)
147
148
149 if !reflect.DeepEqual(tc.TLSConfig, tlsConfigCopy) {
150 t.Errorf("%s: transport's copy of TLSConfig was mutated\n%s", k, cmp.Diff(tc.TLSConfig, tlsConfigCopy))
151 }
152
153 if err != nil {
154 if tc.ExpectError == "" {
155 t.Errorf("%s: expected no error, got %q", k, err.Error())
156 }
157 if tc.ExpectError != "" && !regexp.MustCompile(tc.ExpectError).MatchString(err.Error()) {
158 t.Errorf("%s: expected error containing %q, got %q", k, tc.ExpectError, err.Error())
159 }
160 return
161 }
162
163 tlsConn := conn.(*tls.Conn)
164 if tc.ExpectProto != "" {
165 if tlsConn.ConnectionState().NegotiatedProtocol != tc.ExpectProto {
166 t.Errorf("%s: expected proto %s, got %s", k, tc.ExpectProto, tlsConn.ConnectionState().NegotiatedProtocol)
167 }
168 }
169
170 conn.Close()
171 if tc.ExpectError != "" {
172 t.Errorf("%s: expected error %q, got none", k, tc.ExpectError)
173 }
174 }()
175 }
176
177 }
178
179
180
181
182 var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
183 MIIDGTCCAgGgAwIBAgIRAKfNl1LEAt7nFPYvHBnpv2swDQYJKoZIhvcNAQELBQAw
184 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
185 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP
186 ADCCAQoCggEBAKww39FwmV5lDIbAUIAuSYYVtZke6bca1oyq19ZrRL0uavwPXSJm
187 +Qxt4RKUQhzYhZ/alJp8iRfu/Z+Yv9Beez89dQB9V8YnHj/AX4Jph9lJ2aawWMI6
188 AqPLdIzKLQVVvPw+UVKH9x8yy08H/23AIFGyK4Dbht+KZJeUbJQFiGlRFJim8atx
189 KA3C9NzCHw6hyhP46jguLl65rcxLMSzcTz97ToG0MP66YEUbsA/YzFTKDwht7ESH
190 nRMBnQ4wZfWpvAiXMr3XJGOa3NYJy1A+WkWyrfZO7guwsZ4L6dGqnlPpzA5QkKYx
191 H9Z5K1bUaYEi0Yi2ug7Jkvd1HE179nkF7t0CAwEAAaNoMGYwDgYDVR0PAQH/BAQD
192 AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R
193 BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI
194 hvcNAQELBQADggEBAAKSQToD1iLujFhQwaLnPVRV6r4nEFVXCxXYtQNEX1DVSKSj
195 JYbBGJnL50oc0N4Ar+Spqofm+THkiTQJUzptPtnYIzNpKYdE6+bPwqURWzFEI2OF
196 ks3fYZ4ZdbMbmJRo1qPJO34emm4KrOl9aoV0qwp2QyTvHgLroU3icKoe4e7+p4KK
197 02Rt3qczHvCKoUnw6m07Ql0n9e7Ncpujcs2A8PaQ1iPX+BVOmvjTVT8y5NSRDzwL
198 a2wur8BSZ5E8SVzzvNZJlLSi6BbObQUjALHkjVYm11dWv/BY8jHdt+iFhbNBRASx
199 ENuih3pX1Poki1qRYOtB/vAS99E1ORj9zJlUlzo=
200 -----END CERTIFICATE-----`)
201
202
203 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
204 MIIEogIBAAKCAQEArDDf0XCZXmUMhsBQgC5JhhW1mR7ptxrWjKrX1mtEvS5q/A9d
205 Imb5DG3hEpRCHNiFn9qUmnyJF+79n5i/0F57Pz11AH1XxiceP8BfgmmH2UnZprBY
206 wjoCo8t0jMotBVW8/D5RUof3HzLLTwf/bcAgUbIrgNuG34pkl5RslAWIaVEUmKbx
207 q3EoDcL03MIfDqHKE/jqOC4uXrmtzEsxLNxPP3tOgbQw/rpgRRuwD9jMVMoPCG3s
208 RIedEwGdDjBl9am8CJcyvdckY5rc1gnLUD5aRbKt9k7uC7Cxngvp0aqeU+nMDlCQ
209 pjEf1nkrVtRpgSLRiLa6DsmS93UcTXv2eQXu3QIDAQABAoIBACAYnB+2FWB7BXK4
210 tkiuWBYeRdNc58OxxPxDfCgDprR8yoRheMLI3vNqJ+IGsKwf0AiT/c8uF3/WlIAD
211 QP3eHqsTEZQdyRaug/zuJt9wPFpMYb2ocWMC3Ssa6Ya0yN+Ns8Rw+UehAHdYSH1a
212 yEn03hFcXK+QO/u/GDEJAZQ108+NdznT4ql59tt791d97meNlMVJwkwVf/NqtDqi
213 UNx6BvSj5+6MoWjU8hqrYv9pkzP386QRsl70tVH+0LZd5XUZsSyof/IdV1EmfGUR
214 5les8tsd+fuo3LaPObksJu+GBwvEStmQPjZjiBUzw0Sx8VYTJfZr7gl2h4mmk/AJ
215 F5P+fSECgYEAzwDcJCuYPA8nzVB+ZOM+Wl+3uUKG/Xn8Yx8uWtWU7o9qsJmasLqO
216 sLtz1zadPtYOBXsb4H5PisNPuosVEqnthjRwmPhIA3tK/X3UzhnriACCrKpg3Ix0
217 uJG2vqpdaPXYxmyTQfI8YSp5X0gTg3R4xQqmbGMyAQg+1NzcGAf+qQ8CgYEA1PKX
218 vkxzJuSPsfQYr34fnZRuogANNGUaWCTYMhH6sK8qrJu5RXmEemaraqT/esUUu1fl
219 cTAxRqUb8ysexA+RKR848hFkrvAR5M1t6xK2hPuSec1Lm9HNfHoFB7Pa5t7APoJ9
220 8NkjNzI0mL9YqYcfJpzfFrxtzfLwlm6B3irS8VMCgYBg3skmUBRcvsbkiO+tLL7I
221 MhTbKGvdgNGAXV4m+d5JSWonHKrMW3Fc+Uv7gb5SYn+LRxJDmziD+mR8KowBAO57
222 qFys6TtiDbeJKvKERJL5QSvlu5G6hCw3F1GKplUyQiJgsPy0lrR00BieYy9mjAHc
223 S+CXxk/nNcGZgYWp5UviNwKBgC7t46kpmfsJRe222LXcOsV0j8kd78sLOPoR7J9k
224 PPYxNFtj2jnIZPzAoahYAoGg60e6QDNopoNmIbm+WAJnV9tTKS6XzLOM7rSY3U+A
225 CT9XXdl/99i4LOvwzCj9ZxGYJ4/fHDg28j7YzqSXDsgVojTVP4j4L87CamkMo4w9
226 rc1HAoGARE2WActS2PF75jRXCjj4SjB/3vOJVGKxrJdoo2HPzY0psTmdJJULOGYZ
227 MU1KC4EDzhSfM3juBbEhaZx9NFZOHVp2hxZpg77B5cQXGH6HIiZ20jCNjdcioHl9
228 HeVeFG/9rJG0NcQe3pIm9f0EY5JCbzr0fa2tTPV3N9jGHc0sFtI=
229 -----END RSA PRIVATE KEY-----
230 `)
231
View as plain text