# Cosmos DB Persistence Usage with Encryption This sample shows how to use [Azure Cosmos DB client-side encryption](https://learn.microsoft.com/azure/cosmos-db/how-to-always-encrypted) to encrypt selected saga properties before they leave the process. The sample uses a client/server scenario in which the server stores saga data with Cosmos DB Persistence. ## Prerequisites Ensure that an instance of the latest [Azure Cosmos DB Emulator](https://learn.microsoft.com/en-us/azure/cosmos-db/local-emulator) is running. ## Sample structure This sample contains three projects, `SharedMessages`, `Client` and `Server`. ### SharedMessages The shared message contracts used by all endpoints. ### Client * Sends the `StartOrder` message to `Server`. * Receives and handles the `OrderCompleted` event. ### Server * Receives the `StartOrder` message and initiates an `OrderSaga`. * `OrderSaga` requests a timeout with an instance of `CompleteOrder` with the saga data. * `OrderSaga` publishes an `OrderCompleted` event when the `CompleteOrder` timeout fires. ## Running the sample 1. Start the `Server` project and wait for the endpoint to report that it has started. 1. Start the `Client` project. 1. Press S in the client window to start an order. 1. After the saga timeout expires, verify that the server reports the completed saga and the client receives the `OrderCompleted` event. ## Implementation highlights ### Persistence config In Program.cs of the Server project, the endpoint is configured to use Cosmos DB Persistence: ```cs var endpointConfiguration = new EndpointConfiguration("Samples.CosmosDB.Encryption.Server"); var persistence = endpointConfiguration.UsePersistence(); persistence.DatabaseName("Samples.CosmosDB.Encryption"); // NServiceBus must use the encryption-enabled client so saga reads and writes apply the encryption policy. persistence.CosmosClient(cosmosClient); persistence.DefaultContainer("Server", "/id"); // The container is created explicitly with its encryption policy, so persistence must not create it. persistence.DisableContainerCreation(); ``` In the non-transactional mode, the saga id is used as a partition key, and, therefore, the container needs to use `/id` as the partition key path. ### Encryption setup The server wraps the regular `CosmosClient` with the Cosmos DB encryption client before passing it to NServiceBus. It then creates a client encryption key and a container with an encryption policy. ```cs var containerProperties = new ContainerProperties(ContainerName, PartitionKeyPath) { DefaultTimeToLive = -1, ClientEncryptionPolicy = new ClientEncryptionPolicy( [ // NServiceBus derives the saga ID from the correlation value and performs a point read, // so the correlated property can use randomized encryption. Encrypted("/OrderId"), Encrypted("/OrderDescription"), // Policy format version 2 allows encrypting id and partition key // but requires deterministic encryption for these properties. EncryptedDeterministic("/id"), EncryptedDeterministic("/PartitionKey") // Deliberately NOT encrypted: // /_NServiceBus-Persistence-Metadata - pessimistic locking patches a path INSIDE // this object (SagaDataContainer-ReservedUntil). // Only top-level paths can be encrypted, so // including it would encrypt the whole subtree // and break the lock. ], policyFormatVersion: 2) }; ``` The policy uses [encryption policy format version 2](https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-always-encrypted#choosing-a-client-encryption-policy), which allows all saga data properties to be encrypted. `OrderId` and `OrderDescription` use randomized encryption, while `id` and `PartitionKey` use [deterministic encryption](https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-always-encrypted#randomized-vs-deterministic-encryption), because Cosmos DB requires these values for point reads and routing. The NServiceBus persistence metadata remains unencrypted, because pessimistic locking updates a nested property within that object. The key encryption key resolver stores a generated RSA private key under the server output directory. This is suitable only for demonstrating the encryption flow; production systems should use a secure key store, such as Azure Key Vault, and must retain the key for as long as encrypted data needs to be read. > [!NOTE] > The server deletes and recreates the `Samples.CosmosDB.Encryption` database each time it starts so that the sample always uses the expected encryption key and policy. Starting the server removes all existing sample data. ## Order saga data The data stored by the saga is defined in the `OrderSagaData.cs` file in the `Server` project: ```cs public class OrderSagaData : ContainSagaData { public Guid OrderId { get; set; } public string OrderDescription { get; set; } } ``` ## Order saga The handlers for this data are in the `OrderSaga.cs` file in the `Server` project: ```cs public class OrderSaga(ILogger logger) : Saga, IAmStartedByMessages, IHandleTimeouts { protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { mapper.MapSaga(saga => saga.OrderId).ToMessage(msg => msg.OrderId); } public Task Handle(StartOrder message, IMessageHandlerContext context) { var orderDescription = $"The saga for order {message.OrderId}"; Data.OrderDescription = orderDescription; logger.LogInformation($"Received StartOrder message {Data.OrderId}. Starting Saga"); var shipOrder = new ShipOrder { OrderId = message.OrderId }; logger.LogInformation("Order will complete in 5 seconds"); var timeoutData = new CompleteOrder { OrderDescription = orderDescription, }; return Task.WhenAll( context.SendLocal(shipOrder), RequestTimeout(context, TimeSpan.FromSeconds(5), timeoutData) ); } public Task Timeout(CompleteOrder state, IMessageHandlerContext context) { logger.LogInformation($"Saga with OrderId {Data.OrderId} completed"); MarkAsComplete(); var orderCompleted = new OrderCompleted { OrderId = Data.OrderId }; return context.Publish(orderCompleted); } } ```