Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 53 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,21 +188,27 @@ Global Flags:
Get info about a certificate and a key and see if their public keys match:

```shell
❯ https-wrench certinfo --cert-bundle rsa-pkcs8-crt.pem --key-file rsa-pkcs8-plaintext-private-key.pem
❯ https-wrench certinfo \
--cert-bundle rsa-pkcs8-crt.pem \
--key-file rsa-pkcs8-plaintext-private-key.pem
```

Get info about a certificate exposed by a remote TLS endpoint:

```shell
❯ https-wrench certinfo --tls-endpoint repo.os76.xyz:443
❯ https-wrench certinfo \
--tls-endpoint repo.os76.xyz:443
```

Get info about a self signed certificate exposed by a remote TLS endpoint,
validate it against a CA certificate and check if a specific privave key has
been used to generate the certificate:

```shell
❯ https-wrench certinfo --tls-endpoint localhost:9443 --ca-bundle rootCA.pem --key-file key.pem
❯ https-wrench certinfo \
--tls-endpoint localhost:9443 \
--ca-bundle rootCA.pem \
--key-file key.pem
```

### HTTPS Wrench jwtinfo
Expand All @@ -226,29 +232,48 @@ Examples:
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
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-json $REQ_VALUES

# Request a JWT token using values file
https-wrench jwtinfo --request-url $REQ_URL --request-values-file request-values.json
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-file request-values.json

# Request a JWT token using request-values flag
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values username=test \
--request-values password=test \
--request-values scope=login

# Request and validate a JWT token
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-json $REQ_VALUES \
--validation-url $VALIDATION_URL

# Request a JWT token, write it to a file and refresh it before expiration
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --token-output-file /tmp/token --refresh
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-json $REQ_VALUES \
--token-output-file /tmp/token \
--refresh

Usage:
https-wrench jwtinfo [flags]

Flags:
-h, --help help for jwtinfo
--refresh Run in foreground and automatically refresh the token
--renew-threshold float Token renewal threshold as a percentage of lifetime (default 80)
--renew-threshold float Percentage of token lifetime to wait before refreshing (default 80)
--request-url string HTTP address to use for the JWT token request
--request-values string Key-value pairs to use for the JWT token request (e.g., key=value)
--request-values-file string File containing the JSON encoded values to use for the JWT token request
--request-values-json string JSON encoded values to use for the JWT token request
--token-file string File containing the JWT token
--token-output-file string File where the acquired/refreshed token will be written
--token-output-file string File to write the refreshed token to
--validation-url string Url of the JSON Web Key Set (JWKS) to use for validating the JWT token

Global Flags:
Expand All @@ -267,13 +292,30 @@ Decode a token from a file:
Request a token and save it to a file:

```shell
❯ https-wrench jwtinfo --request-url https://auth.example.com/token --request-values-json '{"client_id":"foo"}' --token-output-file ./token.jwt
❯ https-wrench jwtinfo \
--request-url https://auth.example.com/token \
--request-values-json '{"client_id":"foo"}' \
--token-output-file ./token.jwt
```

Request a token using key-value pairs:

```shell
❯ https-wrench jwtinfo \
--request-url https://auth.example.com/token \
--request-values client_id=foo \
--request-values client_secret=bar
```

Request a token, save it to a file, and keep it refreshed until interrupted:

```shell
❯ https-wrench jwtinfo --request-url https://auth.example.com/token --request-values-json '{"client_id":"foo"}' --token-output-file ./token.jwt --refresh --renew-threshold 90
❯ https-wrench jwtinfo \
--request-url https://auth.example.com/token \
--request-values-json '{"client_id":"foo"}' \
--token-output-file ./token.jwt \
--renew-threshold 90 \
--refresh
```

### HTTPS Wrench jwks
Expand Down
12 changes: 6 additions & 6 deletions devenv.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1777299001,
"narHash": "sha256-r1tFf3mRY5/Fh5DskQLiXjb4AUnM+tOA3pNyrLkXNfA=",
"lastModified": 1777837414,
"narHash": "sha256-L7g797htlkWyFW+6Y4qibuyaVMcDaVjdBTcaPMbKPmY=",
"owner": "cachix",
"repo": "devenv",
"rev": "cbcbe22f0990293d0b540fbc7703b1361cbce060",
"rev": "9708ea1ebc52d6189cff09b837067daefb0bf0e7",
"type": "github"
},
"original": {
Expand Down Expand Up @@ -109,11 +109,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1777077449,
"narHash": "sha256-AIiMJiqvGrN4HyLEbKAoCSRRYn0rnlW5VbKNIMIYqm4=",
"lastModified": 1777673416,
"narHash": "sha256-5c2POKPOjU40Kh0MirOdScBLG0bu9TAuPYAtPRNZMBs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a4bf06618f0b5ee50f14ed8f0da77d34ecc19160",
"rev": "26ef669cffa904b6f6832ab57b77892a37c1a671",
"type": "github"
},
"original": {
Expand Down
15 changes: 15 additions & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,21 @@ in {
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-file ~/.config/https-wrench/jwtinfo_test_keycloak_req_values.json --validation-url "$VALIDATION_URL"
'';

scripts.run-jwtinfo-test-keycloak-mixed-value-flags.exec = ''
gum format "### JwtInfo request against priv Keycloak with mixed values flags"

REQ_URL="https://keycloak.k3s.os76.xyz/realms/os76/protocol/openid-connect/token"
VALIDATION_URL="https://keycloak.k3s.os76.xyz/realms/os76/protocol/openid-connect/certs"

./dist/https-wrench jwtinfo --request-url "$REQ_URL" \
--request-values client_id=istio-test-01 \
--request-values-file ~/.config/https-wrench/jwtinfo_test_keycloak_req_values_pw_only.json \
--request-values username=xeno \
--request-values scope=openid \
--request-values grant_type=password \
--validation-url "$VALIDATION_URL"
'';

scripts.run-go-tests.exec = ''
gum format "## Run GO tests"

Expand Down
123 changes: 83 additions & 40 deletions internal/cmd/jwtinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package cmd

import (
"context"
"fmt"
"io"
"net/http"
"os"
Expand All @@ -15,9 +16,11 @@ import (
"github.com/MicahParks/keyfunc/v3"
"github.com/spf13/cobra"
"github.com/xenos76/https-wrench/internal/jwtinfo"
"github.com/xenos76/https-wrench/internal/style"
)

var (
flagNameRequestValues = "request-values"
flagNameRequestJSONValues = "request-values-json"
flagNameRequestValuesFile = "request-values-file"
flagNameRequestURL = "request-url"
Expand All @@ -26,17 +29,39 @@ var (
flagNameRefresh = "refresh"
flagNameTokenOutputFile = "token-output-file"
flagNameRenewThreshold = "renew-threshold"
requestJSONValues string
requestValuesFile string
requestURL string
tokenFile string
jwksURL string
refresh bool
tokenOutputFile string
renewThreshold float64
keyfuncDefOverride keyfunc.Override

// requestSteps tracks the sequence of request-related flags as they appear on the command line.
requestSteps []requestValueStep
)

// requestValueStep represents a single occurrence of a request flag and its value.
type requestValueStep struct {
kind string // "json", "file", or "kv"
value string
}

// stepFlag implements the pflag.Value interface to capture the order of flag occurrences.
type stepFlag struct {
kind string
}

func (*stepFlag) String() string { return "" }

// Set appends the flag's value and its type to the global requestSteps slice.
func (f *stepFlag) Set(s string) error {
requestSteps = append(requestSteps, requestValueStep{kind: f.kind, value: s})
return nil
}

func (*stepFlag) Type() string { return "string" }

var jwtinfoCmd = &cobra.Command{
Use: "jwtinfo",
Short: "Inspect and validate JSON Web Tokens (JWT)",
Expand All @@ -51,16 +76,34 @@ Examples:
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
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-json $REQ_VALUES

# Request a JWT token using values file
https-wrench jwtinfo --request-url $REQ_URL --request-values-file request-values.json
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-file request-values.json

# Request a JWT token using request-values flag
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values username=test \
--request-values password=test \
--request-values scope=login

# Request and validate a JWT token
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-json $REQ_VALUES \
--validation-url $VALIDATION_URL

# Request a JWT token, write it to a file and refresh it before expiration
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --token-output-file /tmp/token --refresh
https-wrench jwtinfo \
--request-url $REQ_URL \
--request-values-json $REQ_VALUES \
--token-output-file /tmp/token \
--refresh
`,
Run: func(cmd *cobra.Command, _ []string) {
var (
Expand All @@ -70,7 +113,11 @@ Examples:
requestValuesMap = make(map[string]string)
)

// TODO: remove global --config option
if refresh && requestURL == "" {
fmt.Fprintln(cmd.OutOrStdout(), style.LgSprintf(style.Error, "Error: --refresh requires --request-url"))
return
}

if tokenFile != "" {
tokenData, err = jwtinfo.ReadTokenFromFile(tokenFile)
if err != nil {
Expand All @@ -84,32 +131,29 @@ Examples:
}

if requestURL != "" {
if requestValuesFile != "" {
requestValuesMap, err = jwtinfo.ReadRequestValuesFile(
requestValuesFile,
requestValuesMap,
)
if err != nil {
cmd.Printf(
"error while reading request's values from file: %s",
err,
for _, step := range requestSteps {
switch step.kind {
case "json":
requestValuesMap, err = jwtinfo.ParseRequestJSONValues(
step.value,
requestValuesMap,
)

return
case "file":
requestValuesMap, err = jwtinfo.ReadRequestValuesFile(
step.value,
requestValuesMap,
)
case "kv":
requestValuesMap, err = jwtinfo.ParseKVValue(
step.value,
requestValuesMap,
)
default:
continue
}
}

if requestJSONValues != "" {
requestValuesMap, err = jwtinfo.ParseRequestJSONValues(
requestJSONValues,
requestValuesMap,
)
if err != nil {
cmd.Printf(
"error while parsing request's values JSON string: %s",
err,
)

cmd.Printf("error processing %s: %s\n", step.kind, err)
return
}
}
Expand Down Expand Up @@ -153,11 +197,6 @@ Examples:
}

if refresh {
if requestURL == "" {
cmd.Printf("Error: --refresh requires --request-url\n")
return
}

// Setup graceful shutdown
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
Expand Down Expand Up @@ -211,20 +250,24 @@ func init() {
"HTTP address to use for the JWT token request",
)

jwtinfoCmd.Flags().StringVar(
&requestJSONValues,
jwtinfoCmd.Flags().Var(
&stepFlag{kind: "json"},
flagNameRequestJSONValues,
"",
"JSON encoded values to use for the JWT token request",
)

jwtinfoCmd.Flags().StringVar(
&requestValuesFile,
jwtinfoCmd.Flags().Var(
&stepFlag{kind: "file"},
flagNameRequestValuesFile,
"",
"File containing the JSON encoded values to use for the JWT token request",
)

jwtinfoCmd.Flags().Var(
&stepFlag{kind: "kv"},
flagNameRequestValues,
"Key-value pairs to use for the JWT token request (e.g., key=value)",
)

jwtinfoCmd.Flags().StringVar(
&jwksURL,
flagNameJwksURL,
Expand Down
Loading
Loading