Skip to content

Commit

Permalink
Merge pull request #243 from pace/add-redaction-system
Browse files Browse the repository at this point in the history
add redaction system package #238
  • Loading branch information
daemonfire300 authored Jan 5, 2021
2 parents 91e17a6 + 254f3c0 commit 08348d0
Show file tree
Hide file tree
Showing 14 changed files with 401 additions and 11 deletions.
11 changes: 0 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
github.com/adjust/gocheck v0.0.0-20131111155431-fbc315b36e0e h1:eiFUF06iaKUDS3HVFSlRYEL0ddnQ+HAGIis/kENW+Ug=
github.com/adjust/gocheck v0.0.0-20131111155431-fbc315b36e0e/go.mod h1:x8X/algNhAAR28ODU+0TzjBwcr7CHA1F/o27Ov/rFGQ=
github.com/adjust/rmq v1.0.0 h1:VTD1iLXIQD3tr4mQlgOOOkz6jMbIiKdnpDXQyAqPOLQ=
github.com/adjust/rmq/v2 v2.0.0-20200523123200-98c5e969f342 h1:B3oELdorwpFJeJeqXEMV8grlXIGeKWmPONdUO2ghH2c=
github.com/adjust/rmq/v2 v2.0.0-20200523123200-98c5e969f342/go.mod h1:fWScdzE1sTK0bCeW2abHbcq24cwiKrbXWnPiYL+ijWM=
github.com/adjust/rmq/v2 v2.0.0 h1:6zOAGye6y9X91M3a7u5ROFiOlv0x9IuIfYVwthfSx+g=
github.com/adjust/rmq/v2 v2.0.0/go.mod h1:fWScdzE1sTK0bCeW2abHbcq24cwiKrbXWnPiYL+ijWM=
github.com/adjust/rmq/v3 v3.0.0 h1:+tfWjcbcK+O09WTEa/wzmxmGuvC0FgtKCbNKjI8aXmY=
github.com/adjust/rmq/v3 v3.0.0/go.mod h1:rji/DBwOpm3DfRfSYS/w8IrVRMz9+P+ffm4nQXPC0Bw=
github.com/adjust/uniuri v0.0.0-20130923163420-498743145e60 h1:ogL5Ct/E8o3w/QiBWDFJV9fOXglEiXI+YaYIqWNCJ8Y=
github.com/adjust/uniuri v0.0.0-20130923163420-498743145e60/go.mod h1:pgVmNTYfZOWG+PrCVPcvgUy5Z/uowI78tK8ARMsdVXw=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
Expand Down Expand Up @@ -207,5 +198,3 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
4 changes: 4 additions & 0 deletions http/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/pace/bricks/maintenance/log"
"github.com/pace/bricks/maintenance/metric"
"github.com/pace/bricks/maintenance/tracing"
redactMdw "github.com/pace/bricks/pkg/redact/middleware"
)

// Router returns the default microservice endpoints for
Expand Down Expand Up @@ -46,6 +47,9 @@ func Router() *mux.Router {
// report use of external dependencies
r.Use(middleware.ExternalDependency)

// support redacting of data accross the full request scope
r.Use(redactMdw.Redact)

// makes some infos about the request accessable from the context
r.Use(middleware.RequestInContext)

Expand Down
25 changes: 25 additions & 0 deletions http/transport/dump_round_tripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/caarlos0/env"
"github.com/pace/bricks/maintenance/log"
"github.com/pace/bricks/pkg/redact"
)

// DumpRoundTripper dumps requests and responses in one log event.
Expand All @@ -22,6 +23,7 @@ type DumpRoundTripper struct {
DumpRequestHEX bool
DumpResponseHEX bool
DumpBody bool
DumpNoRedact bool
}

type DumpRoundTripperOption string
Expand All @@ -32,6 +34,7 @@ const (
DumpRoundTripperOptionRequestHEX = "request-hex"
DumpRoundTripperOptionResponseHEX = "response-hex"
DumpRoundTripperOptionBody = "body"
DumpRoundTripperOptionNoRedact = "no-redact"
)

// NewDumpRoundTripperEnv creates a new RoundTripper based on the configuration
Expand Down Expand Up @@ -65,6 +68,8 @@ func NewDumpRoundTripper(options ...string) *DumpRoundTripper {
rt.DumpResponseHEX = true
case DumpRoundTripperOptionBody:
rt.DumpBody = true
case DumpRoundTripperOptionNoRedact:
rt.DumpNoRedact = true
default:
log.Fatalf("Failed to parse dump round tripper options from env: %v", option)
}
Expand All @@ -89,6 +94,13 @@ func (l DumpRoundTripper) AnyEnabled() bool {

// RoundTrip executes a single HTTP transaction via Transport()
func (l *DumpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
var redactor *redact.PatternRedactor

// check if the content redaction is enabled
if !l.DumpNoRedact {
redactor = redact.Ctx(req.Context())
}

// fast path if logging is disabled
if !l.AnyEnabled() {
return l.Transport().RoundTrip(req)
Expand All @@ -102,6 +114,12 @@ func (l *DumpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
if err != nil {
reqDump = []byte(err.Error())
}

// in case a redactor is present, redact the content before logging
if redactor != nil {
reqDump = []byte(redactor.Mask(string(reqDump)))
}

if l.DumpRequest {
dl = dl.Bytes(DumpRoundTripperOptionRequest, reqDump)
}
Expand All @@ -114,12 +132,19 @@ func (l *DumpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
if err != nil {
return resp, err
}

// response logging
if l.DumpResponse || l.DumpResponseHEX {
respDump, err := httputil.DumpResponse(resp, l.DumpBody)
if err != nil {
respDump = []byte(err.Error())
}

// in case a redactor is present, redact the content before logging
if redactor != nil {
respDump = []byte(redactor.Mask(string(respDump)))
}

if l.DumpResponse {
dl = dl.Bytes(DumpRoundTripperOptionResponse, respDump)
}
Expand Down
25 changes: 25 additions & 0 deletions http/transport/dump_round_tripper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/pace/bricks/maintenance/log"
"github.com/pace/bricks/pkg/redact"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -57,6 +58,30 @@ func TestNewDumpRoundTripper(t *testing.T) {
assert.Contains(t, out.String(), `"message":"HTTP Transport Dump"`)
}

func TestNewDumpRoundTripperRedacted(t *testing.T) {
out := &bytes.Buffer{}
ctx := log.Output(out).WithContext(context.Background())

rt := NewDumpRoundTripper(
DumpRoundTripperOptionRequest,
DumpRoundTripperOptionResponse,
DumpRoundTripperOptionBody,
)

req := httptest.NewRequest("GET", "/foo", bytes.NewBufferString("Foo DE12345678909876543210 bar"))
ctx = redact.Default.WithContext(ctx)
req = req.WithContext(ctx)
rt.SetTransport(&transportWithResponse{})

_, err := rt.RoundTrip(req)
assert.NoError(t, err)

assert.Contains(t, out.String(), `"level":"debug"`)
assert.Contains(t, out.String(), `"request":"GET /foo HTTP/1.1\r\nHost: example.com\r\n\r\nFoo ******************3210 bar"`)
assert.Contains(t, out.String(), `"response":"HTTP/0.0 000 status code 0\r\nContent-Length: 0\r\n\r\n"`)
assert.Contains(t, out.String(), `"message":"HTTP Transport Dump"`)
}

func TestNewDumpRoundTripperSimple(t *testing.T) {
out := &bytes.Buffer{}
ctx := log.Output(out).WithContext(context.Background())
Expand Down
4 changes: 4 additions & 0 deletions maintenance/errors/raven/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/certifi/gocertifi"
"github.com/pace/bricks/maintenance/log"
"github.com/pace/bricks/pkg/redact"
pkgErrors "github.com/pkg/errors"
)

Expand Down Expand Up @@ -970,6 +971,9 @@ func serializedPacket(packet *Packet) (io.Reader, string, error) {
return nil, "", fmt.Errorf("error marshaling packet %+v to JSON: %v", packet, err)
}

// redact all data going out to sentry using the default redactor
packetJSON = []byte(redact.Default.Mask(string(packetJSON)))

// Only deflate/base64 the packet if it is bigger than 1KB, as there is
// overhead.
if len(packetJSON) > 1000 {
Expand Down
2 changes: 2 additions & 0 deletions pkg/context/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/pace/bricks/locale"
"github.com/pace/bricks/maintenance/errors"
"github.com/pace/bricks/maintenance/log"
"github.com/pace/bricks/pkg/redact"
)

// Transfer takes the logger, log.Sink, authentication, request and
Expand All @@ -20,5 +21,6 @@ func Transfer(in context.Context) context.Context {
out = oauth2.ContextTransfer(in, out)
out = errors.ContextTransfer(in, out)
out = http.ContextTransfer(in, out)
out = redact.ContextTransfer(in, out)
return locale.ContextTransfer(in, out)
}
30 changes: 30 additions & 0 deletions pkg/redact/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright © 2020 by PACE Telematics GmbH. All rights reserved.
// Created at 2020/12/18 by Vincent Landgraf

package redact

import "context"

type patternRedactorKey struct{}

// WithContext allows storing the PatternRedactor inside a context for passing it on
func (r *PatternRedactor) WithContext(ctx context.Context) context.Context {
return context.WithValue(ctx, patternRedactorKey{}, r)
}

// Ctx returns the PatternRedactor stored within the context. If no redactor
// has been defined, an empty redactor is returned that does nothing
func Ctx(ctx context.Context) *PatternRedactor {
if rd, ok := ctx.Value(patternRedactorKey{}).(*PatternRedactor); ok {
return rd.Clone()
}
return NewPatternRedactor(RedactionSchemeDoNothing())
}

// ContextTransfer copies a request representation from one context to another.
func ContextTransfer(ctx, targetCtx context.Context) context.Context {
if redactor := Ctx(ctx); redactor != nil {
return context.WithValue(targetCtx, patternRedactorKey{}, redactor)
}
return targetCtx
}
15 changes: 15 additions & 0 deletions pkg/redact/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright © 2021 by PACE Telematics GmbH. All rights reserved.
// Created at 2021/01/04 by Vincent Landgraf

package redact

// redactionSafe last 4 digits are usually concidered safe (e.g. credit cards, iban, ...)
const redactionSafe = 4

var Default *PatternRedactor

func init() {
scheme := RedactionSchemeKeepLastJWTNoSignature(redactionSafe)
Default = NewPatternRedactor(scheme)
Default.AddPatterns(AllPatterns...)
}
24 changes: 24 additions & 0 deletions pkg/redact/middleware/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright © 2020 by PACE Telematics GmbH. All rights reserved.
// Created at 2020/12/18 by Vincent Landgraf

package middleware

import (
"net/http"

"github.com/pace/bricks/pkg/redact"
)

// Redact provides a pattern redactor middleware to the request context
func Redact(next http.Handler) http.Handler {
return RedactWithScheme(next, redact.Default)
}

// RedactWithScheme provides a pattern redactor middleware to the request context
// using the provided scheme
func RedactWithScheme(next http.Handler, redactor *redact.PatternRedactor) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := redactor.WithContext(r.Context())
next.ServeHTTP(w, r.WithContext(ctx))
})
}
52 changes: 52 additions & 0 deletions pkg/redact/pattern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright © 2020 by PACE Telematics GmbH. All rights reserved.
// Created at 2020/12/18 by Vincent Landgraf

package redact

import "regexp"

// Sources:
// CreditCard: https://www.regular-expressions.info/creditcard.html

// AllPatterns is a list of all default redaction patterns
var AllPatterns = []*regexp.Regexp{
PatternIBAN,
PatternJWT,
PatternCCVisa,
PatternCCMasterCard,
PatternCCAmericanExpress,
PatternCCDinersClub,
PatternCCDiscover,
PatternCCJCB,
}

var (
PatternIBAN = regexp.MustCompile(
`[a-zA-Z]{2}` + // DE, NL, ...
`[0-9]{2}` + // 80
`(?:[ ]?[0-9a-zA-Z]{4})` + // 5001, INGB
`(?:[ ]?[0-9]{4}){2,3}` + // 0517 2589 4683, 8731 3269
`(?:[ ]?[0-9]{1,2})?`, // 43, 66
)

// All Visa card numbers start with a 4. New cards have 16 digits. Old cards have 13.
PatternCCVisa = regexp.MustCompile(`4[0-9]{12}(?:[0-9]{3})?`)

// MasterCard numbers either start with the numbers 51 through 55 or with the numbers 2221 through 2720. All have 16 digits.
PatternCCMasterCard = regexp.MustCompile(`(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}`)

// American Express card numbers start with 34 or 37 and have 15 digits.
PatternCCAmericanExpress = regexp.MustCompile(`3[47][0-9]{13}`)

// Diners Club card numbers begin with 300 through 305, 36 or 38. All have 14 digits. There are Diners Club cards that begin with 5 and have 16 digits. These are a joint venture between Diners Club and MasterCard, and should be processed like a MasterCard.
PatternCCDinersClub = regexp.MustCompile(`3(?:0[0-5]|[68][0-9])[0-9]{11}`)

// Discover card numbers begin with 6011 or 65. All have 16 digits.
PatternCCDiscover = regexp.MustCompile(`6(?:011|5[0-9]{2})[0-9]{12}`)

// JCB cards beginning with 2131 or 1800 have 15 digits. JCB cards beginning with 35 have 16 digits.
PatternCCJCB = regexp.MustCompile(`(?:2131|1800|35\d{3})\d{11}`)

// PatternJWT JsonWebToken
PatternJWT = regexp.MustCompile(`(?:ey[a-zA-Z0-9=_-]+.){2}.[a-zA-Z0-9=_-]+`)
)
57 changes: 57 additions & 0 deletions pkg/redact/pattern_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright © 2020 by PACE Telematics GmbH. All rights reserved.
// Created at 2020/12/18 by Vincent Landgraf

package redact

import (
"fmt"
"regexp"
"testing"

"github.com/stretchr/testify/assert"
)

func validatePattern(t *testing.T, p *regexp.Regexp, s string, expected bool) {
assert.Equal(t, expected, p.MatchString(s), fmt.Sprintf("expected %q to match %v, but didn't", s, p))
}

func TestIBANPattern(t *testing.T) {
validatePattern(t, PatternIBAN, "NL29INGB8731326943", true)
validatePattern(t, PatternIBAN, "DE80500105172589468366", true)
validatePattern(t, PatternIBAN, "NL29 INGB 8731 3269 43", true)
validatePattern(t, PatternIBAN, "DE80 5001 0517 2589 4683 66", true)
validatePattern(t, PatternIBAN, "CM7168156782527355483576522", true)
validatePattern(t, PatternIBAN, "TL045597565817778146141", true)
validatePattern(t, PatternIBAN, "AL85214511261456316638277339", true)
validatePattern(t, PatternIBAN, "fL20-ING-B0-00-12-34-567", false)
validatePattern(t, PatternIBAN, "fX22YYY1234567890123", true)
validatePattern(t, PatternIBAN, "[email protected]", false)
}

func TestCreditCardPattern(t *testing.T) {
// Testnumbers from https://www.paypalobjects.com/en_AU/vhelp/paypalmanager_help/credit_card_numbers.htm
validatePattern(t, PatternCCAmericanExpress, "378282246310005", true)
validatePattern(t, PatternCCAmericanExpress, "371449635398431", true)
validatePattern(t, PatternCCAmericanExpress, "378734493671000", true)

validatePattern(t, PatternCCDinersClub, "30569309025904", true)
validatePattern(t, PatternCCDinersClub, "38520000023237", true)

validatePattern(t, PatternCCDiscover, "6011111111111117", true)
validatePattern(t, PatternCCDiscover, "6011000990139424", true)

validatePattern(t, PatternCCJCB, "3530111333300000", true)
validatePattern(t, PatternCCJCB, "3566002020360505", true)

validatePattern(t, PatternCCMasterCard, "5555555555554444", true)
validatePattern(t, PatternCCMasterCard, "5105105105105100", true)

validatePattern(t, PatternCCVisa, "4111111111111111", true)
validatePattern(t, PatternCCVisa, "4012888888881881", true)
validatePattern(t, PatternCCVisa, "4222222222222", true)
}

func TestPatternJWT(t *testing.T) {
validatePattern(t, PatternJWT, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", true)
validatePattern(t, PatternJWT, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI", true)
}
Loading

0 comments on commit 08348d0

Please sign in to comment.