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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ Generate a JWKS with a SHA-256-derived KID:
❯ https-wrench jwks --public-key-file public.pem
```

### Sample output
## Sample output

<details>
<summary>HTTPS Wrench requests, sample configuration output</summary>
Expand Down
2 changes: 2 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"github.com/xenos76/https-wrench/internal/requests"
)

// HTTPSWrenchConfig represents the top-level configuration for the application.
type HTTPSWrenchConfig struct {
Debug bool `mapstructure:"debug"`
Verbose bool `mapstructure:"verbose"`
CaBundle string `mapstructure:"caBundle"`
requests.RequestsMetaConfig `mapstructure:",squash"`
}

// NewHTTPSWrenchConfig returns a new HTTPSWrenchConfig with default values.
func NewHTTPSWrenchConfig() *HTTPSWrenchConfig {
c := HTTPSWrenchConfig{}
return &c
Expand Down
3 changes: 3 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Distributed under an open-source license: https://github.com/xenOs76/https-wrenc
},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() error {
err := rootCmd.Execute()
if err != nil {
Expand Down Expand Up @@ -135,6 +137,7 @@ func initConfig() {
}
}

// LoadConfig reads the configuration file and unmarshals it into an HTTPSWrenchConfig struct.
func LoadConfig() (*HTTPSWrenchConfig, error) {
config := NewHTTPSWrenchConfig()

Expand Down
57 changes: 38 additions & 19 deletions internal/certinfo/certinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,44 @@ import (
)

const (
TLSTimeout = 3 * time.Second
// TLSTimeout is the maximum time to wait for a TLS handshake.
TLSTimeout = 3 * time.Second
// CertExpWarnDays is the number of days before expiration to start showing warnings.
CertExpWarnDays = 40
privateKeyPwEnvVar = "CERTINFO_PKEY_PW"
emptyString = ""
)

// CertinfoConfig holds the configuration and results for certificate and key information retrieval.
type CertinfoConfig struct {
CACertsPool *x509.CertPool
CACertsFilePath string
CertsBundle []*x509.Certificate
CertsBundleFilePath string
CertsBundleFromKey bool
PrivKey crypto.PrivateKey
PrivKeyFilePath string
TLSEndpoint string
TLSEndpointHost string
TLSEndpointPort string
TLSEndpointCerts []*x509.Certificate
// CACertsPool is the pool of root CA certificates used for verification.
CACertsPool *x509.CertPool
// CACertsFilePath is the path to the CA certificate bundle file.
CACertsFilePath string
// CertsBundle is a slice of certificates loaded from a local bundle.
CertsBundle []*x509.Certificate
// CertsBundleFilePath is the path to the certificate bundle file.
CertsBundleFilePath string
// CertsBundleFromKey indicates if the certificate was derived from a private key.
CertsBundleFromKey bool
// PrivKey is the loaded private key.
PrivKey crypto.PrivateKey
// PrivKeyFilePath is the path to the private key file.
PrivKeyFilePath string
// TLSEndpoint is the host:port string of the remote TLS endpoint.
TLSEndpoint string
// TLSEndpointHost is the hostname part of the TLS endpoint.
TLSEndpointHost string
// TLSEndpointPort is the port part of the TLS endpoint.
TLSEndpointPort string
// TLSEndpointCerts is the slice of certificates retrieved from the remote endpoint.
TLSEndpointCerts []*x509.Certificate
// TLSEndpointCertsFromKey indicates if the endpoint certificates were matched with a private key.
TLSEndpointCertsFromKey bool
TLSServerName string
TLSInsecure bool
// TLSServerName is the ServerName used for SNI.
TLSServerName string
// TLSInsecure indicates if certificate verification should be skipped.
TLSInsecure bool
}

// Reader defines an interface for reading files and passwords.
Expand All @@ -48,10 +64,11 @@ type (
)

var (
//nolint:revive
// TLSServerName is the default ServerName to use for SNI.
TLSServerName string
TLSInsecure bool
inputReader InputReader
// TLSInsecure indicates if certificate verification should be skipped globally.
TLSInsecure bool
inputReader InputReader
)

// ReadFile reads the content of a file from the filesystem.
Expand Down Expand Up @@ -84,6 +101,8 @@ func NewCertinfoConfig() (*CertinfoConfig, error) {
}

// SetCaPoolFromFile loads a CA certificate pool from the specified PEM bundle file.
// Note that x509.SystemCertPool is not used in this case. All certificates
// from the system certificate pool are excluded.
func (c *CertinfoConfig) SetCaPoolFromFile(filePath string, fileReader Reader) error {
if filePath != emptyString {
caCertsPool, err := GetRootCertsFromFile(
Expand Down Expand Up @@ -120,8 +139,8 @@ func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) er
}

// SetPrivateKeyFromFile loads a private key from the specified PEM file.
// If the key is encrypted, it will attempt to retrieve the passphrase from an environment variable or
// interactive prompt.
// If the key is encrypted, it will attempt to retrieve the passphrase from the environment
// variable passed as argument or from the interactive prompt.
func (c *CertinfoConfig) SetPrivateKeyFromFile(
filePath string,
keyPwEnvVar string,
Expand Down
30 changes: 21 additions & 9 deletions internal/jwtinfo/jwtinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,37 @@ var (

// JwtTokenData holds the raw and parsed data for access and refresh tokens.
type JwtTokenData struct {
AccessTokenRaw string `json:"access_token"` //nolint:tagliatelle // OAuth token field name
AccessTokenJwt *jwt.Token
AccessTokenHeader []byte
AccessTokenClaims []byte
RefreshTokenRaw string `json:"refresh_token"` //nolint:tagliatelle // OAuth token field name
RefreshTokenJwt *jwt.Token
// AccessTokenRaw is the raw base64-encoded access token string.
AccessTokenRaw string `json:"access_token"` //nolint:tagliatelle // OAuth token field name
// AccessTokenJwt is the parsed access token object.
AccessTokenJwt *jwt.Token
// AccessTokenHeader is the decoded JSON header of the access token.
AccessTokenHeader []byte
// AccessTokenClaims is the decoded JSON claims of the access token.
AccessTokenClaims []byte
// RefreshTokenRaw is the raw base64-encoded refresh token string.
RefreshTokenRaw string `json:"refresh_token"` //nolint:tagliatelle // OAuth token field name
// RefreshTokenJwt is the parsed refresh token object.
RefreshTokenJwt *jwt.Token
// RefreshTokenHeader is the decoded JSON header of the refresh token.
RefreshTokenHeader []byte
// RefreshTokenClaims is the decoded JSON claims of the refresh token.
RefreshTokenClaims []byte
}

// allReader is a function type that reads all data from an io.Reader.
type allReader func(io.Reader) ([]byte, error)
// AllReader is a function type that reads all data from an io.Reader.
type AllReader func(io.Reader) ([]byte, error)

// RequestToken makes an HTTP POST request to the given URL with the provided values
// to retrieve a JWT token. It handles both application/jwt and application/json
// response types.
//
//nolint:revive
func RequestToken(ctx context.Context, reqURL string, reqValues map[string]string, client *http.Client, readAll allReader) (JwtTokenData, error) {
func RequestToken(ctx context.Context, reqURL string, reqValues map[string]string, client *http.Client, readAll AllReader) (JwtTokenData, error) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if readAll == nil {
return JwtTokenData{}, errors.New("nil body reader function")
}

if reqURL == emptyString {
return JwtTokenData{}, errors.New("empty string provided as request URL")
}
Expand Down
4 changes: 2 additions & 2 deletions internal/jwtinfo/jwtinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func TestParseWithJWKS(t *testing.T) {
user string
pass string
scope string
bodyReader allReader
bodyReader AllReader
expError bool
expReqError bool
}{
Expand Down Expand Up @@ -814,7 +814,7 @@ func TestPrintTokenInfo(t *testing.T) {
user string
pass string
scope string
bodyReader allReader
bodyReader AllReader
skipValidation bool
expError bool
expReqError bool
Expand Down
88 changes: 58 additions & 30 deletions internal/requests/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
emptyString = ""
)

// ErrMethodNotFound is returned when an unsupported HTTP method is specified.
var ErrMethodNotFound = errors.New("HTTP method not found")

var allowedHTTPMethods = map[string]string{
Expand Down Expand Up @@ -69,7 +70,9 @@ var contentTypeMatchingItems = []struct {
}

type (
URI string
// URI represents a partial URL path (e.g. /index.html).
URI string
// ResponseHeader represents a formatted string of HTTP response headers.
ResponseHeader string
)

Expand All @@ -87,23 +90,40 @@ type RequestHeader struct {

// RequestConfig defines the configuration for a single HTTP request set.
type RequestConfig struct {
Name string `mapstructure:"name"`
ClientTimeout int `mapstructure:"clientTimeout"`
UserAgent string `mapstructure:"userAgent"`
TransportOverrideURL string `mapstructure:"transportOverrideUrl"`
EnableProxyProtocolV2 bool `mapstructure:"enableProxyProtocolV2"`
Insecure bool `mapstructure:"insecure"`
RequestDebug bool `mapstructure:"requestDebug"`
RequestHeaders []RequestHeader `mapstructure:"requestHeaders"`
RequestMethod string `mapstructure:"requestMethod"`
RequestBody string `mapstructure:"requestBody"`
ResponseDebug bool `mapstructure:"responseDebug"`
ResponseHeadersFilter []string `mapstructure:"responseHeadersFilter"`
ResponseBodyMatchRegexp string `mapstructure:"responseBodyMatchRegexp"`
PrintResponseBody bool `mapstructure:"printResponseBody"`
PrintResponseHeaders bool `mapstructure:"printResponseHeaders"`
PrintResponseCertificates bool `mapstructure:"printResponseCertificates"`
Hosts []Host `mapstructure:"hosts"`
// Name is a descriptive name for this request configuration.
Name string `mapstructure:"name"`
// ClientTimeout is the timeout in seconds for the HTTP client.
ClientTimeout int `mapstructure:"clientTimeout"`
// UserAgent is the custom User-Agent string to use for the request.
UserAgent string `mapstructure:"userAgent"`
// TransportOverrideURL is an optional address to connect to instead of the target host.
TransportOverrideURL string `mapstructure:"transportOverrideUrl"`
// EnableProxyProtocolV2 enables PROXY protocol v2 headers for the connection.
EnableProxyProtocolV2 bool `mapstructure:"enableProxyProtocolV2"`
// Insecure skips TLS certificate verification.
Insecure bool `mapstructure:"insecure"`
// RequestDebug enables dumping the outgoing HTTP request.
RequestDebug bool `mapstructure:"requestDebug"`
// RequestHeaders is a slice of custom HTTP headers to include in the request.
RequestHeaders []RequestHeader `mapstructure:"requestHeaders"`
// RequestMethod is the HTTP method to use (GET, POST, etc.).
RequestMethod string `mapstructure:"requestMethod"`
// RequestBody is the body of the HTTP request.
RequestBody string `mapstructure:"requestBody"`
// ResponseDebug enables dumping the incoming HTTP response.
ResponseDebug bool `mapstructure:"responseDebug"`
// ResponseHeadersFilter is a list of header keys to display in the output.
ResponseHeadersFilter []string `mapstructure:"responseHeadersFilter"`
// ResponseBodyMatchRegexp is a regular expression to match against the response body.
ResponseBodyMatchRegexp string `mapstructure:"responseBodyMatchRegexp"`
// PrintResponseBody indicates if the response body should be printed to the output.
PrintResponseBody bool `mapstructure:"printResponseBody"`
// PrintResponseHeaders indicates if the response headers should be printed to the output.
PrintResponseHeaders bool `mapstructure:"printResponseHeaders"`
// PrintResponseCertificates indicates if the response TLS certificates should be printed.
PrintResponseCertificates bool `mapstructure:"printResponseCertificates"`
// Hosts is a list of target hosts and their URIs.
Hosts []Host `mapstructure:"hosts"`
}

// RequestHTTPClient wraps an http.Client with additional configuration for requests.
Expand All @@ -116,24 +136,32 @@ type RequestHTTPClient struct {

// ResponseData holds the results and metadata of an executed HTTP request.
type ResponseData struct {
Request RequestConfig
TransportAddress string
URL string
ResponseBody string
// Request is the original configuration for the executed request.
Request RequestConfig
// TransportAddress is the network address the request was sent to.
TransportAddress string
// URL is the full URL requested.
URL string
// ResponseBody is the content of the HTTP response.
ResponseBody string
// ResponseBodyRegexpMatched indicates if the response body matched the configured regexp.
ResponseBodyRegexpMatched bool
Response *http.Response
Error error
// Response is the raw HTTP response object.
Response *http.Response
// Error is any error encountered during the request.
Error error
}

// RequestsMetaConfig holds the global configuration and the list of requests to execute.
type RequestsMetaConfig struct {
// TODO: can we remove the following
// three lines and just embed an "options"
// struct to take care of metadata?
RequestDebug bool
// RequestDebug enables global request debugging.
RequestDebug bool
// RequestVerbose enables global verbose output.
RequestVerbose bool
CACertsPool *x509.CertPool
Requests []RequestConfig `mapstructure:"requests"`
// CACertsPool is the certificate pool used for validating server certificates.
CACertsPool *x509.CertPool
// Requests is the list of request configurations to execute.
Requests []RequestConfig `mapstructure:"requests"`
}

// NewRequestsMetaConfig creates a new RequestsMetaConfig with the system's certificate pool.
Expand Down
1 change: 0 additions & 1 deletion internal/requests/requests_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ func (rd *ResponseData) ImportResponseBody() {
}

// PrintResponseData prints the collected response data (status, headers, body) if verbose mode is enabled.
//
//nolint:revive
func (rd ResponseData) PrintResponseData(isVerbose bool) {
if !isVerbose {
Expand Down
Loading
Loading