Skip to content

הגנה על ערכים נבחרים ב־appsettings באמצעות ASP.NET Core Data Protection

כל הקוד נועד להמחשה ולא הורץ.

מנגנון Protected Configuration של .NET Framework יכול להגן בשקיפות על מקטעי תצורה נבחרים. שמו הנכון של כלי העזר ההיסטורי הוא aspnet_regiis.exe, ולא aspnet_regiss.exe; בחירת הספק חשובה, ו־Protected Configuration אינו זהה ל־DPAPI בלבד. ספק תצורת ה־JSON של ASP.NET Core אינו מחיל אוטומטית את המנגנון הישן הזה על appsettings.json.

עדיף להשאיר סודות ייצור מחוץ לתצורה שבניהול גרסאות ולהשתמש במערכת ניהול סודות קיימת ומתאימה. Data Protection יכול להגן על ערכי תצורה מקומיים, אך אינו מסתיר אותם מזהות היישום שחייבת לפענח אותם. User Secrets לפיתוח אינם אחסון מוצפן. הדוגמה המצומצמת הבאה מיועדת למחשב Windows יחיד ולזהות שירות אחת.

רישום קריאה מפורשת בלבד לערכים

מתודת ההרחבה להמחשה מחליפה את עטיפת IConfiguration הניתנת לשינוי שבמקור. היא קוראת שני ערכים מוצפנים נדרשים ויוצרת מופע IOptions<ProtectedValues>. לכל מאפיין שרשרת מטרות נפרדת. ערך מוצפן חסר או לא תקין גורם לכשל, ולא להפיכתו לסוד ריק. מאחר שהאפשרויות הן תמונת מצב מסוג singleton, הדוגמה אינה מממשת רענון בעקבות שינוי או כתיבה לקובצי JSON.

using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

public sealed class ProtectedValues
{
    public string Secret1 { get; init; } = "";
    public string Secret2 { get; init; } = "";
}

public static class ProtectedConfiguration
{
    public const string ApplicationName = "SysSeder.ProtectedConfig.v1";

    public static IServiceCollection AddProtectedValues(
        this IServiceCollection services, IConfiguration configuration,
        string absoluteKeyDirectory)
    {
        if (!OperatingSystem.IsWindows())
            throw new PlatformNotSupportedException("This example requires Windows DPAPI.");
        if (!Path.IsPathFullyQualified(absoluteKeyDirectory)
            || !Directory.Exists(absoluteKeyDirectory))
            throw new ArgumentException("Provide a pre-provisioned absolute key directory.");
        services.AddDataProtection()
            .SetApplicationName(ApplicationName)
            .PersistKeysToFileSystem(new DirectoryInfo(absoluteKeyDirectory))
            .ProtectKeysWithDpapi(protectToLocalMachine: false);

        services.AddSingleton<IOptions<ProtectedValues>>(provider =>
        {
            var dp = provider.GetRequiredService<IDataProtectionProvider>();
            string Read(string property)
            {
                string ciphertext = configuration[$"ProtectedValues:{property}"]
                    ?? throw new InvalidOperationException($"Missing protected value: {property}");
                return ForProperty(dp, property).Unprotect(ciphertext);
            }
            return Options.Create(new ProtectedValues
            {
                Secret1 = Read(nameof(ProtectedValues.Secret1)),
                Secret2 = Read(nameof(ProtectedValues.Secret2))
            });
        });
        return services;
    }

    public static IDataProtector ForProperty(IDataProtectionProvider dp, string property)
    {
        if (property != nameof(ProtectedValues.Secret1)
            && property != nameof(ProtectedValues.Secret2))
            throw new ArgumentException("Unknown protected property.");
        return dp.CreateProtector("SysSeder.Configuration.v1", "ProtectedValues", property);
    }

    // Call only in an authorized local administrative utility, not an HTTP action.
    public static string ProtectForStorage(IDataProtectionProvider dp,
        string property, string plaintext)
    {
        ArgumentNullException.ThrowIfNull(plaintext);
        return ForProperty(dp, property).Protect(plaintext);
    }
}

הארגומנט של תיקיית המפתחות המוחלטת הוא הגדרת פריסה של היישום, לא נתיב מילולי להעתקה. יש להכין אותה מחוץ לשורש האתר ולהגביל הרשאות קריאה וכתיבה לשירות ולמנהלים מורשים. קביעת אחסון מפורש מבטלת את ההגנה האוטומטית במנוחה, ולכן הדוגמה מפעילה במפורש DPAPI עבור המשתמש הנוכחי. לא נבחרת הגנה כלל־מחשב.

רישום הערכים וצריכתם

מוסיפים לפרויקט את מתודת ההרחבה שלעיל ומשתמשים בה באתחול היישום הקיים. המסגרת המשותפת של ASP.NET Core מספקת את ממשקי Data Protection, תצורה, הזרקת תלויות ואפשרויות; כלי עזר מקומי נפרד צריך גם הוא תלויות Data Protection תואמות. לא אומתו כאן גרסת חבילה או הרצה בפועל.

// In the existing ASP.NET Core application's service-registration code:
string keyDirectory = builder.Configuration["DataProtection:KeyDirectory"]
    ?? throw new InvalidOperationException("Missing key-directory setting.");
builder.Services.AddProtectedValues(builder.Configuration, keyDirectory);

// After building the host, resolve once to detect missing/invalid values at startup:
var app = builder.Build();
_ = app.Services.GetRequiredService<IOptions<ProtectedValues>>().Value;
// Continue configuring the application's existing request pipeline.

רכיבים צורכים מזריקים IOptions<ProtectedValues> וקוראים את Value.Secret1 או Value.Secret2. מזריקים IDataProtectionProvider רק לרכיבים שבאמת צריכים להגן על נתונים או לפענחם. שינוי ערך ב־IConfiguration אינו דרך כללית לשמור קובץ appsettings מוגן.

יצירת ערכים מוצפנים בלי נקודת קצה להצפנה ב־HTTP

משתמשים ב־ProtectForStorage מתוך כלי עזר מקומי מורשה, המוגדר עם אותה טבעת מפתחות, אותו שם יישום ואותה מטרת מאפיין. בדוגמת DPAPI למשתמש הנוכחי עליו לפעול תחת אותה זהות Windows ובהקשר מחשב תואם. מכניסים את הטקסט הגלוי דרך ערוץ קלט מוגן; אין להעביר סודות בפרמטרי שאילתה של URL, בארגומנטים של שורת הפקודה או ביומנים. שומרים רק את הטקסט המוצפן שהתקבל במאפיין התצורה המתאים. הדוגמה מסירה במכוון את בקר ה־GET המקורי.

יש לגבות את טבעת המפתחות המוגנת יחד עם המידע הנדרש לשחזור ההגנה עליה. משאירים מפתחות הנדרשים לפענוח ערכים קיימים; מחיקת הטבעת עלולה להפוך את הערכים לבלתי ניתנים לשחזור. פריסה בכמה מחשבים מחייבת תכנון מפורש של שיתוף מפתחות והגנה במנוחה, ולא העתקה של ההגדרה המקומית הזאת ל־Windows. שמות מטרות ושמות יישומים אינם גבול אבטחה מול תהליך שנפרץ ובעל גישה למפתחות, והצפנה במנוחה אינה מונעת כתיבה זדונית למאגר המפתחות. לא שונו עבור מאמר זה תיקייה, מפתח, חשבון או תצורה.

מקורות ראשוניים

המאמר המקורי באנגלית: Implementing protection of a section in appsettings.json in .NET core. תאריך הפרסום המקורי: 2021-10-14T16:25:00.018+03:00.

Post a Comment

Previous Post Next Post