Broadway Sensitive Serializer
Github:Â https://github.com/matiux
Slack GRUSP:Â matiux
Email:Â m.galacci@gmail.com
Linkedin: Matteo Galacci
Realizzazione di un microservizio che coinvolge dati utente sensibili
Requisiti business
Requisiti tecnici
CQRS (Command Query Responsibility Segregation) è un pattern che mira a segregare le responsabilità per le query e per i comandi, diversificando le operazioni di lettura e scrittura su un determinato modello.
Separazione a parte, ciò che andremo a persistere sarà sempre l'ultimo stato del modello come accade nei sistemi CRUD.
Questo porta a diversi oggetti concreti, separati in modelli di scrittura e modelli di lettura.
Event Store è quindi un registro di tutti gli eventi che, se riapplicati al modello che li ha generati, nello stesso ordine di generazione, lo portano all'ultimo stato.
L'idea è che i comandi eseguiti su un modello, portano all'emissione di eventi che sono memorizzati nell'Event Store.
ES, utilizzato insieme a CQRS, "trasforma" la parte di scrittura dei modelli CQRS in una successione di eventi persistiti in un Event Store, uno specifico data store che funge da registro cronologico e immutabile degli eventi.
Con CQRS + ES, abbiamo un "archivio" di eventi in cui tutti gli eventi sono scritti in ordine cronologico e raggruppati per ciascun modello tramite l'id.
L'Event Store è immutabile per sua natura; dopo aver scritto un evento, non può più cambiare. Se necessario, verranno emessi eventi di compensazione.
Se l'ES è il registro cronologico di tutte le operazioni di scrittura avvenute su uno specifico Aggregato, allora una proiezione è una rappresentazione (view) specifica dell'Aggregato.
Per un singolo Aggregato potremmo avere tante view quante sono le nostre esigenze
All'emissione di un evento, un listener può ascoltare quell'evento per proiettarne una vista. Più listeners possono ascoltare lo stesso evento per proiettare view diverse dello stesso set di dati.
L'interessato ha il diritto di ottenere dal titolare del trattamento la cancellazione dei dati personali che lo riguardano senza ingiustificato ritardo e il titolare del trattamento ha l'obbligo di cancellare senza ingiustificato ritardo i dati personali
Paradosso?
La storia rimane invariata, ma i dati non sono comprensibili
Normal payload
{
"class": "SensitiveUser\\User\\Domain\\Event\\UserRegistered",
"payload": {
"id": "b0fce205-d816-46ac-886f-06de19236750",
"name": "Matteo",
"surname": "Galacci",
"email": "m.galacci@gmail.com"
"occurred_at": "2022-01-08T14:22:38.065+00:00",
}
}
{
"class": "SensitiveUser\\User\\Domain\\Event\\UserRegistered",
"payload": {
"id": "b0fce205-d816-46ac-886f-06de19236750",
"name": "Matteo",
"surname": "#-#2Iuofg4NJ...rbmQ==:bxQo+zXfjUgrD0jHuht0mQ==",
"email": "#-#OFLfN9XDKt6...wtam0pcqs6vDJFRU=:bxQo+zXfjUgrD0jHuht0mQ==",
"occurred_at": "2022-01-08T14:22:38.065+00:00",
}
}
Sensitized payload
Quando un utente si avvale del diritto all'oblio, dovresti fare tre cose:
* Non essendoci la chiave crittografica la lettura dall'ES produrrà idratazione con dati non in chiaro. Questo ovviamente comporta particolari verifiche nei Value Objects o nell'Aggregato
Questo progetto non riguarda la sicurezza generale o la fuga di dati.Â
Cosa non è Broadway Sensitive Serializer
Cosa è Broadway Sensitive Serializer
Un client chiederà all'aggregato User di creare un nuovo utente, che non solo creerà l'istanza, ma anche il relativo evento, UserCreated.
Aggregate and persistence
class BroadwayUsers extends EventSourcingRepository implements Users
{
public function add(User $user): void
{
parent::save($user);
}
}
class User extends EventSourcedAggregateRoot
{
public static function crea(
UserId $userId,
string $name,
string $surname,
Email $email,
DateTimeImmutable $regDate
): self
{
$user = new self();
$user->apply(new UserCreated($userId, $name, $surname, $email, $regDate));
return $user;
}
}
$user = User::create($userId, $name, $surname, $email, $registrationDate);
$users->add($user);
Quando chiediamo a Broadway di persistere un Aggregato, l'EventSourcingRepository prende dall'Aggregato tutti gli eventi non ancora commitati e chiede all'implementazione specifica dell'Event Store di serializzarli e quindi salvarli. Ad esempio, nel caso di Broadway DBALEventStore:
Event serialization
private function insertMessage(Connection $connection, DomainMessage $domainMessage): void
{
$data = [
'uuid' => $this->convertIdentifierToStorageValue((string) $domainMessage->getId()),
'playhead' => $domainMessage->getPlayhead(),
'metadata' => json_encode(
$this->metadataSerializer->serialize($domainMessage->getMetadata())
),
'payload' => json_encode(
$this->payloadSerializer->serialize($domainMessage->getPayload()) // <<===
),
'recorded_on' => $domainMessage->getRecordedOn()->toString(),
'type' => $domainMessage->getType(),
];
$connection->insert($this->tableName, $data);
}
Persisted event payload from Broadway with DBAL driver
{
"class": "SensitiveUser\\User\\Domain\\Event\\UserRegistered",
"payload": {
"id": "446effc9-4f5c-4369-8e89-91cb5c8509b9",
"name": "Matteo",
"surname": "Galacci",
"email": "m.galacci@gmail.com",
"occurred_at": "2022-01-08T14:22:38.065+00:00"
}
}
Persisted Event Payload from Broadway with DBAL Drivers with Whole Strategy
{
"class": "SensitiveUser\\User\\Domain\\Event\\UserRegistered",
"payload": {
"id": "b0fce205-d816-46ac-886f-06de19236750",
"name": "#-#EXWLg\/JANMK\/M+DmlpnOyQ==:bxQo+zXfjUgrD0jHuht0mQ==",
"surname": "#-#2Iuofg4NKKPLAG2kdJrbmQ==:bxQo+zXfjUgrD0jHuht0mQ==",
"email": "#-#OFLfN9XDKtWrmCmUb6mhY0Iz2V6wtam0pcqs6vDJFRU=:bxQo+zXfjUgrD0jHuht0mQ==",
"occurred_at": "2022-01-08T14:25:13.483+00:00",
}
}
Persisted event payload from Broadway with DBAL driver with Partial Strategy
{
"class": "SensitiveUser\\User\\Domain\\Event\\UserRegistered",
"payload": {
"id": "96607c7a-f4cd-4dd7-a406-9cde00913f79",
"name": "Dario",
"surname": "#-#SXZXQsvLTCVX8Kel0yaoHg==:iEMqT4YFE7OQzKdClNaDUg==",
"email": "#-#jTYqDtzJ8HHabEnJMMtuaiwiFcmCkZzel5985nSf\/Ig=:iEMqT4YFE7OQzKdClNaDUg==",
"occurred_at": "2022-01-14T15:04:58.323+00:00"
}
}
Persisted Event Payload from Broadway with DBAL Drivers with Custom Strategy
{
"class": "SensitiveUser\\User\\Domain\\Event\\UserRegistered",
"payload": {
"id": "c9298698-b30e-40c5-8d85-624fdf57f9df",
"name": "Matteo",
"surname": "Galacci",
"email": "#-#aw+tw7shnEs2px030QS9WgRmGZckEGnIeR0a8ByMkPI=:Q0jkEOZtOs56tMkc8SjP5g==",
"occurred_at": "2022-01-08T14:26:39.483+00:00",
}
}
Double key encryption
AGGREGATE_KEY
AGGREGATE_MASTER_KEY
CONTRO
PRO
services:
broadway_sensitive_serializer.aggregate_keys.dbal:
class: Matiux\Broadway\SensitiveSerializer\Dbal\DBALAggregateKeys
arguments:
$connection: "@doctrine.dbal.default_connection"
$tableName: "aggregate_keys"
$useBinary: false
$binaryUuidConverter: "@broadway.uuid.converter"
broadway:
# a service definition id implementing Broadway\EventStore\EventStore
event_store: broadway.event_store.dbal
# a service definition id implementing Broadway\ReadModel\RepositoryFactory
read_model: broadway.read_model.in_memory.repository_factory
# service definition ids implementing Broadway\Serializer\Serializer
serializer:
#payload: broadway.simple_interface_serializer
payload: broadway_sensitive_serializer.serializer
readmodel: broadway.simple_interface_serializer
metadata: broadway.simple_interface_serializer
broadway_sensitive_serializer:
#Master key to encrypt the keys of aggregates.
#Get it from an external service or environment variable
aggregate_master_key: 'm4$t3rS3kr3tk31'
#For now is the only one generator implemented. 256-bit encryption key
key_generator: open-ssl
#To use the DBAL implementation, install matiux/broadway-sensitive-serializer-dbal
aggregate_keys: broadway_sensitive_serializer.aggregate_keys.dbal
#Default implementation, of little use outside of testing
#aggregate_keys: broadway_sensitive_serializer.aggregate_keys.in_memory
data_manager:
name: AES256 #For now, it is the only encryption strategy implemented
#Encryption key to sensitize data. If null you will need to pass the key at runtime.
#This is the convenient way
key: null
#Initialization vector. If null it will be generated internally and iv_encoding must
#be set to true. This is the convenient way
iv: null
#Encrypt the iv and is appends to encrypted value. It makes sense to set it to true if
#the iv option is set to null. This is the convenient way
iv_encoding: true
strategy:
name: whole
#Enable AggregateKey model auto creation. This is the convenient way
aggregate_key_auto_creation: true
excluded_id_key: id # The key of the aggregate id which should not be encrypted
excluded_keys: # List of keys to be excluded from encryption
- occurred_at
events: # List of events supported by the strategy
- SensitiveUser\User\Domain\Event\AddressAdded
- SensitiveUser\User\Domain\Event\UserRegistered
broadway_sensitive_serializer:
aggregate_master_key: 'm4$t3rS3kr3tk31'
key_generator: open-ssl
aggregate_keys: broadway_sensitive_serializer.aggregate_keys.dbal
data_manager:
name: AES256
key: null
iv: null
iv_encoding: true
strategy:
name: partial
aggregate_key_auto_creation: true
events: # List of events supported by the strategy
- SensitiveUser\User\Domain\Event\AddressAdded:
- address # List of keys to sensitize
- SensitiveUser\User\Domain\Event\UserRegistered:
- name
- surname
broadway_sensitive_serializer:
aggregate_master_key: 'm4$t3rS3kr3tk31'
key_generator: open-ssl
aggregate_keys: broadway_sensitive_serializer.aggregate_keys.dbal
data_manager:
name: AES256
key: null
iv: null
iv_encoding: true
strategy:
name: custom
aggregate_key_auto_creation: true
services:
_defaults:
autowire: true
autoconfigure: true
public: false
SensitiveUser\User\Domain\EventSensitizer\UserRegisteredSensitizer:
parent: Matiux\Broadway\SensitiveSerializer\Serializer\Strategy\PayloadSensitizer
tags:
- { name: broadway.sensitive_serializer.custom }