Skip to content

Commit

Permalink
Implement redaction of sensitive data in logs (#158)
Browse files Browse the repository at this point in the history
* Implement redaction of sensitive data in logs

* Redact sensitive information from error logging in proxyHTTP
  • Loading branch information
anfragment authored Nov 25, 2024
1 parent f6bb644 commit 1200d1b
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 22 deletions.
3 changes: 2 additions & 1 deletion internal/filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/anfragment/zen/internal/cfg"
"github.com/anfragment/zen/internal/logger"
"github.com/anfragment/zen/internal/rule"
)

Expand Down Expand Up @@ -221,7 +222,7 @@ func (f *Filter) HandleResponse(req *http.Request, res *http.Response) error {
if isDocumentNavigation(req, res) {
if err := f.scriptletsInjector.Inject(req, res); err != nil {
// The error is recoverable, so we log it and continue processing the response.
log.Printf("error injecting scriptlets for %q: %v", req.URL, err)
log.Printf("error injecting scriptlets for %q: %v", logger.Redacted(req.URL), err)
}
}

Expand Down
12 changes: 12 additions & 0 deletions internal/logger/redacted_nonprod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !prod

package logger

import "fmt"

// Redacted redacts sensitive data in production logs.
// In non-production environments, it returns the string representation of the input value.
// In a production environment, it always returns the constant "[REDACTED]" to ensure sensitive information is not exposed.
func Redacted(input any) string {
return fmt.Sprint(input)
}
10 changes: 10 additions & 0 deletions internal/logger/redacted_prod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build prod

package logger

// Redacted redacts sensitive data in production logs.
// In non-production environments, it returns the string representation of the input value.
// In a production environment, it always returns the constant "[REDACTED]" to ensure sensitive information is not exposed.
func Redacted(input any) string {
return "[REDACTED]"
}
28 changes: 15 additions & 13 deletions internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"strings"
"sync"
"time"

"github.com/anfragment/zen/internal/logger"
)

// certGenerator is an interface capable of generating certificates for the proxy.
Expand Down Expand Up @@ -211,7 +213,7 @@ func (p *Proxy) proxyHTTP(w http.ResponseWriter, r *http.Request) {

resp, err := p.requestClient.Do(r)
if err != nil {
log.Printf("error making request: %v", err)
log.Printf("error making request: %v", logger.Redacted(err)) // The error might contain information about the hostname we are connecting to.
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
Expand Down Expand Up @@ -246,7 +248,7 @@ func (p *Proxy) proxyConnect(w http.ResponseWriter, connReq *http.Request) {

clientConn, _, err := hj.Hijack()
if err != nil {
log.Printf("hijacking connection(%s): %v", connReq.Host, err)
log.Printf("hijacking connection(%s): %v", logger.Redacted(connReq.Host), err)
return
}
defer clientConn.Close()
Expand All @@ -261,7 +263,7 @@ func (p *Proxy) proxyConnect(w http.ResponseWriter, connReq *http.Request) {

host, _, err := net.SplitHostPort(connReq.Host)
if err != nil {
log.Printf("splitting host and port(%s): %v", connReq.Host, err)
log.Printf("splitting host and port(%s): %v", logger.Redacted(connReq.Host), err)
return
}

Expand All @@ -274,12 +276,12 @@ func (p *Proxy) proxyConnect(w http.ResponseWriter, connReq *http.Request) {

tlsCert, err := p.certGenerator.GetCertificate(host)
if err != nil {
log.Printf("getting certificate(%s): %v", connReq.Host, err)
log.Printf("getting certificate(%s): %v", logger.Redacted(connReq.Host), err)
return
}

if _, err := clientConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")); err != nil {
log.Printf("writing 200 OK to client(%s): %v", connReq.Host, err)
log.Printf("writing 200 OK to client(%s): %v", logger.Redacted(connReq.Host), err)
return
}

Expand All @@ -297,13 +299,13 @@ func (p *Proxy) proxyConnect(w http.ResponseWriter, connReq *http.Request) {
if err != nil {
if err != io.EOF {
if strings.Contains(err.Error(), "tls: ") {
log.Printf("adding %s to ignored hosts", host)
log.Printf("adding %s to ignored hosts", logger.Redacted(host))
p.ignoredHostsMu.Lock()
p.ignoredHosts = append(p.ignoredHosts, host)
p.ignoredHostsMu.Unlock()
}

log.Printf("reading request(%s): %v", connReq.Host, err)
log.Printf("reading request(%s): %v", logger.Redacted(connReq.Host), err)
}
break
}
Expand All @@ -325,28 +327,28 @@ func (p *Proxy) proxyConnect(w http.ResponseWriter, connReq *http.Request) {
resp, err := p.requestTransport.RoundTrip(req)
if err != nil {
if strings.Contains(err.Error(), "tls: ") {
log.Printf("adding %s to ignored hosts", host)
log.Printf("adding %s to ignored hosts", logger.Redacted(host))
p.ignoredHostsMu.Lock()
p.ignoredHosts = append(p.ignoredHosts, host)
p.ignoredHostsMu.Unlock()
}

log.Printf("roundtrip(%s): %v", connReq.Host, err)
log.Printf("roundtrip(%s): %v", logger.Redacted(connReq.Host), err)
// TODO: better error presentation
response := fmt.Sprintf("HTTP/1.1 502 Bad Gateway\r\n\r\n%s", err.Error())
tlsConn.Write([]byte(response))
break
}

if err := p.filter.HandleResponse(req, resp); err != nil {
log.Printf("error handling response by filter for %s, %v", req.URL, err)
log.Printf("error handling response by filter for %s, %v", logger.Redacted(req.URL), err)
response := fmt.Sprintf("HTTP/1.1 502 Bad Gateway\r\n\r\n%s", err.Error())
tlsConn.Write([]byte(response))
break
}

if err := resp.Write(tlsConn); err != nil {
log.Printf("writing response(%s): %v", connReq.Host, err)
log.Printf("writing response(%s): %v", logger.Redacted(connReq.Host), err)
resp.Body.Close()
break
}
Expand Down Expand Up @@ -383,14 +385,14 @@ func (p *Proxy) shouldMITM(host string) bool {
func (p *Proxy) tunnel(w net.Conn, r *http.Request) {
remoteConn, err := net.Dial("tcp", r.Host)
if err != nil {
log.Printf("dialing remote(%s): %v", r.Host, err)
log.Printf("dialing remote(%s): %v", logger.Redacted(r.Host), err)
w.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
return
}
defer remoteConn.Close()

if _, err := w.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")); err != nil {
log.Printf("writing 200 OK to client(%s): %v", r.Host, err)
log.Printf("writing 200 OK to client(%s): %v", logger.Redacted(r.Host), err)
return
}

Expand Down
14 changes: 8 additions & 6 deletions internal/proxy/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (
"log"
"net/http"
"strings"

"github.com/anfragment/zen/internal/logger"
)

func (p *Proxy) proxyWebsocketTLS(req *http.Request, tlsConfig *tls.Config, clientConn *tls.Conn) {
dialer := &tls.Dialer{NetDialer: p.netDialer, Config: tlsConfig}
targetConn, err := dialer.Dial("tcp", req.URL.Host)
if err != nil {
log.Printf("dialing websocket backend(%s): %v", req.URL.Host, err)
log.Printf("dialing websocket backend(%s): %v", logger.Redacted(req.URL.Host), err)
clientConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
return
}
Expand All @@ -30,7 +32,7 @@ func (p *Proxy) proxyWebsocket(w http.ResponseWriter, req *http.Request) {
targetConn, err := p.netDialer.Dial("tcp", req.URL.Host)
if err != nil {
w.WriteHeader(http.StatusBadGateway)
log.Printf("dialing websocket backend(%s): %v", req.URL.Host, err)
log.Printf("dialing websocket backend(%s): %v", logger.Redacted(req.URL.Host), err)
return
}
defer targetConn.Close()
Expand All @@ -43,7 +45,7 @@ func (p *Proxy) proxyWebsocket(w http.ResponseWriter, req *http.Request) {
}
clientConn, _, err := hj.Hijack()
if err != nil {
log.Printf("hijacking websocket client(%s): %v", req.URL.Host, err)
log.Printf("hijacking websocket client(%s): %v", logger.Redacted(req.URL.Host), err)
return
}

Expand All @@ -58,7 +60,7 @@ func websocketHandshake(req *http.Request, targetConn io.ReadWriter, clientConn
err := req.Write(targetConn)
if err != nil {
clientConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
log.Printf("writing websocket request to backend(%s): %v", req.URL.Host, err)
log.Printf("writing websocket request to backend(%s): %v", logger.Redacted(req.URL.Host), err)
return err
}

Expand All @@ -67,14 +69,14 @@ func websocketHandshake(req *http.Request, targetConn io.ReadWriter, clientConn
resp, err := http.ReadResponse(targetReader, req)
if err != nil {
clientConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
log.Printf("reading websocket response from backend(%s): %v", req.URL.Host, err)
log.Printf("reading websocket response from backend(%s): %v", logger.Redacted(req.URL.Host), err)
return err
}
defer resp.Body.Close()

err = resp.Write(clientConn)
if err != nil {
log.Printf("writing websocket response to client(%s): %v", req.URL.Host, err)
log.Printf("writing websocket response to client(%s): %v", logger.Redacted(req.URL.Host), err)
return err
}

Expand Down
6 changes: 4 additions & 2 deletions internal/scriptlet/injector.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"strings"

"github.com/andybalholm/brotli"
"github.com/anfragment/zen/internal/logger"
"github.com/klauspost/compress/zstd"
"golang.org/x/net/html/charset"
)
Expand Down Expand Up @@ -68,8 +69,9 @@ func NewInjector(store Store) (*Injector, error) {
//
// In case of an error, the response body is unchanged and the caller may proceed as if the function had not been called.
func (inj *Injector) Inject(req *http.Request, res *http.Response) error {
scriptlets := inj.store.Get(req.URL.Hostname())
log.Printf("got %d scriptlets for %q", len(scriptlets), req.URL.Hostname())
hostname := req.URL.Hostname()
scriptlets := inj.store.Get(hostname)
log.Printf("got %d scriptlets for %q", len(scriptlets), logger.Redacted(hostname))
if len(scriptlets) == 0 {
return nil
}
Expand Down

0 comments on commit 1200d1b

Please sign in to comment.