summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/config.py31
-rw-r--r--sites/docs/api/config.rst3
-rw-r--r--sites/www/changelog.rst5
-rw-r--r--tests/configs/match-final11
-rw-r--r--tests/test_config.py11
5 files changed, 52 insertions, 9 deletions
diff --git a/paramiko/config.py b/paramiko/config.py
index 48bcb101..938ddc6f 100644
--- a/paramiko/config.py
+++ b/paramiko/config.py
@@ -235,10 +235,16 @@ class SSHConfig:
hostname = self.canonicalize(hostname, options, domains)
# Overwrite HostName again here (this is also what OpenSSH does)
options["hostname"] = hostname
- options = self._lookup(hostname, options, canonical=True)
+ options = self._lookup(
+ hostname, options, canonical=True, final=True
+ )
+ else:
+ options = self._lookup(
+ hostname, options, canonical=False, final=True
+ )
return options
- def _lookup(self, hostname, options=None, canonical=False):
+ def _lookup(self, hostname, options=None, canonical=False, final=False):
# Init
if options is None:
options = SSHConfigDict()
@@ -248,7 +254,11 @@ class SSHConfig:
if not (
self._pattern_matches(context.get("host", []), hostname)
or self._does_match(
- context.get("matches", []), hostname, canonical, options
+ context.get("matches", []),
+ hostname,
+ canonical,
+ final,
+ options,
)
):
continue
@@ -263,9 +273,10 @@ class SSHConfig:
options[key].extend(
x for x in value if x not in options[key]
)
- # Expand variables in resulting values (besides 'Match exec' which was
- # already handled above)
- options = self._expand_variables(options, hostname)
+ if final:
+ # Expand variables in resulting values
+ # (besides 'Match exec' which was already handled above)
+ options = self._expand_variables(options, hostname)
return options
def canonicalize(self, hostname, options, domains):
@@ -336,7 +347,9 @@ class SSHConfig:
match = True
return match
- def _does_match(self, match_list, target_hostname, canonical, options):
+ def _does_match(
+ self, match_list, target_hostname, canonical, final, options
+ ):
matched = []
candidates = match_list[:]
local_username = getpass.getuser()
@@ -353,6 +366,8 @@ class SSHConfig:
if type_ == "canonical":
if self._should_fail(canonical, candidate):
return False
+ if type_ == "final":
+ passed = final
# The parse step ensures we only see this by itself or after
# canonical, so it's also an easy hard pass. (No negation here as
# that would be uh, pretty weird?)
@@ -511,7 +526,7 @@ class SSHConfig:
type_ = type_[1:]
match["type"] = type_
# all/canonical have no params (everything else does)
- if type_ in ("all", "canonical"):
+ if type_ in ("all", "canonical", "final"):
matches.append(match)
continue
if not tokens:
diff --git a/sites/docs/api/config.rst b/sites/docs/api/config.rst
index d42de8ac..19fa6f7b 100644
--- a/sites/docs/api/config.rst
+++ b/sites/docs/api/config.rst
@@ -61,7 +61,8 @@ Paramiko releases) are included. A keyword by itself means no known departures.
- ``Host``
- ``HostName``: used in ``%h`` :ref:`token expansion <TOKENS>`
-- ``Match``: fully supported, with the following caveats:
+- ``Match``: supports (canonical, final, exec, host, originalhost, user, localuser, all), with
+ the following caveats:
- You must have the optional dependency Invoke installed; see :ref:`the
installation docs <paramiko-itself>` (in brief: install
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index 9b903258..a05ab520 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,6 +2,11 @@
Changelog
=========
+- :feature:`1907` (solves :issue:`1992`) Add support and tests for
+ ssh_config ``Match final …`` which is frequently used in ProxyJump
+ configurations to exclude the jump host from the ProxyJump Match statement.
+ Patch by ``@commonism``.
+
- :feature:`2058` (solves :issue:`1587` and possibly others) Add an explicit
``max_concurrent_prefetch_requests`` argument to `paramiko.client.SSHClient.get`
and `paramiko.client.SSHClient.getfo`, allowing users to limit the number
diff --git a/tests/configs/match-final b/tests/configs/match-final
new file mode 100644
index 00000000..f03787da
--- /dev/null
+++ b/tests/configs/match-final
@@ -0,0 +1,11 @@
+Host jump
+ HostName jump.example.orig
+ Port 1003
+
+Host finally
+ HostName finally.example.org
+ Port 1001
+
+Match final host "*.example.org" !host jump.example.org
+ ProxyJump jump
+ Port 1002
diff --git a/tests/test_config.py b/tests/test_config.py
index fcb120b6..89b48472 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1030,3 +1030,14 @@ class TestComplexMatching:
# !canonical in a config that is canonicalized - does NOT match
result = load_config("match-canonical-yes").lookup("www")
assert result["user"] == "hidden"
+
+
+class TestFinalMatching(object):
+ def test_final(self):
+ result = load_config("match-final").lookup("finally")
+ assert result["proxyjump"] == "jump"
+ assert result["port"] == "1001"
+
+ def test_negated(self):
+ result = load_config("match-final").lookup("jump")
+ assert result["port"] == "1003"