Session mod files encryption (#766)
* feat(backend): added session mod files encryption
This commit is contained in:
parent
ca77c6b531
commit
a166482227
15 changed files with 2820 additions and 2539 deletions
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"log"
|
||||
"openreplay/backend/internal/storage"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
|
@ -78,6 +79,15 @@ func main() {
|
|||
currDuration, newDuration)
|
||||
return true
|
||||
}
|
||||
if cfg.UseEncryption {
|
||||
if key := storage.GenerateEncryptionKey(); key != nil {
|
||||
if err := pg.InsertSessionEncryptionKey(sessionID, key); err != nil {
|
||||
log.Printf("can't save session encryption key: %s, session will not be encrypted", err)
|
||||
} else {
|
||||
msg.EncryptionKey = string(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := producer.Produce(cfg.TopicRawWeb, sessionID, msg.Encode()); err != nil {
|
||||
log.Printf("can't send sessionEnd to topic: %s; sessID: %d", err, sessionID)
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func main() {
|
|||
messages.NewMessageIterator(
|
||||
func(msg messages.Message) {
|
||||
sesEnd := msg.(*messages.SessionEnd)
|
||||
if err := srv.UploadSessionFiles(msg.SessionID()); err != nil {
|
||||
if err := srv.UploadSessionFiles(sesEnd); err != nil {
|
||||
log.Printf("can't find session: %d", msg.SessionID())
|
||||
sessionFinder.Find(msg.SessionID(), sesEnd.Timestamp)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ type Config struct {
|
|||
TopicRawWeb string `env:"TOPIC_RAW_WEB,required"`
|
||||
ProducerTimeout int `env:"PRODUCER_TIMEOUT,default=2000"`
|
||||
PartitionsNumber int `env:"PARTITIONS_NUMBER,required"`
|
||||
UseEncryption bool `env:"USE_ENCRYPTION,default=false"`
|
||||
}
|
||||
|
||||
func New() *Config {
|
||||
|
|
|
|||
17
backend/internal/storage/encryptor.go
Normal file
17
backend/internal/storage/encryptor.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func GenerateEncryptionKey() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func EncryptData(data, fullKey []byte) ([]byte, error) {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
||||
|
||||
func DecryptData(data, fullKey []byte) ([]byte, error) {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"log"
|
||||
config "openreplay/backend/internal/config/storage"
|
||||
"openreplay/backend/pkg/flakeid"
|
||||
"openreplay/backend/pkg/messages"
|
||||
"openreplay/backend/pkg/monitoring"
|
||||
"openreplay/backend/pkg/storage"
|
||||
"os"
|
||||
|
|
@ -68,19 +69,19 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *Storage) UploadSessionFiles(sessID uint64) error {
|
||||
sessionDir := strconv.FormatUint(sessID, 10)
|
||||
if err := s.uploadKey(sessID, sessionDir+"/dom.mob", true, 5); err != nil {
|
||||
func (s *Storage) UploadSessionFiles(msg *messages.SessionEnd) error {
|
||||
sessionDir := strconv.FormatUint(msg.SessionID(), 10)
|
||||
if err := s.uploadKey(msg.SessionID(), sessionDir+"/dom.mob", true, 5, msg.EncryptionKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.uploadKey(sessID, sessionDir+"/devtools.mob", false, 4); err != nil {
|
||||
if err := s.uploadKey(msg.SessionID(), sessionDir+"/devtools.mob", false, 4, msg.EncryptionKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: make a bit cleaner
|
||||
func (s *Storage) uploadKey(sessID uint64, key string, shouldSplit bool, retryCount int) error {
|
||||
func (s *Storage) uploadKey(sessID uint64, key string, shouldSplit bool, retryCount int, encryptionKey string) error {
|
||||
if retryCount <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -95,6 +96,14 @@ func (s *Storage) uploadKey(sessID uint64, key string, shouldSplit bool, retryCo
|
|||
}
|
||||
defer file.Close()
|
||||
|
||||
var fileSize int64 = 0
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
log.Printf("can't get file info: %s", err)
|
||||
} else {
|
||||
fileSize = fileInfo.Size()
|
||||
}
|
||||
var encryptedData []byte
|
||||
if shouldSplit {
|
||||
nRead, err := file.Read(s.startBytes)
|
||||
if err != nil {
|
||||
|
|
@ -105,45 +114,104 @@ func (s *Storage) uploadKey(sessID uint64, key string, shouldSplit bool, retryCo
|
|||
time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))),
|
||||
)
|
||||
time.AfterFunc(s.cfg.RetryTimeout, func() {
|
||||
s.uploadKey(sessID, key, shouldSplit, retryCount-1)
|
||||
s.uploadKey(sessID, key, shouldSplit, retryCount-1, encryptionKey)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
s.readingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()))
|
||||
|
||||
start = time.Now()
|
||||
startReader := bytes.NewBuffer(s.startBytes[:nRead])
|
||||
// Encrypt session file if we have encryption key
|
||||
if encryptionKey != "" {
|
||||
encryptedData, err = EncryptData(s.startBytes[:nRead], []byte(encryptionKey))
|
||||
if err != nil {
|
||||
log.Printf("can't encrypt data: %s", err)
|
||||
encryptedData = s.startBytes[:nRead]
|
||||
}
|
||||
} else {
|
||||
encryptedData = s.startBytes[:nRead]
|
||||
}
|
||||
// Compress and save to s3
|
||||
startReader := bytes.NewBuffer(encryptedData)
|
||||
if err := s.s3.Upload(s.gzipFile(startReader), key+"s", "application/octet-stream", true); err != nil {
|
||||
log.Fatalf("Storage: start upload failed. %v\n", err)
|
||||
}
|
||||
// TODO: fix possible error (if we read less then FileSplitSize)
|
||||
if nRead == s.cfg.FileSplitSize {
|
||||
if err := s.s3.Upload(s.gzipFile(file), key+"e", "application/octet-stream", true); err != nil {
|
||||
restPartSize := fileSize - int64(nRead)
|
||||
fileData := make([]byte, restPartSize)
|
||||
nRead, err = file.Read(fileData)
|
||||
if err != nil {
|
||||
log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s",
|
||||
err,
|
||||
key,
|
||||
sessID%16,
|
||||
time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
if int64(nRead) != restPartSize {
|
||||
log.Printf("can't read the rest part of file")
|
||||
}
|
||||
|
||||
// Encrypt session file if we have encryption key
|
||||
if encryptionKey != "" {
|
||||
encryptedData, err = EncryptData(fileData, []byte(encryptionKey))
|
||||
if err != nil {
|
||||
log.Printf("can't encrypt data: %s", err)
|
||||
encryptedData = fileData
|
||||
}
|
||||
} else {
|
||||
encryptedData = fileData
|
||||
}
|
||||
// Compress and save to s3
|
||||
endReader := bytes.NewBuffer(encryptedData)
|
||||
if err := s.s3.Upload(s.gzipFile(endReader), key+"e", "application/octet-stream", true); err != nil {
|
||||
log.Fatalf("Storage: end upload failed. %v\n", err)
|
||||
}
|
||||
}
|
||||
s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()))
|
||||
} else {
|
||||
start = time.Now()
|
||||
if err := s.s3.Upload(s.gzipFile(file), key+"s", "application/octet-stream", true); err != nil {
|
||||
fileData := make([]byte, fileSize)
|
||||
nRead, err := file.Read(fileData)
|
||||
if err != nil {
|
||||
log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s",
|
||||
err,
|
||||
key,
|
||||
sessID%16,
|
||||
time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
if int64(nRead) != fileSize {
|
||||
log.Printf("can't read the rest part of file")
|
||||
}
|
||||
|
||||
// Encrypt session file if we have encryption key
|
||||
if encryptionKey != "" {
|
||||
encryptedData, err = EncryptData(fileData, []byte(encryptionKey))
|
||||
if err != nil {
|
||||
log.Printf("can't encrypt data: %s", err)
|
||||
encryptedData = fileData
|
||||
}
|
||||
} else {
|
||||
encryptedData = fileData
|
||||
}
|
||||
endReader := bytes.NewBuffer(encryptedData)
|
||||
if err := s.s3.Upload(s.gzipFile(endReader), key+"s", "application/octet-stream", true); err != nil {
|
||||
log.Fatalf("Storage: end upload failed. %v\n", err)
|
||||
}
|
||||
s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()))
|
||||
}
|
||||
|
||||
// Save metrics
|
||||
var fileSize float64 = 0
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
log.Printf("can't get file info: %s", err)
|
||||
} else {
|
||||
fileSize = float64(fileInfo.Size())
|
||||
}
|
||||
ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*200)
|
||||
if shouldSplit {
|
||||
s.totalSessions.Add(ctx, 1)
|
||||
s.sessionDOMSize.Record(ctx, fileSize)
|
||||
s.sessionDOMSize.Record(ctx, float64(fileSize))
|
||||
} else {
|
||||
s.sessionDevtoolsSize.Record(ctx, fileSize)
|
||||
s.sessionDevtoolsSize.Record(ctx, float64(fileSize))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
4
backend/pkg/db/cache/messages-common.go
vendored
4
backend/pkg/db/cache/messages-common.go
vendored
|
|
@ -11,6 +11,10 @@ func (c *PGCache) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64,
|
|||
return c.Conn.InsertSessionEnd(sessionID, timestamp)
|
||||
}
|
||||
|
||||
func (c *PGCache) InsertSessionEncryptionKey(sessionID uint64, key []byte) error {
|
||||
return c.Conn.InsertSessionEncryptionKey(sessionID, key)
|
||||
}
|
||||
|
||||
func (c *PGCache) HandleSessionEnd(sessionID uint64) error {
|
||||
if err := c.Conn.HandleSessionEnd(sessionID); err != nil {
|
||||
log.Printf("can't handle session end: %s", err)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ func (conn *Conn) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64,
|
|||
return dur, nil
|
||||
}
|
||||
|
||||
func (conn *Conn) InsertSessionEncryptionKey(sessionID uint64, key []byte) error {
|
||||
return conn.c.Exec(`UPDATE sessions SET file_key = $2 WHERE session_id = $1`, sessionID, string(key))
|
||||
}
|
||||
|
||||
func (conn *Conn) HandleSessionEnd(sessionID uint64) error {
|
||||
sqlRequest := `
|
||||
UPDATE sessions
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
65
ee/backend/internal/storage/encryptor.go
Normal file
65
ee/backend/internal/storage/encryptor.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
const letterSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
func GenerateEncryptionKey() []byte {
|
||||
return append(generateRandomBytes(16), generateRandomBytes(16)...)
|
||||
}
|
||||
|
||||
func generateRandomBytes(size int) []byte {
|
||||
b := make([]byte, size)
|
||||
for i := range b {
|
||||
b[i] = letterSet[rand.Int63()%int64(len(letterSet))]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func fillLastBlock(rawText []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(rawText)%blockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(rawText, padText...)
|
||||
}
|
||||
|
||||
func EncryptData(data, fullKey []byte) ([]byte, error) {
|
||||
if len(fullKey) != 32 {
|
||||
return nil, errors.New("wrong format of encryption key")
|
||||
}
|
||||
key, iv := fullKey[:16], fullKey[16:]
|
||||
// Fill the last block of data by zeros
|
||||
paddedData := fillLastBlock(data, aes.BlockSize)
|
||||
// Create new AES cipher with CBC encryptor
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cbc encryptor failed: %s", err)
|
||||
}
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
// Encrypting data
|
||||
ciphertext := make([]byte, len(paddedData))
|
||||
mode.CryptBlocks(ciphertext, paddedData)
|
||||
// Return encrypted data
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func DecryptData(data, fullKey []byte) ([]byte, error) {
|
||||
if len(fullKey) != 32 {
|
||||
return nil, errors.New("wrong format of encryption key")
|
||||
}
|
||||
key, iv := fullKey[:16], fullKey[16:]
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cbc encryptor failed: %s", err)
|
||||
}
|
||||
cbc := cipher.NewCBCDecrypter(block, iv)
|
||||
res := make([]byte, len(data))
|
||||
cbc.CryptBlocks(res, data)
|
||||
return res, nil
|
||||
}
|
||||
|
|
@ -89,7 +89,9 @@ func (s *sessionFinderImpl) worker() {
|
|||
}
|
||||
|
||||
func (s *sessionFinderImpl) findSession(sessionID, timestamp, partition uint64) {
|
||||
err := s.storage.UploadSessionFiles(sessionID)
|
||||
sessEnd := &messages.SessionEnd{Timestamp: timestamp}
|
||||
sessEnd.SetSessionID(sessionID)
|
||||
err := s.storage.UploadSessionFiles(sessEnd)
|
||||
if err == nil {
|
||||
log.Printf("found session: %d in partition: %d, original: %d",
|
||||
sessionID, partition, sessionID%numberOfPartitions)
|
||||
|
|
|
|||
|
|
@ -66,8 +66,9 @@ class SessionStart(Message):
|
|||
class SessionEnd(Message):
|
||||
__id__ = 3
|
||||
|
||||
def __init__(self, timestamp):
|
||||
def __init__(self, timestamp, encryption_key):
|
||||
self.timestamp = timestamp
|
||||
self.encryption_key = encryption_key
|
||||
|
||||
|
||||
class SetPageLocation(Message):
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ class MessageCodec(Codec):
|
|||
|
||||
if message_id == 3:
|
||||
return SessionEnd(
|
||||
timestamp=self.read_uint(reader)
|
||||
timestamp=self.read_uint(reader),
|
||||
encryption_key=self.read_string(reader)
|
||||
)
|
||||
|
||||
if message_id == 4:
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ end
|
|||
# end
|
||||
message 3, 'SessionEnd', :tracker => false, :replayer => false do
|
||||
uint 'Timestamp'
|
||||
string 'EncryptionKey'
|
||||
end
|
||||
message 4, 'SetPageLocation' do
|
||||
string 'URL'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue