-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '2.x' into port-pr-15510
- Loading branch information
Showing
13 changed files
with
267 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import ipaddress | ||
import socket | ||
from urllib.parse import urlparse | ||
|
||
|
||
def validate_restricted_url(url: str): | ||
""" | ||
Validate that the provided URL is safe for outbound requests. This prevents | ||
attacks like SSRF (Server Side Request Forgery), where an attacker can make | ||
requests to internal services (like the GCP metadata service, localhost addresses, | ||
or in-cluster Kubernetes services) | ||
Args: | ||
url: The URL to validate. | ||
Raises: | ||
ValueError: If the URL is a restricted URL. | ||
""" | ||
|
||
try: | ||
parsed_url = urlparse(url) | ||
except ValueError: | ||
raise ValueError(f"{url!r} is not a valid URL.") | ||
|
||
if parsed_url.scheme not in ("http", "https"): | ||
raise ValueError( | ||
f"{url!r} is not a valid URL. Only HTTP and HTTPS URLs are allowed." | ||
) | ||
|
||
hostname = parsed_url.hostname or "" | ||
|
||
# Remove IPv6 brackets if present | ||
if hostname.startswith("[") and hostname.endswith("]"): | ||
hostname = hostname[1:-1] | ||
|
||
if not hostname: | ||
raise ValueError(f"{url!r} is not a valid URL.") | ||
|
||
try: | ||
ip_address = socket.gethostbyname(hostname) | ||
ip = ipaddress.ip_address(ip_address) | ||
except socket.gaierror: | ||
try: | ||
ip = ipaddress.ip_address(hostname) | ||
except ValueError: | ||
raise ValueError(f"{url!r} is not a valid URL. It could not be resolved.") | ||
|
||
if ip.is_private: | ||
raise ValueError( | ||
f"{url!r} is not a valid URL. It resolves to the private address {ip}." | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import pytest | ||
|
||
from prefect.utilities.urls import validate_restricted_url | ||
|
||
RESTRICTED_URLS = [ | ||
("", ""), | ||
(" ", ""), | ||
("[]", ""), | ||
("not a url", ""), | ||
("http://", ""), | ||
("https://", ""), | ||
("http://[]/foo/bar", ""), | ||
("ftp://example.com", "HTTP and HTTPS"), | ||
("gopher://example.com", "HTTP and HTTPS"), | ||
("https://localhost", "private address"), | ||
("https://127.0.0.1", "private address"), | ||
("https://[::1]", "private address"), | ||
("https://[fc00:1234:5678:9abc::10]", "private address"), | ||
("https://[fd12:3456:789a:1::1]", "private address"), | ||
("https://[fe80::1234:5678:9abc]", "private address"), | ||
("https://10.0.0.1", "private address"), | ||
("https://10.255.255.255", "private address"), | ||
("https://172.16.0.1", "private address"), | ||
("https://172.31.255.255", "private address"), | ||
("https://192.168.1.1", "private address"), | ||
("https://192.168.1.255", "private address"), | ||
("https://169.254.0.1", "private address"), | ||
("https://169.254.169.254", "private address"), | ||
("https://169.254.254.255", "private address"), | ||
# These will resolve to a private address in production, but not in tests, | ||
# so we'll use "resolve" as the reason to catch both cases | ||
("https://metadata.google.internal", "resolve"), | ||
("https://anything.privatecloud", "resolve"), | ||
("https://anything.privatecloud.svc", "resolve"), | ||
("https://anything.privatecloud.svc.cluster.local", "resolve"), | ||
("https://cluster-internal", "resolve"), | ||
("https://network-internal.cloud.svc", "resolve"), | ||
("https://private-internal.cloud.svc.cluster.local", "resolve"), | ||
] | ||
|
||
|
||
@pytest.mark.parametrize("value, reason", RESTRICTED_URLS) | ||
def test_validate_restricted_url_validates(value: str, reason: str): | ||
with pytest.raises(ValueError, match=f"is not a valid URL.*{reason}"): | ||
validate_restricted_url(url=value) |
Oops, something went wrong.