From 748d72bdbd500c837c2ad29612cca2191e5213fb Mon Sep 17 00:00:00 2001 From: Nicolas Maltais Date: Mon, 3 Jul 2023 13:50:35 -0400 Subject: [PATCH 1/8] add --jwts-filename flag to use pre-existing jwts from a specific file --- README.md | 1 + cmd/payloader/run.go | 4 ++ config/config.go | 13 ++++++- pkgs/jwt-generator/cache.go | 1 + pkgs/jwt-generator/jwt.go | 61 +++++++++++++++++++++++++++++++ pkgs/payloader/payloader.go | 38 +++++++++++-------- pkgs/payloader/payloader_test.go | 26 +++++++++++++ pkgs/payloader/worker/generate.go | 5 ++- test/jwtstestfile.txt | 5 +++ wrapper/wrapper.go | 4 +- 10 files changed, 136 insertions(+), 22 deletions(-) create mode 100644 test/jwtstestfile.txt diff --git a/README.md b/README.md index d4d9095..8171bbc 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Flags: --jwt-kid string JWT KID --jwt-sub string JWT subject (sub) claim --jwt-claims string JWT custom claims as a JSON string, ex: {"iat": 1719410063, "browser": "chrome"} + -f, --jwts-filename string File name in .cache where the JWTs to use are stored -m, --method string request method (default "GET") --mtls-cert string mTLS cert path --mtls-key string mTLS cert private key path diff --git a/cmd/payloader/run.go b/cmd/payloader/run.go index ad7e5de..209554f 100644 --- a/cmd/payloader/run.go +++ b/cmd/payloader/run.go @@ -28,6 +28,7 @@ const ( argJWTAud = "jwt-aud" argJWTHeader = "jwt-header" argJWTKid = "jwt-kid" + argJWTsFilename = "jwts-filename" argHeaders = "headers" argBody = "body" argBodyFile = "body-file" @@ -55,6 +56,7 @@ var ( jwtAud string jwtHeader string jwtKID string + jwtsFilename string headers *[]string body string bodyFile string @@ -92,6 +94,7 @@ var runCmd = &cobra.Command{ jwtIss, jwtAud, jwtHeader, + jwtsFilename, *headers, body, bodyFile, @@ -128,6 +131,7 @@ func init() { runCmd.Flags().StringVar(&jwtIss, argJWTIss, "", "JWT issuer (iss) claim") runCmd.Flags().StringVar(&jwtSub, argJWTSUb, "", "JWT subject (sub) claim") runCmd.Flags().StringVar(&jwtCustomClaims, argJWTCustomClaims, "", "JWT custom claims") + runCmd.Flags().StringVarP(&jwtsFilename, argJWTsFilename, "f", "", "File name in .cache where the JWTs to use are stored") runCmd.Flags().StringVar(&jwtHeader, argJWTHeader, "", "JWT header field name") runCmd.MarkFlagsRequiredTogether(argMTLSCert, argMTLSKey) diff --git a/config/config.go b/config/config.go index 91ddc16..b314158 100644 --- a/config/config.go +++ b/config/config.go @@ -34,6 +34,7 @@ type Config struct { JwtIss string JwtAud string JwtHeader string + JwtsFilename string SendJWT bool Headers []string Body string @@ -41,7 +42,7 @@ type Config struct { Client string } -func NewConfig(ctx context.Context, reqURI, mTLScert, mTLSKey string, disableKeepAlive bool, reqs int64, conns uint, totalTime time.Duration, skipVerify bool, readTimeout, writeTimeout time.Duration, method string, verbose bool, ticker time.Duration, jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader string, headers []string, body, bodyFile string, client string) *Config { +func NewConfig(ctx context.Context, reqURI, mTLScert, mTLSKey string, disableKeepAlive bool, reqs int64, conns uint, totalTime time.Duration, skipVerify bool, readTimeout, writeTimeout time.Duration, method string, verbose bool, ticker time.Duration, jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader, jwtsFilename string, headers []string, body, bodyFile string, client string) *Config { return &Config{ Ctx: ctx, ReqURI: reqURI, @@ -64,6 +65,7 @@ func NewConfig(ctx context.Context, reqURI, mTLScert, mTLSKey string, disableKee JwtIss: jwtIss, JwtAud: jwtAud, JwtHeader: jwtHeader, + JwtsFilename: jwtsFilename, Headers: headers, Body: body, BodyFile: bodyFile, @@ -139,7 +141,7 @@ func (c *Config) Validate() error { } } - if (c.JwtHeader == "") != (c.JwtKey == "") { + if c.JwtsFilename == "" && (c.JwtHeader == "") != (c.JwtKey == "") { if c.JwtHeader == "" { return errors.New("config: empty jwt header") } @@ -163,6 +165,13 @@ func (c *Config) Validate() error { c.SendJWT = true } + if c.JwtsFilename != "" { + if c.ReqTarget == 0 { + return errors.New("can only send jwts when request number is specified") + } + c.SendJWT = true + } + if len(c.Headers) > 0 { for _, h := range c.Headers { if !strings.Contains(h, ":") { diff --git a/pkgs/jwt-generator/cache.go b/pkgs/jwt-generator/cache.go index 885ea8c..030b344 100644 --- a/pkgs/jwt-generator/cache.go +++ b/pkgs/jwt-generator/cache.go @@ -20,6 +20,7 @@ func newCache(f *os.File) (*cache, error) { c := cache{f: f} c.scanner = bufio.NewScanner(c.f) + // Get count found on first line of the file c.scanner.Split(bufio.ScanLines) if c.scanner.Scan() { bb := make([]byte, 8) diff --git a/pkgs/jwt-generator/jwt.go b/pkgs/jwt-generator/jwt.go index a1c8e1e..594cd9c 100644 --- a/pkgs/jwt-generator/jwt.go +++ b/pkgs/jwt-generator/jwt.go @@ -17,6 +17,8 @@ import ( "path/filepath" "runtime" "time" + "regexp" + "bufio" ) const ( @@ -32,6 +34,7 @@ type Config struct { JwtCustomClaimsJSON string JwtIss string JwtAud string + JwtsFilename string signer definition.Signer store *cache } @@ -58,6 +61,64 @@ func (c *Config) validate() error { return nil } +// Gets a certain number of JWTs from a file, looping through / reusing them if necessary +func GetJWTsFromFile(fpath string, fname string, count int64) (<-chan string, <-chan error) { + // Open channels + recv := make(chan string, 1000000) + errs := make(chan error, 1) + + // Open the file + filename := fname + if (filename != "") { + filename = filepath.Join(fpath, filename) + } else { + errs <- fmt.Errorf("jwt_generator: retrieving; no filename") + close(errs) + close(recv) + return recv, errs + } + file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + errs <- fmt.Errorf("jwt_generator: retrieving; failed to open file containing JWTs") + close(errs) + close(recv) + return recv, errs + } + + numJwtsUsedSoFar := int64(0) + for numJwtsUsedSoFar < count { + // Set pointer to beginning of file + if _, err := file.Seek(0, 0); err != nil { + errs <- err + close(errs) + close(recv) + return recv, errs + } + + // Parse file lines for JWTs + scanner := bufio.NewScanner(file) + // JWT Regex + jwtRegex, _ := regexp.Compile(`[\w-]{2,}\.[\w-]{2,}\.[\w-]{2,}`) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + res := jwtRegex.Find(scanner.Bytes()) + if res != nil { + recv <- string(res) + numJwtsUsedSoFar++ + } else { + errs <- fmt.Errorf("jwt_generator: retrieving; error matching JWT with regex %v", err) + } + } + // Loops if user asked for more requests than there were JWTs in the file, so JWTs get reused + } + + // Close the file + if err = file.Close(); err != nil { + fmt.Printf("Could not close the file due to this %s error \n", err) + } + return recv, errs +} + func (j *JWTGenerator) getFileName(dir string) string { hash := sha256.New() hash.Write([]byte(j.config.JwtAud)) diff --git a/pkgs/payloader/payloader.go b/pkgs/payloader/payloader.go index a78f9dd..074410a 100644 --- a/pkgs/payloader/payloader.go +++ b/pkgs/payloader/payloader.go @@ -97,26 +97,32 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { pterm.Error.Println("Can't save jwts if no cache directory") return nil, errors.New("cache directory couldn't be determined") } - - pterm.Info.Printf("Sending jwts with requests, checking for jwts in cache\n") - - jwt := jwt_generator.NewJWTGenerator(&jwt_generator.Config{ - Ctx: p.config.Ctx, - Kid: p.config.JwtKID, - JwtKeyPath: p.config.JwtKey, - JwtSub: p.config.JwtSub, - JwtCustomClaimsJSON: p.config.JwtCustomClaimsJSON, - JwtIss: p.config.JwtIss, - JwtAud: p.config.JwtAud, - }) - if err := os.MkdirAll(JwtCacheDir, 0755); err != nil { return nil, err } - if err := jwt.Generate(p.config.ReqTarget, JwtCacheDir, false); err != nil { - return nil, err + + pterm.Info.Printf("Sending jwts with requests, ") + if p.config.JwtsFilename != "" { + pterm.Info.Printf("using JWTs from file provided\n") + jwtStream, jwtStreamErrs = jwt_generator.GetJWTsFromFile(JwtCacheDir, p.config.JwtsFilename, p.config.ReqTarget) + } else { + pterm.Info.Printf("checking for JWTs in cache\n") + + jwt := jwt_generator.NewJWTGenerator(&jwt_generator.Config{ + Ctx: p.config.Ctx, + Kid: p.config.JwtKID, + JwtKeyPath: p.config.JwtKey, + JwtSub: p.config.JwtSub, + JwtCustomClaimsJSON: p.config.JwtCustomClaimsJSON, + JwtIss: p.config.JwtIss, + JwtAud: p.config.JwtAud, + }) + + if err := jwt.Generate(p.config.ReqTarget, JwtCacheDir, false); err != nil { + return nil, err + } + jwtStream, jwtStreamErrs = jwt.JWTS(p.config.ReqTarget) } - jwtStream, jwtStreamErrs = jwt.JWTS(p.config.ReqTarget) } reqsPerWorker := p.config.ReqTarget / int64(p.config.Conns) diff --git a/pkgs/payloader/payloader_test.go b/pkgs/payloader/payloader_test.go index 643c96f..3668556 100644 --- a/pkgs/payloader/payloader_test.go +++ b/pkgs/payloader/payloader_test.go @@ -348,6 +348,32 @@ func testPayLoader_Run(t *testing.T, addr, client string, cleanup func()) { } }, }, + { + name: "GET using JWT file only", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: addr, + ReqTarget: 10, + Conns: 1, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "GET", + Client: client, + VerboseTicker: time.Second, + Headers: []string{"content-type: application/json"}, + JwtHeader: "Authorization", + JwtsFilename: filepath.Join("..", "..", "test", "jwtstestfile.txt"), + SkipVerify: true, + }}, + want: &GoPayloaderResults{ + CompletedReqs: 10, + FailedReqs: 0, + Responses: map[worker.ResponseCode]int64{ + 200: 10, + }, + Errors: nil, + }, + }, { name: "Error hostname incorrect format - missing port", fields: fields{config: &config.Config{ diff --git a/pkgs/payloader/worker/generate.go b/pkgs/payloader/worker/generate.go index 087d76c..1e280c1 100644 --- a/pkgs/payloader/worker/generate.go +++ b/pkgs/payloader/worker/generate.go @@ -94,8 +94,9 @@ func jwtMiddleware(w *WorkerBase) { case <-w.config.Ctx.Done(): // user cancelled return - //case err := <-w.config.JwtStreamErr: - // pterm.Error.Printf("Failed to get jwts from cache, got error; %v \n", err) + case err := <-w.config.JwtStreamErr: + // pterm.Error.Printf("Failed to get jwts from cache, got error; %v \n", err) + fmt.Printf("Failed to get jwts from cache, got error; %v \n", err) // return TODO fix case jwt := <-w.config.JwtStreamReceiver: //fmt.Printf("GOT JWT %sHELP \n", jwt) diff --git a/test/jwtstestfile.txt b/test/jwtstestfile.txt new file mode 100644 index 0000000..a5e9130 --- /dev/null +++ b/test/jwtstestfile.txt @@ -0,0 +1,5 @@ +eyJhbGciOiJSUzUxMiIsImtpZCI6IjEzMzI1NTc1dGV2ZGZiZHNmc2YiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzb21lLWF1ZCIsImV4cCI6MTcxOTYwODQ3NCwiaXNzIjoic29tZS1pc3N1ZXIiLCJqdGkiOiI1ZWI2Y2I2NC1jNmIxLTRkZmEtYTVlZC0wYTQ0ZjI4ZTcyODMiLCJzdWIiOiJzb21lLXN1YmplY3QifQ.wDjfS7oIHIlBS2BvWq5HPt0u_eFYioVGT-wMgkqDvzEFBl1ElHy6h2WRVQqGg5jIU1_dKfiA5IwXrG-2Eilm40CKOGBVBXijaqgqtpSIirRi00zKfllMczkB8Mv4ZtAFquYCvqnfdM4fyAEklJtA0JI7mcMok06dzA5oLU2HGKM +eyJhbGciOiJSUzUxMiIsImtpZCI6IjEzMzI1NTc1dGV2ZGZiZHNmc2YiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzb21lLWF1ZCIsImV4cCI6MTcxOTYwODQ3NCwiaXNzIjoic29tZS1pc3N1ZXIiLCJqdGkiOiJmN2E3MzI5Ny0yZDA2LTRlN2EtYmE4Yi03YjJjOTY3MGFkNmMiLCJzdWIiOiJzb21lLXN1YmplY3QifQ.K9qVL3dwi_TZNWSXNeWfXk91JTIZmm9Mp9Di09YMK_H9k7rO3qrpJtlBNuwe78QMZ9lhgGOW7ObUvvYdMqaHgLBBGqUEIHflOFcVUVx-pWdHJvq37pnNb5wX7fJQUqZkrpugC5kIk2Od3UKaCGBmHast5V3YF8LcBsd7fnufvEo +eyJhbGciOiJSUzUxMiIsImtpZCI6IjEzMzI1NTc1dGV2ZGZiZHNmc2YiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzb21lLWF1ZCIsImV4cCI6MTcxOTYwODQ3NCwiaXNzIjoic29tZS1pc3N1ZXIiLCJqdGkiOiI1MzgwNGVlMy00M2E2LTQ4MGYtYmNkYS05ODAyNTE2MTUwYWMiLCJzdWIiOiJzb21lLXN1YmplY3QifQ.opl49eC3Dom-B5I3D8lqCnqyb_T3MmaS8nOjT6Wc83BBEFRxwEy2ZE4jSkv0cqeQ4-CfNHXHalsudZbyMRklZRDmK98VnBQUJBlrVQ2wRaRyv55CjQ06QG8E00fTOVQYgHH65JpqomBTkoFHwCCLYc88Epsz93Dlce7WHKr8KrE +eyJhbGciOiJSUzUxMiIsImtpZCI6IjEzMzI1NTc1dGV2ZGZiZHNmc2YiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzb21lLWF1ZCIsImV4cCI6MTcxOTYwODQ3NCwiaXNzIjoic29tZS1pc3N1ZXIiLCJqdGkiOiJjMGE0ZWVlYS1hMzZiLTRlOTEtOWQ4NS1jYjAxZDg1MjI4MGMiLCJzdWIiOiJzb21lLXN1YmplY3QifQ.ScocLLUIo3d92h9sCdX36XC1LbOu8_lQXqzQfIN_6m-pwi4F-7cAr18QcPybgHIDv2x1NoGMX16iJclK0ntn6OG_IukCZJOiy3GyrLmM3YZNu0b66J394-n0-6CHw3HbM17-KqnYxAUERSaj6pgiLJBtl-XagPY-nZOThgVUDhM +eyJhbGciOiJSUzUxMiIsImtpZCI6IjEzMzI1NTc1dGV2ZGZiZHNmc2YiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzb21lLWF1ZCIsImV4cCI6MTcxOTYwODQ3NCwiaXNzIjoic29tZS1pc3N1ZXIiLCJqdGkiOiJiMDBiODFmMS1kMGQzLTQ4ZGYtODQ5MC1iZDU5MGMzNWU2ZDQiLCJzdWIiOiJzb21lLXN1YmplY3QifQ.gN2XgYFOVsxvzanhYTlICvrreuSIIhOYGd3jffhVt1jRsisY_nmFj_JtN-pLAESkxBxZFnLvvk2QCUcscbMt80WCDH5MAkmVL3qqawQcqkSV2SnHrJ43gXxniuH2eL5EaQQUpW7a6UhVYWyGWECTE0MY5P1aA4lE_SidS5CXc1s diff --git a/wrapper/wrapper.go b/wrapper/wrapper.go index 19659e2..31a211d 100644 --- a/wrapper/wrapper.go +++ b/wrapper/wrapper.go @@ -15,7 +15,7 @@ import ( "github.com/domsolutions/gopayloader/pkgs/payloader" ) -func RunGoPayLoader(reqURI, mTLScert, mTLSKey string, disableKeepAlive bool, reqs int64, conns uint, totalTime time.Duration, skipVerify bool, readTimeout, writeTimeout time.Duration, method string, verbose bool, ticker time.Duration, jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader string, headers []string, body, bodyFile string, client string) error { +func RunGoPayLoader(reqURI, mTLScert, mTLSKey string, disableKeepAlive bool, reqs int64, conns uint, totalTime time.Duration, skipVerify bool, readTimeout, writeTimeout time.Duration, method string, verbose bool, ticker time.Duration, jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader, jwtsFilename string, headers []string, body, bodyFile string, client string) error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -33,7 +33,7 @@ func RunGoPayLoader(reqURI, mTLScert, mTLSKey string, disableKeepAlive bool, req method, verbose, ticker, - jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader, headers, body, bodyFile, client) + jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader, jwtsFilename, headers, body, bodyFile, client) if err := conf.Validate(); err != nil { return err } From ae415b9d5f4a93ea6a1157fcab1fea6d2493b893 Mon Sep 17 00:00:00 2001 From: Nicolas Maltais Date: Mon, 3 Jul 2023 14:01:19 -0400 Subject: [PATCH 2/8] re-comment out print statement in jwtMiddleware --- pkgs/payloader/worker/generate.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/payloader/worker/generate.go b/pkgs/payloader/worker/generate.go index 1e280c1..087d76c 100644 --- a/pkgs/payloader/worker/generate.go +++ b/pkgs/payloader/worker/generate.go @@ -94,9 +94,8 @@ func jwtMiddleware(w *WorkerBase) { case <-w.config.Ctx.Done(): // user cancelled return - case err := <-w.config.JwtStreamErr: - // pterm.Error.Printf("Failed to get jwts from cache, got error; %v \n", err) - fmt.Printf("Failed to get jwts from cache, got error; %v \n", err) + //case err := <-w.config.JwtStreamErr: + // pterm.Error.Printf("Failed to get jwts from cache, got error; %v \n", err) // return TODO fix case jwt := <-w.config.JwtStreamReceiver: //fmt.Printf("GOT JWT %sHELP \n", jwt) From c11ea5e7d7a4f14757649d1559fc192a92283915 Mon Sep 17 00:00:00 2001 From: Nicolas Maltais Date: Mon, 3 Jul 2023 16:59:33 -0400 Subject: [PATCH 3/8] Fix bug in reading jwts from file, adjust error checking logic for arguments --- cmd/payloader/run.go | 8 ++++++-- config/config.go | 21 ++++++++++++++------- pkgs/jwt-generator/jwt.go | 27 ++++++++++++++------------- pkgs/payloader/payloader.go | 18 ++++++++++++++---- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/cmd/payloader/run.go b/cmd/payloader/run.go index 209554f..29bc158 100644 --- a/cmd/payloader/run.go +++ b/cmd/payloader/run.go @@ -135,8 +135,12 @@ func init() { runCmd.Flags().StringVar(&jwtHeader, argJWTHeader, "", "JWT header field name") runCmd.MarkFlagsRequiredTogether(argMTLSCert, argMTLSKey) - runCmd.MarkFlagsRequiredTogether(argJWTKey, argJWTHeader) runCmd.MarkFlagsMutuallyExclusive(argBody, argBodyFile) - + runCmd.MarkFlagsMutuallyExclusive(argJWTsFilename, argJWTKid) + runCmd.MarkFlagsMutuallyExclusive(argJWTsFilename, argJWTAud) + runCmd.MarkFlagsMutuallyExclusive(argJWTsFilename, argJWTIss) + runCmd.MarkFlagsMutuallyExclusive(argJWTsFilename, argJWTCustomClaims) + runCmd.MarkFlagsMutuallyExclusive(argJWTsFilename, argJWTSUb) + runCmd.MarkFlagsMutuallyExclusive(argJWTsFilename, argJWTKey) rootCmd.AddCommand(runCmd) } diff --git a/config/config.go b/config/config.go index b314158..a882fc6 100644 --- a/config/config.go +++ b/config/config.go @@ -141,14 +141,14 @@ func (c *Config) Validate() error { } } - if c.JwtsFilename == "" && (c.JwtHeader == "") != (c.JwtKey == "") { - if c.JwtHeader == "" { - return errors.New("config: empty jwt header") - } + // Require JwtHeader if JwtKey or JwtsFilename is present + if (c.JwtsFilename != "" || c.JwtKey != "") && c.JwtHeader == "" { + return errors.New("config: empty jwt header") + } - if c.JwtKey == "" { - return errors.New("empty jwt key") - } + // Require JwtKey or JwtsFilename if JwtHeader is present + if c.JwtHeader != "" && c.JwtsFilename == "" && c.JwtKey == "" { + return errors.New("config: empty jwt filename and jwt key, one of those is needed to send requests with JWTs") } if c.JwtKey != "" { @@ -166,6 +166,13 @@ func (c *Config) Validate() error { } if c.JwtsFilename != "" { + _, err := os.OpenFile(c.JwtsFilename, os.O_RDONLY, os.ModePerm) + if err != nil { + if os.IsNotExist(err) { + return errors.New("config: jwt file does not exist") + } + return fmt.Errorf("config: jwt file error checking file exists; %v", err) + } if c.ReqTarget == 0 { return errors.New("can only send jwts when request number is specified") } diff --git a/pkgs/jwt-generator/jwt.go b/pkgs/jwt-generator/jwt.go index 594cd9c..640259d 100644 --- a/pkgs/jwt-generator/jwt.go +++ b/pkgs/jwt-generator/jwt.go @@ -62,22 +62,13 @@ func (c *Config) validate() error { } // Gets a certain number of JWTs from a file, looping through / reusing them if necessary -func GetJWTsFromFile(fpath string, fname string, count int64) (<-chan string, <-chan error) { +func GetJWTsFromFile(fname string, count int64) (<-chan string, <-chan error) { // Open channels recv := make(chan string, 1000000) errs := make(chan error, 1) // Open the file - filename := fname - if (filename != "") { - filename = filepath.Join(fpath, filename) - } else { - errs <- fmt.Errorf("jwt_generator: retrieving; no filename") - close(errs) - close(recv) - return recv, errs - } - file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0666) + file, err := os.OpenFile(fname, os.O_RDONLY, os.ModePerm) if err != nil { errs <- fmt.Errorf("jwt_generator: retrieving; failed to open file containing JWTs") close(errs) @@ -100,15 +91,25 @@ func GetJWTsFromFile(fpath string, fname string, count int64) (<-chan string, <- // JWT Regex jwtRegex, _ := regexp.Compile(`[\w-]{2,}\.[\w-]{2,}\.[\w-]{2,}`) scanner.Split(bufio.ScanLines) + fileContainsAtLeastOneJWT := false for scanner.Scan() { res := jwtRegex.Find(scanner.Bytes()) if res != nil { + fileContainsAtLeastOneJWT = true recv <- string(res) numJwtsUsedSoFar++ - } else { - errs <- fmt.Errorf("jwt_generator: retrieving; error matching JWT with regex %v", err) + // Stop reading file when enough JWTs have been fetched + if numJwtsUsedSoFar >= count { + break + } } } + if !fileContainsAtLeastOneJWT { + errs <- fmt.Errorf("jwt_generator: retrieving; file doesn't contain a JWT") + close(errs) + close(recv) + return recv, errs + } // Loops if user asked for more requests than there were JWTs in the file, so JWTs get reused } diff --git a/pkgs/payloader/payloader.go b/pkgs/payloader/payloader.go index 074410a..7294eb4 100644 --- a/pkgs/payloader/payloader.go +++ b/pkgs/payloader/payloader.go @@ -3,6 +3,7 @@ package payloader import ( "context" "errors" + "fmt" "github.com/domsolutions/gopayloader/config" http_clients "github.com/domsolutions/gopayloader/pkgs/http-clients" jwt_generator "github.com/domsolutions/gopayloader/pkgs/jwt-generator" @@ -101,12 +102,12 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { return nil, err } - pterm.Info.Printf("Sending jwts with requests, ") + pterm.Info.Printf("Sending jwts with requests\n") if p.config.JwtsFilename != "" { - pterm.Info.Printf("using JWTs from file provided\n") - jwtStream, jwtStreamErrs = jwt_generator.GetJWTsFromFile(JwtCacheDir, p.config.JwtsFilename, p.config.ReqTarget) + pterm.Info.Printf("Using JWTs from file provided\n") + jwtStream, jwtStreamErrs = jwt_generator.GetJWTsFromFile(p.config.JwtsFilename, p.config.ReqTarget) } else { - pterm.Info.Printf("checking for JWTs in cache\n") + pterm.Info.Printf("Checking for JWTs in cache\n") jwt := jwt_generator.NewJWTGenerator(&jwt_generator.Config{ Ctx: p.config.Ctx, @@ -123,6 +124,15 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { } jwtStream, jwtStreamErrs = jwt.JWTS(p.config.ReqTarget) } + + // Do not continue if there's an error in the JWT error channel + select { + case err := <-jwtStreamErrs: + fmt.Printf("Failed to get jwts from cache, got error; %v \n", err) + return nil, err + default: + break + } } reqsPerWorker := p.config.ReqTarget / int64(p.config.Conns) From 3341d2286c7606d68b6ca853d1cca150924df831 Mon Sep 17 00:00:00 2001 From: Nicolas Maltais Date: Mon, 10 Jul 2023 10:48:42 -0400 Subject: [PATCH 4/8] Changes for pull-request --- README.md | 2 +- cmd/payloader/run.go | 46 ++++++++++++++++++------------------- config/config.go | 2 +- pkgs/jwt-generator/jwt.go | 44 +++++++++++++++++------------------ pkgs/payloader/payloader.go | 4 ++-- 5 files changed, 48 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 8171bbc..7b839e5 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Flags: --jwt-kid string JWT KID --jwt-sub string JWT subject (sub) claim --jwt-claims string JWT custom claims as a JSON string, ex: {"iat": 1719410063, "browser": "chrome"} - -f, --jwts-filename string File name in .cache where the JWTs to use are stored + -f, --jwts-filename string File path & name where the JWTs to use are stored -m, --method string request method (default "GET") --mtls-cert string mTLS cert path --mtls-key string mTLS cert private key path diff --git a/cmd/payloader/run.go b/cmd/payloader/run.go index 29bc158..3851463 100644 --- a/cmd/payloader/run.go +++ b/cmd/payloader/run.go @@ -9,30 +9,30 @@ import ( ) const ( - argMethod = "method" - argConnections = "connections" - argRequests = "requests" - argKeepAlive = "disable-keep-alive" - argVerifySigner = "skip-verify" - argTime = "time" - argMTLSKey = "mtls-key" - argMTLSCert = "mtls-cert" - argReadTimeout = "read-timeout" - argWriteTimeout = "write-timeout" - argVerbose = "verbose" - argTicker = "ticker" - argJWTKey = "jwt-key" - argJWTSUb = "jwt-sub" + argMethod = "method" + argConnections = "connections" + argRequests = "requests" + argKeepAlive = "disable-keep-alive" + argVerifySigner = "skip-verify" + argTime = "time" + argMTLSKey = "mtls-key" + argMTLSCert = "mtls-cert" + argReadTimeout = "read-timeout" + argWriteTimeout = "write-timeout" + argVerbose = "verbose" + argTicker = "ticker" + argJWTKey = "jwt-key" + argJWTSUb = "jwt-sub" argJWTCustomClaims = "jwt-claims" - argJWTIss = "jwt-iss" - argJWTAud = "jwt-aud" - argJWTHeader = "jwt-header" - argJWTKid = "jwt-kid" + argJWTIss = "jwt-iss" + argJWTAud = "jwt-aud" + argJWTHeader = "jwt-header" + argJWTKid = "jwt-kid" argJWTsFilename = "jwts-filename" - argHeaders = "headers" - argBody = "body" - argBodyFile = "body-file" - argClient = "client" + argHeaders = "headers" + argBody = "body" + argBodyFile = "body-file" + argClient = "client" ) var ( @@ -56,7 +56,7 @@ var ( jwtAud string jwtHeader string jwtKID string - jwtsFilename string + jwtsFilename string headers *[]string body string bodyFile string diff --git a/config/config.go b/config/config.go index a882fc6..69e12c6 100644 --- a/config/config.go +++ b/config/config.go @@ -169,7 +169,7 @@ func (c *Config) Validate() error { _, err := os.OpenFile(c.JwtsFilename, os.O_RDONLY, os.ModePerm) if err != nil { if os.IsNotExist(err) { - return errors.New("config: jwt file does not exist") + return errors.New("config: jwt file does not exist: " + c.JwtsFilename) } return fmt.Errorf("config: jwt file error checking file exists; %v", err) } diff --git a/pkgs/jwt-generator/jwt.go b/pkgs/jwt-generator/jwt.go index 640259d..8375cb2 100644 --- a/pkgs/jwt-generator/jwt.go +++ b/pkgs/jwt-generator/jwt.go @@ -17,7 +17,6 @@ import ( "path/filepath" "runtime" "time" - "regexp" "bufio" ) @@ -26,17 +25,17 @@ const ( ) type Config struct { - Ctx context.Context - Kid string - JwtKeyPath string - jwtKeyBlob []byte - JwtSub string + Ctx context.Context + Kid string + JwtKeyPath string + jwtKeyBlob []byte + JwtSub string JwtCustomClaimsJSON string - JwtIss string - JwtAud string + JwtIss string + JwtAud string JwtsFilename string - signer definition.Signer - store *cache + signer definition.Signer + store *cache } type JWTGenerator struct { @@ -62,7 +61,7 @@ func (c *Config) validate() error { } // Gets a certain number of JWTs from a file, looping through / reusing them if necessary -func GetJWTsFromFile(fname string, count int64) (<-chan string, <-chan error) { +func GetUserSuppliedJWTs(fname string, count int64) (<-chan string, <-chan error) { // Open channels recv := make(chan string, 1000000) errs := make(chan error, 1) @@ -86,25 +85,24 @@ func GetJWTsFromFile(fname string, count int64) (<-chan string, <-chan error) { return recv, errs } - // Parse file lines for JWTs + // Read file lines scanner := bufio.NewScanner(file) - // JWT Regex - jwtRegex, _ := regexp.Compile(`[\w-]{2,}\.[\w-]{2,}\.[\w-]{2,}`) scanner.Split(bufio.ScanLines) fileContainsAtLeastOneJWT := false for scanner.Scan() { - res := jwtRegex.Find(scanner.Bytes()) - if res != nil { - fileContainsAtLeastOneJWT = true - recv <- string(res) - numJwtsUsedSoFar++ - // Stop reading file when enough JWTs have been fetched - if numJwtsUsedSoFar >= count { - break - } + fileContainsAtLeastOneJWT = true + recv <- string(scanner.Bytes()) + numJwtsUsedSoFar++ + // Stop reading file when enough JWTs have been fetched + if numJwtsUsedSoFar >= count { + break } } if !fileContainsAtLeastOneJWT { + // Close the file + if err = file.Close(); err != nil { + fmt.Printf("Could not close the file due to this %s error \n", err) + } errs <- fmt.Errorf("jwt_generator: retrieving; file doesn't contain a JWT") close(errs) close(recv) diff --git a/pkgs/payloader/payloader.go b/pkgs/payloader/payloader.go index 7294eb4..8bb8157 100644 --- a/pkgs/payloader/payloader.go +++ b/pkgs/payloader/payloader.go @@ -104,8 +104,8 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { pterm.Info.Printf("Sending jwts with requests\n") if p.config.JwtsFilename != "" { - pterm.Info.Printf("Using JWTs from file provided\n") - jwtStream, jwtStreamErrs = jwt_generator.GetJWTsFromFile(p.config.JwtsFilename, p.config.ReqTarget) + pterm.Info.Printf("Using JWTs from %s\n", p.config.JwtsFilename) + jwtStream, jwtStreamErrs = jwt_generator.GetUserSuppliedJWTs(p.config.JwtsFilename, p.config.ReqTarget) } else { pterm.Info.Printf("Checking for JWTs in cache\n") From 73f425a24714cec21196c076ff5e80f5db0088d2 Mon Sep 17 00:00:00 2001 From: dominicriordan Date: Mon, 21 Aug 2023 09:45:10 +0100 Subject: [PATCH 5/8] change doc --- cmd/payloader/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/payloader/run.go b/cmd/payloader/run.go index 3851463..0f363be 100644 --- a/cmd/payloader/run.go +++ b/cmd/payloader/run.go @@ -131,7 +131,7 @@ func init() { runCmd.Flags().StringVar(&jwtIss, argJWTIss, "", "JWT issuer (iss) claim") runCmd.Flags().StringVar(&jwtSub, argJWTSUb, "", "JWT subject (sub) claim") runCmd.Flags().StringVar(&jwtCustomClaims, argJWTCustomClaims, "", "JWT custom claims") - runCmd.Flags().StringVarP(&jwtsFilename, argJWTsFilename, "f", "", "File name in .cache where the JWTs to use are stored") + runCmd.Flags().StringVarP(&jwtsFilename, argJWTsFilename, "f", "", "File path for pre-generated JWTs, separated by new lines") runCmd.Flags().StringVar(&jwtHeader, argJWTHeader, "", "JWT header field name") runCmd.MarkFlagsRequiredTogether(argMTLSCert, argMTLSKey) From e5e476ff620ae55b64e3214fc075049768dcb03f Mon Sep 17 00:00:00 2001 From: dominicriordan Date: Mon, 21 Aug 2023 12:13:53 +0100 Subject: [PATCH 6/8] wip --- pkgs/http-clients/definitions.go | 1 - pkgs/jwt-generator/cache.go | 2 + pkgs/jwt-generator/jwt.go | 69 +++-- pkgs/payloader/payloader.go | 30 +-- pkgs/payloader/payloader_test.go | 411 +++++++++++++++--------------- pkgs/payloader/worker/generate.go | 8 +- 6 files changed, 253 insertions(+), 268 deletions(-) diff --git a/pkgs/http-clients/definitions.go b/pkgs/http-clients/definitions.go index 57c9bed..75cffd9 100644 --- a/pkgs/http-clients/definitions.go +++ b/pkgs/http-clients/definitions.go @@ -41,7 +41,6 @@ type Config struct { Method string Verbose bool JwtStreamReceiver <-chan string - JwtStreamErr <-chan error JWTHeader string Headers []string Body string diff --git a/pkgs/jwt-generator/cache.go b/pkgs/jwt-generator/cache.go index 030b344..dc177e3 100644 --- a/pkgs/jwt-generator/cache.go +++ b/pkgs/jwt-generator/cache.go @@ -111,6 +111,7 @@ func (c *cache) save(tokens []string) error { if stat.Size() > 0 { pos = stat.Size() } + if _, err := c.f.WriteAt([]byte(strings.Join(tokens, "\n")+"\n"), pos); err != nil { return err } @@ -118,6 +119,7 @@ func (c *cache) save(tokens []string) error { b := make([]byte, 8) newCount := uint64(int64(add) + c.count) binary.LittleEndian.PutUint64(b, newCount) + _, err = c.f.WriteAt(b, 0) if err != nil { return err diff --git a/pkgs/jwt-generator/jwt.go b/pkgs/jwt-generator/jwt.go index 8375cb2..835a65b 100644 --- a/pkgs/jwt-generator/jwt.go +++ b/pkgs/jwt-generator/jwt.go @@ -1,23 +1,23 @@ package jwt_generator import ( + "bufio" "context" "crypto/sha256" "encoding/hex" "errors" "fmt" - "strings" + config "github.com/domsolutions/gopayloader/config" jwt_signer "github.com/domsolutions/gopayloader/pkgs/jwt-signer" "github.com/domsolutions/gopayloader/pkgs/jwt-signer/definition" - config "github.com/domsolutions/gopayloader/config" "github.com/golang-jwt/jwt" "github.com/google/uuid" "github.com/pterm/pterm" "os" "path/filepath" "runtime" + "strings" "time" - "bufio" ) const ( @@ -60,62 +60,57 @@ func (c *Config) validate() error { return nil } -// Gets a certain number of JWTs from a file, looping through / reusing them if necessary +// GetUserSuppliedJWTs Gets a count number of JWTs from a file, reusing them if not enough exist to match count func GetUserSuppliedJWTs(fname string, count int64) (<-chan string, <-chan error) { - // Open channels recv := make(chan string, 1000000) errs := make(chan error, 1) + go getUserJWTS(fname, count, errs, recv) + + // give goroutine time to prime channel with jwts for workers + time.Sleep(1 * time.Second) + return recv, errs +} + +func getUserJWTS(fname string, count int64, errs chan<- error, jwts chan<- string) { + defer func() { + close(errs) + close(jwts) + }() - // Open the file file, err := os.OpenFile(fname, os.O_RDONLY, os.ModePerm) if err != nil { - errs <- fmt.Errorf("jwt_generator: retrieving; failed to open file containing JWTs") - close(errs) - close(recv) - return recv, errs + errs <- fmt.Errorf("jwt_generator: retrieving; failed to open file containing JWTs; %v", err) + return } + defer file.Close() - numJwtsUsedSoFar := int64(0) - for numJwtsUsedSoFar < count { + jwtsSent := int64(0) + for jwtsSent < count { // Set pointer to beginning of file if _, err := file.Seek(0, 0); err != nil { errs <- err - close(errs) - close(recv) - return recv, errs + return } - - // Read file lines scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) - fileContainsAtLeastOneJWT := false + + fileValid := false for scanner.Scan() { - fileContainsAtLeastOneJWT = true - recv <- string(scanner.Bytes()) - numJwtsUsedSoFar++ + fileValid = true + jwts <- string(scanner.Bytes()) + jwtsSent++ // Stop reading file when enough JWTs have been fetched - if numJwtsUsedSoFar >= count { - break + if jwtsSent == count { + return } } - if !fileContainsAtLeastOneJWT { - // Close the file - if err = file.Close(); err != nil { - fmt.Printf("Could not close the file due to this %s error \n", err) - } + + if !fileValid { errs <- fmt.Errorf("jwt_generator: retrieving; file doesn't contain a JWT") - close(errs) - close(recv) - return recv, errs + return } // Loops if user asked for more requests than there were JWTs in the file, so JWTs get reused } - - // Close the file - if err = file.Close(); err != nil { - fmt.Printf("Could not close the file due to this %s error \n", err) - } - return recv, errs } func (j *JWTGenerator) getFileName(dir string) string { diff --git a/pkgs/payloader/payloader.go b/pkgs/payloader/payloader.go index 8bb8157..275438b 100644 --- a/pkgs/payloader/payloader.go +++ b/pkgs/payloader/payloader.go @@ -3,7 +3,6 @@ package payloader import ( "context" "errors" - "fmt" "github.com/domsolutions/gopayloader/config" http_clients "github.com/domsolutions/gopayloader/pkgs/http-clients" jwt_generator "github.com/domsolutions/gopayloader/pkgs/jwt-generator" @@ -90,7 +89,7 @@ func (p *PayLoader) startWorkers(wg *sync.WaitGroup) { } func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { - var jwtStreamErrs <-chan error + var jwtErr <-chan error var jwtStream <-chan string if p.config.SendJWT && p.config.ReqTarget != 0 { @@ -104,11 +103,10 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { pterm.Info.Printf("Sending jwts with requests\n") if p.config.JwtsFilename != "" { - pterm.Info.Printf("Using JWTs from %s\n", p.config.JwtsFilename) - jwtStream, jwtStreamErrs = jwt_generator.GetUserSuppliedJWTs(p.config.JwtsFilename, p.config.ReqTarget) + pterm.Info.Printf("Using JWTs from %s \n", p.config.JwtsFilename) + jwtStream, jwtErr = jwt_generator.GetUserSuppliedJWTs(p.config.JwtsFilename, p.config.ReqTarget) } else { pterm.Info.Printf("Checking for JWTs in cache\n") - jwt := jwt_generator.NewJWTGenerator(&jwt_generator.Config{ Ctx: p.config.Ctx, Kid: p.config.JwtKID, @@ -118,20 +116,11 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { JwtIss: p.config.JwtIss, JwtAud: p.config.JwtAud, }) - + if err := jwt.Generate(p.config.ReqTarget, JwtCacheDir, false); err != nil { return nil, err } - jwtStream, jwtStreamErrs = jwt.JWTS(p.config.ReqTarget) - } - - // Do not continue if there's an error in the JWT error channel - select { - case err := <-jwtStreamErrs: - fmt.Printf("Failed to get jwts from cache, got error; %v \n", err) - return nil, err - default: - break + jwtStream, jwtErr = jwt.JWTS(p.config.ReqTarget) } } @@ -197,7 +186,6 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { if p.config.SendJWT { c.JwtStreamReceiver = jwtStream - c.JwtStreamErr = jwtStreamErrs c.JWTHeader = p.config.JwtHeader } @@ -222,6 +210,14 @@ func (p *PayLoader) handleReqs() (*GoPayloaderResults, error) { results := &GoPayloaderResults{} go p.calcReqStats(ctx, reqStats, results) + if jwtErr != nil { + err, _ := <-jwtErr + if err != nil { + pterm.Error.Printf("Failed to retrieve JWTs; %v \n", err) + return nil, err + } + } + workersComplete.Wait() pterm.Success.Printf("Payload complete, calculating results\n") diff --git a/pkgs/payloader/payloader_test.go b/pkgs/payloader/payloader_test.go index 3668556..b26de8c 100644 --- a/pkgs/payloader/payloader_test.go +++ b/pkgs/payloader/payloader_test.go @@ -3,7 +3,6 @@ package payloader import ( "context" "crypto/tls" - "errors" "github.com/domsolutions/gopayloader/config" "github.com/domsolutions/gopayloader/pkgs/payloader/worker" "github.com/quic-go/quic-go" @@ -151,156 +150,156 @@ func testPayLoader_Run(t *testing.T, addr, client string, cleanup func()) { wantErr error check func(t *testing.T) }{ - { - name: "GET 10 connections for 210 requests", - fields: fields{config: &config.Config{ - Ctx: context.Background(), - ReqURI: addr, - ReqTarget: 210, - Conns: 10, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Method: "GET", - Client: client, - VerboseTicker: time.Second, - SkipVerify: true, - }}, - want: &GoPayloaderResults{ - CompletedReqs: 210, - FailedReqs: 0, - Responses: map[worker.ResponseCode]int64{ - 200: 210, - }, - Errors: nil, - }, - }, - { - name: "POST 10 connections for 2 second long test", - fields: fields{config: &config.Config{ - Ctx: context.Background(), - ReqURI: addr, - Conns: 10, - Duration: 2 * time.Second, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Method: "POST", - Client: client, - VerboseTicker: time.Second, - SkipVerify: true, - }}, - }, - { - name: "PUT 10 connections for 10 second long test with 100 requests", - fields: fields{config: &config.Config{ - Ctx: context.Background(), - ReqURI: addr, - Conns: 10, - ReqTarget: 100, - Duration: 10 * time.Second, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Method: "PUT", - Client: client, - VerboseTicker: time.Second, - SkipVerify: true, - }}, - want: &GoPayloaderResults{ - CompletedReqs: 100, - FailedReqs: 0, - Responses: map[worker.ResponseCode]int64{ - 200: 100, - }, - Errors: nil, - }, - }, - { - name: "GET 10 connections for 210 requests with jwts", - fields: fields{config: &config.Config{ - Ctx: context.Background(), - ReqURI: addr, - ReqTarget: 210, - Conns: 10, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Method: "GET", - Client: client, - VerboseTicker: time.Second, - JwtHeader: "some-jwt", - JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), - SkipVerify: true, - }}, - want: &GoPayloaderResults{ - CompletedReqs: 210, - FailedReqs: 0, - Responses: map[worker.ResponseCode]int64{ - 200: 210, - }, - Errors: nil, - }, - check: func(t *testing.T) { - f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt"), os.O_RDONLY, os.ModePerm) - if err != nil { - if os.IsNotExist(err) { - t.Fatal(err) - } - t.Fatal(err) - } - stat, err := f.Stat() - if err != nil { - t.Fatal(err) - } - if stat.Size() == 0 { - t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt") - } - }, - }, - { - name: "GET 101 connections for 210 requests with jwts with all available jwt fields and header", - fields: fields{config: &config.Config{ - Ctx: context.Background(), - ReqURI: addr, - ReqTarget: 210, - Conns: 11, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Method: "GET", - Client: client, - VerboseTicker: time.Second, - Headers: []string{"content-type: application/json"}, - JwtHeader: "some-jwt", - JwtAud: "some-aud", - JwtSub: "some-subject", - JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", - JwtIss: "some-issuer", - JwtKID: "13325575tevdfbdsfsf", - JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), - SkipVerify: true, - }}, - want: &GoPayloaderResults{ - CompletedReqs: 210, - FailedReqs: 0, - Responses: map[worker.ResponseCode]int64{ - 200: 210, - }, - Errors: nil, - }, - check: func(t *testing.T) { - f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt"), os.O_RDONLY, os.ModePerm) - if err != nil { - if os.IsNotExist(err) { - t.Fatal(err) - } - t.Fatal(err) - } - stat, err := f.Stat() - if err != nil { - t.Fatal(err) - } - if stat.Size() == 0 { - t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt") - } - }, - }, + //{ + // name: "GET 10 connections for 210 requests", + // fields: fields{config: &config.Config{ + // Ctx: context.Background(), + // ReqURI: addr, + // ReqTarget: 210, + // Conns: 10, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 5 * time.Second, + // Method: "GET", + // Client: client, + // VerboseTicker: time.Second, + // SkipVerify: true, + // }}, + // want: &GoPayloaderResults{ + // CompletedReqs: 210, + // FailedReqs: 0, + // Responses: map[worker.ResponseCode]int64{ + // 200: 210, + // }, + // Errors: nil, + // }, + //}, + //{ + // name: "POST 10 connections for 2 second long test", + // fields: fields{config: &config.Config{ + // Ctx: context.Background(), + // ReqURI: addr, + // Conns: 10, + // Duration: 2 * time.Second, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 5 * time.Second, + // Method: "POST", + // Client: client, + // VerboseTicker: time.Second, + // SkipVerify: true, + // }}, + //}, + //{ + // name: "PUT 10 connections for 10 second long test with 100 requests", + // fields: fields{config: &config.Config{ + // Ctx: context.Background(), + // ReqURI: addr, + // Conns: 10, + // ReqTarget: 100, + // Duration: 10 * time.Second, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 5 * time.Second, + // Method: "PUT", + // Client: client, + // VerboseTicker: time.Second, + // SkipVerify: true, + // }}, + // want: &GoPayloaderResults{ + // CompletedReqs: 100, + // FailedReqs: 0, + // Responses: map[worker.ResponseCode]int64{ + // 200: 100, + // }, + // Errors: nil, + // }, + //}, + //{ + // name: "GET 10 connections for 210 requests with jwts", + // fields: fields{config: &config.Config{ + // Ctx: context.Background(), + // ReqURI: addr, + // ReqTarget: 210, + // Conns: 10, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 5 * time.Second, + // Method: "GET", + // Client: client, + // VerboseTicker: time.Second, + // JwtHeader: "some-jwt", + // JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), + // SkipVerify: true, + // }}, + // want: &GoPayloaderResults{ + // CompletedReqs: 210, + // FailedReqs: 0, + // Responses: map[worker.ResponseCode]int64{ + // 200: 210, + // }, + // Errors: nil, + // }, + // check: func(t *testing.T) { + // f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt"), os.O_RDONLY, os.ModePerm) + // if err != nil { + // if os.IsNotExist(err) { + // t.Fatal(err) + // } + // t.Fatal(err) + // } + // stat, err := f.Stat() + // if err != nil { + // t.Fatal(err) + // } + // if stat.Size() == 0 { + // t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt") + // } + // }, + //}, + //{ + // name: "GET 101 connections for 210 requests with jwts with all available jwt fields and header", + // fields: fields{config: &config.Config{ + // Ctx: context.Background(), + // ReqURI: addr, + // ReqTarget: 210, + // Conns: 11, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 5 * time.Second, + // Method: "GET", + // Client: client, + // VerboseTicker: time.Second, + // Headers: []string{"content-type: application/json"}, + // JwtHeader: "some-jwt", + // JwtAud: "some-aud", + // JwtSub: "some-subject", + // JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", + // JwtIss: "some-issuer", + // JwtKID: "13325575tevdfbdsfsf", + // JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), + // SkipVerify: true, + // }}, + // want: &GoPayloaderResults{ + // CompletedReqs: 210, + // FailedReqs: 0, + // Responses: map[worker.ResponseCode]int64{ + // 200: 210, + // }, + // Errors: nil, + // }, + // check: func(t *testing.T) { + // f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt"), os.O_RDONLY, os.ModePerm) + // if err != nil { + // if os.IsNotExist(err) { + // t.Fatal(err) + // } + // t.Fatal(err) + // } + // stat, err := f.Stat() + // if err != nil { + // t.Fatal(err) + // } + // if stat.Size() == 0 { + // t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt") + // } + // }, + //}, { name: "GET RSA JWT", fields: fields{config: &config.Config{ @@ -348,61 +347,61 @@ func testPayLoader_Run(t *testing.T, addr, client string, cleanup func()) { } }, }, - { - name: "GET using JWT file only", - fields: fields{config: &config.Config{ - Ctx: context.Background(), - ReqURI: addr, - ReqTarget: 10, - Conns: 1, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Method: "GET", - Client: client, - VerboseTicker: time.Second, - Headers: []string{"content-type: application/json"}, - JwtHeader: "Authorization", - JwtsFilename: filepath.Join("..", "..", "test", "jwtstestfile.txt"), - SkipVerify: true, - }}, - want: &GoPayloaderResults{ - CompletedReqs: 10, - FailedReqs: 0, - Responses: map[worker.ResponseCode]int64{ - 200: 10, - }, - Errors: nil, - }, - }, - { - name: "Error hostname incorrect format - missing port", - fields: fields{config: &config.Config{ - Ctx: context.Background(), - ReqURI: "http://localhost/", - ReqTarget: 210, - Conns: 101, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Method: "GET", - Client: client, - VerboseTicker: time.Second, - Headers: []string{"content-type: application/json"}, - JwtHeader: "some-jwt", - JwtAud: "some-aud", - JwtSub: "some-subject", - JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", - JwtIss: "some-issuer", - JwtKID: "13325575tevdfbdsfsf", - JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), - SkipVerify: true, - }}, - want: &GoPayloaderResults{ - CompletedReqs: 0, - FailedReqs: 0, - Errors: nil, - }, - wantErr: errors.New("url not in correct format http://localhost/ needs to be like protocol://host:port/path i.e. https://localhost:443/some-path"), - }, + //{ + // name: "GET using JWT file only", + // fields: fields{config: &config.Config{ + // Ctx: context.Background(), + // ReqURI: addr, + // ReqTarget: 10, + // Conns: 1, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 5 * time.Second, + // Method: "GET", + // Client: client, + // VerboseTicker: time.Second, + // Headers: []string{"content-type: application/json"}, + // JwtHeader: "Authorization", + // JwtsFilename: filepath.Join("..", "..", "test", "jwtstestfile.txt"), + // SkipVerify: true, + // }}, + // want: &GoPayloaderResults{ + // CompletedReqs: 10, + // FailedReqs: 0, + // Responses: map[worker.ResponseCode]int64{ + // 200: 10, + // }, + // Errors: nil, + // }, + //}, + //{ + // name: "Error hostname incorrect format - missing port", + // fields: fields{config: &config.Config{ + // Ctx: context.Background(), + // ReqURI: "http://localhost/", + // ReqTarget: 210, + // Conns: 101, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 5 * time.Second, + // Method: "GET", + // Client: client, + // VerboseTicker: time.Second, + // Headers: []string{"content-type: application/json"}, + // JwtHeader: "some-jwt", + // JwtAud: "some-aud", + // JwtSub: "some-subject", + // JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", + // JwtIss: "some-issuer", + // JwtKID: "13325575tevdfbdsfsf", + // JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), + // SkipVerify: true, + // }}, + // want: &GoPayloaderResults{ + // CompletedReqs: 0, + // FailedReqs: 0, + // Errors: nil, + // }, + // wantErr: errors.New("url not in correct format http://localhost/ needs to be like protocol://host:port/path i.e. https://localhost:443/some-path"), + //}, } if cleanup != nil { diff --git a/pkgs/payloader/worker/generate.go b/pkgs/payloader/worker/generate.go index 087d76c..f95d668 100644 --- a/pkgs/payloader/worker/generate.go +++ b/pkgs/payloader/worker/generate.go @@ -89,14 +89,8 @@ func getReq(client http_clients.GoPayLoaderClient, config *http_clients.Config) return req, nil } -func jwtMiddleware(w *WorkerBase) { +func jwtMiddleware(w *WorkerBase) { select { - case <-w.config.Ctx.Done(): - // user cancelled - return - //case err := <-w.config.JwtStreamErr: - // pterm.Error.Printf("Failed to get jwts from cache, got error; %v \n", err) - // return TODO fix case jwt := <-w.config.JwtStreamReceiver: //fmt.Printf("GOT JWT %sHELP \n", jwt) w.req.SetHeader(w.config.JWTHeader, jwt) From 1b3f0f64346a504f4681706f344e436b8ce2d0c1 Mon Sep 17 00:00:00 2001 From: dominicriordan Date: Mon, 21 Aug 2023 14:00:28 +0100 Subject: [PATCH 7/8] store jwt count as ASCII string, encountered errors as storing as bytes --- pkgs/jwt-generator/cache.go | 61 ++++- pkgs/payloader/payloader_test.go | 411 +++++++++++++++--------------- pkgs/payloader/worker/generate.go | 3 +- 3 files changed, 255 insertions(+), 220 deletions(-) diff --git a/pkgs/jwt-generator/cache.go b/pkgs/jwt-generator/cache.go index dc177e3..0d7e5d3 100644 --- a/pkgs/jwt-generator/cache.go +++ b/pkgs/jwt-generator/cache.go @@ -2,14 +2,16 @@ package jwt_generator import ( "bufio" - "encoding/binary" "errors" "fmt" "os" + "strconv" "strings" "time" ) +const byteSizeCounter = 20 + type cache struct { f *os.File count int64 @@ -23,12 +25,19 @@ func newCache(f *os.File) (*cache, error) { // Get count found on first line of the file c.scanner.Split(bufio.ScanLines) if c.scanner.Scan() { - bb := make([]byte, 8) + bb := make([]byte, byteSizeCounter) _, err := f.ReadAt(bb, 0) if err != nil { return nil, err } - c.count = int64(binary.LittleEndian.Uint64(bb)) + + s := string(bb) + count, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return nil, err + } + + c.count = count return &c, nil } return &c, nil @@ -62,14 +71,31 @@ func (c *cache) get(count int64) (<-chan string, <-chan error) { } meta := c.scanner.Bytes() - if len(meta) < 8 { + if len(meta) < byteSizeCounter { errs <- fmt.Errorf("jwt_generator: retrieving; corrupt jwt cache, wanted 8 bytes got %d", len(meta)) close(errs) close(recv) return recv, errs } - if count > int64(binary.LittleEndian.Uint64(meta[0:8])) { + num := make([]byte, 0) + for _, m := range meta { + if m == 0 { + break + } + num = append(num, m) + } + + s := string(num) + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + errs <- fmt.Errorf("failed to get jwt count; %v", err) + close(errs) + close(recv) + return recv, errs + } + + if count > i { errs <- errors.New("jwt_generator: retrieving; not enough jwts stored in cache") close(errs) close(recv) @@ -84,20 +110,25 @@ func (c *cache) get(count int64) (<-chan string, <-chan error) { func (c *cache) retrieve(count int64, recv chan<- string, errs chan<- error) { var i int64 = 0 + defer func() { + close(errs) + close(recv) + }() for i = 0; i < count; i++ { if c.scanner.Scan() { recv <- string(c.scanner.Bytes()) continue } - // reached EOF or err + if err := c.scanner.Err(); err != nil { errs <- err - close(errs) + return } - break + + errs <- errors.New("unable to read anymore jwts from file") + return } - close(recv) } func (c *cache) save(tokens []string) error { @@ -116,16 +147,20 @@ func (c *cache) save(tokens []string) error { return err } - b := make([]byte, 8) - newCount := uint64(int64(add) + c.count) - binary.LittleEndian.PutUint64(b, newCount) + newCount := int64(add) + c.count + s := strconv.FormatInt(newCount, 10) + + b := make([]byte, byteSizeCounter) + for i, ss := range s { + b[i] = byte(ss) + } _, err = c.f.WriteAt(b, 0) if err != nil { return err } - _, err = c.f.WriteAt([]byte{byte('\n')}, 9) + _, err = c.f.WriteAt([]byte{byte('\n')}, byteSizeCounter) if err != nil { return err } diff --git a/pkgs/payloader/payloader_test.go b/pkgs/payloader/payloader_test.go index b26de8c..f4be43f 100644 --- a/pkgs/payloader/payloader_test.go +++ b/pkgs/payloader/payloader_test.go @@ -3,6 +3,7 @@ package payloader import ( "context" "crypto/tls" + "errors" "github.com/domsolutions/gopayloader/config" "github.com/domsolutions/gopayloader/pkgs/payloader/worker" "github.com/quic-go/quic-go" @@ -150,156 +151,156 @@ func testPayLoader_Run(t *testing.T, addr, client string, cleanup func()) { wantErr error check func(t *testing.T) }{ - //{ - // name: "GET 10 connections for 210 requests", - // fields: fields{config: &config.Config{ - // Ctx: context.Background(), - // ReqURI: addr, - // ReqTarget: 210, - // Conns: 10, - // ReadTimeout: 5 * time.Second, - // WriteTimeout: 5 * time.Second, - // Method: "GET", - // Client: client, - // VerboseTicker: time.Second, - // SkipVerify: true, - // }}, - // want: &GoPayloaderResults{ - // CompletedReqs: 210, - // FailedReqs: 0, - // Responses: map[worker.ResponseCode]int64{ - // 200: 210, - // }, - // Errors: nil, - // }, - //}, - //{ - // name: "POST 10 connections for 2 second long test", - // fields: fields{config: &config.Config{ - // Ctx: context.Background(), - // ReqURI: addr, - // Conns: 10, - // Duration: 2 * time.Second, - // ReadTimeout: 5 * time.Second, - // WriteTimeout: 5 * time.Second, - // Method: "POST", - // Client: client, - // VerboseTicker: time.Second, - // SkipVerify: true, - // }}, - //}, - //{ - // name: "PUT 10 connections for 10 second long test with 100 requests", - // fields: fields{config: &config.Config{ - // Ctx: context.Background(), - // ReqURI: addr, - // Conns: 10, - // ReqTarget: 100, - // Duration: 10 * time.Second, - // ReadTimeout: 5 * time.Second, - // WriteTimeout: 5 * time.Second, - // Method: "PUT", - // Client: client, - // VerboseTicker: time.Second, - // SkipVerify: true, - // }}, - // want: &GoPayloaderResults{ - // CompletedReqs: 100, - // FailedReqs: 0, - // Responses: map[worker.ResponseCode]int64{ - // 200: 100, - // }, - // Errors: nil, - // }, - //}, - //{ - // name: "GET 10 connections for 210 requests with jwts", - // fields: fields{config: &config.Config{ - // Ctx: context.Background(), - // ReqURI: addr, - // ReqTarget: 210, - // Conns: 10, - // ReadTimeout: 5 * time.Second, - // WriteTimeout: 5 * time.Second, - // Method: "GET", - // Client: client, - // VerboseTicker: time.Second, - // JwtHeader: "some-jwt", - // JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), - // SkipVerify: true, - // }}, - // want: &GoPayloaderResults{ - // CompletedReqs: 210, - // FailedReqs: 0, - // Responses: map[worker.ResponseCode]int64{ - // 200: 210, - // }, - // Errors: nil, - // }, - // check: func(t *testing.T) { - // f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt"), os.O_RDONLY, os.ModePerm) - // if err != nil { - // if os.IsNotExist(err) { - // t.Fatal(err) - // } - // t.Fatal(err) - // } - // stat, err := f.Stat() - // if err != nil { - // t.Fatal(err) - // } - // if stat.Size() == 0 { - // t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt") - // } - // }, - //}, - //{ - // name: "GET 101 connections for 210 requests with jwts with all available jwt fields and header", - // fields: fields{config: &config.Config{ - // Ctx: context.Background(), - // ReqURI: addr, - // ReqTarget: 210, - // Conns: 11, - // ReadTimeout: 5 * time.Second, - // WriteTimeout: 5 * time.Second, - // Method: "GET", - // Client: client, - // VerboseTicker: time.Second, - // Headers: []string{"content-type: application/json"}, - // JwtHeader: "some-jwt", - // JwtAud: "some-aud", - // JwtSub: "some-subject", - // JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", - // JwtIss: "some-issuer", - // JwtKID: "13325575tevdfbdsfsf", - // JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), - // SkipVerify: true, - // }}, - // want: &GoPayloaderResults{ - // CompletedReqs: 210, - // FailedReqs: 0, - // Responses: map[worker.ResponseCode]int64{ - // 200: 210, - // }, - // Errors: nil, - // }, - // check: func(t *testing.T) { - // f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt"), os.O_RDONLY, os.ModePerm) - // if err != nil { - // if os.IsNotExist(err) { - // t.Fatal(err) - // } - // t.Fatal(err) - // } - // stat, err := f.Stat() - // if err != nil { - // t.Fatal(err) - // } - // if stat.Size() == 0 { - // t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt") - // } - // }, - //}, + { + name: "GET 10 connections for 210 requests", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: addr, + ReqTarget: 210, + Conns: 10, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "GET", + Client: client, + VerboseTicker: time.Second, + SkipVerify: true, + }}, + want: &GoPayloaderResults{ + CompletedReqs: 210, + FailedReqs: 0, + Responses: map[worker.ResponseCode]int64{ + 200: 210, + }, + Errors: nil, + }, + }, + { + name: "POST 10 connections for 2 second long test", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: addr, + Conns: 10, + Duration: 2 * time.Second, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "POST", + Client: client, + VerboseTicker: time.Second, + SkipVerify: true, + }}, + }, + { + name: "PUT 10 connections for 10 second long test with 100 requests", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: addr, + Conns: 10, + ReqTarget: 100, + Duration: 10 * time.Second, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "PUT", + Client: client, + VerboseTicker: time.Second, + SkipVerify: true, + }}, + want: &GoPayloaderResults{ + CompletedReqs: 100, + FailedReqs: 0, + Responses: map[worker.ResponseCode]int64{ + 200: 100, + }, + Errors: nil, + }, + }, + { + name: "GET 10 connections for 210 requests with jwts", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: addr, + ReqTarget: 210, + Conns: 10, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "GET", + Client: client, + VerboseTicker: time.Second, + JwtHeader: "some-jwt", + JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), + SkipVerify: true, + }}, + want: &GoPayloaderResults{ + CompletedReqs: 210, + FailedReqs: 0, + Responses: map[worker.ResponseCode]int64{ + 200: 210, + }, + Errors: nil, + }, + check: func(t *testing.T) { + f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt"), os.O_RDONLY, os.ModePerm) + if err != nil { + if os.IsNotExist(err) { + t.Fatal(err) + } + t.Fatal(err) + } + stat, err := f.Stat() + if err != nil { + t.Fatal(err) + } + if stat.Size() == 0 { + t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-b7d91ad840dd089d10e2c3bbad56b43f0c558f4ec93a81b05b9f1fa9c8d4ad6a.txt") + } + }, + }, + { + name: "GET 101 connections for 210 requests with jwts with all available jwt fields and header", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: addr, + ReqTarget: 210, + Conns: 11, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "GET", + Client: client, + VerboseTicker: time.Second, + Headers: []string{"content-type: application/json"}, + JwtHeader: "some-jwt", + JwtAud: "some-aud", + JwtSub: "some-subject", + JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", + JwtIss: "some-issuer", + JwtKID: "13325575tevdfbdsfsf", + JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), + SkipVerify: true, + }}, + want: &GoPayloaderResults{ + CompletedReqs: 210, + FailedReqs: 0, + Responses: map[worker.ResponseCode]int64{ + 200: 210, + }, + Errors: nil, + }, + check: func(t *testing.T) { + f, err := os.OpenFile(filepath.Join(JwtCacheDir, "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt"), os.O_RDONLY, os.ModePerm) + if err != nil { + if os.IsNotExist(err) { + t.Fatal(err) + } + t.Fatal(err) + } + stat, err := f.Stat() + if err != nil { + t.Fatal(err) + } + if stat.Size() == 0 { + t.Fatalf("file size 0 for jwt cache store for %s \n", "gopayloader-jwtstore-4f12b598aa74a10a1a94931f0f93ef9f7afb43e138060ed6ce7f5c9906447c1f.txt") + } + }, + }, { name: "GET RSA JWT", fields: fields{config: &config.Config{ @@ -347,61 +348,61 @@ func testPayLoader_Run(t *testing.T, addr, client string, cleanup func()) { } }, }, - //{ - // name: "GET using JWT file only", - // fields: fields{config: &config.Config{ - // Ctx: context.Background(), - // ReqURI: addr, - // ReqTarget: 10, - // Conns: 1, - // ReadTimeout: 5 * time.Second, - // WriteTimeout: 5 * time.Second, - // Method: "GET", - // Client: client, - // VerboseTicker: time.Second, - // Headers: []string{"content-type: application/json"}, - // JwtHeader: "Authorization", - // JwtsFilename: filepath.Join("..", "..", "test", "jwtstestfile.txt"), - // SkipVerify: true, - // }}, - // want: &GoPayloaderResults{ - // CompletedReqs: 10, - // FailedReqs: 0, - // Responses: map[worker.ResponseCode]int64{ - // 200: 10, - // }, - // Errors: nil, - // }, - //}, - //{ - // name: "Error hostname incorrect format - missing port", - // fields: fields{config: &config.Config{ - // Ctx: context.Background(), - // ReqURI: "http://localhost/", - // ReqTarget: 210, - // Conns: 101, - // ReadTimeout: 5 * time.Second, - // WriteTimeout: 5 * time.Second, - // Method: "GET", - // Client: client, - // VerboseTicker: time.Second, - // Headers: []string{"content-type: application/json"}, - // JwtHeader: "some-jwt", - // JwtAud: "some-aud", - // JwtSub: "some-subject", - // JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", - // JwtIss: "some-issuer", - // JwtKID: "13325575tevdfbdsfsf", - // JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), - // SkipVerify: true, - // }}, - // want: &GoPayloaderResults{ - // CompletedReqs: 0, - // FailedReqs: 0, - // Errors: nil, - // }, - // wantErr: errors.New("url not in correct format http://localhost/ needs to be like protocol://host:port/path i.e. https://localhost:443/some-path"), - //}, + { + name: "GET using JWT file only", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: addr, + ReqTarget: 10, + Conns: 1, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "GET", + Client: client, + VerboseTicker: time.Second, + Headers: []string{"content-type: application/json"}, + JwtHeader: "Authorization", + JwtsFilename: filepath.Join("..", "..", "test", "jwtstestfile.txt"), + SkipVerify: true, + }}, + want: &GoPayloaderResults{ + CompletedReqs: 10, + FailedReqs: 0, + Responses: map[worker.ResponseCode]int64{ + 200: 10, + }, + Errors: nil, + }, + }, + { + name: "Error hostname incorrect format - missing port", + fields: fields{config: &config.Config{ + Ctx: context.Background(), + ReqURI: "http://localhost/", + ReqTarget: 210, + Conns: 101, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Method: "GET", + Client: client, + VerboseTicker: time.Second, + Headers: []string{"content-type: application/json"}, + JwtHeader: "some-jwt", + JwtAud: "some-aud", + JwtSub: "some-subject", + JwtCustomClaimsJSON: "{\"custom-claim1\": \"abc\", \"custom-claim2\": \"def\"}", + JwtIss: "some-issuer", + JwtKID: "13325575tevdfbdsfsf", + JwtKey: filepath.Join("..", "..", "test", "private-key-jwt.pem"), + SkipVerify: true, + }}, + want: &GoPayloaderResults{ + CompletedReqs: 0, + FailedReqs: 0, + Errors: nil, + }, + wantErr: errors.New("url not in correct format http://localhost/ needs to be like protocol://host:port/path i.e. https://localhost:443/some-path"), + }, } if cleanup != nil { diff --git a/pkgs/payloader/worker/generate.go b/pkgs/payloader/worker/generate.go index f95d668..a3002fb 100644 --- a/pkgs/payloader/worker/generate.go +++ b/pkgs/payloader/worker/generate.go @@ -89,10 +89,9 @@ func getReq(client http_clients.GoPayLoaderClient, config *http_clients.Config) return req, nil } -func jwtMiddleware(w *WorkerBase) { +func jwtMiddleware(w *WorkerBase) { select { case jwt := <-w.config.JwtStreamReceiver: - //fmt.Printf("GOT JWT %sHELP \n", jwt) w.req.SetHeader(w.config.JWTHeader, jwt) } } From c2d5af6f31e1ca8700312e44d9a2e407aca655b4 Mon Sep 17 00:00:00 2001 From: dominicriordan Date: Mon, 21 Aug 2023 14:33:20 +0100 Subject: [PATCH 8/8] ctrl+c to stop test server, fix bug where not reading jwt count correctly --- README.md | 11 +++++++++-- cmd/payloader/test-server.go | 22 ++++++++++++++++++++-- pkgs/jwt-generator/cache.go | 33 +++++++++++++++++++++------------ pkgs/jwt-generator/jwt.go | 2 +- version/version.go | 2 +- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 500085f..7521477 100644 --- a/README.md +++ b/README.md @@ -87,13 +87,13 @@ Flags: -H, --headers strings headers to send in request, can have multiple i.e -H 'content-type:application/json' -H' connection:close' -h, --help help for run --jwt-aud string JWT audience (aud) claim + --jwt-claims string JWT custom claims --jwt-header string JWT header field name --jwt-iss string JWT issuer (iss) claim --jwt-key string JWT signing private key path --jwt-kid string JWT KID --jwt-sub string JWT subject (sub) claim - --jwt-claims string JWT custom claims as a JSON string, ex: {"iat": 1719410063, "browser": "chrome"} - -f, --jwts-filename string File path & name where the JWTs to use are stored + -f, --jwts-filename string File path for pre-generated JWTs, separated by new lines -m, --method string request method (default "GET") --mtls-cert string mTLS cert path --mtls-key string mTLS cert private key path @@ -222,6 +222,13 @@ https://github.com/domsolutions/gopayloader +-----------------------+-------------------------------+ ``` +If you have your own JWTs you want to test, you can supply a file to send the JWTs i.e. `./my-jwts.txt` where each jwt is separated by a new line. + +```shell +./gopayloader run http://localhost:8081 -c 1 -r 1000000 --jwt-header "my-jwt" -f ./my-jwts.txt +``` + + To remove all generated jwts; ```shell diff --git a/cmd/payloader/test-server.go b/cmd/payloader/test-server.go index 4224fab..5d91a13 100644 --- a/cmd/payloader/test-server.go +++ b/cmd/payloader/test-server.go @@ -11,9 +11,11 @@ import ( "log" "net/http" "os" + "os/signal" "path/filepath" "strconv" "strings" + "syscall" "time" ) @@ -87,9 +89,25 @@ var runServerCmd = &cobra.Command{ }, } - if err := server.ListenAndServe(addr); err != nil { - return err + errs := make(chan error) + go func() { + if err := server.ListenAndServe(addr); err != nil { + log.Println(err) + errs <- err + } + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + select { + case <-c: + log.Println("User cancelled, shutting down") + server.Shutdown() + case err := <-errs: + log.Printf("Got error from server; %v \n", err) } + return nil } diff --git a/pkgs/jwt-generator/cache.go b/pkgs/jwt-generator/cache.go index 0d7e5d3..cf2935e 100644 --- a/pkgs/jwt-generator/cache.go +++ b/pkgs/jwt-generator/cache.go @@ -4,6 +4,7 @@ import ( "bufio" "errors" "fmt" + "github.com/pterm/pterm" "os" "strconv" "strings" @@ -31,9 +32,9 @@ func newCache(f *os.File) (*cache, error) { return nil, err } - s := string(bb) - count, err := strconv.ParseInt(s, 10, 64) + count, err := getCount(bb) if err != nil { + pterm.Error.Printf("Got error reading jwt count from cache; %v", err) return nil, err } @@ -47,6 +48,23 @@ func (c *cache) getJwtCount() int64 { return c.count } +func getCount(bb []byte) (int64, error) { + num := make([]byte, 0) + for _, m := range bb { + if m == 0 { + break + } + num = append(num, m) + } + + s := string(num) + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return 0, err + } + return i, nil +} + func (c *cache) get(count int64) (<-chan string, <-chan error) { recv := make(chan string, 1000000) errs := make(chan error, 1) @@ -78,16 +96,7 @@ func (c *cache) get(count int64) (<-chan string, <-chan error) { return recv, errs } - num := make([]byte, 0) - for _, m := range meta { - if m == 0 { - break - } - num = append(num, m) - } - - s := string(num) - i, err := strconv.ParseInt(s, 10, 64) + i, err := getCount(meta) if err != nil { errs <- fmt.Errorf("failed to get jwt count; %v", err) close(errs) diff --git a/pkgs/jwt-generator/jwt.go b/pkgs/jwt-generator/jwt.go index 835a65b..361a515 100644 --- a/pkgs/jwt-generator/jwt.go +++ b/pkgs/jwt-generator/jwt.go @@ -141,7 +141,7 @@ func (j *JWTGenerator) Generate(reqJwtCount int64, dir string, retrying bool) er return err } f.Close() - pterm.Debug.Printf("jwt cache %s file corrupt, attempting to delete and recreate; got error; %v \n", fname, err) + pterm.Error.Printf("jwt cache %s file corrupt, attempting to delete and recreate; got error; %v \n", fname, err) if err := os.Remove(fname); err != nil { pterm.Error.Printf("Couldn't remove cache file %s; %v", fname, err) return err diff --git a/version/version.go b/version/version.go index 77580b5..b8789ea 100644 --- a/version/version.go +++ b/version/version.go @@ -1,3 +1,3 @@ package version -const Version = "0.3.2" +const Version = "0.3.3"