Valet-nyckelmönster

Azure
Azure Storage

Använd ett token som ger klienter begränsad direktåtkomst till en viss resurs för att avlasta dataöverföring från programmet. Det är särskilt användbart i program där molnbaserade lagringssystem eller köer används, och kan minimera kostnaderna och maximera skalbarhet och prestanda.

Kontext och problem

Klientprogram och webbläsare behöver ofta läsa och skriva filer eller dataströmmar till och från ett programs lagring. Normalt hanterar programmet förflyttningen av data – antingen genom att hämta dem från lagringen och strömma dem till klienten, eller genom att läsa den uppladdade dataströmmen från klienten och lagra den i datalagret. Den här metoden absorberar dock värdefulla resurser som beräkning, minne och bandbredd.

Datalager kan hantera överföring och hämtning av data direkt, utan att programmet behöver utföra bearbetningen för att flytta data. Men för detta krävs vanligtvis att klienten har åtkomst till lagrets säkerhetsautentiseringsuppgifter. Det här kan vara en användbar teknik för att minimera kostnaderna för dataöverföring och kravet för att skala ut programmet, och för att maximera prestandan. Det innebär dock att programmet inte längre kan hantera datasäkerheten. När klienten har en anslutning till datalagret för direktåtkomst kan inte programmet fungera som gatekeeper. Det styr inte längre processen och kan inte förhindra efterföljande överföringar eller hämtningar från datalagret.

Detta är inte en realistisk metod i distribuerade system som behöver hantera klienter som inte är betrodda. I stället måste programmen kunna styra dataåtkomsten säkert och detaljerat, men ändå minska belastningen på servern genom att konfigurera den här anslutningen och därefter låta klienten kommunicera direkt med datalagret för de nödvändiga läs- och skrivåtgärderna.

Lösning

Du måste lösa problemet med att styra åtkomsten till ett datalager där lagret inte kan hantera autentisering och auktorisering av klienter. En vanlig lösning är att begränsa åtkomsten till datalagrets offentliga anslutning och ge klienten en nyckel eller token som datalagret kan verifiera.

En sådan nyckel eller token brukar benämnas valet-nyckel. Den ger tidsbegränsad åtkomst till specifika resurser och tillåter endast fördefinierade åtgärder som att läsa och skriva till lagring eller köer, eller att ladda upp och ladda ned i en webbläsare. Programmen kan snabbt och enkelt skapa och utfärda valet-nycklar till klientenheter och webbläsare, så att klienterna tillåts att utföra nödvändiga åtgärder utan att programmet behöver hantera dataöverföringen direkt. Detta eliminerar bearbetningskostnaderna och påverkan på prestanda och skalbarhet från programmet och servern.

Klienten använder sin token för att få åtkomst till en specifik resurs i datalagret under en viss tidsperiod och med specifika åtkomstbegränsningar, som bilden visar. Efter den angivna perioden blir nyckeln ogiltig och tillåter inte åtkomst till resursen.

Figur 1 – Översikt över mönstret

Du kan också konfigurera en nyckel som har andra beroenden, till exempel dataomfång. Beroende på datalagerfunktionerna kan nyckeln till exempel specificera en hel tabell i ett datalager eller endast vissa rader i en tabell. I molnlagringssystem kan nyckeln specificera en container eller enbart ett visst objekt i containern.

Programmet kan också göra nyckeln ogiltig. Det är en bra metod om klienten meddelar servern att dataöverföringen har slutförts. Servern kan sedan ogiltigförklara den nyckeln för att förhindra ytterligare åtkomst.

Det här mönstret kan förenkla hanteringen av resursåtkomst eftersom det inte finns några krav på att skapa och autentisera en användare, bevilja behörighet och sedan ta bort användaren. Det gör det också enkelt att begränsa platsen, behörigheten och giltighetsperioden – allt genom att helt enkelt generera en nyckel vid körning. Viktiga faktorer är att begränsa giltighetsperioden och i synnerhet platsen för resursen så mycket som möjligt, så att mottagaren kan bara använda den för det avsedda syftet.

Problem och överväganden

Tänk på följande när du bestämmer hur du ska implementera mönstret:

Hantera nyckelns giltighetsstatus och tidsperiod. Om nyckeln läcker ut eller på annat sätt komprometteras, låser den upp målobjektet och gör det tillgängligt för skadlig användning under giltighetsperioden. En nyckel kan vanligtvis återkallas eller inaktiveras, beroende på hur det har utfärdats. Principer på serversidan kan ändras eller den servernyckel som den signerades med kan vara ogiltig. Ange en kort giltighetsperiod för att minimera risken för att obehöriga åtgärder ska utföras i datalagret. Men om giltighetsperioden är för kort kanske inte klienten hinner slutföra åtgärden innan nyckelns giltighet går ut. Tillåt behöriga användare att förnya nyckeln innan giltighetsperioden går ut om fler behöver ha åtkomst till den skyddade resursen.

Styr nyckelns åtkomstnivå. Vanligtvis ska nyckeln endast tillåta användaren att utföra det som krävs för att slutföra åtgärden, till exempel skrivskyddad åtkomst om klienten inte ska kunna ladda upp data till datalagret. För filuppladdning är det vanligt att ange en nyckel som endast ger skrivbehörighet, samt plats och giltighetsperiod. Det är viktigt att den resurs eller resursuppsättning som gäller för nyckeln anges korrekt.

Överväg hur du kontrollerar användarnas beteende. Om du implementerar det här mönstret innebär det en viss förlust av kontroll över de resurser som användarna beviljas åtkomst till. Vilken kontrollnivå som kan användas begränsas av funktionerna i de principer och behörigheter som är tillgängliga för tjänsten eller i måldatalagret. Till exempel går det vanligtvis inte att skapa en nyckel som begränsar storleken på data som ska skrivas till lagringen eller antalet gånger som nyckeln kan användas för åtkomst till en fil. Detta kan ge mycket höga, oväntade kostnader för dataöverföring, även när den används av den avsedda klienten. Det kan orsakas av ett fel i koden som gör att upp- eller nedladdning görs upprepade gånger. Om du vill begränsa antalet gånger som en fil kan laddas upp kan du där det är möjligt tvinga klienten att meddela programmet när en åtgärd har slutförts. Vissa datalager skapar till exempel händelser som programkoden kan använda för att övervaka åtgärder och styra användarbeteendet. Det är dock svårt att framtvinga kvoter för enskilda användare i ett scenario med flera klienter där samma nyckel används av alla användare från en klientorganisation.

Validera alla överförda data och eventuellt rensa dem. En obehörig användare som får åtkomst till nyckeln kan ladda upp data som är avsedd att skada systemet. Auktoriserade användare kan också ladda upp data som är ogiltiga. När dessa behandlas kan det orsaka ett fel eller systemfel. För att skydda dig mot det kan du säkerställa att alla uppladdade data verifieras och söks igenom efter skadlig kod innan de används.

Granska alla åtgärder. Många nyckelbaserade mekanismer kan logga åtgärder som till exempel uppladdningar, nedladdningar och fel. De här loggarna kan ingå i en granskningsprocess och kan även användas för fakturering om användaren debiteras enligt filstorlek eller datavolym. Använd loggarna för att identifiera autentiseringsfel kan bero på problem med nyckelleverantören eller oavsiktlig borttagning av en lagrad åtkomstprincip.

Leverera nyckeln på ett säkert sätt. Den kan vara inbäddad i en URL som användaren aktiverar på en webbsida eller användas i en serveromdirigering så att nedladdningen sker automatiskt. Använd alltid HTTPS för att leverera nyckeln via en säker kanal.

Skydda känsliga data under överföringen. Känsliga data som levereras via programmet sker vanligtvis med hjälp av TLS, och detta bör tillämpas för klienter som kommer åt datalagret direkt.

Här är några andra problem som du bör vara medveten om när du implementerar det här mönstret:

  • Om klienten inte meddelar eller inte kan meddela servern när åtgärden har slutförts, och den ena begränsningen är nyckelns giltighetsperiod, kan inte programmet utföra granskningsåtgärder som att räkna antalet upp- eller nedladdningar eller förhindra flera upp- eller nedladdningar.

  • Flexibiliteten när det gäller vilka nyckelprinciper som kan genereras kan begränsas. Till exempel kanske en del mekanismer endast tillåter användning av en tidsinställd giltighetsperiod. Andra inte kan ange en tillräcklig detaljnivå för läs- och skrivbehörighet.

  • Om du anger starttid för giltighetsperioden för din nyckel eller token måste du se till att den är lite tidigare än aktuell servertiden, ifall klientens klockor inte är exakt synkroniserade. Om ingen standard har angetts är standard den aktuella servertiden.

  • Den URL som innehåller nyckeln kommer att loggas i serverloggfilerna. Nyckeln hinner vanligtvis gå ut innan loggfilerna analyseras, men se ändå till att begränsa åtkomsten till loggfilerna. Om loggdata skickas till ett övervakningssystem eller lagras på en annan plats bör du överväga att använda en fördröjning för att förhindra att nycklar läcker ut innan giltighetstiden har gått ut.

  • Om klientkoden körs i en webbläsare kan webbläsaren behöva ha stöd för resursdelning över flera ursprung (CORS) om du vill aktivera kod som körs i webbläsaren för åtkomst till data i en annan domän än den som användes för sidan. Vissa äldre webbläsare och vissa datalager stöder inte CORS, och kod som körs i dessa webbläsare kanske inte kan använda en valet-nyckel för att ge åtkomst till data i en annan domän, till exempel ett molnlagringskonto.

När du ska använda det här mönstret

Det här mönstret är användbart i följande situationer:

  • Minimera resursinläsningen och maximera prestanda och skalbarhet. När en valet-nyckel används måste inte resursen vara låst, inget fjärrserveranrop krävs, det finns ingen gräns för hur många valet-nycklar som kan utfärdas och det förhindrar att en enskild felpunkt uppstår på grund av dataöverföring via programkoden. Att skapa en valet-nyckel är oftast en enkel krypteringsåtgärd där en sträng signeras med en nyckel.

  • Minimera driftskostnaderna. Aktivera direktåtkomst till butiker och köer är resurs- och kostnadseffektivt, kan ge färre nätverksförfrågningar och kan göra att ett mindre antal beräkningsresurser krävs.

  • När klienter regelbundet laddar upp eller ned data, i synnerhet om det är en stor volym eller om varje åtgärd omfattar stora filer.

  • När programmet har begränsade beräkningsresurser tillgängliga, antingen på grund av värdbegränsningar eller kostnadsöverväganden. I det här scenariot är mönstret ännu mer användbart om det finns många samtidiga upp- eller nedladdningar eftersom programmet inte behöver hantera dataöverföringen.

  • När data lagras i ett fjärranslutet datalager eller ett annat datacenter. Om programmet måste fungera som en gatekeeper kan det tillkomma kostnader för ytterligare bandbredd för överföring av data mellan datacenter eller i offentliga och privata nätverk mellan klienten och programmet och därefter mellan programmet och datalagret.

Det här mönstret kanske inte är användbart i följande situationer:

  • Om programmet måste utföra någon uppgift på informationen innan den lagras eller innan den skickas till klienten. Om programmet till exempel behöver utföra verifiering, logga åtkomst eller köra en dataomvandling. Vissa datalager och klienter kan dock förhandla och utföra enkla omvandlingar som komprimering och dekomprimering (till exempel kan en webbläsare vanligtvis hantera gzip-format).

  • Om utformningen av ett befintligt program gör det svårt att införliva mönstret. När man använder det här mönstret krävs oftast en annan arkitekturmetod för att leverera och ta emot data.

  • Om det är nödvändigt att underhålla granskningshistorik och styra hur många gånger en överföringsåtgärd körs, och den valet-nyckelmekanism som används inte har stöd för aviseringar som servern kan använda för att hantera åtgärderna.

  • Om det är nödvändigt att begränsa datastorleken, särskilt under uppladdningar. Den enda lösningen på detta är att programmet kontrollerar datastorleken när åtgärden har slutförts eller kontrollerar storleken på uppladdningar efter en angiven tidsperiod eller enligt ett schema.

Design av arbetsbelastning

En arkitekt bör utvärdera hur Valet Key-mönstret kan användas i arbetsbelastningens design för att uppfylla de mål och principer som beskrivs i grundpelarna i Azure Well-Architected Framework. Till exempel:

Grundpelare Så här stöder det här mönstret pelarmål
Beslut om säkerhetsdesign bidrar till att säkerställa konfidentialitet, integritet och tillgänglighet för arbetsbelastningens data och system. Det här mönstret gör det möjligt för en klient att direkt komma åt en resurs utan att behöva långvariga eller stående autentiseringsuppgifter. Alla åtkomstbegäranden börjar med en granskningsbar transaktion. Den beviljade åtkomsten begränsas sedan i både omfattning och varaktighet. Det här mönstret gör det också enklare att återkalla den beviljade åtkomsten.

- SE:05 Identitets- och åtkomsthantering
Kostnadsoptimering fokuserar på att upprätthålla och förbättra arbetsbelastningens avkastning på investeringen. Den här designen avlastar bearbetningen som en exklusiv relation mellan klienten och resursen utan att lägga till en komponent för att hantera alla klientbegäranden direkt. Fördelen är mest dramatisk när klientbegäranden är tillräckligt frekventa eller tillräckligt stora för att kräva betydande proxyresurser.

- CO:09 Flödeskostnader
Prestandaeffektivitet hjälper din arbetsbelastning att effektivt uppfylla kraven genom optimeringar inom skalning, data och kod. Att inte använda en mellanliggande resurs för att proxyhantera bearbetningen av åtkomst avlastningar som en exklusiv relation mellan klienten och resursen utan att kräva en ambassadörskomponent som behöver hantera alla klientbegäranden på ett högpresterande sätt. Fördelen med att använda det här mönstret är viktigast när proxyn inte lägger till värde i transaktionen.

- PE:07 Kod och infrastruktur

Som med alla designbeslut bör du överväga eventuella kompromisser mot målen för de andra pelarna som kan införas med det här mönstret.

Exempel

Azure har stöd för signaturer för delad åtkomst i Azure Storage för detaljerad åtkomstkontroll till blobbar, tabeller och köer samt för Service Bus-köer och -ämnen. En signaturtoken för delad åtkomst kan konfigureras för att tillhandahålla särskilda behörigheter som läs-, skriv- uppdaterings- och raderingsbehörighet till en viss tabell, ett nyckelintervall inom en tabell, en kö, en blob eller en blob-container. Giltigheten kan vara en angiven tidsperiod. Den här funktionen passar bra för att använda en betjäntnyckel för åtkomst.

Överväg en arbetsbelastning som har hundratals mobil- eller skrivbordsklienter som ofta laddar upp stora binärfiler. Utan det här mönstret har arbetsbelastningen i princip två alternativ. Den första är att tillhandahålla stående åtkomst och konfiguration till alla klienter för att utföra uppladdningar direkt till ett lagringskonto. Det andra är att implementera mönstret Gateway-routning för att konfigurera en slutpunkt där klienter använder proportionell åtkomst till lagring, men detta kanske inte lägger till ytterligare värde i transaktionen. Båda metoderna har problem som hanteras i mönsterkontexten:

  • Långlivade, i förväg delade hemligheter. Potentiellt utan mycket sätt att tillhandahålla olika nycklar till olika klienter.
  • Lade till kostnader för att köra en beräkningstjänst som har tillräckligt med resurser för att hantera för närvarande mottagande stora filer.
  • Potentiellt långsammare klientinteraktioner genom att lägga till ett extra lager av beräkning och nätverkshopp i uppladdningsprocessen.

Med hjälp av Valet Key-mönstret åtgärdas säkerhets-, kostnadsoptimerings- och prestandaproblemen.

Diagram som visar en klient som har åtkomst till ett lagringskonto efter att först ha hämtat en åtkomsttoken från ett API.

  1. Klienter autentiserar till en låg vikt och skala till noll Azure Function värdhanterat API för att begära åtkomst.

  2. API:et validerar begäran och hämtar och returnerar sedan en tids- och omfångsbegränsad SaS-token.

    Den token som genereras av API:et begränsar klienten till följande begränsningar:

    • Vilket lagringskonto som ska användas. Det innebär att klienten inte behöver känna till den här informationen i förväg.
    • En specifik container och filnamn som ska användas. se till att token kan användas med högst en fil.
    • Ett kort åtgärdsfönster, till exempel tre minuter. Den här korta tidsperioden säkerställer att token har en TTL som inte sträcker sig förbi dess verktyg.
    • Behörigheter för att bara skapa en blob, inte ladda ned, uppdatera eller ta bort.
  3. Denna token används sedan av klienten, inom den smala tidsperioden, för att ladda upp filen direkt till lagringskontot.

API:et genererar dessa token till auktoriserade klienter med hjälp av en användardelegeringsnyckel baserat på API:ets egen hanterade Identitet för Microsoft Entra-ID. Loggning är aktiverat på både lagringskontona och tokengenererings-API:et tillåter korrelation mellan tokenbegäranden och tokenanvändning. API:et kan använda information om klientautentisering eller andra tillgängliga data för att avgöra vilket lagringskonto eller vilken container som ska användas, till exempel i en situation med flera klienter.

Ett fullständigt exempel finns på GitHub i exemplet med Valet Key-mönster. Följande kodfragment anpassas från det exemplet. Den första visar hur Azure-funktionen (som finns i ValetKey.Web) genererar en användardelegering av signaturtoken för delad åtkomst med hjälp av Azure-funktionens egen hanterade identitet.

[Function("FileServices")]
public async Task<StorageEntitySas> GenerateTokenAsync([HttpTrigger(...)] HttpRequestData req, ..., 
                                                        CancellationToken cancellationToken)
{
  // Authorize the caller, select a blob storage account, container, and file name.
  // Authenticate to the storage account with the Azure Function's managed identity.
  ...

  return await GetSharedAccessReferenceForUploadAsync(blobContainerClient, blobName, cancellationToken);
}

/// <summary>
/// Return an access key that allows the caller to upload a blob to this
/// specific destination for about three minutes.
/// </summary>
private async Task<StorageEntitySas> GetSharedAccessReferenceForUploadAsync(BlobContainerClient blobContainerClient, 
                                                                            string blobName,
                                                                            CancellationToken cancellationToken)
{
  var blobServiceClient = blobContainerClient.GetParentBlobServiceClient();
  var blobClient = blobContainerClient.GetBlockBlobClient(blobName);

  // Allows generating a SaS token that is evaluated as the union of the RBAC permissions on the managed identity
  // (for example, Blob Data Contributor) and then narrowed further by the specific permissions in the SaS token.
  var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow.AddMinutes(-3),
                                                                            DateTimeOffset.UtcNow.AddMinutes(3),
                                                                            cancellationToken);

  // Limit the scope of this SaS token to the following:
  var blobSasBuilder = new BlobSasBuilder
  {
      BlobContainerName = blobContainerClient.Name,     // - Specific container
      BlobName = blobClient.Name,                       // - Specific filename
      Resource = "b",                                   // - Blob only
      StartsOn = DateTimeOffset.UtcNow.AddMinutes(-3),  // - For about three minutes (+/- for clock drift)
      ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(3),  // - For about three minutes (+/- for clock drift)
      Protocol = SasProtocol.Https                      // - Over HTTPS
  };
  blobSasBuilder.SetPermissions(BlobSasPermissions.Create);

  return new StorageEntitySas
  {
      BlobUri = blobClient.Uri,
      Signature = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName).ToString();
  };
}

Följande kodfragment är det dataöverföringsobjekt (DTO) som används av både API:et och klienten.

public class StorageEntitySas
{
  public Uri? BlobUri { get; internal set; }
  public string? Signature { get; internal set; }
}

Klienten (som finns i ValetKey.Client) använder sedan den URI och token som returneras från API:et för att utföra uppladdningen utan att kräva ytterligare resurser och med fullständig prestanda från klient till lagring.

...

// Get the SaS token (valet key)
var blobSas = await httpClient.GetFromJsonAsync<StorageEntitySas>(tokenServiceEndpoint);
var sasUri = new UriBuilder(blobSas.BlobUri)
{
    Query = blobSas.Signature
};

// Create a blob client using the SaS token as credentials
var blob = new BlobClient(sasUri.Uri);

// Upload the file directly to blob storage
using (var stream = await GetFileToUploadAsync(cancellationToken))
{
    await blob.UploadAsync(stream, cancellationToken);
}

...

Nästa steg

Följande vägledning kan vara relevant när du implementerar det här mönstret:

Följande mönster kan också vara relevanta när du implementerar det här mönstret:

  • Gatekeeper-mönster. Det här programmet kan användas tillsammans med mönstret Valet-nyckel för att skydda program och tjänster med hjälp av en dedikerad värdinstans som fungerar som koordinator mellan klienter och programmet eller tjänsten. Gatekeepern validerar och sanerar begäranden, och skickar begäranden och data mellan klienten och programmet. Detta kan ge ett extra lager säkerhet och minska risken för angrepp på systemet.
  • Mönster för värddator för statiskt innehåll. Beskriver hur du distribuerar statiska resurser till en molnbaserad lagringstjänst som kan leverera dessa resurser direkt till klienten för att minska behovet av dyra beräkningsinstanser. Valet-nyckelmönstret kan användas för att skydda resurserna där de inte är avsedda att vara allmänt tillgängliga.