From a97729b99543e42b70a872d781c0109e4385b1ec Mon Sep 17 00:00:00 2001 From: Mengxi Zhang Date: Tue, 11 Jul 2023 02:35:46 -0700 Subject: [PATCH 1/3] feat: allow request with x509 cert --- hey.go | 16 ++++++++++++++++ requester/requester.go | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/hey.go b/hey.go index f727e26b..55a3f7dc 100644 --- a/hey.go +++ b/hey.go @@ -16,6 +16,7 @@ package main import ( + "crypto/tls" "flag" "fmt" "io/ioutil" @@ -64,6 +65,9 @@ var ( disableKeepAlives = flag.Bool("disable-keepalive", false, "") disableRedirects = flag.Bool("disable-redirects", false, "") proxyAddr = flag.String("x", "", "") + + certFile = flag.String("cert", "", "") + certKey = flag.String("cert-key", "", "") ) var usage = `Usage: hey [options...] @@ -101,6 +105,9 @@ Options: -disable-redirects Disable following of HTTP redirects -cpus Number of used cpu cores. (default for current machine is %d cores) + + -cert file path to the X509 certificate + -cert-key file path to the X509 certidicate key ` func main() { @@ -221,6 +228,14 @@ func main() { req.Header = header + var cert tls.Certificate + if *certFile != "" && *certKey != "" { + cert, err = tls.LoadX509KeyPair(*certFile, *certKey) + if err != nil { + usageAndExit(err.Error()) + } + } + w := &requester.Work{ Request: req, RequestBody: bodyAll, @@ -234,6 +249,7 @@ func main() { H2: *h2, ProxyAddr: proxyURL, Output: *output, + Cert: &cert, } w.Init() diff --git a/requester/requester.go b/requester/requester.go index fd7277e7..09eab056 100644 --- a/requester/requester.go +++ b/requester/requester.go @@ -98,6 +98,9 @@ type Work struct { start time.Duration report *report + + // X509 Certificate + Cert *tls.Certificate } func (b *Work) writer() io.Writer { @@ -245,6 +248,10 @@ func (b *Work) runWorkers() { DisableKeepAlives: b.DisableKeepAlives, Proxy: http.ProxyURL(b.ProxyAddr), } + if b.Cert != nil { + tr.TLSClientConfig.Certificates = []tls.Certificate{*b.Cert} + } + if b.H2 { http2.ConfigureTransport(tr) } else { From d8a7c572f0f4c4b5f77cb058c70ce916e4b28ec1 Mon Sep 17 00:00:00 2001 From: Mengxi Zhang Date: Wed, 19 Jul 2023 00:13:41 -0700 Subject: [PATCH 2/3] chore: add unit test for mutual TLS case --- requester/requester_test.go | 66 ++++++++++++++++++++++++++++++++++++ requester/unittestClient.crt | 21 ++++++++++++ requester/unittestClient.key | 28 +++++++++++++++ requester/unittestServer.crt | 21 ++++++++++++ requester/unittestServer.key | 28 +++++++++++++++ 5 files changed, 164 insertions(+) create mode 100644 requester/unittestClient.crt create mode 100644 requester/unittestClient.key create mode 100644 requester/unittestServer.crt create mode 100644 requester/unittestServer.key diff --git a/requester/requester_test.go b/requester/requester_test.go index 9188f77f..e0f1c12b 100644 --- a/requester/requester_test.go +++ b/requester/requester_test.go @@ -16,6 +16,8 @@ package requester import ( "bytes" + "crypto/tls" + "crypto/x509" "io/ioutil" "net/http" "net/http/httptest" @@ -132,3 +134,67 @@ func TestBody(t *testing.T) { t.Errorf("Expected to work 10 times, found %v", count) } } + +func TestCert(t *testing.T) { + var count int64 + const clientCertFile = "unittestClient.crt" + const clientKeyFile = "unittestClient.key" + const serverCertFile = "unittestServer.crt" + const serverKeyFile = "unittestServer.key" + + // Set up and run server + go func() { + // Route + handler := func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt64(&count, int64(1)) + } + mux := http.NewServeMux() + mux.HandleFunc("/", handler) + + // Cert validation + cert, _ := ioutil.ReadFile(clientCertFile) + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(cert) + tlsConfig := &tls.Config{ + ClientCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + tlsConfig.BuildNameToCertificate() + + server := &http.Server{ + Addr: ":7788", + Handler: mux, + TLSConfig: tlsConfig, + } + + // Note client does not need to validate the server cert + // because `Work` has `InsecureSkipVerify: true` + // Here we specify server cert just to make it a HTTPS server + err := server.ListenAndServeTLS(serverCertFile, serverKeyFile) + if err != nil && err != http.ErrServerClosed { + t.Errorf("Failed to start HTTPS server: %v", err) + } + }() + + // Have this just to ensure the server is up and running + time.Sleep(100) + + // Set up and run clients + const numOfRun int64 = 20 + cert, _ := tls.LoadX509KeyPair(clientCertFile, clientKeyFile) + req, _ := http.NewRequest("GET", "https://localhost:7788/", nil) + w := &Work{ + Request: req, + N: int(numOfRun), + C: 2, + Cert: &cert, + } + w.Run() + + // Assert on number of requests handled by the server + // Note the test should have failed before here with `tls: bad certificate` + // if `Worker` does not handle `Cert` properly + if count != numOfRun { + t.Errorf("Expected to send %v requests, found %v", numOfRun, count) + } +} diff --git a/requester/unittestClient.crt b/requester/unittestClient.crt new file mode 100644 index 00000000..9d26f51d --- /dev/null +++ b/requester/unittestClient.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDiDCCAnACCQDzNHLvHscPqTANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcx +GjAYBgNVBAoMEVlvdXIgT3JnYW5pemF0aW9uMRIwEAYDVQQLDAlZb3VyIFVuaXQx +GTAXBgNVBAMMEGNsaWVudC51bml0LnRlc3QwHhcNMjMwNzE5MDY0MDA2WhcNMzMw +NzE2MDY0MDA2WjCBhTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +FjAUBgNVBAcMDU1vdW50YWluIFZpZXcxGjAYBgNVBAoMEVlvdXIgT3JnYW5pemF0 +aW9uMRIwEAYDVQQLDAlZb3VyIFVuaXQxGTAXBgNVBAMMEGNsaWVudC51bml0LnRl +c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDH6rIvdDNSgDEizFyL +dk7rgdGIeveTHCZN4Z3U08RbAraWJiebf3NjusJvo80mAZo3Tj0u5j/iu/QTQxyy +OBIdYPp96jyIEb3ipuc0Q0Y9V/CKMlnGsbWSiU6VcMEVU2MXwJV5fns4iYeeW7r2 +7fjSPo93SgYEhf+2gUbC4grpdCUrgKHPnqwKyQMK3x5SJCEY4kjfelnDJYb2fGS0 +RsUCuqgN+bYaxerTmzWNPT6kFlUXfXCYn1oY5B7QsG38EWwYP1MC/JwOpHdUsEAM +QWF2aG5FYtWkhdjwntFrUyhzLjectJll0RIIU/Uuftt+AKFDQ6pVbqIqVpjkDnLu +2iWRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAC7pepPuB8Ze+DgMuED9f8Q97CxX +U+16FmwWdU/roNwshRqTNsLuTFLaORZSFviHM5N22O+4ct+dMEBgbwJhfBoaJRTN +rgGEumUfaNfCL05lKjDzKubLX1nlOO3TvJPu97D1v67FnpDJYS1cL7URqRKlif4x +E8c3Jxjh/n4lY02moeMamCYajAp12GWQCXq9dvyGthz2G+ubAtdPSh06O/C5Am8t +UIq3iTx0m7P8wdEIV4L+LLLt23STpO9wUi/vFE0Gac5bTUGRlnBld6UuInwLs4hh +sFyZh/4bVe6IjBVIW/p15TyiISSdq0nquPOnQmHvi5aksQoWEPX47p9ZoA0= +-----END CERTIFICATE----- diff --git a/requester/unittestClient.key b/requester/unittestClient.key new file mode 100644 index 00000000..b74894a1 --- /dev/null +++ b/requester/unittestClient.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDH6rIvdDNSgDEi +zFyLdk7rgdGIeveTHCZN4Z3U08RbAraWJiebf3NjusJvo80mAZo3Tj0u5j/iu/QT +QxyyOBIdYPp96jyIEb3ipuc0Q0Y9V/CKMlnGsbWSiU6VcMEVU2MXwJV5fns4iYee +W7r27fjSPo93SgYEhf+2gUbC4grpdCUrgKHPnqwKyQMK3x5SJCEY4kjfelnDJYb2 +fGS0RsUCuqgN+bYaxerTmzWNPT6kFlUXfXCYn1oY5B7QsG38EWwYP1MC/JwOpHdU +sEAMQWF2aG5FYtWkhdjwntFrUyhzLjectJll0RIIU/Uuftt+AKFDQ6pVbqIqVpjk +DnLu2iWRAgMBAAECggEBAKEJLcVJ7fl755inU7jHcSUF6nnsy7bFixlbLx78AoRp +OBjU3TzFunZQP0Vchekii04XiPNZZ4bFbgOCIQByaC0gLEb3QxE8cV+8oCsaMd9C +EjHQAz8pcSB72EBKlk4OYJkGeaFnP+y34/Ws4Hr+EFuTJ0+o4hYMtoIVuqFLIg0Q +IcK2jrEyJesqiqoZQqBWeb/FexnNmNA0I+qWZxAlhHEWjyFeha7d2vh/KeM0VgwY +bDm/R3DALq9EYpkzQeFPYjgLoNNzXgsQtROgh0tIRz9i6r0giU81iqeRIIN+dLpd +uoRv55gAf2GNg+q8tWFHIATBBYUCsKQvDQuIfyYeKMECgYEA/sTTChuU2o7PVFPq +qplFONfOf+bpmiKc3yqkoTJVjhr+P5g6Fd94c6DnLqxM6wtwTFT0bW9MkP7iAIX7 +rqPJx0U/Cj8vh+z9zvKmNbzQWs7WY9bBUWMn9GAlZr7lxb9zWcCT5MA1Xd4965qH +i7VGV7ArfOAzYXaUn5XpJYepJ/kCgYEAyOIDkaSQoinFvMtBJS4Hk/eVuRQRPN3K +YKm8b0b4yAmOGJeWtkZVCUMws2fa8JruKUxHBuA3jLm5kEbir0CIEHtRDNYwUUFC +kTiVmVd4HPVLYEANTNaxPRVJzQix/hbkLHOtJ/bHyDsI8SaZ8oH1kF7KIaUVXKY9 +RDgs+vi1QFkCgYEAoLqH8f6IoIIsZyUyDUL5Gu17h9GkWuuvUFPU3SWmOCrrcC+X +pakAkuJWN2nYdQkYZe/K7SekhG1pI69jo0AN0rvrE4ndcMGhNmh7V7exMzj+pKod ++Dy4PkJSFkolJ+aq3VrTcyOYB4poQjWRgiBxKm0oYnYHtFtdVHj9SAIYQ/ECgYEA +xi60dqt0RYgQnQGpc5TFxSUtgTpbB0GIt3S9gsryBefnWDu1ZH5expSTJ2v+hAFV +lUi7if0K0LsqZzyHx35Svm/qtk9Zu9A2bC726CFzTp5xjrOia3wjv6+Q78b0U0ki +MvisjBDbaJ9VYxRCLQ4pxhS+NhM3z//x0avaNH/J0YECgYEAsVrXC1tTQH8uQMZx +FW60fMndDIOFts52cqM5zMig7IduHYafBKm/nYme41hZbCcelMPZnnWhazhyQecV +MMAArAMddYqNlgojz9C/v/F8w1gvKNbRLX1mHTDySzUC+oUtWMNliQ5MxcEMsURj +ChGitvqdy5PgvnGDPMClx4kyl0w= +-----END PRIVATE KEY----- diff --git a/requester/unittestServer.crt b/requester/unittestServer.crt new file mode 100644 index 00000000..c9fa947c --- /dev/null +++ b/requester/unittestServer.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDiDCCAnACCQDWcjME6qTtXDANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcx +GjAYBgNVBAoMEVlvdXIgT3JnYW5pemF0aW9uMRIwEAYDVQQLDAlZb3VyIFVuaXQx +GTAXBgNVBAMMEHNlcnZlci51bml0LnRlc3QwHhcNMjMwNzE5MDcwNDU4WhcNMzMw +NzE2MDcwNDU4WjCBhTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +FjAUBgNVBAcMDU1vdW50YWluIFZpZXcxGjAYBgNVBAoMEVlvdXIgT3JnYW5pemF0 +aW9uMRIwEAYDVQQLDAlZb3VyIFVuaXQxGTAXBgNVBAMMEHNlcnZlci51bml0LnRl +c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4boqLg2f4X3xmrMa5 +TIueyFkfjn16XWI5FCRY1WQ69LRDaFTdMyCGuf9PFTwFXFAzyLI4O+v88cKSBQSx +Z4rkaUHEvkHon1DN0whM4m05TrQtc7WuQsvUr5vWayD4Tn8NL2YEC3SqNOSuJp29 +N05WjsnTN0mAkfWAPMGJTW4K/7dSVt4RfAxfwAMZ2pAHUHtuXfMqUHiyZizJg3N4 +IA9t2jMicAHsY7maXM3FJ9QWDsXhMRN6/D32emJFJU64EE2Pyd41skDXfS6AMtAn +X1aBugmpVfDa7IPJHnN7LhsGDupIHEPc3foAmX+dO/xNhg9aiA54UW5piGr87b5N +EGfDAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABqQwJ4UdZKYd7xO8PApr2yPcIQH +xjHI2OvxCltQtey4Pnpn7ruMIsfcXdYiUUsziywX+lwfD9riPDiU7ytYXuFr7tES +vZLsXlHOD5pIvSOiGjl4TUPZk4sx33ZSBBg08inAiP350gsZeTcBu0W3OjRwnm1N +Rp4UC3M5zVwSp0p7VA6oa0MIR6igPdbADDh19FBsC0oSN+AxVG4do8ip0/EYuLC7 +y9X4vbzMj5eONBcVOI0EFE/yvkZuN5ZNVW/J5sT84EinmQ6Y02EiYrq0i4BHA02k +bGoTZ0CEDNHUF/cxlnOGfuJcNrkyOAfxWwkxLwSwuLD7f/aDMkxPDNrU34g= +-----END CERTIFICATE----- diff --git a/requester/unittestServer.key b/requester/unittestServer.key new file mode 100644 index 00000000..19d83f47 --- /dev/null +++ b/requester/unittestServer.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4boqLg2f4X3xm +rMa5TIueyFkfjn16XWI5FCRY1WQ69LRDaFTdMyCGuf9PFTwFXFAzyLI4O+v88cKS +BQSxZ4rkaUHEvkHon1DN0whM4m05TrQtc7WuQsvUr5vWayD4Tn8NL2YEC3SqNOSu +Jp29N05WjsnTN0mAkfWAPMGJTW4K/7dSVt4RfAxfwAMZ2pAHUHtuXfMqUHiyZizJ +g3N4IA9t2jMicAHsY7maXM3FJ9QWDsXhMRN6/D32emJFJU64EE2Pyd41skDXfS6A +MtAnX1aBugmpVfDa7IPJHnN7LhsGDupIHEPc3foAmX+dO/xNhg9aiA54UW5piGr8 +7b5NEGfDAgMBAAECggEAA81Lj9qynv0g7GDta853JlvM8oiWb5pTNwgCQ86KVWjL ++oPBFBpEtmJJMBEo/pdLk/W0LtKVZ854C1iNIzcWNuUwlOSzKR93o/aQuj0EsWS8 +9B7phOEdoJHKLZ5hvJypo9TxTm4KBqQ2fpyPLeJg/AnPgUoner4HiJA4ESOGfDWG +UzR8VxTumuqNIde1b4dkEBr51F/zATGGC/KffezlbAqW1f9+xZvVzDlt1mZ93m0w +V8RXfk+NZK+T3JA0f/FCi20uWXRho3g5Me6kOgOKkc9bpUvb53/myB3aU8lIGWl7 +Eb3trQoW57UNLDYkYo5Ox0k6fqYuvVusVojxVD3vqQKBgQDjP4YWH9isaYcv+JWc +41hohhwz/G+DrxTnMWRzptHBQBumVPkdsLUtNgyB9jDx3MWRTxmurFzkdq0eQkzf +MqzUaPtNfZUtjoJ86lCqzgb7Lk9OmZ1lv+a6ea84jLpTRme9ya3Z83qOqZNKH/ar +sdhsM9qt9v4cun4HSyPNU7IWRQKBgQDPxDafeaPQKw9RQVK60THyCBVrK0SOeOPk +G3/vOmYdfeVrJtcyF/FccJZlO3q3mqwplrLqRf31+OQXE1GB36GIj0R4WBfxBAgF +f+8ARkAuWa8My/pCFZBwb7eH7rdCmIIh1xzaqfQzGaNRe+J6WlzZ/Y6IIbHM8FYN +oH37XGvKZwKBgBkEkk93PBRnHcHzPQ1jngUL1qkGfoRhzwxQzL1KvUboNuMN8csJ +/Dg4/hGEuAi4hGul6K7pPOTOB+sP44SjAJH16My0Kas1SDpWVYEoY25dv89obAKa +qN0YrmisXYrzclJblK8s9e4kzxlXAYIPd0MaRlXmnj0tbtiAtjVRpIZ9AoGASvTj +MA3Wh9fBIvOKQVQPzN4VvCBUD4KV1UoGkexjwugUyN+ua6gvr8X+vG8FCxCGZEq2 +KccupHsy7xBNK6newUHO1gwSNyXZLwLE2zh9FzvL57X/h6/3+FiVwjjhbOlQqZzO +ECWYsIbjYRZs0u+e6BaOQZbGasWahjgMu47QZKkCgYBKdtsdOKe8v2iIzpXUr5Zf +WLgs87PFUv79EUOYTcc+bFtIJvGHlo+tvb5MMByk9doXyWtApWnbV4e9bYAXzPwv +GRC4kG41egF3/JDr84P+fZP4jpR0sk1RCqI6o5OyoUaeV9aSYrIKr7skzdwj+GG/ +UHlXK8MqTMQkDWYVRRS+mQ== +-----END PRIVATE KEY----- From 547241f4740a350962c02c1fe27a450d408194bf Mon Sep 17 00:00:00 2001 From: Mengxi Zhang Date: Fri, 9 Aug 2024 15:32:03 -0700 Subject: [PATCH 3/3] add version --- Makefile | 1 + hey.go | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 50fdfa54..f0dde756 100644 --- a/Makefile +++ b/Makefile @@ -4,3 +4,4 @@ release: GOOS=windows GOARCH=amd64 go build -o ./bin/$(binary)_windows_amd64 GOOS=linux GOARCH=amd64 go build -o ./bin/$(binary)_linux_amd64 GOOS=darwin GOARCH=amd64 go build -o ./bin/$(binary)_darwin_amd64 + GOOS=darwin GOARCH=arm64 go build -o ./bin/$(binary)_darwin_arm64 diff --git a/hey.go b/hey.go index 55a3f7dc..82997cd5 100644 --- a/hey.go +++ b/hey.go @@ -36,7 +36,7 @@ import ( const ( headerRegexp = `^([\w-]+):\s*(.+)` authRegexp = `^(.+):([^\s].+)` - heyUA = "hey/0.0.1" + heyVersion = "mengxi-hey/1.0.0" ) var ( @@ -70,7 +70,10 @@ var ( certKey = flag.String("cert-key", "", "") ) -var usage = `Usage: hey [options...] +var usage = ` +Version: %s + +Usage: hey [options...] Options: -n Number of requests to run. Default is 200. @@ -92,7 +95,7 @@ Options: -d HTTP request body. -D HTTP request body from file. For example, /home/user/file.txt or ./file.txt. -T Content-type, defaults to "text/html". - -U User-Agent, defaults to version "hey/0.0.1". + -U User-Agent, defaults to version "%s". -a Basic authentication, username:password. -x HTTP Proxy address as host:port. -h2 Enable HTTP/2. @@ -106,13 +109,13 @@ Options: -cpus Number of used cpu cores. (default for current machine is %d cores) - -cert file path to the X509 certificate - -cert-key file path to the X509 certidicate key + -cert file path to the client X509 certificate + -cert-key file path to the client X509 certidicate key ` func main() { flag.Usage = func() { - fmt.Fprint(os.Stderr, fmt.Sprintf(usage, runtime.NumCPU())) + fmt.Fprint(os.Stderr, fmt.Sprintf(usage, heyVersion, heyVersion, runtime.NumCPU())) } var hs headerSlice @@ -214,15 +217,15 @@ func main() { ua := header.Get("User-Agent") if ua == "" { - ua = heyUA + ua = heyVersion } else { - ua += " " + heyUA + ua += " " + heyVersion } header.Set("User-Agent", ua) // set userAgent header if set if *userAgent != "" { - ua = *userAgent + " " + heyUA + ua = *userAgent + " " + heyVersion header.Set("User-Agent", ua) }