Skip to content

Commit e5d47b9

Browse files
committed
[iris] Fix ssh_key_file to fall back to config's impersonate_service_account
ssh_key_file() was called without the impersonation SA in workers.py, causing OS Login registration to fail for users authenticating via a personal Google account. Now falls back to ssh_config.impersonate_service_account when no explicit SA is passed. Also surfaces gcloud stderr on failure instead of swallowing it.
1 parent 6ffe272 commit e5d47b9

2 files changed

Lines changed: 22 additions & 6 deletions

File tree

lib/iris/src/iris/cluster/providers/gcp/ssh.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def _register_os_login(self, pub_key_path: str, impersonate_sa: str | None) -> N
7676
if impersonate_sa:
7777
cmd.append(f"--impersonate-service-account={impersonate_sa}")
7878
logger.info("Registering SSH key with OS Login (ttl=%ds, sa=%s)", self._TTL, impersonate_sa)
79-
subprocess.run(cmd, check=True, capture_output=True, text=True)
79+
result = subprocess.run(cmd, capture_output=True, text=True)
80+
if result.returncode != 0:
81+
raise RuntimeError(f"Failed to register SSH key with OS Login: {result.stderr.strip()}")
8082
self._registration_expiry = time.monotonic() + self._TTL - self._REFRESH_MARGIN
8183

8284

@@ -91,7 +93,8 @@ def ssh_key_file(
9193
return ssh_config.key_file
9294
if uses_os_login(ssh_config):
9395
path = os.path.expanduser("~/.ssh/google_compute_engine")
94-
_os_login_key_provisioner.ensure_key(path, impersonate_service_account)
96+
effective_sa = impersonate_service_account or ssh_impersonate_service_account(ssh_config)
97+
_os_login_key_provisioner.ensure_key(path, effective_sa)
9598
return path
9699
return None
97100

lib/iris/tests/cluster/providers/gcp/test_ssh.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from __future__ import annotations
77

8+
import subprocess
89
import threading
910
from unittest.mock import patch
1011

@@ -50,7 +51,7 @@ def provisioner():
5051
return OsLoginKeyProvisioner()
5152

5253

53-
@patch("iris.cluster.providers.gcp.ssh.subprocess.run")
54+
@patch("iris.cluster.providers.gcp.ssh.subprocess.run", return_value=subprocess.CompletedProcess([], 0))
5455
@patch("iris.cluster.providers.gcp.ssh.os.path.exists", return_value=False)
5556
@patch("iris.cluster.providers.gcp.ssh.os.makedirs")
5657
def test_ensure_key_generates_and_registers(mock_makedirs, mock_exists, mock_run, provisioner):
@@ -77,7 +78,7 @@ def test_ensure_key_skips_when_valid(mock_exists, mock_run, provisioner):
7778
mock_run.assert_not_called()
7879

7980

80-
@patch("iris.cluster.providers.gcp.ssh.subprocess.run")
81+
@patch("iris.cluster.providers.gcp.ssh.subprocess.run", return_value=subprocess.CompletedProcess([], 0))
8182
@patch("iris.cluster.providers.gcp.ssh.os.path.exists", return_value=True)
8283
@patch("iris.cluster.providers.gcp.ssh.time.monotonic", return_value=100000.0)
8384
def test_ensure_key_reregisters_on_expiry(mock_monotonic, mock_exists, mock_run, provisioner):
@@ -91,7 +92,7 @@ def test_ensure_key_reregisters_on_expiry(mock_monotonic, mock_exists, mock_run,
9192
assert "os-login" in mock_run.call_args.args[0]
9293

9394

94-
@patch("iris.cluster.providers.gcp.ssh.subprocess.run")
95+
@patch("iris.cluster.providers.gcp.ssh.subprocess.run", return_value=subprocess.CompletedProcess([], 0))
9596
@patch("iris.cluster.providers.gcp.ssh.os.path.exists", return_value=False)
9697
@patch("iris.cluster.providers.gcp.ssh.os.makedirs")
9798
def test_ensure_key_no_impersonate_sa(mock_makedirs, mock_exists, mock_run, provisioner):
@@ -102,7 +103,7 @@ def test_ensure_key_no_impersonate_sa(mock_makedirs, mock_exists, mock_run, prov
102103
assert not any("--impersonate-service-account" in arg for arg in cmd)
103104

104105

105-
@patch("iris.cluster.providers.gcp.ssh.subprocess.run")
106+
@patch("iris.cluster.providers.gcp.ssh.subprocess.run", return_value=subprocess.CompletedProcess([], 0))
106107
@patch("iris.cluster.providers.gcp.ssh.os.path.exists", return_value=False)
107108
@patch("iris.cluster.providers.gcp.ssh.os.makedirs")
108109
def test_ensure_key_thread_safety(mock_makedirs, mock_exists, mock_run, provisioner):
@@ -155,6 +156,18 @@ def test_ssh_key_file_os_login_provisions(mock_provisioner):
155156
assert call_args.args[1] == "sa@test.iam.gserviceaccount.com"
156157

157158

159+
@patch("iris.cluster.providers.gcp.ssh._os_login_key_provisioner")
160+
def test_ssh_key_file_os_login_uses_config_sa_as_fallback(mock_provisioner):
161+
"""When no explicit SA is passed, ssh_key_file falls back to the config's impersonate_service_account."""
162+
cfg = _os_login_config()
163+
cfg.impersonate_service_account = "sa-from-config@project.iam.gserviceaccount.com"
164+
result = ssh_key_file(cfg)
165+
assert result is not None
166+
mock_provisioner.ensure_key.assert_called_once()
167+
call_args = mock_provisioner.ensure_key.call_args
168+
assert call_args.args[1] == "sa-from-config@project.iam.gserviceaccount.com"
169+
170+
158171
@patch("iris.cluster.providers.gcp.ssh._os_login_key_provisioner")
159172
def test_ssh_key_file_none_config(mock_provisioner):
160173
result = ssh_key_file(None)

0 commit comments

Comments
 (0)