github.com/openshift-online/ocm-sdk-go@v0.1.473/testing/servers.go (about) 1 /* 2 Copyright (c) 2019 Red Hat, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package testing 18 19 import ( 20 "crypto/rand" 21 "crypto/rsa" 22 "crypto/tls" 23 "crypto/x509" 24 "crypto/x509/pkix" 25 "encoding/pem" 26 "log" 27 "math/big" 28 "net" 29 "net/http" 30 "net/url" 31 "os" 32 "path/filepath" 33 "time" 34 35 jsonpatch "github.com/evanphx/json-patch/v5" 36 "github.com/onsi/gomega/ghttp" 37 "golang.org/x/net/http2" 38 "golang.org/x/net/http2/h2c" 39 40 . "github.com/onsi/ginkgo/v2/dsl/core" // nolint 41 . "github.com/onsi/gomega" // nolint 42 ) 43 44 // MakeTCPServer creates a test server that listens in a TCP socket and configured so that it 45 // sends log messages to the Ginkgo writer. 46 func MakeTCPServer() *ghttp.Server { 47 server := ghttp.NewUnstartedServer() 48 server.Writer = GinkgoWriter 49 server.HTTPTestServer.Config.ErrorLog = log.New(GinkgoWriter, "", log.LstdFlags) 50 server.HTTPTestServer.EnableHTTP2 = true 51 server.HTTPTestServer.Start() 52 return server 53 } 54 55 // MakeUnixServer creates a test server that listens in a Unix socket and configured so that it 56 // sends log messages to the Ginkgo writer. It returns the created server and name of a temporary 57 // file containing the Unix socket. This file will be in a temporary directory, and the caller is 58 // resposible for removing the directory once it is no longer needed. 59 func MakeUnixServer() (server *ghttp.Server, socket string) { 60 // Create a temporary directory for the Unix sockets: 61 sockets, err := os.MkdirTemp("", "sockets") 62 Expect(err).ToNot(HaveOccurred()) 63 socket = filepath.Join(sockets, "server.socket") 64 65 // Create the listener: 66 listener, err := net.Listen("unix", socket) 67 Expect(err).ToNot(HaveOccurred()) 68 69 // Create and configure the server: 70 server = ghttp.NewUnstartedServer() 71 server.Writer = GinkgoWriter 72 server.HTTPTestServer.Config.ErrorLog = log.New(GinkgoWriter, "", log.LstdFlags) 73 server.HTTPTestServer.EnableHTTP2 = true 74 server.HTTPTestServer.Listener = listener 75 server.HTTPTestServer.Start() 76 77 return 78 } 79 80 // MakeTCPTLSServer creates a test server configured so that it sends log messages to the Ginkgo 81 // writer. It returns the created server and the name of a temporary file that contains the CA 82 // certificate that the client should trust in order to connect to the server. It is the 83 // responsibility of the caller to delete this temporary file when it is no longer needed. 84 func MakeTCPTLSServer() (server *ghttp.Server, ca string) { 85 // Create and configure the server: 86 server = ghttp.NewUnstartedServer() 87 server.Writer = GinkgoWriter 88 server.HTTPTestServer.Config.ErrorLog = log.New(GinkgoWriter, "", log.LstdFlags) 89 server.HTTPTestServer.EnableHTTP2 = true 90 server.HTTPTestServer.StartTLS() 91 92 // Fetch the CA certificate: 93 address, err := url.Parse(server.URL()) 94 Expect(err).ToNot(HaveOccurred()) 95 ca = fetchCACertificate("tcp", address.Host) 96 97 return 98 } 99 100 // MakeUnixTLSServer creates a test server that listens in a Unix socket and configured so that it 101 // sends log messages to the Ginkgo writer. It returns the created server, the name of a temporary 102 // file that contains the CA certificate that the client should trust in order to connect to the 103 // server and the name of a directory containing the Unix sockets. This file will be in a temporary 104 // directory. It is the responsibility of the caller to remove these temporary directories and 105 // files. 106 func MakeUnixTLSServer() (server *ghttp.Server, ca, socket string) { 107 // Create a temporary directory for the Unix sockets: 108 sockets, err := os.MkdirTemp("", "sockets") 109 Expect(err).ToNot(HaveOccurred()) 110 socket = filepath.Join(sockets, "server.socket") 111 112 // Create the listener: 113 listener, err := net.Listen("unix", socket) 114 Expect(err).ToNot(HaveOccurred()) 115 116 // Create and configure the server: 117 server = ghttp.NewUnstartedServer() 118 server.Writer = GinkgoWriter 119 server.HTTPTestServer.Config.ErrorLog = log.New(GinkgoWriter, "", log.LstdFlags) 120 server.HTTPTestServer.EnableHTTP2 = true 121 server.HTTPTestServer.Listener = listener 122 server.HTTPTestServer.StartTLS() 123 124 // Fetch the CA certificate: 125 ca = fetchCACertificate("unix", socket) 126 127 return 128 } 129 130 // MakeTCPH2CServer creates a test server that supports HTTP/2 without TLS, configured so that it 131 // sends log messages to the Ginkgo writer. 132 func MakeTCPH2CServer() *ghttp.Server { 133 // Create the server that supports HTTP/2 without TLS: 134 h2s := &http2.Server{} 135 136 // Create the regular server: 137 server := ghttp.NewUnstartedServer() 138 server.Writer = GinkgoWriter 139 server.HTTPTestServer.Config.ErrorLog = log.New(GinkgoWriter, "", log.LstdFlags) 140 server.HTTPTestServer.EnableHTTP2 = true 141 142 // Wrap the handler of the regular server with the handler that detects HTTP/2 requests 143 // without TLS and delegates them to the HTTP/2 server that supports that: 144 server.HTTPTestServer.Config.Handler = h2c.NewHandler(server.HTTPTestServer.Config.Handler, h2s) 145 146 // Start the server: 147 server.Start() 148 149 return server 150 } 151 152 // MakeUnixH2cServer creates a test server that listens in a Unix socket and supports HTTP/2 without 153 // TLS, configured so that it sends log messages to the Ginkgo writer. It returns the created server 154 // and name of a temporary file containing the Unix socket. This file will be in a temporary 155 // directory, and the caller is resposible for removing the directory once it is no longer needed. 156 func MakeUnixH2CServer() (server *ghttp.Server, socket string) { 157 // Create a temporary directory for the Unix sockets: 158 sockets, err := os.MkdirTemp("", "sockets") 159 Expect(err).ToNot(HaveOccurred()) 160 socket = filepath.Join(sockets, "server.socket") 161 162 // Create the listener: 163 listener, err := net.Listen("unix", socket) 164 Expect(err).ToNot(HaveOccurred()) 165 166 // Create the server that supports HTTP/2 without TLS: 167 h2s := &http2.Server{} 168 169 // Create the regular server: 170 server = ghttp.NewUnstartedServer() 171 server.Writer = GinkgoWriter 172 server.HTTPTestServer.Config.ErrorLog = log.New(GinkgoWriter, "", log.LstdFlags) 173 server.HTTPTestServer.EnableHTTP2 = true 174 server.HTTPTestServer.Listener = listener 175 176 // Wrap the handler of the regular server with the handler that detects HTTP/2 requests 177 // without TLS and delegates them to the HTTP/2 server that supports that: 178 server.HTTPTestServer.Config.Handler = h2c.NewHandler(server.HTTPTestServer.Config.Handler, h2s) 179 180 // Start the server: 181 server.Start() 182 183 return 184 } 185 186 // fetchCACertificates connects to the given network address and extracts the CA certificate from 187 // the TLS handshake. It returns the path of a temporary file containing that CA certificate encoded 188 // in PEM format. It is the responsibility of the caller to delete that file when it is no longer 189 // needed. 190 func fetchCACertificate(network, address string) string { 191 // Connect to the server and do the TLS handshake to obtain the certificate chain: 192 conn, err := tls.Dial(network, address, &tls.Config{ 193 InsecureSkipVerify: true, // nolint 194 }) 195 Expect(err).ToNot(HaveOccurred()) 196 defer func() { 197 err = conn.Close() 198 Expect(err).ToNot(HaveOccurred()) 199 }() 200 err = conn.Handshake() 201 Expect(err).ToNot(HaveOccurred()) 202 certs := conn.ConnectionState().PeerCertificates 203 Expect(certs).ToNot(BeNil()) 204 Expect(len(certs)).To(BeNumerically(">=", 1)) 205 cert := certs[len(certs)-1] 206 Expect(cert).ToNot(BeNil()) 207 208 // Serialize the CA certificate: 209 Expect(cert.Raw).ToNot(BeNil()) 210 block := &pem.Block{ 211 Type: "CERTIFICATE", 212 Bytes: cert.Raw, 213 } 214 buffer := pem.EncodeToMemory(block) 215 Expect(buffer).ToNot(BeNil()) 216 217 // Store the CA certificate in a temporary file: 218 file, err := os.CreateTemp("", "*.test.ca") 219 Expect(err).ToNot(HaveOccurred()) 220 _, err = file.Write(buffer) 221 Expect(err).ToNot(HaveOccurred()) 222 err = file.Close() 223 Expect(err).ToNot(HaveOccurred()) 224 225 // Return the path of the temporary file: 226 return file.Name() 227 } 228 229 // RespondeWithContent responds with the given status code, content type and body. 230 func RespondWithContent(status int, contentType, body string) http.HandlerFunc { 231 return ghttp.RespondWith( 232 status, 233 body, 234 http.Header{ 235 "Content-Type": []string{ 236 contentType, 237 }, 238 }, 239 ) 240 } 241 242 // RespondWithJSON responds with the given status code and JSON body. 243 func RespondWithJSON(status int, body string) http.HandlerFunc { 244 return RespondWithContent(status, "application/json", body) 245 } 246 247 // RespondWithJSONTemplate responds with the given status code and with a JSON body that is 248 // generated from the given template and arguments. See the EvaluateTemplate function for details 249 // on how the template and the arguments are combined. 250 func RespondWithJSONTemplate(status int, source string, args ...interface{}) http.HandlerFunc { 251 return RespondWithJSON(status, EvaluateTemplate(source, args...)) 252 } 253 254 // RespondWithPatchedJSON responds with the given status code and the result of 255 // patching the given JSON with the given patch. 256 func RespondWithPatchedJSON(status int, body string, patch string) http.HandlerFunc { 257 patchObject, err := jsonpatch.DecodePatch([]byte(patch)) 258 Expect(err).ToNot(HaveOccurred()) 259 patchResult, err := patchObject.Apply([]byte(body)) 260 Expect(err).ToNot(HaveOccurred()) 261 return RespondWithJSON(status, string(patchResult)) 262 } 263 264 // RespondWithCookie responds to the request adding a cookie with the given name and value. 265 func RespondWithCookie(name, value string) http.HandlerFunc { 266 return func(w http.ResponseWriter, r *http.Request) { 267 http.SetCookie(w, &http.Cookie{ 268 Name: name, 269 Value: value, 270 }) 271 } 272 } 273 274 // VerifyCookie checks that the request contains a cookie with the given name and value. 275 func VerifyCookie(name, value string) http.HandlerFunc { 276 return func(w http.ResponseWriter, r *http.Request) { 277 cookie, err := r.Cookie(name) 278 Expect(err).ToNot(HaveOccurred()) 279 Expect(cookie).ToNot(BeNil()) 280 Expect(cookie.Value).To(Equal(value)) 281 } 282 } 283 284 // LocalhostCertificate returns a self signed TLS certificate valid for the name `localhost` DNS 285 // name, for the `127.0.0.1` IPv4 address and for the `::1` IPv6 address. 286 // 287 // A similar certificate can be generated with the following command: 288 // 289 // openssl req \ 290 // -x509 \ 291 // -newkey rsa:4096 \ 292 // -nodes \ 293 // -keyout tls.key \ 294 // -out tls.crt \ 295 // -subj '/CN=localhost' \ 296 // -addext 'subjectAltName=DNS:localhost,IP:127.0.0.1,IP:::1' \ 297 // -days 1 298 func LocalhostCertificate() tls.Certificate { 299 if localhostCertificate == nil { 300 key, err := rsa.GenerateKey(rand.Reader, 4096) 301 Expect(err).ToNot(HaveOccurred()) 302 now := time.Now() 303 spec := x509.Certificate{ 304 SerialNumber: big.NewInt(0), 305 Subject: pkix.Name{ 306 CommonName: "localhost", 307 }, 308 DNSNames: []string{ 309 "localhost", 310 }, 311 IPAddresses: []net.IP{ 312 net.ParseIP("127.0.0.1"), 313 net.ParseIP("::1"), 314 }, 315 NotBefore: now, 316 NotAfter: now.Add(24 * time.Hour), 317 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 318 ExtKeyUsage: []x509.ExtKeyUsage{ 319 x509.ExtKeyUsageServerAuth, 320 }, 321 } 322 data, err := x509.CreateCertificate(rand.Reader, &spec, &spec, &key.PublicKey, key) 323 Expect(err).ToNot(HaveOccurred()) 324 localhostCertificate = &tls.Certificate{ 325 Certificate: [][]byte{data}, 326 PrivateKey: key, 327 } 328 } 329 return *localhostCertificate 330 } 331 332 // localhostCertificate contains the TLS certificate returned by the LocalhostCertificate function. 333 var localhostCertificate *tls.Certificate