prefect-client 2.20.10__py3-none-any.whl → 2.20.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ from typing import Dict, List, Optional
4
4
 
5
5
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
6
6
  from prefect.logging import LogEavesdropper
7
+ from prefect.utilities.urls import validate_restricted_url
7
8
 
8
9
  if HAS_PYDANTIC_V2:
9
10
  from pydantic.v1 import AnyHttpUrl, Field, SecretStr
@@ -88,6 +89,26 @@ class AppriseNotificationBlock(AbstractAppriseNotificationBlock, ABC):
88
89
  description="Incoming webhook URL used to send notifications.",
89
90
  examples=["https://hooks.example.com/XXX"],
90
91
  )
92
+ allow_private_urls: bool = Field(
93
+ default=True,
94
+ description="Whether to allow notifications to private URLs. Defaults to True.",
95
+ )
96
+
97
+ @sync_compatible
98
+ async def notify(
99
+ self,
100
+ body: str,
101
+ subject: Optional[str] = None,
102
+ ):
103
+ if not self.allow_private_urls:
104
+ try:
105
+ validate_restricted_url(self.url.get_secret_value())
106
+ except ValueError as exc:
107
+ if self._raise_on_failure:
108
+ raise NotificationError(str(exc))
109
+ raise
110
+
111
+ await super().notify(body, subject)
91
112
 
92
113
 
93
114
  # TODO: Move to prefect-slack once collection block auto-registration is
prefect/blocks/webhook.py CHANGED
@@ -3,6 +3,7 @@ from typing import Optional
3
3
  from httpx import AsyncClient, AsyncHTTPTransport, Response
4
4
 
5
5
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
6
+ from prefect.utilities.urls import validate_restricted_url
6
7
 
7
8
  if HAS_PYDANTIC_V2:
8
9
  from pydantic.v1 import Field, SecretStr
@@ -38,7 +39,10 @@ class Webhook(Block):
38
39
  description="The webhook URL.",
39
40
  examples=["https://hooks.slack.com/XXX"],
40
41
  )
41
-
42
+ allow_private_urls: bool = Field(
43
+ default=True,
44
+ description="Whether to allow notifications to private URLs. Defaults to True.",
45
+ )
42
46
  headers: SecretDict = Field(
43
47
  default_factory=lambda: SecretDict(dict()),
44
48
  title="Webhook Headers",
@@ -55,6 +59,9 @@ class Webhook(Block):
55
59
  Args:
56
60
  payload: an optional payload to send when calling the webhook.
57
61
  """
62
+ if not self.allow_private_urls:
63
+ validate_restricted_url(self.url.get_secret_value())
64
+
58
65
  async with self._client:
59
66
  return await self._client.request(
60
67
  method=self.method,
@@ -0,0 +1,49 @@
1
+ import ipaddress
2
+ import socket
3
+ from urllib.parse import urlparse
4
+
5
+
6
+ def validate_restricted_url(url: str):
7
+ """
8
+ Validate that the provided URL is safe for outbound requests. This prevents
9
+ attacks like SSRF (Server Side Request Forgery), where an attacker can make
10
+ requests to internal services (like the GCP metadata service, localhost addresses,
11
+ or in-cluster Kubernetes services)
12
+ Args:
13
+ url: The URL to validate.
14
+ Raises:
15
+ ValueError: If the URL is a restricted URL.
16
+ """
17
+
18
+ try:
19
+ parsed_url = urlparse(url)
20
+ except ValueError:
21
+ raise ValueError(f"{url!r} is not a valid URL.")
22
+
23
+ if parsed_url.scheme not in ("http", "https"):
24
+ raise ValueError(
25
+ f"{url!r} is not a valid URL. Only HTTP and HTTPS URLs are allowed."
26
+ )
27
+
28
+ hostname = parsed_url.hostname or ""
29
+
30
+ # Remove IPv6 brackets if present
31
+ if hostname.startswith("[") and hostname.endswith("]"):
32
+ hostname = hostname[1:-1]
33
+
34
+ if not hostname:
35
+ raise ValueError(f"{url!r} is not a valid URL.")
36
+
37
+ try:
38
+ ip_address = socket.gethostbyname(hostname)
39
+ ip = ipaddress.ip_address(ip_address)
40
+ except socket.gaierror:
41
+ try:
42
+ ip = ipaddress.ip_address(hostname)
43
+ except ValueError:
44
+ raise ValueError(f"{url!r} is not a valid URL. It could not be resolved.")
45
+
46
+ if ip.is_private:
47
+ raise ValueError(
48
+ f"{url!r} is not a valid URL. It resolves to the private address {ip}."
49
+ )
prefect/workers/base.py CHANGED
@@ -137,6 +137,12 @@ class BaseJobConfiguration(BaseModel):
137
137
  variables = cls._get_base_config_defaults(
138
138
  variables_schema.get("properties", {})
139
139
  )
140
+
141
+ # copy variable defaults for `env` to job config before they're replaced by
142
+ # deployment overrides
143
+ if variables.get("env"):
144
+ job_config["env"] = variables.get("env")
145
+
140
146
  variables.update(values)
141
147
 
142
148
  # deep merge `env`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 2.20.10
3
+ Version: 2.20.11
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
@@ -22,7 +22,7 @@ Classifier: Topic :: Software Development :: Libraries
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: anyio<5.0.0,>=4.4.0
25
+ Requires-Dist: anyio<4.6.1,>=4.4.0
26
26
  Requires-Dist: asgi-lifespan<3.0,>=1.0
27
27
  Requires-Dist: cachetools<6.0,>=5.3
28
28
  Requires-Dist: cloudpickle<4.0,>=2.0
@@ -159,9 +159,9 @@ prefect/blocks/abstract.py,sha256=AiAs0MC5JKCf0Xg0yofC5Qu2TZ52AjDMP1ntMGuP2dY,16
159
159
  prefect/blocks/core.py,sha256=_NKdrHvN2f0LdgqX9MYXp0PJQW36e6cs2Za7hVfrCIw,43870
160
160
  prefect/blocks/fields.py,sha256=ANOzbNyDCBIvm6ktgbLTMs7JW2Sf6CruyATjAW61ks0,1607
161
161
  prefect/blocks/kubernetes.py,sha256=IN-hZkzIRvqjd_dzPZby3q8p7m2oUWqArBq24BU9cDg,4071
162
- prefect/blocks/notifications.py,sha256=vdGxLBh1VNicoI8G2bZk0X55mQB1DqVuej7xiCec8pM,28822
162
+ prefect/blocks/notifications.py,sha256=EMW38ZGkQERQHJ32zkqod_GkFIJssq5ra5_rpRZOc_c,29487
163
163
  prefect/blocks/system.py,sha256=aIRiFKlXIQ1sMaqniMXYolFsx2IVN3taBMH3KCThB2I,3089
164
- prefect/blocks/webhook.py,sha256=VzQ-qcRtW8MMuYEGYwFgt1QXtWedUtVmeTo7iE2UQ78,2008
164
+ prefect/blocks/webhook.py,sha256=iyPkhgTL04oqz7R2I0jddS2oBWVdp7qvBlRxEbbwueo,2327
165
165
  prefect/client/__init__.py,sha256=yJ5FRF9RxNUio2V_HmyKCKw5G6CZO0h8cv6xA_Hkpcc,477
166
166
  prefect/client/base.py,sha256=YSPeE7hV0NCuD6WzoAACDYGFK4Yq35d26pITZ3elNyY,24669
167
167
  prefect/client/cloud.py,sha256=E54OAFr7bY5IXhhMBdjGwLQiR0eU-WWFoEEiOq2l53I,4104
@@ -278,18 +278,19 @@ prefect/utilities/slugify.py,sha256=57Vb14t13F3zm1P65KAu8nVeAz0iJCd1Qc5eMG-R5y8,
278
278
  prefect/utilities/templating.py,sha256=t32Gcsvvm8ibzdqXwcWzY7JkwftPn73FiiLYEnQWyKM,13237
279
279
  prefect/utilities/text.py,sha256=eXGIsCcZ7h_6hy8T5GDQjL8GiKyktoOqavYub0QjgO4,445
280
280
  prefect/utilities/timeout.py,sha256=nxmuPxROIT-i8gPffpARuxnxu58H0vkmbjTVIgef0_0,805
281
+ prefect/utilities/urls.py,sha256=sxv2lG8u3tX_zPZp66qEAFgZmGreG_D_9jGAqqYqaKc,1522
281
282
  prefect/utilities/visualization.py,sha256=9Pc8ImgnBpnszWTFxYm42cmtHjNEAsGZ8ugkn8w_dJk,6501
282
283
  prefect/utilities/schema_tools/__init__.py,sha256=KsFsTEHQqgp89TkDpjggkgBBywoHQPxbx-m6YQhiNEI,322
283
284
  prefect/utilities/schema_tools/hydration.py,sha256=RNuJK4Vd__V69gdQbaWSVhSkV0AUISfGzH_xd0p6Zh0,8291
284
285
  prefect/utilities/schema_tools/validation.py,sha256=zZHL_UFxAlgaUzi-qsEOrhWtZ7EkFQvPkX_YN1EJNTo,8414
285
286
  prefect/workers/__init__.py,sha256=6el2Q856CuRPa5Hdrbm9QyAWB_ovcT2bImSFsoWI46k,66
286
- prefect/workers/base.py,sha256=LNcVu0FIDBYv2XnWH1a2uV2Yyngtlyq8_pvLC4dxbrc,45576
287
+ prefect/workers/base.py,sha256=r0kM2EbT69yQsd3L7Lq99IqaBHhZgc6tcpdh3YekSJY,45779
287
288
  prefect/workers/block.py,sha256=aYY__uq3v1eq1kkbVukxyhQNbkknaKYo6-_3tcrfKKA,8067
288
289
  prefect/workers/process.py,sha256=pPtCdA7fKQ4OsvoitT-cayZeh5HgLX4xBUYlb2Zad-Q,9475
289
290
  prefect/workers/server.py,sha256=WVZJxR8nTMzK0ov0BD0xw5OyQpT26AxlXbsGQ1OrxeQ,1551
290
291
  prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
291
- prefect_client-2.20.10.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
292
- prefect_client-2.20.10.dist-info/METADATA,sha256=AWEDtW-Rm_YudnjrVA3gBSllWBa0d_EVfiMwpcVDodI,7392
293
- prefect_client-2.20.10.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
294
- prefect_client-2.20.10.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
295
- prefect_client-2.20.10.dist-info/RECORD,,
292
+ prefect_client-2.20.11.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
293
+ prefect_client-2.20.11.dist-info/METADATA,sha256=YMdpw_0mas-4W0mXLs8FpuoyxKW-PlSvCxwdHPi0ArA,7392
294
+ prefect_client-2.20.11.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
295
+ prefect_client-2.20.11.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
296
+ prefect_client-2.20.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5