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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## v1.20.0 (31 January 2026)

- Fix transparent mode not picking up rules or logging traffic.

## v1.19.3 (31 January 2026)

- Added TPROXY support for transparent proxy mode (enables handling forwarded traffic from Tailscale exit nodes and routers)
Expand Down
110 changes: 66 additions & 44 deletions gatesentryproxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,31 +321,23 @@ func (h ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
action = ACTION_SSL_BUMP
}

var ruleMatch interface{}
ruleShouldMITM := false
ruleMatched := false
if IProxy.RuleMatchHandler != nil {
host, _, _ := net.SplitHostPort(r.URL.Host)
if host == "" {
host = r.URL.Host
}
ruleMatch = IProxy.RuleMatchHandler(host, user)

if ruleMatch != nil {
passthru.UserData = ruleMatch

matchVal := reflect.ValueOf(ruleMatch)
if matchVal.Kind() == reflect.Struct {
matchedField := matchVal.FieldByName("Matched")
if matchedField.IsValid() && matchedField.Kind() == reflect.Bool && matchedField.Bool() {
ruleMatched = true
mitmField := matchVal.FieldByName("ShouldMITM")
if mitmField.IsValid() && mitmField.Kind() == reflect.Bool {
ruleShouldMITM = mitmField.Bool()
}
}
}
requestHost, _, _ := net.SplitHostPort(r.URL.Host)
if requestHost == "" {
requestHost = r.URL.Host
}

shouldBlock, ruleMatch, ruleShouldMITM := CheckProxyRules(requestHost, user)
if shouldBlock {
if DebugLogging {
log.Printf("[Proxy] Blocking request to %s by rule", r.URL.String())
}
LogProxyAction(r.URL.String(), user, ProxyActionBlockedUrl)
return
}

ruleMatched := ruleMatch != nil
if ruleMatch != nil {
passthru.UserData = ruleMatch
}

shouldMitm := IProxy.DoMitm(r.URL.Host)
Expand Down Expand Up @@ -374,26 +366,6 @@ func (h ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}

// Block entire domain if rule says to block
if ruleMatched && passthru.UserData != nil {
matchVal := reflect.ValueOf(passthru.UserData)
if matchVal.Kind() == reflect.Struct {
shouldBlockField := matchVal.FieldByName("ShouldBlock")
urlRegexField := matchVal.FieldByName("BlockURLRegexes")

log.Println("Checking for domain block for ", r.URL.String())
// If action is block and no URL patterns, block entire domain
if shouldBlockField.IsValid() && shouldBlockField.Kind() == reflect.Bool && shouldBlockField.Bool() {
if !urlRegexField.IsValid() || urlRegexField.Len() == 0 {
passthru.ProxyActionToLog = ProxyActionBlockedUrl
IProxy.LogHandler(GSLogData{Url: r.URL.String(), User: user, Action: ProxyActionBlockedUrl})
// Just close connection - don't establish tunnel
return
}
}
}
}

if action == ACTION_SSL_BUMP {
HandleSSLBump(r, w, user, authUser, passthru, IProxy)
return
Expand Down Expand Up @@ -656,6 +628,56 @@ func sendBlockMessageBytes(w http.ResponseWriter, r *http.Request, resp *http.Re

}

// CheckProxyRules checks proxy rules for a given host and user.
// Returns: shouldBlock (bool), ruleMatch (interface{}), shouldMITM (bool)
func CheckProxyRules(host string, user string) (bool, interface{}, bool) {
if IProxy == nil || IProxy.RuleMatchHandler == nil {
return false, nil, false
}

ruleMatch := IProxy.RuleMatchHandler(host, user)
if ruleMatch == nil {
return false, nil, false
}

matchVal := reflect.ValueOf(ruleMatch)
if matchVal.Kind() != reflect.Struct {
return false, nil, false
}

matchedField := matchVal.FieldByName("Matched")
if !matchedField.IsValid() || matchedField.Kind() != reflect.Bool || !matchedField.Bool() {
return false, nil, false
}

shouldBlockField := matchVal.FieldByName("ShouldBlock")
urlRegexField := matchVal.FieldByName("BlockURLRegexes")
mitmField := matchVal.FieldByName("ShouldMITM")

shouldBlock := false
shouldMITM := false

if shouldBlockField.IsValid() && shouldBlockField.Kind() == reflect.Bool && shouldBlockField.Bool() {
// Only block if no URL regexes specified (domain-level block)
if !urlRegexField.IsValid() || urlRegexField.Len() == 0 {
shouldBlock = true
}
}

if mitmField.IsValid() && mitmField.Kind() == reflect.Bool {
shouldMITM = mitmField.Bool()
}

return shouldBlock, ruleMatch, shouldMITM
}

// LogProxyAction logs a proxy action with the given URL, user, and action
func LogProxyAction(url string, user string, action ProxyAction) {
if IProxy != nil && IProxy.LogHandler != nil {
IProxy.LogHandler(GSLogData{Url: url, User: user, Action: action})
}
}

// copyResponseHeader writes resp's header and status code to w.
func copyResponseHeader(w http.ResponseWriter, resp *http.Response) {
newHeader := w.Header()
Expand Down
39 changes: 39 additions & 0 deletions gatesentryproxy/transparent_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ func (l *TransparentProxyListener) handleTransparentHTTP(conn net.Conn, original
req.Host = originalDst
}

// Check proxy rules before processing
host, _, _ := net.SplitHostPort(originalDst)
if host == "" {
host = originalDst
}
user := ""

shouldBlock, _, _ := CheckProxyRules(host, user)
if shouldBlock {
if DebugLogging {
log.Printf("[Transparent] Blocking HTTP request to %s by rule", originalDst)
}
LogProxyAction(req.URL.String(), user, ProxyActionBlockedUrl)
conn.Close()
return
}

respWriter := &connResponseWriter{
conn: conn,
header: make(http.Header),
Expand Down Expand Up @@ -156,11 +173,32 @@ func (l *TransparentProxyListener) handleTransparentHTTPS(conn net.Conn, origina

passthru := NewGSProxyPassthru()

// Check proxy rules before processing
shouldBlock, ruleMatch, ruleShouldMitm := CheckProxyRules(host, user)
if shouldBlock {
if DebugLogging {
log.Printf("[Transparent] Blocking HTTPS connection to %s by rule", serverAddr)
}
LogProxyAction("https://"+serverAddr, user, ProxyActionBlockedUrl)
conn.Close()
return
}

// Store rule match data for later use (MITM decision, URL regex checking)
if ruleMatch != nil {
passthru.UserData = ruleMatch
}

shouldMitm := false
if IProxy != nil && IProxy.DoMitm != nil {
shouldMitm = IProxy.DoMitm(serverAddr)
}

// If rule matched and specified MITM setting, use it
if ruleMatch != nil {
shouldMitm = ruleShouldMitm
}

if shouldMitm {
if DebugLogging {
log.Printf("[Transparent] Performing SSL Bump for %s", serverAddr)
Expand All @@ -170,6 +208,7 @@ func (l *TransparentProxyListener) handleTransparentHTTPS(conn net.Conn, origina
if DebugLogging {
log.Printf("[Transparent] Direct tunnel for %s", serverAddr)
}
LogProxyAction("https://"+serverAddr, user, ProxyActionSSLDirect)
ConnectDirect(conn, serverAddr, nil, passthru)
}
}
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var GSPROXYPORT = "10413"
var GSWEBADMINPORT = "10786"
var GSBASEDIR = ""
var Baseendpointv2 = "https://www.gatesentryfilter.com/api/"
var GATESENTRY_VERSION = "1.19.3"
var GATESENTRY_VERSION = "1.20.0"
var GS_BOUND_ADDRESS = ":"
var R *application.GSRuntime

Expand Down
Loading