Integrera en tredjepartsmotor med OneLake-säkerhet (förhandsversion)

Den här artikeln beskriver hur utvecklare av tredjepartsmotorer kan integreras med OneLake-säkerhet för att fråga efter data från OneLake samtidigt som säkerhet på radnivå (RLS) och säkerhet på kolumnnivå (CLS) tillämpas. Integreringen använder den auktoriserade motormodellen, där motorn läser data direkt från OneLake och tillämpar säkerhetsprinciper i sitt eget beräkningslager.

Anmärkning

Den här funktionen är en del av en förhandsversion och tillhandahålls endast i utvärderings- och utvecklingssyfte. Den kan ändras baserat på feedback och rekommenderas inte för produktionsanvändning.

Översikt

OneLake-säkerhet definierar detaljerade åtkomstkontrollprinciper – inklusive säkerhet på tabellnivå, radnivå och kolumnnivå – en gång i OneLake. Microsoft Fabric-motorer som Spark och SQL-analysslutpunkten tillämpar dessa principer vid frågetillfället. OneLake-säkerhet garanterar dock tillämpning av detaljerade åtkomstkontrollprinciper oavsett hur data används. Därför blockeras obehöriga externa begäranden om att läsa filer från OneLake för att säkerställa att data inte läcker ut.

Den auktoriserade motormodellen löser problemet. Du registrerar en dedikerad identitet (tjänstens huvudnamn eller hanterade identitet) som har fullständig läsbehörighet till data och som även kan läsa säkerhetsmetadata. Motorn använder den här identiteten för att:

  1. Läs rådatafilerna från OneLake.
  2. Hämta de effektiva säkerhetsprinciperna för en viss användare genom att anropa Get authorized access for a principal API (Hämta auktoriserad åtkomst för ett huvudnamns-API ).
  3. Använd de returnerade rad- och kolumnfiltren i ett eget frågekörningslager.
  4. Returnera endast tillåtna data till slutanvändaren.

Den här metoden ger motorn fullständig kontroll över frågeplanering och cachelagring, samtidigt som säkerhetstillämpningen förblir konsekvent med vad Fabric-motorer tillhandahåller och ger användaren kontroll över auktorisering.

Förutsättningar

Kontrollera att du har följande innan du börjar integrera:

  • Ett Microsoft Entra-tjänsthuvudnamn eller en hanterad identitet som motorn använder för att få åtkomst till OneLake. Endast Microsoft Entra-identiteter stöds.
  • Arbetsytans medlemsroll (eller högre) för motoridentiteten på målarbetsytan. Detta ger identiteten de behörigheter som krävs för att läsa datafiler och säkerhetsmetadata från OneLake.
  • Ett Infrastrukturobjekt (lakehouse, speglad databas eller speglad katalog) med OneLake-säkerhet aktiverat.
  • OneLake-säkerhetsroller som har konfigurerats på objektet med de RLS- eller CLS-principer som du vill tillämpa.
  • Motoridentiteten måste ha obegränsad läsbehörighet till de tabeller som den läser. Om RLS- eller CLS-principer tillämpas på själva motoridentiteten returnerar API-anropen fel.

Arkitektur

Följande diagram visar auktoriseringsflödet på hög nivå för en auktoriserad motorintegrering.

┌──────────────┐       ┌──────────────────┐       ┌───────────┐
│  End user    │──1──▶│  3rd-party engine │──2──▶│  OneLake  │
│  (query)     │       │(service principal)│◀──3──│  (data +  │
│              │◀──6──│                   │──4──▶│  security)│
└──────────────┘       └──────────────────┘       └───────────┘
  1. Slutanvändaren skickar en fråga till tredjepartsmotorn.
  2. Motoridentiteten autentiserar till OneLake och läser rådatafilerna (Delta parquet) med onelake-API:er.
  3. OneLake returnerar begärda data.
  4. Motorn anropar API:et principalAccess och skickar slutanvändarens Microsoft Entra-objekt-ID för att få användarens effektiva åtkomst.
  5. Motorn tillämpar de returnerade åtkomstfiltren (tabellåtkomst, RLS-predikat, CLS-kolumnlistor) på data i sitt eget beräkningslager.
  6. Motorn returnerar endast de filtrerade, tillåtna resultaten till slutanvändaren.

Steg 1: Konfigurera motorns identitet

Motorn behöver en Microsoft Entra-identitet som OneLake känner igen och litar på. Den här identiteten läser datafiler och säkerhetsmetadata för motorns räkning.

  1. Skapa eller identifiera ett huvudnamn för tjänsten eller en hanterad identitet i Microsoft Entra-ID för din motor. Mer information finns i Program- och tjänsthuvudnamnsobjekt i Microsoft Entra-ID.

  2. Lägg till identiteten i arbetsytans medlemsroll. I Fabricportalen går du till arbetsyteinställningarna och lägger till service principal i Medlemsrollen. Detta ger identiteten:

    • Läsåtkomst till alla datafiler i OneLake för objekt på den arbetsytan.
    • Åtkomst för att läsa metadata för säkerhetsroll i OneLake via de auktoriserade motorernas API:er.

    Mer information om arbetsyteroller finns i Roller i arbetsytor.

  3. Säkerställ att identiteten har obegränsad åtkomst. Motoridentiteten måste ha fullständig läsbehörighet till varje tabell som den frågar efter. Om någon OneLake-säkerhetsroll tillämpar RLS- eller CLS-begränsningar på motoridentiteten misslyckas dataläsningar och API-anrop. Det bästa sättet är att inte lägga till motoridentiteten i några OneLake-säkerhetsroller som innehåller RLS- eller CLS-begränsningar.

Viktigt!

Du kan återkalla motorns åtkomst när som helst genom att ta bort den från arbetsyterollen. Återkallande av åtkomst börjar gälla inom cirka 2 minuter.

Steg 2: Läsa data från OneLake

När motoridentiteten har konfigurerats kan motorn läsa datafiler direkt från OneLake med hjälp av Standard Azure Data Lake Storage (ADLS) Gen2-kompatibla API:er.

OneLake-data är tillgängliga på:

https://onelake.dfs.fabric.microsoft.com/{workspaceId}/{itemId}/Tables/{schema}/{tableName}/

Motorn autentiserar med hjälp av en ägartoken som hämtats via autentiseringsflödet för Microsoft Entra OAuth 2.0-klientautentiseringsuppgifter. Använd OneLake-resursomfånget https://storage.azure.com/.default när du begär token.

Exempel: Autentisera och läsa data (Python)

from azure.identity import ClientSecretCredential
from azure.storage.filedatalake import DataLakeServiceClient

tenant_id = "<your-tenant-id>"
client_id = "<your-service-principal-client-id>"
client_secret = "<your-service-principal-secret>"

credential = ClientSecretCredential(tenant_id, client_id, client_secret)

service_client = DataLakeServiceClient(
    account_url="https://onelake.dfs.fabric.microsoft.com",
    credential=credential
)

# Access a specific item in a workspace
file_system_client = service_client.get_file_system_client("<workspace-id>")
directory_client = file_system_client.get_directory_client("<item-id>/Tables/dbo/Customers")

# List and read Delta parquet files
for path in directory_client.get_paths():
    if path.name.endswith(".parquet"):
        file_client = file_system_client.get_file_client(path.name)
        downloaded = file_client.download_file()
        data = downloaded.readall()
        # Process the parquet data with your engine

Mer information om OneLake-API:er finns i OneLake-åtkomst med API:er.

Steg 3: Hämta användarens effektiva åtkomst

När du har läst rådata måste motorn avgöra vad den frågande användaren får se. Anropa Get authorized access for a principal API (Hämta auktoriserad åtkomst för ett huvud-API) för att få användarens effektiva åtkomst för objektet.

API-slutpunkt

GET https://onelake.dfs.fabric.microsoft.com/v1.0/workspaces/{workspaceId}/artifacts/{artifactId}/securityPolicy/principalAccess

Begärandetext

{
  "aadObjectId": "<end-user-entra-object-id>",
  "inputPath": "Tables",
  "maxResults": 500 //optional, default is 500
}
Parameter Type Obligatoriskt Beskrivning
aadObjectId snöre Ja Microsoft Entra-objekt-ID för slutanvändaren vars åtkomst du vill kontrollera.
inputPath snöre Ja Antingen Tables eller Files. Returnerar användarens åtkomst för det angivna avsnittet i objektet. För de flesta frågemotorer kommer inputPath att vara Tables.
continuationToken snöre No Används för att hämta fortsatta resultat när resultatuppsättningen överskrider maxResults.
maximalt antal resultat heltal No Maximalt antal objekt per sida. Standardvärdet är 500.

Exempelsvar (endast RLS)

{
  "identityETag": "3fc4dc476ded773e4cf43936190bf20fa9480a077b25edc0b4bbe247112542f6",
  "metadataETag": "\"eyJhciI6IlwiMHg4R...\"",
  "value": [
    {
      "path": "Tables/dbo/Customers",
      "access": ["Read"],
      "rows": "SELECT * FROM [dbo].[Customers] WHERE [customerId] = '123'",
      "effect": "Permit"
    },
    {
      "path": "Tables/dbo/Employees",
      "access": ["Read"],
      "rows": "SELECT * FROM [dbo].[Employees] WHERE [address] = '123'",
      "effect": "Permit"
    },
    {
      "path": "Tables/dbo/EmployeeTerritories",
      "access": ["Read"],
      "effect": "Permit"
    }
  ]
}

Exempelsvar (RLS och CLS)

När säkerhet på kolumnnivå konfigureras i en tabell innehåller svaret en columns matris som endast visar de kolumner som användaren har behörighet att komma åt. Kolumner som inte finns i den här matrisen är dolda för användaren.

{
  "identityETag": "79372bc169b00882d9abec3d404032131e96bc406e15c6766514723021e153eb",
  "metadataETag": "\"eyJhciI6IlwiMHg4R...\"",
  "value": [
    {
      "path": "Tables/dbo/Customers",
      "access": ["Read"],
      "columns": [
        {
          "name": "address",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        },
        {
          "name": "city",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        },
        {
          "name": "contactTitle",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        },
        {
          "name": "country",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        },
        {
          "name": "fax",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        },
        {
          "name": "phone",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        },
        {
          "name": "postalCode",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        },
        {
          "name": "region",
          "columnEffect": "Permit",
          "columnAction": ["Read"]
        }
      ],
      "rows": "SELECT * FROM [dbo].[Customers] WHERE [customerID] = 'ALFKI'",
      "effect": "Permit"
    },
    {
      "path": "Tables/dbo/Employees",
      "access": ["Read"],
      "rows": "SELECT * FROM [dbo].[Employees] WHERE [address] = '123'",
      "effect": "Permit"
    }
  ]
}

Förstå svaret

Svaret innehåller en matris med PrincipalAccessEntry objekt som var och en representerar en tabell som användaren har åtkomst till. Tabeller som inte finns i svaret är inte tillgängliga för användaren.

Fält Type Beskrivning
path snöre Sökvägen till tabellen som användaren kan komma åt, till exempel Tables/dbo/Customers.
access string[] Matrisen med åtkomsttyper som beviljats. För närvarande stöds endast Read.
columns objekt[] En matris med kolumnobjekt som användaren har behörighet att komma åt. Varje objekt innehåller name (kolumnnamn), columnEffect (Permit) och columnAction (["Read"]). Om det här fältet saknas gäller ingen CLS och alla kolumner tillåts. Om det finns ska endast de angivna kolumnerna returneras.
rows snöre En T-SQL-instruktion SELECT som representerar säkerhetsfiltret på radnivå. Endast rader som matchar det här predikatet ska returneras till användaren. Om det här fältet saknas gäller ingen RLS och alla rader tillåts.
effect snöre Effekttypen. För närvarande alltid Permit.

Viktigt!

Fältet rows innehåller ett T-SQL-uttryck som motorn måste parsa och tillämpa som ett filterpredikat. Uttrycket använder ett SELECT * FROM [schema].[table] WHERE ... format. Motorn måste extrahera WHERE klausulen och tillämpa den på den data som returneras.

ETags för cachelagring

Svaret innehåller två ETag-värden som möjliggör effektiv cachelagring:

  • identityETag: Representerar det aktuella tillståndet för användarens identitets- och gruppmedlemskap. Cacha användarens åtkomstresultat och återanvänd det tills den här ETag ändras.
  • metadataETag: Representerar det aktuella tillståndet för objektets säkerhetskonfiguration. Cachelagra rollmetadata och återanvänd den tills denna ETag ändras.

Använd dessa ETags med If-None-Match begärandehuvudet för att undvika att hämta oförändrade data igen. Detta förbättrar prestanda för cacheminnen för flera användare.

Exempel: Hämta effektiv åtkomst (Python)

import requests

# Get a token for the OneLake DFS endpoint
token = credential.get_token("https://storage.azure.com/.default").token

workspace_id = "<workspace-id>"
artifact_id = "<artifact-id>"
user_object_id = "<end-user-entra-object-id>"

url = (
    f"https://onelake.dfs.fabric.microsoft.com/v1.0/"
    f"workspaces/{workspace_id}/artifacts/{artifact_id}/"
    f"securityPolicy/principalAccess"
)

headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
}

body = {
    "aadObjectId": user_object_id,
    "inputPath": "Tables"
}

response = requests.get(url, headers=headers, json=body)
access_data = response.json()

# The response contains the user's effective access
for entry in access_data["value"]:
    print(f"Table: {entry['path']}, Access: {entry['access']}")
    if "columns" in entry:
        col_names = [col["name"] for col in entry["columns"]]
        print(f"  CLS permitted columns: {col_names}")
    if "rows" in entry:
        print(f"  RLS filter: {entry['rows']}")

Steg 4: Tillämpa säkerhetsfilter

När du har hämtat användarens verkliga åtkomst måste din motor tillämpa säkerhetspolicyerna på datan innan resultaten returneras. Det här steget är kritiskt – motorn ansvarar för att principerna verkställs korrekt.

Filtrering på tabellnivå

Returnera endast data från tabeller som visas i svaret principalAccess . Om en tabell inte visas har användaren ingen åtkomst till den och inga data ska returneras.

# Build a set of accessible tables for the user
accessible_tables = {entry["path"] for entry in access_data["value"]}

# Before returning query results, verify the table is accessible
def is_table_accessible(table_path: str) -> bool:
    return table_path in accessible_tables

Säkerhetsfiltrering på radnivå

När ett rows fält finns i en åtkomstpost måste motorn parsa T-SQL-predikatet och tillämpa det som ett filter på tabellens data. Värdet rows är en SELECT instruktion med en WHERE sats som definierar vilka rader användaren kan se.

Viktigt!

Om motorn inte kan parsa SQL-instruktioner bör frågor mot tabeller med en egenskap som inte är null rows misslyckas med ett fel och returnera inga data. Detta säkerställer att användarna endast får åtkomst till det de får se.

Till exempel följande RLS-filter:

SELECT * FROM [dbo].[Customers] WHERE [customerId] = '123' UNION SELECT * FROM [dbo].[Customers] WHERE [customerID] = 'ALFKI'

Motorn bör extrahera predikaten och använda dem för att filtrera data:

import sqlparse

def extract_rls_predicates(rls_expression: str) -> list:
    """
    Parse the RLS T-SQL expression and extract WHERE clause predicates.
    The expression may contain UNION of multiple SELECT statements.
    """
    predicates = []
    statements = rls_expression.split(" UNION ")
    for stmt in statements:
        parsed = sqlparse.parse(stmt)[0]
        where_seen = False
        where_clause = []
        for token in parsed.tokens:
            if where_seen:
                where_clause.append(str(token).strip())
            if token.ttype is sqlparse.tokens.Keyword and token.value.upper() == "WHERE":
                where_seen = True
        if where_clause:
            predicates.append(" ".join(where_clause))
    return predicates


def apply_rls_filter(dataframe, access_entry: dict):
    """Apply RLS filtering to a dataframe based on the access entry."""
    if "rows" not in access_entry:
        return dataframe  # No RLS, return all rows

    predicates = extract_rls_predicates(access_entry["rows"])
    # Combine predicates with OR (UNION semantic)
    combined_filter = " OR ".join(f"({p})" for p in predicates)
    return dataframe.filter(combined_filter)

Viktigt!

När fältet rows saknas från en åtkomstpost gäller ingen RLS för tabellen och alla rader ska returneras. När fältet finns måste motorn filtrera data. Att returnera ofiltrerade data för en tabell med RLS är en säkerhetsöverträdelse.

Säkerhetsfiltrering på kolumnnivå

När CLS har konfigurerats i en tabell principalAccess innehåller svaret en columns matris som uttryckligen visar de kolumner som användaren har behörighet att komma åt. Varje kolumnobjekt innehåller:

Fastighet Type Beskrivning
name snöre Kolumnnamnet (skiftlägeskänsligt).
columnEffect snöre Den effekt som tillämpas på kolumnen. För närvarande alltid Permit.
columnAction string[] De åtgärder som tillåts i kolumnen. För närvarande stöds endast Read.

Om fältet columnssaknas från en åtkomstpost gäller ingen CLS och alla kolumner i tabellen tillåts. Om fältet columnsfinns måste motorn endast returnera de kolumner som visas.

def get_permitted_columns(access_entry: dict) -> list | None:
    """
    Return the list of permitted column names for a table.
    Returns None if no CLS applies (all columns are permitted).
    """
    if "columns" not in access_entry:
        return None  # No CLS, all columns are permitted

    return [
        col["name"]
        for col in access_entry["columns"]
        if col.get("columnEffect") == "Permit"
        and "Read" in col.get("columnAction", [])
    ]


def apply_cls_filter(dataframe, access_entry: dict):
    """Apply CLS filtering to a dataframe based on the access entry."""
    permitted_columns = get_permitted_columns(access_entry)
    if permitted_columns is None:
        return dataframe  # No CLS, return all columns

    # Only keep columns that are in the permitted list
    return dataframe.select(permitted_columns)

Viktigt!

När fältet columns saknas från en åtkomstpost gäller ingen CLS och alla kolumner ska returneras. När fältet finns får motorn bara returnera kolumnerna i listan. Att returnera dolda kolumner är en säkerhetsöverträdelse.

Hantera tabeller utan åtkomst

Om en användare frågar en tabell som inte visas i principalAccess svaret måste motorn neka åtkomst. Återgå inte till att returnera ofiltrerade data.

def query_table(table_path: str, user_access: dict):
    """Query a table with OneLake security enforcement."""
    # Find the user's access entry for this table
    entry = next(
        (e for e in user_access["value"] if e["path"] == table_path),
        None
    )

    if entry is None:
        raise PermissionError(
            f"Access denied: user doesn't have permission to access {table_path}"
        )

    # Read the data from OneLake
    data = read_table_from_onelake(table_path)

    # Apply column-level security
    data = apply_cls_filter(data, entry)

    # Apply row-level security
    data = apply_rls_filter(data, entry)

    return data

Steg 5: Hantera cachelagring och ändringsidentifiering

För integreringar i produktionsklass, särskilt motorer med datacacheminnen för flera användare, måste du hantera ändringar i säkerhetsprinciper och medlemskap i användargrupper.

Säkerhetsmetadata för cache

Använd identityETag och metadataETag värdena från principalAccess-svaret för att avgöra när den cachelagrade säkerhetsinformationen är inaktuell:

  • identityETag: Ändringar när användarens gruppmedlemskap eller identitetsegenskaper uppdateras. Cacha användarens effektiva åtkomst baserat på (userId, identityETag).
  • metadataETag: Ändras när OneLake-säkerhetsrollerna eller principerna för objektet uppdateras. Cacherolldefinitioner som är viktiga för (artifactId, metadataETag).

Avfrågning efter ändringar

Avsök API:et principalAccess med jämna mellanrum för att identifiera ändringar. API:et bör avsökas före frågekörningen för att säkerställa att inget har ändrats, i stället för att direkt betjäna resultat från cacheminnet. If-None-Match Använd rubriken med den tidigare mottagna ETag för att minimera bandbredden:

headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json",
    "If-None-Match": f'"{cached_etag}"'
}

response = requests.get(url, headers=headers, json=body)

if response.status_code == 304:
    # Security hasn't changed, use cached data
    pass
elif response.status_code == 200:
    # Security has changed, update cache
    new_access_data = response.json()
    update_cache(user_id, new_access_data)

Överväganden för svarstid

  • Det tar ungefär 5 minuter att sprida ändringar i OneLake-säkerhetsrolldefinitioner.
  • Ändringar av användargruppsmedlemskap i Microsoft Entra-ID tar ungefär 1 timme att återspegla i OneLake.
  • Vissa Fabric-motorer har ett eget cachelagringslager, så det kan kräva extra tid.

Utforma avsökningsintervallet och cachelagrade TTL i enlighet med detta. En rekommenderad metod är att avsöka var 5:e minut för ändringar av säkerhetsmetadata och uppdatera användarspecifik åtkomst för varje fråga eller med ett kortare intervall.

Steg 6: Hantera sidindelning

API:et principalAccess stöder sidnumrering för objekt med många tabeller. När svaret innehåller fler poster än maxResultsinnehåller svaret en continuationToken.

all_entries = []
continuation_token = None

while True:
    body = {
        "aadObjectId": user_object_id,
        "inputPath": "Tables",
        "maxResults": 500
    }
    if continuation_token:
        body["continuationToken"] = continuation_token

    response = requests.get(url, headers=headers, json=body)
    data = response.json()
    all_entries.extend(data["value"])

    # Check for continuation token in response
    continuation_token = data.get("continuationToken")
    if not continuation_token:
        break

Felhantering

Hantera följande felscenarier i din integrering:

HTTP-status Felkod Beskrivning Rekommenderad åtgärd
200 - Framgång. Bearbeta svaret.
404 ObjektInteHittat Arbetsytan eller objektet finns inte, eller så har motoridentiteten inte åtkomst. Kontrollera arbetsytans ID och artefakt-ID. Bekräfta att motoridentiteten har medlemsbehörighet till arbetsytan.
412 FörutsättningMisslyckades Den angivna ETag-filen i If-Match matchar inte den aktuella resursen ETag. Hämta resursen igen utan If-Match header för att hämta den senaste ETag.
429 - Hastighetsgränsen har överskridits. Vänta tills du har angett varaktigheten Retry-After i rubriken innan du försöker igen.

Metodtips för säkerhet

Följ dessa metodtips för att säkerställa en säker integrering:

  • Skydda autentiseringsuppgifterna för motoridentiteten. Tjänstehuvudkonto har förhöjd åtkomst till data i OneLake. Lagra autentiseringsuppgifter på ett säkert sätt med tjänster som Azure Key Vault.
  • Exponera inte rådata för slutanvändare. Använd alltid de säkerhetsfilter som returneras av API:et principalAccess innan du returnerar några data. Att hoppa över upprätthållandet av reglerna är en säkerhetsöverträdelse.
  • Verifiera RLS-predikat noggrant. Parsa och tillämpa T-SQL WHERE satsens villkorsuttryck exakt. Felaktig parsning kan leda till dataläckage. Om parsningsfel eller osäker syntaxmappning inträffar misslyckas frågan med ett RLS-parsningsfel i stället för att visa partiella eller osäkra resultat för användaren.
  • Hantera saknade tabeller som nekad åtkomst. Om en tabell inte finns i API-svaret har användaren inte åtkomst. Återgå aldrig till ofiltrerade data, OneLake-säkerhet använder alltid neka som standard.
  • Revidera åtkomst Logga vilka användare som har åtkomst till vilka tabeller och vilka säkerhetsprinciper som tillämpades för efterlevnad och felsökning.
  • Fråga om säkerhetsändringar. Använd ETags för att identifiera ändringar och uppdatera cachelagrade principer snabbt.

Begränsningar

  • API:et principalAccess är i förhandsversion och kan ändras baserat på feedback.
  • Read Endast åtkomsttypen och Permit effekten stöds i dag.
  • Identiteten för motorn måste ha obegränsad åtkomst på root-nivå. Om RLS eller CLS gäller för motoridentiteten misslyckas API-anropen.
  • RLS-predikat använder T-SQL-syntax. Motorn ansvarar för att parsa och tillämpa predikaten korrekt.
  • Det tar ungefär 5 minuter att sprida ändringar av säkerhetsprinciper. Ändringar av användargruppsmedlemskap tar cirka 1 timme.