日志 redaction(脱敏/屏蔽)

了解如何为敏感数据实现日志 redaction

本指南演示了如何在 OpenTelemetry .NET 日志中实现敏感信息 redaction。 redaction 是保护个人身份信息 (PII)、凭据和其他机密信息等敏感数据的重要实践。

为什么需要 redaction 日志?

Redacting 敏感信息免受日志记录至关重要,原因如下:

  1. 合规性:满足 GDPR、HIPAA 或 PCI DSS 等监管要求
  2. 安全性:防止日志文件或日志管理系统中敏感数据的泄露
  3. 隐私:通过删除个人身份信息 (PII) 来保护用户隐私
  4. 降低风险:最大程度地减少潜在日志数据泄露的影响

实现基本的 redaction processor

OpenTelemetry 允许您创建自定义处理器,可以在导出日志记录之前对其进行修改。以下是创建基本 redaction processor 的方法:

using System.Collections;
using OpenTelemetry;
using OpenTelemetry.Logs;

internal sealed class MyRedactionProcessor : BaseProcessor<LogRecord>
{
    public override void OnEnd(LogRecord logRecord)
    {
        if (logRecord.Attributes != null)
        {
            logRecord.Attributes = new MyClassWithRedactionEnumerator(logRecord.Attributes);
        }
    }

    internal sealed class MyClassWithRedactionEnumerator : IReadOnlyList<KeyValuePair<string, object?>>
    {
        private readonly IReadOnlyList<KeyValuePair<string, object?>> state;

        public MyClassWithRedactionEnumerator(IReadOnlyList<KeyValuePair<string, object?>> state)
        {
            this.state = state;
        }

        public int Count => this.state.Count;

        public KeyValuePair<string, object?> this[int index]
        {
            get
            {
                var item = this.state[index];
                var entryVal = item.Value?.ToString();
                if (entryVal != null && entryVal.Contains("<secret>"))
                {
                    return new KeyValuePair<string, object?>(item.Key, "***REDACTED***");
                }

                return item;
            }
        }

        public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
        {
            for (var i = 0; i < this.Count; i++)
            {
                yield return this[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}

此处理器检查每个日志属性值,并用“REDACTED”替换任何包含字符串“”的值。

使用 redaction processor

要使用 redaction processor,请将其添加到您的 OpenTelemetry 日志记录配置中。

using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

// Assume MyRedactionProcessor is defined elsewhere
// public class MyRedactionProcessor : BaseProcessor<LogRecord> { ... }

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddProcessor(new MyRedactionProcessor());
        logging.AddConsoleExporter();
    });
});

var logger = loggerFactory.CreateLogger<Program>();
// Message will be redacted by MyRedactionProcessor
logger.FoodPriceChanged("", 9.99);

loggerFactory.Dispose();

运行此代码时,输出中任何包含“”的日志属性都将被 redaction。

高级 redaction 策略

在实际应用中,您会希望通过 SDK 或 OTel Collector processor 实现更复杂的 redaction 策略。

以下是一些使用 SDK 的方法:

1. 使用正则表达式进行模式匹配

public KeyValuePair<string, object?> this[int index]
{
    get
    {
        var item = this.state[index];
        var entryVal = item.Value?.ToString();
        if (entryVal != null)
        {
            // Redact credit card numbers
            var redactedValue = Regex.Replace(
                entryVal,
                @"\b(?:\d{4}[-\s]?){3}\d{4}\b",
                "***CARD-REDACTED***");

            // Redact email addresses
            redactedValue = Regex.Replace(
                redactedValue,
                @"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",
                "***EMAIL-REDACTED***");

            if (redactedValue != entryVal)
            {
                return new KeyValuePair<string, object?>(item.Key, redactedValue);
            }
        }

        return item;
    }
}

2. 基于字段的 redaction

您可能希望 redaction 特定字段名,而不考虑其内容。

public KeyValuePair<string, object?> this[int index]
{
    get
    {
        var item = this.state[index];

        // Redact sensitive fields by name
        var sensitiveFields = new[] { "password", "ssn", "creditcard", "api_key" };
        if (sensitiveFields.Any(field => item.Key.Contains(field, StringComparison.OrdinalIgnoreCase)))
        {
            return new KeyValuePair<string, object?>(item.Key, "***REDACTED***");
        }

        return item;
    }
}

3. 部分 redaction

对于某些数据类型,您可能希望显示部分值。

public KeyValuePair<string, object?> this[int index]
{
    get
    {
        var item = this.state[index];
        var entryVal = item.Value?.ToString();

        if (item.Key.Equals("email", StringComparison.OrdinalIgnoreCase) && entryVal != null)
        {
            var parts = entryVal.Split('@');
            if (parts.Length == 2)
            {
                // Show first character of the username and the domain
                var redactedEmail = $"{parts[0][0]}***@{parts[1]}";
                return new KeyValuePair<string, object?>(item.Key, redactedEmail);
            }
        }

        return item;
    }
}

与 ASP.NET Core 集成

对于 ASP.NET Core 应用程序,您可以集成您的 redaction processor。

builder.Services.AddOpenTelemetry()
    .WithLogging(logging =>
    {
        logging.AddProcessor(new MyRedactionProcessor());
        logging.AddConsoleExporter();
    });

了解更多