mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
add event type & logging interceptor for unknown hosts (#157)
* add event type & logging interceptor for unknown hosts * update logging * add break * rename host_observation interceptor to audit_logger * restore MITMDecider and make AuditLogger telemetry skip MITM on CONNECT
This commit is contained in:
@@ -76,3 +76,8 @@ type Interceptor interface {
|
||||
// Called for each request matching ShouldIntercept
|
||||
HandleRequest(ctx *RequestContext) (*InterceptorResponse, error)
|
||||
}
|
||||
|
||||
// MITMDecider is optional; implement to control whether CONNECT requests are MITM’d.
|
||||
type MITMDecider interface {
|
||||
ShouldMITM(ctx *RequestContext) bool
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package interceptors
|
||||
|
||||
import (
|
||||
"github.com/safedep/pmg/internal/eventlog"
|
||||
"github.com/safedep/pmg/proxy"
|
||||
)
|
||||
|
||||
// AuditLoggerInterceptor logs unknown outbound hosts observed by proxy mode.
|
||||
// It is passive telemetry only and never blocks or mutates requests.
|
||||
type AuditLoggerInterceptor struct{}
|
||||
|
||||
var _ proxy.Interceptor = (*AuditLoggerInterceptor)(nil)
|
||||
var _ proxy.MITMDecider = (*AuditLoggerInterceptor)(nil)
|
||||
|
||||
func NewAuditLoggerInterceptor() *AuditLoggerInterceptor {
|
||||
return &AuditLoggerInterceptor{}
|
||||
}
|
||||
|
||||
func (i *AuditLoggerInterceptor) Name() string {
|
||||
return "audit-logger-interceptor"
|
||||
}
|
||||
|
||||
// ShouldIntercept is always true so we can observe all proxied traffic.
|
||||
func (i *AuditLoggerInterceptor) ShouldIntercept(_ *proxy.RequestContext) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ShouldMITM is false because this interceptor is telemetry-only.
|
||||
func (i *AuditLoggerInterceptor) ShouldMITM(_ *proxy.RequestContext) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *AuditLoggerInterceptor) HandleRequest(ctx *proxy.RequestContext) (*proxy.InterceptorResponse, error) {
|
||||
if ctx == nil || ctx.Hostname == "" {
|
||||
return &proxy.InterceptorResponse{Action: proxy.ActionAllow}, nil
|
||||
}
|
||||
|
||||
if i.isKnownRegistryHost(ctx.Hostname) {
|
||||
return &proxy.InterceptorResponse{Action: proxy.ActionAllow}, nil
|
||||
}
|
||||
|
||||
eventlog.LogProxyHostObserved(ctx.Hostname, ctx.Method, "audit_logger_interceptor", map[string]interface{}{
|
||||
"request_id": ctx.RequestID,
|
||||
})
|
||||
|
||||
return &proxy.InterceptorResponse{Action: proxy.ActionAllow}, nil
|
||||
}
|
||||
|
||||
func (i *AuditLoggerInterceptor) isKnownRegistryHost(hostname string) bool {
|
||||
return npmRegistryDomains.ContainsHostname(hostname) || pypiRegistryDomains.ContainsHostname(hostname)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package interceptors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/safedep/pmg/proxy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAuditLoggerInterceptor_Behavior(t *testing.T) {
|
||||
i := NewAuditLoggerInterceptor()
|
||||
|
||||
assert.Equal(t, "audit-logger-interceptor", i.Name())
|
||||
assert.True(t, i.ShouldIntercept(nil))
|
||||
assert.False(t, i.ShouldMITM(nil))
|
||||
}
|
||||
|
||||
func TestAuditLoggerInterceptor_KnownRegistryHost(t *testing.T) {
|
||||
i := NewAuditLoggerInterceptor()
|
||||
|
||||
resp, err := i.HandleRequest(&proxy.RequestContext{
|
||||
Hostname: "registry.npmjs.org",
|
||||
Method: http.MethodConnect,
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, proxy.ActionAllow, resp.Action)
|
||||
}
|
||||
|
||||
func TestAuditLoggerInterceptor_UnknownHost(t *testing.T) {
|
||||
i := NewAuditLoggerInterceptor()
|
||||
|
||||
resp, err := i.HandleRequest(&proxy.RequestContext{
|
||||
Hostname: "unknown.example.test",
|
||||
Method: http.MethodConnect,
|
||||
RequestID: "req-unknown",
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, proxy.ActionAllow, resp.Action)
|
||||
}
|
||||
+18
-4
@@ -212,11 +212,25 @@ func (ps *proxyServer) configureMITM() {
|
||||
ps.mu.RLock()
|
||||
shouldMITM := false
|
||||
for _, interceptor := range ps.interceptors {
|
||||
if interceptor.ShouldIntercept(reqCtx) {
|
||||
shouldMITM = true
|
||||
log.Debugf("[%s] Interceptor %s will handle %s", reqCtx.RequestID, interceptor.Name(), host)
|
||||
break
|
||||
if !interceptor.ShouldIntercept(reqCtx) {
|
||||
continue
|
||||
}
|
||||
|
||||
mitm := true
|
||||
if decider, ok := interceptor.(MITMDecider); ok {
|
||||
mitm = decider.ShouldMITM(reqCtx)
|
||||
}
|
||||
|
||||
if !mitm {
|
||||
// Allow non-MITM interceptors (e.g., telemetry) to observe CONNECT traffic.
|
||||
if _, err := interceptor.HandleRequest(reqCtx); err != nil {
|
||||
log.Errorf("[%s] Interceptor %s error on CONNECT: %v", reqCtx.RequestID, interceptor.Name(), err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
shouldMITM = true
|
||||
log.Debugf("[%s] Interceptor %s will handle %s", reqCtx.RequestID, interceptor.Name(), host)
|
||||
}
|
||||
ps.mu.RUnlock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user