From 9a1b41b1addc0333211b11da06f3711d2fd3c52e Mon Sep 17 00:00:00 2001 From: Natalia Goc Date: Fri, 13 Sep 2024 15:48:16 +0200 Subject: [PATCH] feat: new helpers and masking/encrypting attributes New helper functions were added to make call-site less likely to need to pull `go.opentelemetry.io/otel/attribute` as a dependency. Additionally `Encrypted` and `Masked` were added to add a possibility of logging sensitive data in a more secure manner. --- example/main.go | 6 ++ go.mod | 4 +- go.sum | 31 +------- pkg/attr/attr.go | 192 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 201 insertions(+), 32 deletions(-) diff --git a/example/main.go b/example/main.go index ca85833..26f2fe2 100644 --- a/example/main.go +++ b/example/main.go @@ -8,6 +8,7 @@ import ( "os/signal" "time" + "git.ma-al.com/maal-libraries/observer/pkg/attr" "git.ma-al.com/maal-libraries/observer/pkg/attr/layer_attr" "git.ma-al.com/maal-libraries/observer/pkg/event" "git.ma-al.com/maal-libraries/observer/pkg/exporters" @@ -111,6 +112,11 @@ func Serv(ctx context.Context) *fiber.Error { return fiber.NewError(500, "xd") } + span.SetAttributes( + attr.Masked("some_masked_value", "some_masked_value"), + attr.Encrypted("some_encrypted_value", "some_encrypted_value"), + ) + return fiber.NewError(500, "x") } diff --git a/go.mod b/go.mod index 6b5b4bc..09dcb02 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,13 @@ go 1.21 require ( github.com/gofiber/fiber/v2 v2.52.4 - github.com/psmarcin/fiber-opentelemetry v1.2.0 + github.com/gofrs/uuid v4.4.0+incompatible go.opentelemetry.io/otel v1.26.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 go.opentelemetry.io/otel/sdk v1.26.0 go.opentelemetry.io/otel/trace v1.26.0 gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0 + gorm.io/gorm v1.25.11 ) require ( @@ -39,5 +40,4 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect - gorm.io/gorm v1.25.11 // indirect ) diff --git a/go.sum b/go.sum index 27ef01b..b9d2730 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,18 @@ -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -26,7 +23,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -38,23 +34,17 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/psmarcin/fiber-opentelemetry v1.2.0 h1:3e3bz1jmwKMnoM5RVU4YzMXBq8tZQzzMDyM7DW1mTz8= -github.com/psmarcin/fiber-opentelemetry v1.2.0/go.mod h1:qcEVkzlD0GrjtCS+hd5/0QhbTOy12KNyaqmp4yfXi1c= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= @@ -65,33 +55,18 @@ go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgS go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= -go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= @@ -104,8 +79,6 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0 h1:Xg23ydYYJLmb9AK3XdcEpplHZd1MpN3X2ZeeMoBClmY= gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0/go.mod h1:CeDeqW4tj9FrgZXF/dQCWZrBdcZWWBenhJtxLH4On2g= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= diff --git a/pkg/attr/attr.go b/pkg/attr/attr.go index a4090c5..e0137c5 100644 --- a/pkg/attr/attr.go +++ b/pkg/attr/attr.go @@ -1,18 +1,27 @@ package attr import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" "encoding/json" + "errors" + "io" "os" "runtime" "runtime/debug" "time" "git.ma-al.com/maal-libraries/observer/pkg/level" + "github.com/gofrs/uuid" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/semconv/v1.25.0" + semconv "go.opentelemetry.io/otel/semconv/v1.25.0" "go.opentelemetry.io/otel/trace" ) +type KV = attribute.KeyValue type KeyValue = attribute.KeyValue type Key = attribute.Key type Value = attribute.Value @@ -72,11 +81,192 @@ const ( LayerUtil = "util" ) +type secretKey struct { + Key []byte + Cipher cipher.Block + Nonce []byte +} + +// Interprets a string as AES secret key (32 bytes) first decoding it with base64 +// It can be used to set the variable `EncryptSecretKey` which is responsible +// for encrypting the `Encrypted` attributes. +func NewSecretKey(key string) (secretKey, error) { + keyBytes, err := base64.RawStdEncoding.DecodeString(key) + if err != nil { + return secretKey{}, err + } + if len(keyBytes) != 32 { + return secretKey{}, errors.New("wrong length of encryption key, should be 32 bits") + } + cipher, err := aes.NewCipher(keyBytes) + if err != nil { + return secretKey{}, err + } + nonce := make([]byte, 12) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return secretKey{}, err + } + return secretKey{ + Key: keyBytes, + Cipher: cipher, + Nonce: nonce, + }, nil +} + +// **Unless set, it will default to a random key that cannot be later retrievied!** +// +// The variable is used to encrypt values provided to the `Encrypted` attribute. +var EncryptSecretKey secretKey = func() secretKey { + key := make([]byte, 32) + rand.Read(key) + cipher, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + nonce := make([]byte, 12) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + panic(err) + } + return secretKey{ + Key: key, + Cipher: cipher, + Nonce: nonce, + } +}() + // Build an attribute with a value formatted as json func JsonAttr(key string, jsonEl map[string]interface{}) attribute.KeyValue { jsonStr, _ := json.Marshal(jsonEl) return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(string(jsonStr))} +} +// Create an arbitrary attribute with value marshalled to json. +// In case of marshalling error, it is returned in place of value. +func Json[M json.Marshaler](key string, val M) attribute.KeyValue { + data, err := json.Marshal(val) + if err != nil { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(err.Error())} + } else { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(string(data))} + } +} + +// Create an arbitrary attribute with a `string` value. +func String(key string, val string) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(val)} +} + +// Create an arbitrary attribute with a `[]string` value. +func StringSlice(key string, val []string) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringSliceValue(val)} +} + +// Create an arbitrary attribute with an `int` value. +func Int(key string, val int) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.IntValue(val)} +} + +// Create an arbitrary attribute with an `int64` value. +func Int64(key string, val int64) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.Int64Value(val)} +} + +// Cast value to an `int` to create a new attribute. +func Uint(key string, val uint) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.IntValue(int(val))} +} + +// Cast value to an `int` to create a new attribute. +func Uint8(key string, val uint8) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.IntValue(int(val))} +} + +// Create an arbitrary attribute using an `uuid.UUID` from `github.com/gofrs/uuid` as value. +func Uuid(key string, val uuid.UUID) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(val.String())} +} + +// Create an arbitrary attribute using standard library's `time.Time` as value. It will be formatted using RFC3339. +func Time(key string, val time.Time) attribute.KeyValue { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(val.Format(time.RFC3339))} +} + +// Create an arbitrary attribute with bytes encoded to base64 (RFC 4648) as value. +func BytesB64(key string, val []byte) attribute.KeyValue { + res := base64.StdEncoding.EncodeToString(val) + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(res)} +} + +// Create an arbitrary attribute with bytes encoded to hexadecimal format as value. +func BytesHex(key string, val []byte) attribute.KeyValue { + res := hex.EncodeToString(val) + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(res)} +} + +// Create an arbitrary attribute with value encrypted using `secretKey` which should be set on +// global variable `EncryptSecretKey` using `NewSecretKey`. The result will be encoded using +// base64. +// +// This approach is an alternative to logs tokenization. It is using AES symmetric encryption +// that is suspectible to brute force attacks. It is a computionally expensive attribute to +// generate. +// +// In most cases, for very sensitive data it would be a better approach to use masking instead. +// Encrypting the fields of the logs/traces can provide an extra protection while they are being +// transported to a log collector and when the collector does not encrypt logs at rest (but most +// should implement this feature). This will mostly protect the logs from developers working +// with them provided that they do not have access to the key. The key should be set from an +// environment variable defined on application deployment. Alternatively it could be set from +// a secure vault, a software for storing private keys. +func Encrypted(key string, val string) attribute.KeyValue { + aesGcm, err := cipher.NewGCM(EncryptSecretKey.Cipher) + if err != nil { + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(err.Error())} + } + resBytes := aesGcm.Seal(nil, EncryptSecretKey.Nonce, []byte(val), nil) + res := base64.StdEncoding.EncodeToString(resBytes) + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(res)} +} + +// Creates an arbitrary attribute with masked value. It will leave only last 4 (or less) characters +// unmasked by 'X' characters. +// +// It is not a good idea to use it for logging passwords as it preserves the lenght of the input. +// +// Masking is a good idea for very sensitive data like official identity numbers, or addresses. +// Storing such data in logs is usually too much of a risk even when it is encrypted. +// However, for the purpose of debugging it might be convenient to be able to distinguish one record +// from another. +func Masked(key string, val string) attribute.KeyValue { + lenght := len(val) + var unmasked int + + if lenght <= 4 { + unmasked = 1 + } else { + if lenght <= 8 { + unmasked = 2 + } else { + if lenght <= 12 { + unmasked = 3 + } else { + unmasked = 4 + } + } + } + + masked := lenght - unmasked + resBytes := make([]byte, lenght) + i := 0 + + for ; i < masked; i++ { + resBytes[i] = byte('X') + } + for ; i < lenght; i++ { + resBytes[i] = byte(val[i]) + } + + return attribute.KeyValue{Key: attribute.Key(key), Value: attribute.StringValue(string(resBytes))} } // An attribute informing about the severity or importance of an event using our own standard of log levels that