Exercise - Redact sensitive data in cloud-native applications
You need add some logging to the order process. You'll use the redaction features of .NET to ensure that the sensitive data isn't leaked into the logs.
In this exercise, you'll:
- Add the
Microsoft.Extensions.Compliance.RedactionNuGet package to each project. - Add the redaction service to the dependency injection container.
- Enable redaction in the logging framework.
- Call the logging framework during the order process.
- Add a custom redaction implementation for EUII data.
- Choose which redaction implementation to use for each type of classified data.
Add the redaction service
You should still have the codespace or Visual Studio Code window open. If not, open it now.
In the TERMINAL window, enter this command:
cd /workspaces/mslearn-dotnet-cloudnative/dotnet-compliance/eShopLite/Store/Add the
Microsoft.Extensions.Compliance.RedactionNuGet package to the project:dotnet add package Microsoft.Extensions.Compliance.RedactionIn the EXPLORER pane, expand the dotnet-compliance/eShopLite/Store folder, then select the Program.cs file.
In the editor, add the following dependencies:
using Microsoft.Extensions.Compliance.Classification; using Microsoft.Extensions.Compliance.Redaction;Scroll down to line 19, under the
Add redactioncomment add the redaction service to the dependency injection container:builder.Services.AddRedaction();
Enabling redaction in the logging framework
In the editor, add this code below the
AddRedaction()line:builder.Services.AddLogging(logging => { logging.EnableRedaction(); logging.AddJsonConsole(); //Enable structure logs on the console to view the redacted data. });The above code enables redaction in the logging framework.
Call the logging framework during the order process
In the EXPLORER pane, expand the dotnet-compliance/eShopLite/Store/Services folder, then select the ProductService.cs file.
In the editor, at the bottom of the file, add this code:
public static partial class Log { [LoggerMessage(1, LogLevel.Information, "Placed Order: {order}")] public static partial void LogOrders(this ILogger logger, [LogProperties] Order order); }In the editor, in the
CreateOrdertask, call theLogOrdersmethod:public async Task<bool> CreateOrder(Order order) { try { _logger.LogOrders(order);The above code calls the
LogOrdersmethod and passes it the current order information.
Test the new redacted logging
With all the above code in place, the app can use the default redaction implementation to redact the Order information. You'll now test this.
On the TERMINAL pane at the bottom, go to the dotnet-compliance/eShopLite folder.
cd ..Update the apps containers.
dotnet publish /p:PublishProfile=DefaultContainerGo to the dotnet-compliance folder, and start the app with Docker:
cd .. docker compose upSelect the PORTS tab, then select the Open in Browser globe icon for the Front End (32000) port.
Select the Products link. Add some products to your shopping basket.
Select the Buy Basket button.
In the TERMINAL window, press Ctrl+F, in the search field enter "EventId":1,.
frontend-1 | {"EventId":1,"LogLevel":"Information","Category":"Store.Services.ProductService","Message":"Placed Order: DataEntities.Order","State":{"Message":"Microsoft.Extensions.Logging.ExtendedLogger\u002BModernTagJoiner","{OriginalFormat}":"Placed Order: {order}","order.Total":209.94,"order.Products":"[\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022]","order":"DataEntities.Order","order.CustomerAddress":"","order.CustomerName":"","order.Id":""}}You should see this JSON formatted log entry. Notice that the order.Total value is in the logs, but the CustomerName and CustomerAddress values are empty strings.
By default, if you don't specify a redaction implementation, the redaction engine will use the
ErasingRedactorimplementation to ensure no sensitive data is leaked into the logs.In the TERMINAL window, press Ctrl+C to stop the app.
Add a custom redaction implementation
You'll now enhance the redaction implementation to use different redaction algorithms for different types of data. First you'll add a new custom redaction implementation that replaces the value with *****.
In the EXPLORER pane, expand the dotnet-compliance/eShopLite/DataEntities folder, then select the Compliance.cs file.
In the editor, at the bottom of the file, add this code:
public class EShopCustomRedactor : Redactor { private const string Stars = "*****"; public override int GetRedactedLength(ReadOnlySpan<char> input) => Stars.Length; public override int Redact(ReadOnlySpan<char> source, Span<char> destination) { Stars.CopyTo(destination); return Stars.Length; } }The above code makes an
EShopCustomRedactorredaction method available to the redaction engine.
Choose which redaction implementation to use
In the EXPLORER pane, expand the dotnet-compliance/eShopLite/Store folder, then select the Program.cs file.
Replace
builder.Services.AddRedaction();code to provide configuration for the redaction engine:builder.Services.AddRedaction(configure => { configure.SetRedactor<ErasingRedactor>(new DataClassificationSet(DataClassifications.EUPDataClassification)); configure.SetRedactor<EShopCustomRedactor>(new DataClassificationSet(DataClassifications.EUIIDataClassification)); });The above code configures the redaction engine to specifically use the
ErasingRedactorimplementation for EUP data and the new customEShopCustomRedactorimplementation for EUII data.
Test the new redaction implementation
In the TERMINAL window, build and run the app:
docker-compose up --buildSelect the PORTS tab, then select the Open in Browser globe icon for the Front End (32000) port.
Select the Products link. Add some products to your shopping basket.
Select the Buy Basket button.
In the TERMINAL window, press Ctrl+F, in the search field enter "EventId":1,.
frontend-1 | {"EventId":1,"LogLevel":"Information","Category":"Store.Services.ProductService","Message":"Placed Order: DataEntities.Order","State":{"Message":"Microsoft.Extensions.Logging.ExtendedLogger\u002BModernTagJoiner","{OriginalFormat}":"Placed Order: {order}","order.Total":269.88,"order.Products":"[\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022,\u0022DataEntities.Product\u0022]","order":"DataEntities.Order","order.CustomerAddress":"*****","order.CustomerName":"*****","order.Id":""}}You should see this JSON formatted log entry. Notice that the order.Id value is still an empty string, but the CustomerName and CustomerAddress values are now
*****.In the TERMINAL window, press Ctrl+C to stop the app.