rjgoyln commented on code in PR #64105:
URL: https://github.com/apache/airflow/pull/64105#discussion_r4057750542
##########
providers/git/src/airflow/providers/git/hooks/git.py:
##########
@@ -293,22 +293,71 @@ def _github_app_askpass_env(self) -> Generator[None]:
self.env["GIT_TERMINAL_PROMPT"] = old_terminal_prompt
os.environ["GIT_TERMINAL_PROMPT"] = old_terminal_prompt
- def _process_git_auth_url(self) -> None:
- if not isinstance(self.repo_url, str):
+ def _extract_repo_host(self) -> str:
+ """Return the ``host[:port]`` of the repo url, as written."""
+ rest = str(self.repo_url).partition("://")[2]
+ return rest.partition("/")[0].rpartition("@")[2]
+
+ @contextlib.contextmanager
+ def _token_askpass_env(self):
+ """Hand the token to git through GIT_ASKPASS so it never reaches the
repo URL."""
+ # Only http(s) consults GIT_ASKPASS, so writing the token to a temp
script for an SSH
+ # connection that happens to carry one would put it on disk for
nothing.
+ if not self.auth_token or not
str(self.repo_url).startswith(("http://", "https://")):
+ yield
return
- if self.auth_token and self.repo_url.startswith("https://"):
- encoded_user = urlquote(self.user_name, safe="")
- encoded_token = urlquote(self.auth_token, safe="")
- self.repo_url = self.repo_url.replace("https://",
f"https://{encoded_user}:{encoded_token}@", 1)
- elif self.auth_token and self.repo_url.startswith("http://"):
- encoded_user = urlquote(self.user_name, safe="")
- encoded_token = urlquote(self.auth_token, safe="")
- self.repo_url = self.repo_url.replace("http://",
f"http://{encoded_user}:{encoded_token}@", 1)
- elif self.repo_url.startswith("http://"):
- # if no auth token, use the repo url as is
- pass
- elif not self.repo_url.startswith("git@") and not
self.repo_url.startswith("https://"):
- self.repo_url = os.path.expanduser(self.repo_url)
+
+ host = self._extract_repo_host()
+ if not host:
+ yield
+ return
+
+ with tempfile.TemporaryDirectory() as askpass_dir:
+ askpass_path = os.path.join(askpass_dir, "askpass.sh")
+ # The credential reaches the script through the environment, so it
is never written to
+ # disk. git names the target in $1 as
``<scheme>://[user@]<host>[:port][/path]``, and
+ # matching it means a submodule hosted elsewhere gets nothing
rather than this
+ # connection's token. Quoted expansions stay literal in a pattern,
so an IPv6 host's
+ # brackets are not read as a glob character class.
+ with open(askpass_path, "w") as askpass_script:
+ askpass_script.write(
+ """#!/bin/sh
+case "$1" in
+
*"://$AIRFLOW_GIT_HOST'"*|*"://$AIRFLOW_GIT_HOST/"*|*"@$AIRFLOW_GIT_HOST'"*|*"@$AIRFLOW_GIT_HOST/"*)
;;
Review Comment:
Thanks for the suggestion. I switched the prompt-based guard to a URL-scoped
credential helper using GIT_CONFIG_COUNT / GIT_CONFIG_KEY_n /
GIT_CONFIG_VALUE_n.
The helper no longer parses or validates the prompt; Git matches the
credential scope against the parsed URL before invoking it. I also added a
regression test using two loopback servers to verify that the intended host
receives the credentials while a crafted cross-origin URL such as
https://github.com'@evil.com/... does not.
I additionally verified the test fails when the host scope is removed, so
the test covers the actual isolation provided by the scoped helper.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]