Több-bérlős alkalmazások rugalmas adatbázis-eszközökkel és sorszintű biztonsággal

A következőre vonatkozik: Azure SQL Database

A rugalmas adatbázis-eszközök és a sorszintű biztonság (RLS) együttműködnek, hogy lehetővé tegyék a több-bérlős alkalmazások adatszintjének skálázását az Azure SQL Database-vel. Ezek a technológiák együttesen segítenek egy magas skálázható adatszinttel rendelkező alkalmazás létrehozásában. Az adatszint támogatja a több-bérlős szegmenseket, és ADO.NET SqlClient- vagy Entity Framework-keretrendszert használ. További információ: Tervezési minták több-bérlős SaaS-alkalmazásokhoz az Azure SQL Database-zel.

  • A rugalmas adatbázis-eszközök lehetővé teszik, hogy a fejlesztők a .NET-kódtárak és az Azure-szolgáltatássablonok használatával szabványos horizontális skálázási eljárásokkal skálázhassák fel az adatszintet. A szegmensek rugalmas adatbázis-ügyfélkódtár használatával történő kezelése segít automatizálni és egyszerűsíteni a jellemzően a horizontális skálázással kapcsolatos számos infrastrukturális feladatot.
  • A sorszintű biztonság lehetővé teszi, hogy a fejlesztők biztonságosan tárolják az adatokat több bérlő számára ugyanabban az adatbázisban. Az RLS biztonsági szabályzatai kiszűrik azokat a sorokat, amelyek nem tartoznak a lekérdezést végrehajtó bérlőhöz. A szűrőlogika adatbázison belüli központosítása leegyszerűsíti a karbantartást, és csökkenti a biztonsági hibák kockázatát. Kockázatos lehet az összes ügyfélkódra támaszkodni a biztonság kikényszerítéséhez.

A funkciók együttes használatával egy alkalmazás több bérlő adatait is tárolhatja ugyanabban a szegmensadatbázisban. Bérlőnként kevesebbe kerül, ha a bérlők megosztanak egy adatbázist. Ugyanakkor ugyanez az alkalmazás a prémium szintű bérlői számára is kínálhatja a lehetőséget, hogy saját dedikált egybérlős szegmensért fizessen. Az egybérlős elkülönítés egyik előnye a szigorúbb teljesítménygarancia. Egy bérlős adatbázisban nincs más bérlő, aki versenge az erőforrásokért.

A cél az, hogy a rugalmas adatbázis-ügyfélkódtár adatfüggő útválasztási API-jait használva automatikusan összekapcsolja az egyes bérlőket a megfelelő szegmensadatbázissal. Csak egy szegmens tartalmaz adott TenantId-értéket az adott bérlőhöz. A TenantId a horizontális skálázási kulcs. A kapcsolat létrejötte után az adatbázisban található RLS biztonsági szabályzat biztosítja, hogy az adott bérlő csak azokat az adatsorokat érhesse el, amelyek tartalmazzák a TenantId azonosítóját.

Megjegyzés:

A bérlőazonosító egynél több oszlopból állhat. Az egyszerűség kedvéért ezt a beszélgetést informálisan feltételezzük egy egyoszlopos TenantId azonosítóval.

Blogging app architecture

A mintaprojekt letöltése

Előfeltételek

Ez a projekt kiterjeszti az Azure SQL-hez készült Elastic DB Tools – Entity Framework Integration című cikkben leírtat a több-bérlős szegmensadatbázisok támogatásának hozzáadásával. A projekt egy egyszerű konzolalkalmazást hoz létre blogok és bejegyzések létrehozásához. A projekt négy bérlőt és két több-bérlős szegmensadatbázist tartalmaz. Ezt a konfigurációt az előző diagram szemlélteti.

Hozza létre és futtassa az alkalmazást. Ez a futtatás a rugalmas adatbázis-eszközök szegmenstérkép-kezelőjét futtatja, és a következő teszteket hajtja végre:

  1. Az Entity Framework és a LINQ használatával hozzon létre egy új blogot, majd jelenítse meg az egyes bérlők összes blogját
  2. A ADO.NET SqlClient használatával megjelenítheti a bérlő összes blogját
  3. Próbáljon meg beszúrni egy blogot a nem megfelelő bérlőhöz, hogy ellenőrizze, hogy hiba történt-e

Figyelje meg, hogy mivel az RLS még nem lett engedélyezve a szegmens-adatbázisokban, ezek a tesztek egy-egy problémát tárnak fel: a bérlők láthatják azokat a blogokat, amelyek nem tartoznak hozzájuk, és az alkalmazás nem akadályozható meg abban, hogy rossz bérlőhöz szúrjon be blogot. A cikk további része leírja, hogyan oldhatja meg ezeket a problémákat a bérlők elkülönítésének kényszerítésével az RLS-sel. Két lépésből áll:

  1. Alkalmazásszint: Módosítsa az alkalmazáskódot úgy, hogy a kapcsolat megnyitása után mindig állítsa be az aktuális TenantId azonosítót a Standard kiadásSSION_CONTEXT. A mintaprojekt már így állítja be a TenantId azonosítót.
  2. Adatszint: Hozzon létre egy RLS biztonsági szabályzatot minden szegmensadatbázisban a sorok szűréséhez a Standard kiadásSSION_CONTEXT tárolt TenantId alapján. Hozzon létre egy szabályzatot az egyes szegmens-adatbázisokhoz, különben a több-bérlős szegmensek sorai nem lesznek szűrve.

1. Alkalmazásszint: A TenantId beállítása a Standard kiadásSSION_CONTEXT

Először a rugalmas adatbázis-ügyfélkódtár adatfüggő útválasztási API-jával csatlakozhat egy szegmensadatbázishoz. Az alkalmazásnak továbbra is meg kell mondania az adatbázisnak, hogy melyik TenantId használja a kapcsolatot. A TenantId megadja az RLS biztonsági szabályzatának, hogy mely sorokat kell más bérlőkhöz tartozóként szűrni. Tárolja az aktuális TenantId azonosítót a kapcsolat Standard kiadásSSION_CONTEXT.

A Standard kiadásSSION_CONTEXT másik alternatíva a CONTEXT_INFO használata. De Standard kiadásSSION_CONTEXT jobb megoldás. Standard kiadásSSION_CONTEXT használata egyszerűbb, alapértelmezés szerint NULL értéket ad vissza, és támogatja a kulcs-érték párokat.

Entity Framework

Az Entity Frameworkt használó alkalmazások esetében a legegyszerűbb módszer a Standard kiadásSSION_CONTEXT beállítása az Adatfüggő útválasztás ef DbContext használatával leírt ElasticScaleContext felülbírálásán belül. Hozzon létre és hajtson végre egy SqlCommandet, amely a Standard kiadásSSION_CONTEXT TenantId azonosítóját a kapcsolathoz megadott shardingKey-ra állítja. Ezután adja vissza az adatfüggő útválasztással közvetített kapcsolatot. Így csak egyszer kell írnia a kódot a Standard kiadásSSION_CONTEXT beállításához.

// ElasticScaleContext.cs
// Constructor for data-dependent routing.
// This call opens a validated connection that is routed to the
// proper shard by the shard map manager.
// Note that the base class constructor call fails for an open connection
// if migrations need to be done and SQL credentials are used.
// This is the reason for the separation of constructors.
// ...
public ElasticScaleContext(ShardMap shardMap, T shardingKey, string connectionStr)
    : base(
        OpenDDRConnection(shardMap, shardingKey, connectionStr),
        true)  // contextOwnsConnection
{
}

public static SqlConnection OpenDDRConnection(
    ShardMap shardMap,
    T shardingKey,
    string connectionStr)
{
    // No initialization.
    Database.SetInitializer<ElasticScaleContext<T>>(null);

    // Ask shard map to broker a validated connection for the given key.
    SqlConnection conn = null;
    try
    {
        conn = shardMap.OpenConnectionForKey(
            shardingKey,
            connectionStr,
            ConnectionOptions.Validate);

        // Set TenantId in SESSION_CONTEXT to shardingKey
        // to enable Row-Level Security filtering.
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandText =
            @"exec sp_set_session_context
                @key=N'TenantId', @value=@shardingKey";
        cmd.Parameters.AddWithValue("@shardingKey", shardingKey);
        cmd.ExecuteNonQuery();

        return conn;
    }
    catch (Exception)
    {
        if (conn != null)
        {
            conn.Dispose();
        }
        throw;
    }
}
// ...

Most a Standard kiadásSSION_CONTEXT automatikusan a megadott TenantId azonosítóval van beállítva az ElasticScaleContext meghívásakor:

// Program.cs
SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
{
    using (var db = new ElasticScaleContext<int>(
        sharding.ShardMap, tenantId, connStrBldr.ConnectionString))
    {
        var query = from b in db.Blogs
                    orderby b.Name
                    select b;

        Console.WriteLine("All blogs for TenantId {0}:", tenantId);
        foreach (var item in query)
        {
            Console.WriteLine(item.Name);
        }
    }
});

ADO.NET SqlClient

Az ADO.NET SqlClientet használó alkalmazásokhoz hozzon létre egy burkolófüggvényt a ShardMap.Open Csatlakozás ionForKey metódus körül. A kapcsolat visszaadása előtt a burkoló automatikusan állítsa a TenantId értéket a Standard kiadásSSION_CONTEXT az aktuális TenantId azonosítóra. Annak érdekében, hogy a Standard kiadásSSION_CONTEXT mindig be legyen állítva, csak ezzel a burkoló függvénnyel nyisson meg kapcsolatokat.

// Program.cs
// Wrapper function for ShardMap.OpenConnectionForKey() that
// automatically sets SESSION_CONTEXT with the correct
// tenantId before returning a connection.
// As a best practice, you should only open connections using this method
// to ensure that SESSION_CONTEXT is always set before executing a query.
// ...
public static SqlConnection OpenConnectionForTenant(
    ShardMap shardMap, int tenantId, string connectionStr)
{
    SqlConnection conn = null;
    try
    {
        // Ask shard map to broker a validated connection for the given key.
        conn = shardMap.OpenConnectionForKey(
            tenantId, connectionStr, ConnectionOptions.Validate);

        // Set TenantId in SESSION_CONTEXT to shardingKey
        // to enable Row-Level Security filtering.
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandText =
            @"exec sp_set_session_context
                @key=N'TenantId', @value=@shardingKey";
        cmd.Parameters.AddWithValue("@shardingKey", tenantId);
        cmd.ExecuteNonQuery();

        return conn;
    }
    catch (Exception)
    {
        if (conn != null)
        {
            conn.Dispose();
        }
        throw;
    }
}

// ...

// Example query via ADO.NET SqlClient.
// If row-level security is enabled, only Tenant 4's blogs are listed.
SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
{
    using (SqlConnection conn = OpenConnectionForTenant(
        sharding.ShardMap, tenantId4, connStrBldr.ConnectionString))
    {
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandText = @"SELECT * FROM Blogs";

        Console.WriteLine(@"--
All blogs for TenantId {0} (using ADO.NET SqlClient):", tenantId4);

        SqlDataReader reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            Console.WriteLine("{0}", reader["Name"]);
        }
    }
});

2. Adatszint: Sorszintű biztonsági szabályzat létrehozása

Biztonsági szabályzat létrehozása az egyes bérlők számára elérhető sorok szűréséhez

Most, hogy az alkalmazás a lekérdezés előtt Standard kiadásSSION_CONTEXT az aktuális TenantId azonosítóval, az RLS biztonsági szabályzata szűrheti a lekérdezéseket, és kizárhatja azokat a sorokat, amelyek más TenantId azonosítóval rendelkeznek.

Az RLS a Transact-SQL-ben implementálva van. A felhasználó által definiált függvény határozza meg a hozzáférési logikát, és egy biztonsági szabályzat ezt a függvényt tetszőleges számú táblához köti. Ehhez a projekthez:

  1. A függvény ellenőrzi, hogy az alkalmazás csatlakozik-e az adatbázishoz, és hogy a Standard kiadásSSION_CONTEXT tárolt TenantId egyezik-e egy adott sor Bérlőazonosítóval.

    • Az alkalmazás nem más SQL-felhasználó, hanem csatlakoztatva van.
  2. A FILTER-predikátum lehetővé teszi, hogy a TenantId szűrőnek megfelelő sorok áthaladjanak Standard kiadás LECT, UPDATE és DELETE lekérdezésekhez.

    • A BLOCK predikátum megakadályozza, hogy azok a sorok, amelyek nem sikerülnek a szűrő in Standard kiadás RTed vagy UPDATEd típusúak legyenek.
    • Ha Standard kiadásSSION_CONTEXT nincs beállítva, a függvény NULL értéket ad vissza, és nem láthatók és nem szúrhatók be sorok.

Az RLS minden szegmensen való engedélyezéséhez hajtsa végre a következő T-SQL-t a Visual Studio (SSDT), az SSMS vagy a projektben található PowerShell-szkript használatával. Vagy ha rugalmas adatbázis-feladatokat használ, automatizálhatja ennek a T-SQL-nek a végrehajtását az összes szegmensen.

CREATE SCHEMA rls; -- Separate schema to organize RLS objects.
GO

CREATE FUNCTION rls.fn_tenantAccessPredicate(@TenantId int)
    RETURNS TABLE
    WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS fn_accessResult
        -- Use the user in your application's connection string.
        -- Here we use 'dbo' only for demo purposes!
        WHERE DATABASE_PRINCIPAL_ID() = DATABASE_PRINCIPAL_ID('dbo')
        AND CAST(SESSION_CONTEXT(N'TenantId') AS int) = @TenantId;
GO

CREATE SECURITY POLICY rls.tenantAccessPolicy
    ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Blogs,
    ADD BLOCK  PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Blogs,
    ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Posts,
    ADD BLOCK  PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Posts;
GO

Tipp.

Egy összetett projektben több száz táblához kell hozzáadnia a predikátumot, ami fárasztó lehet. Létezik egy segéd által tárolt eljárás, amely automatikusan létrehoz egy biztonsági szabályzatot, és egy predikátumot ad hozzá a séma összes táblájára. További információt a Sorszintű biztonság alkalmazása minden táblára – segédszkript (blog) című blogbejegyzésben talál.

Most, ha ismét futtatja a mintaalkalmazást, a bérlők csak a hozzájuk tartozó sorokat látják. Ezenkívül az alkalmazás nem tud olyan sorokat beszúrni, amelyek a szegmensadatbázishoz jelenleg csatlakoztatott bérlőkhöz tartoznak. Emellett az alkalmazás nem tudja frissíteni a TenantId azonosítót az általa látható sorokban. Ha az alkalmazás megkísérli valamelyiket, a dbUpdateException létrejön.

Ha később új táblát ad hozzá, módosítsa a biztonsági szabályzatot a FILTER és a BLOCK predikátumok hozzáadásához az új táblához.

ALTER SECURITY POLICY rls.tenantAccessPolicy
    ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.MyNewTable,
    ADD BLOCK  PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.MyNewTable;
GO

Alapértelmezett korlátozások hozzáadása a TenantId in Standard kiadás RT-hez való automatikus feltöltéséhez

Minden táblára beállíthat egy alapértelmezett korlátozást, amely automatikusan feltölti a TenantId azonosítót a sorok beszúrásakor Standard kiadásSSION_CONTEXT jelenleg tárolt értékkel. Egy példa a következő.

-- Create default constraints to auto-populate TenantId with the
-- value of SESSION_CONTEXT for inserts.
ALTER TABLE Blogs
    ADD CONSTRAINT df_TenantId_Blogs
    DEFAULT CAST(SESSION_CONTEXT(N'TenantId') AS int) FOR TenantId;
GO

ALTER TABLE Posts
    ADD CONSTRAINT df_TenantId_Posts
    DEFAULT CAST(SESSION_CONTEXT(N'TenantId') AS int) FOR TenantId;
GO

Az alkalmazásnak most már nem kell bérlőazonosítót megadnia sorok beszúrásakor:

SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
{
    using (var db = new ElasticScaleContext<int>(
        sharding.ShardMap, tenantId, connStrBldr.ConnectionString))
    {
        // The default constraint sets TenantId automatically!
        var blog = new Blog { Name = name };
        db.Blogs.Add(blog);
        db.SaveChanges();
    }
});

Megjegyzés:

Ha egy Entity Framework-projekthez alapértelmezett korlátozásokat használ, javasoljuk, hogy NE vegye fel a TenantId oszlopot az EF-adatmodellbe. Ez a javaslat azért van, mert az Entity Framework-lekérdezések automatikusan olyan alapértelmezett értékeket adnak meg, amelyek felülírják az Standard kiadásSSION_CONTEXT használó T-SQL-ben létrehozott alapértelmezett korlátozásokat. Ha például alapértelmezett korlátozásokat szeretne használni a mintaprojektben, távolítsa el a TenantId fájlt a DataClasses.cs fájlból (és futtassa a bővítménymigrálást a Csomagkezelő konzolon), és a T-SQL használatával győződjön meg arról, hogy a mező csak az adatbázistáblákban létezik. Így az EF automatikusan helytelen alapértelmezett értékeket ad meg az adatok beszúrásakor.

(Nem kötelező) Az összes sor elérésének engedélyezése egy felügyelő számára

Előfordulhat, hogy egyes alkalmazások olyan felügyelőt szeretnének létrehozni, aki az összes sorhoz hozzáfér. A felügyelők az összes szegmens összes bérlőjén engedélyezhetik a jelentéskészítést. Vagy egy felügyelő el tudja végezni a felosztási egyesítési műveleteket olyan szegmenseken, amelyek bérlői sorokat helyeznek át az adatbázisok között.

Egy felügyelő engedélyezéséhez hozzon létre egy új SQL-felhasználót (superuser ebben a példában) minden szegmensadatbázisban. Ezután módosítsa a biztonsági szabályzatot egy új predikátumfüggvénnyel, amely lehetővé teszi a felhasználó számára az összes sor elérését. Ez a függvény a következő.

-- New predicate function that adds superuser logic.
CREATE FUNCTION rls.fn_tenantAccessPredicateWithSuperUser(@TenantId int)
    RETURNS TABLE
    WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS fn_accessResult
        WHERE
        (
            DATABASE_PRINCIPAL_ID() = DATABASE_PRINCIPAL_ID('dbo') -- Replace 'dbo'.
            AND CAST(SESSION_CONTEXT(N'TenantId') AS int) = @TenantId
        )
        OR
        (
            DATABASE_PRINCIPAL_ID() = DATABASE_PRINCIPAL_ID('superuser')
        );
GO

-- Atomically swap in the new predicate function on each table.
ALTER SECURITY POLICY rls.tenantAccessPolicy
    ALTER FILTER PREDICATE rls.fn_tenantAccessPredicateWithSuperUser(TenantId) ON dbo.Blogs,
    ALTER BLOCK  PREDICATE rls.fn_tenantAccessPredicateWithSuperUser(TenantId) ON dbo.Blogs,
    ALTER FILTER PREDICATE rls.fn_tenantAccessPredicateWithSuperUser(TenantId) ON dbo.Posts,
    ALTER BLOCK  PREDICATE rls.fn_tenantAccessPredicateWithSuperUser(TenantId) ON dbo.Posts;
GO

Maintenance

  • Új szegmensek hozzáadása: Hajtsa végre a T-SQL-szkriptet, hogy engedélyezze az RLS-t az új szegmenseken, ellenkező esetben az ezeken a szegmenseken lévő lekérdezések nem lesznek szűrve.
  • Új táblák hozzáadása: FILTER és BLOCK predikátum hozzáadása a biztonsági szabályzathoz minden szegmensen, amikor új tábla jön létre. Ellenkező esetben az új táblában lévő lekérdezések nem lesznek szűrve. Ez a kiegészítés egy DDL-eseményindítóval automatizálható, a sorszintű biztonság automatikus alkalmazása újonnan létrehozott táblákra (blog) című cikkben leírtak szerint.

Összesítés

A rugalmas adatbázis-eszközök és a sorszintű biztonság együtt használhatók az alkalmazások adatszintjének horizontális felskálázásához a több-bérlős és az egybérlős szegmensek támogatásával. A több-bérlős szegmensek az adatok hatékonyabb tárolására használhatók. Ez a hatékonyság akkor jelentkezik, ha nagy számú bérlő csak néhány sornyi adatmal rendelkezik. Az egybérlős szegmensek támogathatják a szigorúbb teljesítménnyel és elkülönítési követelményekkel rendelkező prémium bérlőket. További információ: Sorszintű biztonsági referencia.

További információforrások

Kérdések és funkciókérések

Ha kérdése van, lépjen kapcsolatba velünk az SQL Database-hez készült Microsoft Q&A kérdésoldalán. És adjon hozzá minden funkciókérést az SQL Database visszajelzési fórumához.