From 7d3882995e58dd5c47783a3f63695a435162622c Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 4 Apr 2026 20:28:57 +0200 Subject: [PATCH 1/3] feat(jwtinfo): allow reading JWT token from file --- cmd/jwtinfo.go | 120 ++++++++++++++++++++++-------------- internal/jwtinfo/jwtinfo.go | 22 +++++++ 2 files changed, 95 insertions(+), 47 deletions(-) diff --git a/cmd/jwtinfo.go b/cmd/jwtinfo.go index c72aa68..113904f 100644 --- a/cmd/jwtinfo.go +++ b/cmd/jwtinfo.go @@ -19,10 +19,12 @@ var ( flagNameRequestJSONValues = "request-values-json" flagNameRequestValuesFile = "request-values-file" flagNameRequestURL = "request-url" + flagNameTokenFile = "token-file" flagNameJwksURL = "validation-url" requestJSONValues string requestValuesFile string requestURL string + tokenFile string jwksURL string keyfuncDefOverride keyfunc.Override ) @@ -37,85 +39,102 @@ Examples: export REQ_VALUES="{\"login\":\"values\"}" export VALIDATION_URL="https://url.to/jkws.json" - # Get the JWT token using inline values + # Read a JWT token from a local file + https-wrench jwtinfo --token-file /var/run/secrets/kubernetes.io/serviceaccount/token + + # Request a JWT token using inline values https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES - # Get the JWT token using values file + # Request a JWT token using values file https-wrench jwtinfo --request-url $REQ_URL --request-values-file request-values.json - # Get and validate the JWT token + # Request and validate a JWT token https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL `, Run: func(cmd *cobra.Command, args []string) { // TODO: display version and exit // TODO: remove global --config option - if len(requestJSONValues+requestURL) == 0 && len(requestValuesFile+requestURL) == 0 { - _ = cmd.Help() - return - } - var err error - client := &http.Client{} - requestValuesMap := make(map[string]string) + var tokenData jwtinfo.JwtTokenData - if requestValuesFile != "" { - requestValuesMap, err = jwtinfo.ReadRequestValuesFile( - requestValuesFile, - requestValuesMap, - ) + if tokenFile != "" { + tokenData, err = jwtinfo.ReadTokenFromFile(tokenFile) if err != nil { fmt.Printf( - "error while reading request's values from file: %s", + "error while reading token value from file: %s", err, ) return } } - if requestJSONValues != "" { - requestValuesMap, err = jwtinfo.ParseRequestJSONValues( - requestJSONValues, + if requestURL != "" { + client := &http.Client{} + requestValuesMap := make(map[string]string) + + if requestValuesFile != "" { + requestValuesMap, err = jwtinfo.ReadRequestValuesFile( + requestValuesFile, + requestValuesMap, + ) + if err != nil { + fmt.Printf( + "error while reading request's values from file: %s", + err, + ) + return + } + } + + if requestJSONValues != "" { + requestValuesMap, err = jwtinfo.ParseRequestJSONValues( + requestJSONValues, + requestValuesMap, + ) + if err != nil { + fmt.Printf( + "error while parsing request's values JSON string: %s", + err, + ) + return + } + } + + tokenData, err = jwtinfo.RequestToken( + requestURL, requestValuesMap, + client, + io.ReadAll, ) if err != nil { - fmt.Printf( - "error while parsing request's values JSON string: %s", - err, - ) + fmt.Printf("error while requesting token data: %s\n", err) return } } - tokenData, err := jwtinfo.RequestToken( - requestURL, - requestValuesMap, - client, - io.ReadAll, - ) - if err != nil { - fmt.Printf("error while requesting token data: %s\n", err) - return - } + if tokenData.AccessTokenRaw != "" { + err = tokenData.DecodeBase64() + if err != nil { + fmt.Printf("DecodeBase64 error: %s\n", err) + return + } - err = tokenData.DecodeBase64() - if err != nil { - fmt.Printf("DecodeBase64 error: %s\n", err) - return - } + if jwksURL != "" { + err = tokenData.ParseWithJWKS(jwksURL, keyfuncDefOverride) + if err != nil { + fmt.Printf("error while parsing token data: %s\n", err) + return + } + } - if jwksURL != "" { - err = tokenData.ParseWithJWKS(jwksURL, keyfuncDefOverride) + err = jwtinfo.PrintTokenInfo(tokenData, os.Stdout) if err != nil { - fmt.Printf("error while parsing token data: %s\n", err) + fmt.Printf("error while printing token data: %s\n", err) return } - } - - err = jwtinfo.PrintTokenInfo(tokenData, os.Stdout) - if err != nil { - fmt.Printf("error while printing token data: %s\n", err) - return + } else { + _ = cmd.Help() } }, } @@ -123,6 +142,13 @@ Examples: func init() { rootCmd.AddCommand(jwtinfoCmd) + jwtinfoCmd.Flags().StringVar( + &tokenFile, + flagNameTokenFile, + "", + "File containing the JWT token", + ) + jwtinfoCmd.Flags().StringVar( &requestURL, flagNameRequestURL, diff --git a/internal/jwtinfo/jwtinfo.go b/internal/jwtinfo/jwtinfo.go index 460864a..51cc3af 100644 --- a/internal/jwtinfo/jwtinfo.go +++ b/internal/jwtinfo/jwtinfo.go @@ -126,6 +126,28 @@ func RequestToken(reqURL string, reqValues map[string]string, client *http.Clien return t, nil } +func ReadTokenFromFile(fileName string) (JwtTokenData, error) { + data, err := os.ReadFile(fileName) + if err != nil { + return JwtTokenData{}, fmt.Errorf("unable to read request's values file: %w", err) + } + + td := JwtTokenData{AccessTokenRaw: string(data)} + + _, _, err = jwt.NewParser().ParseUnverified( + td.AccessTokenRaw, + &jwt.RegisteredClaims{}, + ) + if err != nil { + return JwtTokenData{}, fmt.Errorf( + "unable to parse JWT token from file: %w", + err, + ) + } + + return td, err +} + func ParseRequestJSONValues( reqValues string, reqValuesMap map[string]string, From 31623840203d5a56e7352b613b1280c0a6a30d2b Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 4 Apr 2026 20:43:52 +0200 Subject: [PATCH 2/3] refactor: ReadTokenFromFile trim spaces and fixes errors --- internal/jwtinfo/jwtinfo.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/jwtinfo/jwtinfo.go b/internal/jwtinfo/jwtinfo.go index 51cc3af..aeba925 100644 --- a/internal/jwtinfo/jwtinfo.go +++ b/internal/jwtinfo/jwtinfo.go @@ -129,10 +129,10 @@ func RequestToken(reqURL string, reqValues map[string]string, client *http.Clien func ReadTokenFromFile(fileName string) (JwtTokenData, error) { data, err := os.ReadFile(fileName) if err != nil { - return JwtTokenData{}, fmt.Errorf("unable to read request's values file: %w", err) + return JwtTokenData{}, fmt.Errorf("unable to read token file: %w", err) } - td := JwtTokenData{AccessTokenRaw: string(data)} + td := JwtTokenData{AccessTokenRaw: strings.TrimSpace(string(data))} _, _, err = jwt.NewParser().ParseUnverified( td.AccessTokenRaw, @@ -145,7 +145,7 @@ func ReadTokenFromFile(fileName string) (JwtTokenData, error) { ) } - return td, err + return td, nil } func ParseRequestJSONValues( From 930b7259654007123dd6ef47baa57c0a10506dea Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 4 Apr 2026 20:56:26 +0200 Subject: [PATCH 3/3] refactor: make token request and token read flags mutually exclusive --- cmd/jwtinfo.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/jwtinfo.go b/cmd/jwtinfo.go index 113904f..02bd7c1 100644 --- a/cmd/jwtinfo.go +++ b/cmd/jwtinfo.go @@ -52,7 +52,6 @@ Examples: https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL `, Run: func(cmd *cobra.Command, args []string) { - // TODO: display version and exit // TODO: remove global --config option var err error @@ -177,7 +176,7 @@ func init() { "Url of the JSON Web Key Set (JWKS) to use for validating the JWT token", ) - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // jwtinfoCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // Either read a token from a file or request it from an HTTP address + jwtinfoCmd.MarkFlagsMutuallyExclusive(flagNameTokenFile, flagNameRequestURL) + jwtinfoCmd.MarkFlagsOneRequired(flagNameTokenFile, flagNameRequestURL) }