This is an automated email from the ASF dual-hosted git repository.
vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new a128d6930ef Create documentation about adding `access_control` to
`Asset` object (#66949)
a128d6930ef is described below
commit a128d6930ef2378a3f9638ed171bdcfae426a633
Author: Vincent <[email protected]>
AuthorDate: Tue May 26 12:10:13 2026 -0700
Create documentation about adding `access_control` to `Asset` object
(#66949)
---
.../docs/authoring-and-scheduling/assets.rst | 68 +++++++++++++++++-----
airflow-core/docs/core-concepts/multi-team.rst | 56 ++++++++++++++----
2 files changed, 100 insertions(+), 24 deletions(-)
diff --git a/airflow-core/docs/authoring-and-scheduling/assets.rst
b/airflow-core/docs/authoring-and-scheduling/assets.rst
index 9fbb232cbb3..74a22b65e51 100644
--- a/airflow-core/docs/authoring-and-scheduling/assets.rst
+++ b/airflow-core/docs/authoring-and-scheduling/assets.rst
@@ -404,10 +404,10 @@ As mentioned in :ref:`Fetching information from
previously emitted asset events<
events = inlet_events[AssetAlias("example-alias")]
last_row_count = events[-1].extra["row_count"]
-.. _asset_allow_producer_teams:
+.. _asset_access_control:
-Cross-team asset event filtering with ``allow_producer_teams``
---------------------------------------------------------------
+Cross-team asset event filtering with ``access_control``
+--------------------------------------------------------
.. versionadded:: 3.3.0
@@ -415,40 +415,82 @@ When :doc:`Multi-Team mode </core-concepts/multi-team>`
is enabled, asset events
membership. By default, a consuming Dag only receives asset events produced by
Dags within the same team
or by global (teamless) Dags. This prevents unintended cross-team triggers.
-To allow specific other teams to produce events that trigger your Dag, use the
``allow_producer_teams`` parameter
-on the ``Asset`` definition:
+To configure cross-team access, use the ``access_control`` parameter on the
``Asset`` definition with an
+``AssetAccessControl`` instance:
.. code-block:: python
- from airflow.sdk import Asset
+ from airflow.sdk import Asset, AssetAccessControl
shared_data = Asset(
name="my_data",
uri="s3://bucket/shared/data.csv",
- allow_producer_teams=["team_analytics", "team_ml"],
+ access_control=AssetAccessControl(
+ producer_teams=["team_analytics", "team_ml"],
+ ),
)
In this example, asset events produced by Dags belonging to ``team_analytics``
or ``team_ml`` will be
accepted by any consuming Dag that schedules on ``shared_data``, in addition
to events from the consuming
Dag's own team.
+``AssetAccessControl`` parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``AssetAccessControl`` class accepts the following parameters:
+
+- **producer_teams** (``list[str]``, default ``[]``): List of team names
allowed to produce events
+ consumed by this asset's consumers, in addition to the consumer's own team.
+- **allow_global** (``bool``, default ``True``): Whether teamless (global) Dag
producers can trigger
+ consumers of this asset. When set to ``False``, only Dags with an explicit
team association
+ (same team or listed in ``producer_teams``) can trigger consumers.
+
+Blocking global producers
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, global (teamless) Dags can trigger any consumer. In strict team
isolation scenarios, you
+may want to block teamless producers:
+
+.. code-block:: python
+
+ from airflow.sdk import Asset, AssetAccessControl
+
+ strict_data = Asset(
+ name="strict_data",
+ uri="s3://bucket/strict/data.csv",
+ access_control=AssetAccessControl(
+ producer_teams=["team_analytics"],
+ allow_global=False,
+ ),
+ )
+
+With ``allow_global=False``, only Dags belonging to the consumer's own team or
to ``team_analytics`` can
+trigger consumers of ``strict_data``. Teamless Dag producers are blocked.
+
+.. note::
+
+ The ``allow_global`` flag only affects Dag producers. Teamless API users
are always restricted to
+ triggering teamless consumers only, regardless of this setting.
+
Default behavior
~~~~~~~~~~~~~~~~
-When ``allow_producer_teams`` is not specified (or set to an empty list), the
default same-team filtering applies.
-The rules depend on whether the producer and consumer have a team association:
+When ``access_control`` is not specified, a default ``AssetAccessControl()``
is used (empty
+``producer_teams`` and ``allow_global=True``). The rules depend on whether the
producer and consumer
+have a team association:
- **Both have the same team**: The event is always delivered.
- **Producer has a team, consumer has a different team**: The event is blocked
(unless the
- producer's team is in the asset's ``allow_producer_teams``).
-- **Producer has no team (global Dag)**: The event is delivered to all
consumers, regardless of
- the consumer's team. Global Dags act as shared infrastructure that any team
can depend on.
+ producer's team is in the asset's ``producer_teams``).
+- **Producer has no team (global Dag)**: The event is delivered to all
consumers whose asset has
+ ``allow_global=True`` (the default). Global Dags act as shared
infrastructure that any team can
+ depend on.
- **Consumer has no team (global Dag)**: The consumer accepts events from any
source,
regardless of the producer's team. Teamless consumers act as shared
infrastructure that any
team can feed into.
- **Neither has a team**: The event is delivered (both are global).
-When Multi-Team mode is disabled, ``allow_producer_teams`` is ignored and all
asset events are delivered to all
+When Multi-Team mode is disabled, ``access_control`` is ignored and all asset
events are delivered to all
consuming Dags, preserving backward compatibility.
Asset partitions
diff --git a/airflow-core/docs/core-concepts/multi-team.rst
b/airflow-core/docs/core-concepts/multi-team.rst
index 88a99e2be40..eb6371701b6 100644
--- a/airflow-core/docs/core-concepts/multi-team.rst
+++ b/airflow-core/docs/core-concepts/multi-team.rst
@@ -517,26 +517,41 @@ Default Behavior
By default, a consuming Dag only receives asset events from producers within
the same team or from Dags with no team association, i.e. global Dags.
-Cross-Team Opt-In with ``allow_producer_teams``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Cross-Team Opt-In with ``access_control``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To allow specific teams to produce events that trigger consumers on a given
asset from another team, use the
-``allow_producer_teams`` parameter on the ``Asset`` definition:
+``access_control`` parameter on the ``Asset`` definition with an
``AssetAccessControl`` instance:
.. code-block:: python
- from airflow.sdk import Asset
+ from airflow.sdk import Asset, AssetAccessControl
shared_data = Asset(
name="shared_data",
uri="s3://bucket/shared/data.csv",
- allow_producer_teams=["team_analytics", "team_ml"],
+ access_control=AssetAccessControl(
+ producer_teams=["team_analytics", "team_ml"],
+ ),
)
With this configuration, asset events from ``team_analytics`` or ``team_ml``
will be accepted by any
consuming Dag that schedules on ``shared_data``, in addition to events from
the consumer's own team.
-See :ref:`Cross-team asset event filtering with allow_producer_teams
<asset_allow_producer_teams>` in the Assets
+To block global (teamless) Dag producers from triggering consumers, set
``allow_global=False``:
+
+.. code-block:: python
+
+ strict_data = Asset(
+ name="strict_data",
+ uri="s3://bucket/strict/data.csv",
+ access_control=AssetAccessControl(
+ producer_teams=["team_analytics"],
+ allow_global=False,
+ ),
+ )
+
+See :ref:`Cross-team asset event filtering with access_control
<asset_access_control>` in the Assets
documentation for usage details and validation rules.
Behavioral Rules
@@ -546,79 +561,98 @@ The following table describes the complete filtering
logic:
.. list-table::
:header-rows: 1
- :widths: 20 20 20 15 25
+ :widths: 15 15 18 14 13 25
* - Producer
- Consumer
- - ``allow_producer_teams``
+ - ``producer_teams``
+ - ``allow_global``
- Result
- Reason
* - Team A (DAG)
- Team A
- (any)
+ - (any)
- ✅ Allowed
- Same team
* - Team A (DAG)
- Team B
- ``[]``
+ - (any)
- ❌ Blocked
- Different team, no opt-in
* - Team A (DAG)
- Team B
- ``["team_a"]``
+ - (any)
- ✅ Allowed
- Cross-team opt-in
* - (no team, DAG)
- Team B
- (any)
+ - ``True``
- ✅ Allowed
- - Global producer
+ - Global producer, allow_global is True
+ * - (no team, DAG)
+ - Team B
+ - (any)
+ - ``False``
+ - ❌ Blocked
+ - Global producer blocked by allow_global=False
* - Team A (DAG)
- (no team)
- (any)
+ - (any)
- ✅ Allowed
- Teamless consumer accepts events from any DAG producer
* - (no team, DAG)
- (no team)
- (any)
+ - (any)
- ✅ Allowed
- Both global
* - Team A (API)
- Team A
- (any)
+ - (any)
- ✅ Allowed
- Same team
* - Team A (API)
- Team B
- ``["team_a"]``
+ - (any)
- ✅ Allowed
- Cross-team opt-in
* - Team A (API)
- (no team)
- (any)
+ - (any)
- ✅ Allowed
- Teamless consumer accepts events from any source
* - (no team, API)
- Team B
- (any)
+ - (any)
- ❌ Blocked
- Teamless API user cannot trigger team-bound consumer
* - (no team, API)
- (no team)
- (any)
+ - (any)
- ✅ Allowed
- Both global
Key rules:
- **Same team**: Always allowed.
-- **Global (teamless) DAG producer**: Triggers all consumers regardless of
team.
+- **Global (teamless) DAG producer with** ``allow_global=True``: Triggers all
consumers regardless of team.
+- **Global (teamless) DAG producer with** ``allow_global=False``: Blocked from
triggering team-bound consumers.
- **Teamless API user**: Can only trigger teamless consumers. Unlike a
teamless DAG — which is
deployed by a platform operator and intentionally shared — an API user
without a team has no
verified team affiliation, so their events are restricted to teamless
consumers to
prevent unscoped access to team-bound pipelines.
- **Teamless consumer**: Accepts events from any source (DAG or API),
regardless of team.
-- **Cross-team via** ``allow_producer_teams``: Allowed when the producer's
team is listed in the asset's ``allow_producer_teams``.
+- **Cross-team via** ``producer_teams``: Allowed when the producer's team is
listed in the asset's ``producer_teams``.
- **Multi-Team disabled**: All filtering is skipped; existing behavior is
preserved.
API-Triggered Events