Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions examples/DataMasking/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Powertools for AWS Lambda (.NET) - Data Masking Example

This example shows how to use the [Data Masking](https://github.com/aws-powertools/powertools-lambda-dotnet/issues/1257)
utility to irreversibly **erase** sensitive fields (PII) from a payload before it is logged or returned,
while keeping the non-sensitive fields readable.

This example focuses on the `Erase` operation and its integration with Powertools Logging. For
reversible `Encrypt`/`Decrypt` with AWS KMS, see the utility README.

## How it works

The function receives a JSON order that contains PII (`ssn`, `creditCard`, `email`, `phone`, `street`)
and applies three masking styles:

- **Full mask** (default `*****`) for `customer.ssn`, `payment.creditCard`, and `address.street`
- **Regex substitution** for `customer.email` (keeps the first character: `j****@example.com`)
- **Length-preserving mask** for `customer.phone` (`555-0134` -> `********`)

The result keeps `orderId`, `item`, `payment.amount`, and `address.city` readable, so the payload
remains useful for debugging and monitoring. `LogEvent` is intentionally disabled so the raw PII is
never auto-logged.

**Logging integration:** the handler logs the masked payload with
`Logger.LogInformation(_dataMasking.EraseToNode(...))`. `EraseToNode` returns a `JsonNode`, so the
masked object is logged as structured JSON with no extra string round-trip.

## Prerequisites

- .NET 8.0 - [Install .NET 8.0](https://www.microsoft.com/net/download)
- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)

## Run locally

Build and invoke the function with the sample event:

```bash
sam build
sam local invoke HelloWorldFunction --event events/event.json
```

Expected (abbreviated) response body:

```json
{
"orderId": "ORD-1001",
"item": "Powertools T-Shirt",
"customer": {
"name": "John Doe",
"email": "j****@example.com",
"phone": "********",
"ssn": "*****"
},
"address": { "street": "*****", "city": "Anytown" },
"payment": { "creditCard": "*****", "amount": 42.5 }
}
```

## Deploy

```bash
sam build
sam deploy --guided
```
10 changes: 10 additions & 0 deletions examples/DataMasking/events/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"resource": "/mask",
"path": "/mask",
"httpMethod": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": "{\"orderId\":\"ORD-1001\",\"item\":\"Powertools T-Shirt\",\"customer\":{\"name\":\"John Doe\",\"email\":\"john@example.com\",\"phone\":\"555-0134\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Anytown\"},\"payment\":{\"creditCard\":\"4111111111111111\",\"amount\":42.5}}",
"isBase64Encoded": false
}
84 changes: 84 additions & 0 deletions examples/DataMasking/src/HelloWorld/Function.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using Amazon.Lambda.Serialization.SystemTextJson;
using AWS.Lambda.Powertools.DataMasking;
using AWS.Lambda.Powertools.Logging;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]

namespace HelloWorld;

public class Function
{
private readonly DataMasking _dataMasking = new();

/// <summary>
/// This example receives a JSON order payload that contains PII and demonstrates how to use the
/// Data Masking utility to irreversibly erase sensitive fields before the payload is logged and
/// returned. The non-sensitive fields (orderId, item, city) remain readable so the payload stays
/// useful for debugging and monitoring.
/// </summary>
[Logging(LogEvent = false)] // LogEvent is intentionally off so the raw PII is never auto-logged
public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{
var body = request.Body ?? "{}";

// 1. Full-value masking (default *****) for whole fields
var masked = _dataMasking.Erase(body, new[]
{
"customer.ssn",
"payment.creditCard",
"address.street"
});

// 2. Custom rules: keep the first char of the email, and preserve the phone length
masked = _dataMasking.Erase(masked, new[] { "customer.email" }, new MaskingOptions
{
Pattern = new Regex("^(.).*@", RegexOptions.None, TimeSpan.FromSeconds(1)),
Replacement = "$1****@"
});

masked = _dataMasking.Erase(masked, new[] { "customer.phone" }, new MaskingOptions
{
PreserveLength = true
});

// Logging integration: EraseToNode returns a JsonNode ready for structured logging, so the
// masked payload is logged as a JSON object (not a string) with no extra serialization round-trip.
// Sensitive fields are already erased, so this is safe to log.
Logger.LogInformation(_dataMasking.EraseToNode(body, new[]
{
"customer.ssn",
"customer.email",
"customer.phone",
"payment.creditCard",
"address.street"
}));

return new APIGatewayProxyResponse
{
Body = masked,
StatusCode = 200,
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
};
}
}
16 changes: 16 additions & 0 deletions examples/DataMasking/src/HelloWorld/HelloWorld.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Amazon.Lambda.Core" Version="2.8.0" />
<PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.7.3" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\libraries\src\AWS.Lambda.Powertools.DataMasking\AWS.Lambda.Powertools.DataMasking.csproj" />
<ProjectReference Include="..\..\..\..\libraries\src\AWS.Lambda.Powertools.Logging\AWS.Lambda.Powertools.Logging.csproj" />
</ItemGroup>
</Project>
12 changes: 12 additions & 0 deletions examples/DataMasking/src/HelloWorld/aws-lambda-tools-defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Information": [
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
"To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
"dotnet lambda help",
"All the command line options for the Lambda command can be specified in this file."
],
"profile": "",
"region": "",
"configuration": "Release",
"template": "template.yaml"
}
47 changes: 47 additions & 0 deletions examples/DataMasking/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
Example project for Powertools for AWS Lambda (.NET) Data Masking utility

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 10
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: powertools-dotnet-data-masking-sample
POWERTOOLS_LOG_LEVEL: Information

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
Runtime: dotnet8
CodeUri: ./src/HelloWorld/
Handler: HelloWorld::HelloWorld.Function::FunctionHandler
MemorySize: 256
Events:
MaskOrder:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /mask
Method: post

HelloWorldFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${HelloWorldFunction}"
RetentionInDays: 7

Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
MaskOrderApi:
Description: "API Gateway endpoint URL for Prod stage for the Data Masking function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/mask/"
HelloWorldFunction:
Description: "Data Masking Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
30 changes: 30 additions & 0 deletions libraries/AWS.Lambda.Powertools.sln
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metad
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metadata.Tests", "tests\AWS.Lambda.Powertools.Metadata.Tests\AWS.Lambda.Powertools.Metadata.Tests.csproj", "{B2ED85F6-FDBA-4AD7-8C13-1BF8BF464938}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.DataMasking", "src\AWS.Lambda.Powertools.DataMasking\AWS.Lambda.Powertools.DataMasking.csproj", "{948193CD-A20C-4155-B60C-445FF4AE5D64}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.DataMasking.Tests", "tests\AWS.Lambda.Powertools.DataMasking.Tests\AWS.Lambda.Powertools.DataMasking.Tests.csproj", "{F70E80CC-ACD5-4645-964D-653552621152}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -741,6 +745,30 @@ Global
{B2ED85F6-FDBA-4AD7-8C13-1BF8BF464938}.Release|x64.Build.0 = Release|Any CPU
{B2ED85F6-FDBA-4AD7-8C13-1BF8BF464938}.Release|x86.ActiveCfg = Release|Any CPU
{B2ED85F6-FDBA-4AD7-8C13-1BF8BF464938}.Release|x86.Build.0 = Release|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Debug|x64.ActiveCfg = Debug|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Debug|x64.Build.0 = Debug|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Debug|x86.ActiveCfg = Debug|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Debug|x86.Build.0 = Debug|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Release|Any CPU.Build.0 = Release|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Release|x64.ActiveCfg = Release|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Release|x64.Build.0 = Release|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Release|x86.ActiveCfg = Release|Any CPU
{948193CD-A20C-4155-B60C-445FF4AE5D64}.Release|x86.Build.0 = Release|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Debug|x64.ActiveCfg = Debug|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Debug|x64.Build.0 = Debug|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Debug|x86.ActiveCfg = Debug|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Debug|x86.Build.0 = Debug|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Release|Any CPU.Build.0 = Release|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Release|x64.ActiveCfg = Release|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Release|x64.Build.0 = Release|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Release|x86.ActiveCfg = Release|Any CPU
{F70E80CC-ACD5-4645-964D-653552621152}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -805,5 +833,7 @@ Global
{D2951A1A-D0EF-4CA4-AB4D-5ABAEFD164F5} = {1CFF5568-8486-475F-81F6-06105C437528}
{6B978BB7-6C6E-481A-BE21-2E9E93B06AA0} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5}
{B2ED85F6-FDBA-4AD7-8C13-1BF8BF464938} = {1CFF5568-8486-475F-81F6-06105C437528}
{948193CD-A20C-4155-B60C-445FF4AE5D64} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5}
{F70E80CC-ACD5-4645-964D-653552621152} = {1CFF5568-8486-475F-81F6-06105C437528}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<!-- Remaining properties are defined in Directory.Build.props -->
<PackageId>AWS.Lambda.Powertools.DataMasking</PackageId>
<Description>Powertools for AWS Lambda (.NET) - Data Masking package.</Description>
<AssemblyName>AWS.Lambda.Powertools.DataMasking</AssemblyName>
<RootNamespace>AWS.Lambda.Powertools.DataMasking</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<!-- Package versions are Centrally managed in Directory.Packages.props file -->
<PackageReference Include="AWS.Cryptography.EncryptionSDK" />
<PackageReference Include="AWSSDK.KeyManagementService" />
</ItemGroup>

</Project>
Loading