Documentation
¶
Overview ¶
Package marasi provides an HTTP/HTTPS proxy server with extension support, request/response interception, and SQLite database storage. It is designed to be decoupled from GUI implementations and provides methods to load handlers for building security testing tools, traffic analysis, and HTTP manipulation applications.
The core functionality includes:
- HTTP/HTTPS proxy server with TLS certificate management
- Lua-based extension system for request/response processing
- Request/response interception and modification
- SQLite database storage for traffic analysis
- Scope-based filtering system
- Chrome browser integration for testing
- Launchpad system for organizing test requests
Index ¶
- Variables
- func BufferStreamingBodyModifier(proxy *Proxy, res *http.Response) error
- func CheckpointRequestModifier(proxy *Proxy, req *http.Request) error
- func CheckpointResponseModifier(proxy *Proxy, res *http.Response) error
- func CompassRequestModifier(proxy *Proxy, req *http.Request) error
- func CompassResponseModifier(proxy *Proxy, res *http.Response) error
- func CompressedResponseModifier(proxy *Proxy, res *http.Response) error
- func ExtensionsRequestModifier(proxy *Proxy, req *http.Request) error
- func ExtensionsResponseModifier(proxy *Proxy, res *http.Response) error
- func NewProxyRequest(req *http.Request, requestId uuid.UUID) (*domain.ProxyRequest, error)
- func NewProxyResponse(res *http.Response) (*domain.ProxyResponse, error)
- func OverrideWaypointsModifier(proxy *Proxy, req *http.Request) error
- func PreventLoopModifier(proxy *Proxy, req *http.Request) error
- func ResponseFilterModifier(proxy *Proxy, res *http.Response) error
- func SetupRequestModifier(proxy *Proxy, req *http.Request) error
- func SkipConnectRequestModifier(proxy *Proxy, req *http.Request) error
- func WithBasePipeline() func(*Proxy) error
- func WithConfigDir(appConfigDir string) func(*Proxy) error
- func WithConfigRepository(repo domain.ConfigRepository) func(*Proxy) error
- func WithDBCloser(closer io.Closer) func(*Proxy) error
- func WithDefaultModifierPipeline() func(*Proxy) error
- func WithDefaultRepositories(repo RepositoryProvider) func(*Proxy) error
- func WithExtension(extension *domain.Extension, options ...func(*extensions.Runtime) error) func(*Proxy) error
- func WithExtensionRepository(repo domain.ExtensionRepository) func(*Proxy) error
- func WithExtensions(exts []*domain.Extension, options ...func(*extensions.Runtime) error) func(*Proxy) error
- func WithInterceptHandler(handler func(intercepted *Intercepted) error) func(*Proxy) error
- func WithLaunchpadRepository(repo domain.LaunchpadRepository) func(*Proxy) error
- func WithLogHandler(handler func(log domain.Log) error) func(*Proxy) error
- func WithLogRepository(repo domain.LogRepository) func(*Proxy) error
- func WithLogger(logger *slog.Logger) func(*Proxy) error
- func WithRequestHandler(handler func(req domain.ProxyRequest) error) func(*Proxy) error
- func WithResponseHandler(handler func(res domain.ProxyResponse) error) func(*Proxy) error
- func WithStatsRepository(repo domain.StatsRepository) func(*Proxy) error
- func WithTLS() func(*Proxy) error
- func WithTrafficRepository(repo domain.TrafficRepository) func(*Proxy) error
- func WithWaypointRepository(repo domain.WaypointRepository) func(*Proxy) error
- func WriteRequestModifier(proxy *Proxy, req *http.Request) error
- func WriteResponseModifier(proxy *Proxy, res *http.Response) error
- type ChromePathConfig
- type Config
- type Intercepted
- type InterceptionTuple
- type Proxy
- func (proxy *Proxy) AddRequestModifier(modifier RequestModifierFunc)
- func (proxy *Proxy) AddResponseModifier(modifier ResponseModifierFunc)
- func (proxy *Proxy) Close()
- func (proxy *Proxy) GetClient() (*http.Client, error)
- func (proxy *Proxy) GetConfigDir() (string, error)
- func (proxy *Proxy) GetExtension(name string) (*extensions.Runtime, bool)
- func (proxy *Proxy) GetExtensionRepo() (domain.ExtensionRepository, error)
- func (proxy *Proxy) GetListener(address string, port string) (net.Listener, error)
- func (proxy *Proxy) GetScope() (*compass.Scope, error)
- func (proxy *Proxy) GetTrafficRepo() (domain.TrafficRepository, error)
- func (proxy *Proxy) Launch(raw string, launchpadId string, useHttps bool) error
- func (proxy *Proxy) Serve(listener net.Listener) error
- func (proxy *Proxy) StartChrome() error
- func (proxy *Proxy) SyncWaypoints() error
- func (proxy *Proxy) WithOptions(options ...func(*Proxy) error) error
- func (proxy *Proxy) WriteLog(level string, message string, options ...func(log *domain.Log) error) error
- func (proxy *Proxy) WriteToDB()
- type RepositoryProvider
- type RequestModifierFunc
- type ResponseModifierFunc
- type Waypoint
Constants ¶
This section is empty.
Variables ¶
var ( // ErrDropped is returned when the request / response should be dropped completely. // The request / response will not be processed by any modifier and will not continue to the client / server ErrDropped = errors.New("item dropped by extension or user") // ErrSkipPipeline is returned to stop the modifier pipeline for a request / response. // The request / response will still continue but won't be processed by any future modifiers ErrSkipPipeline = errors.New("stop processing item") // ErrMetadata is returned when metadata is invalid or missing ErrMetadataNotFound = errors.New("invalid or missing metadata") // ErrExtensionNotFound is returned when extension is invalid or missing ErrExtensionNotFound = errors.New("invalid or missing extension") // ErrRequestIDNotFound is returned when requestID is not found ErrRequestIDNotFound = errors.New("invalid or missing requestID") // ErrRebuildRequest is returned when the request is malformed and cannot be rebuilt ErrRebuildRequest = errors.New("cannot rebuild request") // ErrRebuildResponse is returned when the response is malformed and cannot be rebuilt ErrRebuildResponse = errors.New("cannot rebuild response") // ErrRequestHandlerUndefined is returned when the request handler is undefined ErrRequestHandlerUndefined = errors.New("no request handler defined") // ErrResponseHandlerUndefined is returned when the response handler is undefined ErrResponseHandlerUndefined = errors.New("no response handler defined") // ErrProxyRequest is returned when ProxyRequest cannot be created ErrProxyRequest = errors.New("failed to create proxyrequest") // ErrProxyResponse is returned when ErrProxyResponse cannot be created ErrProxyResponse = errors.New("failed to create proxyresponse") // ErrReadBody is returned when there is an error with reading the response body ErrReadBody = errors.New("failed to read the body") )
var ( // ErrConfigDirNotSet is returned when the configuration directory is not set. ErrConfigDirNotSet = errors.New("config dir not set") // ErrScopeNotFound is returned when the scope is not found in the proxy. ErrScopeNotFound = errors.New("scope field is not found") // ErrClientNotFound is returned when the HTTP client is not found in the proxy. ErrClientNotFound = errors.New("http client field not found") // ErrExtensionRepoNotFound is returned when the extension repository is not found. ErrExtensionRepoNotFound = errors.New("extension repo not found") )
var ( // ErrSessionContext is returned when the session could not be fetch from context ErrSessionContext = errors.New("failed to get session from context") )
Functions ¶
func BufferStreamingBodyModifier ¶
BufferStreamingBodyModifier reads the entire streaming response body into memory and replaces the `res.Body` with a new `io.NopCloser` on the full body. It will remove the `Transfer-Encoding` and update the `Content-Length` to reflect the new body.
func CheckpointRequestModifier ¶
CheckpointRequestModifier will intercept requests if the global `proxy.InterceptFlag` is set or the `interceptRequest` function returns true. If a request is intercepted, the modifier will block until the user decides to resume or drop the request. If the request is resumed it will be rebuilt with the same context and metadata from the modified raw request. The metadata will be updated to include "intercepted", "original-request", and "dropped" based on the user action. If the modifier receives `ShouldInterceptResponse` the flag is added to the context so that the response is intercepted regardless of the `processResponse` or `proxy.InterceptFlag`
func CheckpointResponseModifier ¶
CheckpointResponseModifier will intercept response if the global `proxy.InterceptFlag` is set, `interceptResponse` function returns true, or if the context has an intercept flag set as true. If a response is intercepted, the modifier will block until the user decides to resume or drop the response. If the response is resumed it will be rebuilt with the same context and metadata from the modified raw response. The metadata will be updated to include "intercepted", "original-response", and "dropped" based on the user action.
func CompassRequestModifier ¶
CompassRequestModifier will run the `processRequest` function in the compass extension to determine if the request is in scope. After `processRequest`, it will check if the request is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`). If the compass extension is not found the modifier will return `ErrExtensionNotFound` as "compass" is considered a core extension.
func CompassResponseModifier ¶
CompassResponseModifier will run the `processResponse` function in the compass extension to determine if the response is in scope. After `processResponse`, it will check if the response is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`). If the compass extension is not found the modifier will return `ErrExtensionNotFound` as "compass" is considered a core extension.
func CompressedResponseModifier ¶
CompressedResponseModifier decompresses the response bodies and replaces the `res.Body` with the decompressed data. It will remove the "Content-Encoding" header and update the "Content-Length" to the new length. Currently the modifier handles gzip and br compressed bodies.
func ExtensionsRequestModifier ¶
ExtensionsRequestModifier will run the `processRequest` function (if it is defined) for all the loaded extensions (except compass and checkpoint). Initially the modifier will check if the request originated from an extension by reading the "x-extension-id" header. This extension ID will be set in the context so that the response modifier will be able to read it. After processRequest, it will check if the request is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`).
func ExtensionsResponseModifier ¶
ExtensionsResponseModifier will run the `processResponse` function (if it is defined) for all the loaded extensions (except compass and checkpoint). The modifier will check if the extension ID in request context matches the current extension and skip execution if it does. After `processResponse`, it will check if the request is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`).
func NewProxyRequest ¶
NewProxyRequest creates a new domain.ProxyRequest from an http.Request. It extracts metadata from the request context and dumps the raw request.
func NewProxyResponse ¶
func NewProxyResponse(res *http.Response) (*domain.ProxyResponse, error)
NewProxyResponse creates a new domain.ProxyResponse from an http.Response. It extracts metadata from the response context and dumps the raw response.
func OverrideWaypointsModifier ¶
OverrideWaypointsModifier checks if a Waypoint (host override) is defined for this host:port. If a waypoint exists it will write the "original_host" and "override_host" to the metadata. These values are used later in the `DialContext` function. If the metadata is not found the modifier will return `ErrMetadataNotFound` TODO should allow TLS -> Non TLS override
func PreventLoopModifier ¶
PreventLoopModifier skips processing a request if it is made to marasi's active listener address and port, preventing an infinite loop It will normalize localhost & 127.0.0.1 when checking the host and port
func ResponseFilterModifier ¶
ResponseFilterModifier will perform an initial filtering round on responses. It will skip processing for responses to CONNECT requests, responses where the skip flag was set, or SkipRoundTrip is true. It will also add the response time to the context
func SetupRequestModifier ¶
SetupRequestModifier initializes the request context. It will generate and set the request ID, set the request time, initial and set the metadata map, and stores the Martian session. If the request is coming from launchpad, it will set the launchapd ID in the context
func SkipConnectRequestModifier ¶
SkipConnectRequestModifier will skip processing for CONNECT requests
func WithBasePipeline ¶
WithBasePipeline will setup the base modifier pipeline for marasi It will define the main Request & Response modifiers that will execute the attached modifiers and hande `ErrDropped` and `ErrSkipPipeline`. If a response is dropped the `martian.Session` is read from the context and hijacked to close the `conn`
func WithConfigDir ¶
WithConfigDir configures the proxy to use the specified configuration directory. It creates the directory if it doesn't exist and initializes the configuration file using Viper.
Parameters:
- appConfigDir: Path to the configuration directory
Returns:
- func(*Proxy) error: Configuration function that sets up the config directory
func WithConfigRepository ¶
func WithConfigRepository(repo domain.ConfigRepository) func(*Proxy) error
WithConfigRepository injects the config repository implementation.
func WithDBCloser ¶
WithDBCloser injects the database closer.
func WithDefaultModifierPipeline ¶
WithDefaultPipeline will apply the default modifier pipelines The default processing order is: waypoint overrides → extensions → interception → database storage. WithDefaultModifierPipeline will apply the default modifier pipelines for Requests & Responses. The processing order is: (Request): Compass -> Waypoint -> Extensions -> Checkpoint -> Database Write (Response): Buffer Streaming -> Decompress -> Compass -> Extensions -> Checkpoint -> Database Write
func WithDefaultRepositories ¶
func WithDefaultRepositories(repo RepositoryProvider) func(*Proxy) error
WithDefaultRepositories is a convenience option to apply all repository implementations from a single provider.
func WithExtension ¶
func WithExtension(extension *domain.Extension, options ...func(*extensions.Runtime) error) func(*Proxy) error
TODO: Need to fix this, currently if the extension exists it does not overrite it, this breaks opening new projects WithExtension loads a single extension into the proxy. It prepares the extension's Lua state and adds it to the proxy's extension list.
func WithExtensionRepository ¶
func WithExtensionRepository(repo domain.ExtensionRepository) func(*Proxy) error
WithExtensionRepository injects the extension repository implementation.
func WithExtensions ¶
func WithExtensions(exts []*domain.Extension, options ...func(*extensions.Runtime) error) func(*Proxy) error
WithExtensions loads multiple extensions into the proxy. It iterates through the provided extensions and prepares each one.
func WithInterceptHandler ¶
func WithInterceptHandler(handler func(intercepted *Intercepted) error) func(*Proxy) error
WithInterceptHandler takes a handler function that will be executed on each intercept (Request / Response)
func WithLaunchpadRepository ¶
func WithLaunchpadRepository(repo domain.LaunchpadRepository) func(*Proxy) error
WithLaunchpadRepository injects the launchpad repository implementation.
func WithLogHandler ¶
WithLogHandler takes a handler function that will be executed on each Log
func WithLogRepository ¶
func WithLogRepository(repo domain.LogRepository) func(*Proxy) error
WithLogRepository injects the log repository implementation.
func WithLogger ¶
WithLogger sets the structured logger for the proxy. It performs a nil check to ensure the proxy always has a valid logger.
func WithRequestHandler ¶
func WithRequestHandler(handler func(req domain.ProxyRequest) error) func(*Proxy) error
WithRequestHandler takes a handler function that will be executed on each Request
func WithResponseHandler ¶
func WithResponseHandler(handler func(res domain.ProxyResponse) error) func(*Proxy) error
WithResponseHandler takes a handler function that will be executed on each response
func WithStatsRepository ¶
func WithStatsRepository(repo domain.StatsRepository) func(*Proxy) error
WithStatsRepository injects the stats repository implementation.
func WithTLS ¶
WithTLS will configure the proxy CA based on the proxy.ConfigDir It will also configure the http.Client that is used for the launchpad requests TODO - Check if the certificate expired
func WithTrafficRepository ¶
func WithTrafficRepository(repo domain.TrafficRepository) func(*Proxy) error
WithTrafficRepository injects the traffic repository implementation.
func WithWaypointRepository ¶
func WithWaypointRepository(repo domain.WaypointRepository) func(*Proxy) error
WithWaypointRepository injects the waypoint repository implementation.
func WriteRequestModifier ¶
WriteRequestModifier is the final modifier in the default request pipeline. It will create a `ProxyRequest` struct and queue it for database insertion. If the request came from launchpad, it will create a `LaunchpadRequest` struct and queue it for database insertion as well. If the `proxy.OnRequest` handler is defined, it will be called with the `ProxyRequest` otherwise the modifier will return `ErrRequestHandlerUndefined`
func WriteResponseModifier ¶
WriteResponseModifier is the final modifier in the default response pipeline. It will create a `ProxyResponse` struct and queue it for database insertion. If the `proxy.OnResponse` handler is defined, it will be called with the `ProxyResponse` otherwise the modifier will return `ErrResponseHandlerUndefined`
Types ¶
type ChromePathConfig ¶
type Config ¶
type Config struct {
ConfigDir string `mapstructure:"config_dir"` // Current config dir
DesktopOS string `mapstructure:"desktop_os"` // Operating system identifier
ChromeDirs []ChromePathConfig `mapstructure:"chrome_dirs"`
// contains filtered or unexported fields
}
func (*Config) AddChromePath ¶
func (*Config) DeleteChromePath ¶
type Intercepted ¶
type Intercepted struct {
Type string // "request" or "response"
Raw string // Raw HTTP data that can be modified
Channel chan InterceptionTuple // Channel for receiving user decisions
}
Intercepted represents a request or response that has been intercepted for manual inspection and modification before being allowed to continue.
type InterceptionTuple ¶
type InterceptionTuple struct {
Resume bool // Whether to resume the intercepted item
ShouldInterceptResponse bool // Whether to intercept the corresponding response
}
InterceptionTuple contains the user's decision when an intercepted item is resumed, indicating whether to continue and whether to intercept the corresponding response.
type Proxy ¶
type Proxy struct {
ConfigDir string // The configuration directory (defaults to the marasi folder under the user configuration directory)
Config *Config // The marasi proxy configuration (separate from the GUI config)
Modifiers *fifo.Group // Modifier group pipeline
DBWriteChannel chan any // DB Write Channel
InterceptedQueue []*Intercepted // Queue of intercepted requests / responses
OnRequest func(req domain.ProxyRequest) error // Function to be ran on each request - used by the GUI application to handle the new requests
OnResponse func(res domain.ProxyResponse) error // Function to be ran on each response - used by the GUI application to handle the new responses
OnIntercept func(intercepted *Intercepted) error // Function to be ran on each intercept - used by the GUI application to handle the new intercepted items
OnLog func(log domain.Log) error // Function to be ran on each log event - used by the GUI application to handle new log entries
Addr string // IP Address of the proxy
Port string // Port of the proxy
Client *http.Client // HTTP Client that is used by the repeater functionality (autoconfigured to use the proxy)
Extensions []*extensions.Runtime // Slice of loaded extensions
SPKIHash string // SPKI Hash of the current certificate
Cert *x509.Certificate // The proxy's TLS certificate.
MarasiClientTLSConfig *tls.Config // TLSConfig for the proxy.Client
Scope *compass.Scope // Proxy scope configuration through Compass
Waypoints map[string]string // Map of host:port overrides
InterceptFlag bool // Global intercept flag
TrafficRepo domain.TrafficRepository // Repository for traffic data.
LaunchpadRepo domain.LaunchpadRepository // Repository for launchpad data.
WaypointRepo domain.WaypointRepository // Repository for waypoint data.
StatsRepo domain.StatsRepository // Repository for statistics data.
ConfigRepo domain.ConfigRepository // Repository for configuration data.
LogRepo domain.LogRepository // Repository for log data.
ExtensionRepo domain.ExtensionRepository // Repository for extension data.
DBCloser io.Closer // Closer for the database connection.
Logger *slog.Logger // Logger for Marasi
// contains filtered or unexported fields
}
Proxy is the main struct that orchestrates all proxy functionality including request/response processing, extension management, database operations, and TLS handling. It serves as the central coordinator for the Marasi proxy server.
func New ¶
New creates a new Proxy instance with default configuration and applies any provided options. It initializes the underlying martian proxy, database write channel, extensions map, HTTP client, scope, waypoints, and sets up default log modifiers.
Parameters:
- options: Variadic list of option functions to configure the proxy
Returns:
- *Proxy: Configured proxy instance
- error: Configuration error if any option fails
func (*Proxy) AddRequestModifier ¶
func (proxy *Proxy) AddRequestModifier(modifier RequestModifierFunc)
AddRequestModifier accepts RequestModifierFunc and wraps it in a reqAdapter
func (*Proxy) AddResponseModifier ¶
func (proxy *Proxy) AddResponseModifier(modifier ResponseModifierFunc)
AddResponseModifier accepts ResponseModifierFunc and wraps it in a resAdapter
func (*Proxy) Close ¶
func (proxy *Proxy) Close()
Close shuts down the proxy and closes the database connection.
func (*Proxy) GetClient ¶
GetClient returns the proxy's HTTP client. It returns an error if the client is not set.
func (*Proxy) GetConfigDir ¶
GetConfigDir returns the configuration directory path. It returns an error if the configuration directory is not set.
func (*Proxy) GetExtension ¶
func (proxy *Proxy) GetExtension(name string) (*extensions.Runtime, bool)
GetExtension retrieves a loaded extension by its name. It returns the extension and true if found, otherwise nil and false.
func (*Proxy) GetExtensionRepo ¶
func (proxy *Proxy) GetExtensionRepo() (domain.ExtensionRepository, error)
GetExtensionRepo returns the extension repository. It returns an error if the repository is not set.
func (*Proxy) GetListener ¶
func (*Proxy) GetScope ¶
GetScope returns the current scope configuration. It returns an error if the scope is not set.
func (*Proxy) GetTrafficRepo ¶
func (proxy *Proxy) GetTrafficRepo() (domain.TrafficRepository, error)
GetTrafficRepo returns the traffic repository. It returns an error if the repository is not set.
func (*Proxy) Launch ¶
Launch sends a raw HTTP request through the proxy client. It is used for the launchpad functionality to replay and test requests.
func (*Proxy) Serve ¶
Serve starts the proxy and begins accepting connections on the provided listener. It also starts the database writer goroutine.
func (*Proxy) StartChrome ¶
StartChrome launches Chrome with proxy configuration and security settings. It configures Chrome to use the proxy server, creates an isolated user profile, and disables various Chrome features that might interfere with testing.
Returns:
- error: Chrome launch error if executable not found or process fails to start
func (*Proxy) SyncWaypoints ¶
SyncWaypoints fetches the latest waypoints from the repository and updates the proxy's in-memory map.
func (*Proxy) WithOptions ¶
WithOptions applies a series of configuration functions to the proxy instance. Each option function can modify the proxy configuration and return an error if it fails.
Parameters:
- options: Variadic list of configuration functions
Returns:
- error: First error encountered from any option function
type RepositoryProvider ¶
type RepositoryProvider interface {
domain.TrafficRepository
domain.ExtensionRepository
domain.LaunchpadRepository
domain.WaypointRepository
domain.StatsRepository
domain.ConfigRepository
domain.LogRepository
io.Closer
}
RepositoryProvider defines the interface for a provider of all data repositories used by the proxy. This allows for easy injection of a database implementation.
type RequestModifierFunc ¶
RequestModifierFunc is a signature for HTTP request modifiers, it takes in the request and *Proxy
type ResponseModifierFunc ¶
ResponseModifierFunc is a signature for HTTP response modifiers, it takes in the response and *Proxy
Directories
¶
| Path | Synopsis |
|---|---|
|
Package core provides fundamental utilities for the Marasi proxy, including context management for passing data through the request/response lifecycle.
|
Package core provides fundamental utilities for the Marasi proxy, including context management for passing data through the request/response lifecycle. |
|
Package db provides the database layer for the Marasi application.
|
Package db provides the database layer for the Marasi application. |
|
Package domain defines the core business logic and data structures of the Marasi application.
|
Package domain defines the core business logic and data structures of the Marasi application. |
|
Package extensions provides a sandboxed Lua scripting environment for extending the functionality of the proxy.
|
Package extensions provides a sandboxed Lua scripting environment for extending the functionality of the proxy. |
