From 97ca068cd3b097fd0fde59f725c6ea17309c637e Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Thu, 5 Nov 2026 14:54:35 +0000 Subject: [PATCH] fix: create and verify macOS runner role account --- .../defaults/main.yml | 5 ++-- .../meta/argument_specs.yml | 4 +-- .../tasks/main.yml | 13 ++++++--- tests/unit/test_macos_runner_role_account.py | 29 +++++++++++++++++++ 4 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 tests/unit/test_macos_runner_role_account.py diff --git a/roles/github_runner_macos_actions_runner/defaults/main.yml b/roles/github_runner_macos_actions_runner/defaults/main.yml index 2dc79f50..2d3dbc44 100644 --- a/roles/github_runner_macos_actions_runner/defaults/main.yml +++ b/roles/github_runner_macos_actions_runner/defaults/main.yml @@ -9,8 +9,9 @@ github_runner_macos_actions_runner_private_key: "" github_runner_macos_actions_runner_private_key_op_reference: "" github_runner_macos_actions_runner_label: exadev-macos github_runner_macos_actions_runner_name: "{{ inventory_hostname }}-macos" -github_runner_macos_actions_runner_user: github-runner -github_runner_macos_actions_runner_uid: 401 +# macOS role accounts require a leading underscore and a UID in 450-499; they do not need an interactive password. +github_runner_macos_actions_runner_user: _github-runner +github_runner_macos_actions_runner_uid: 498 github_runner_macos_actions_runner_version: "2.338.2" github_runner_macos_actions_runner_sha256: 25a065af409ffa317ab4d5a3b614dd15fc9c40f2172bc1b35a1b1258e919a388 # Repository identity is part of the directory. Changing the repository therefore creates a new unregistered runner rather than leaving a runner enrolled in the old scope. diff --git a/roles/github_runner_macos_actions_runner/meta/argument_specs.yml b/roles/github_runner_macos_actions_runner/meta/argument_specs.yml index fedf910a..f341644b 100644 --- a/roles/github_runner_macos_actions_runner/meta/argument_specs.yml +++ b/roles/github_runner_macos_actions_runner/meta/argument_specs.yml @@ -34,10 +34,10 @@ argument_specs: default: "" github_runner_macos_actions_runner_user: type: str - default: github-runner + default: _github-runner github_runner_macos_actions_runner_uid: type: int - default: 401 + default: 498 github_runner_macos_actions_runner_version: type: str default: "2.338.2" diff --git a/roles/github_runner_macos_actions_runner/tasks/main.yml b/roles/github_runner_macos_actions_runner/tasks/main.yml index 7c4ebc82..6c1bc7eb 100644 --- a/roles/github_runner_macos_actions_runner/tasks/main.yml +++ b/roles/github_runner_macos_actions_runner/tasks/main.yml @@ -84,16 +84,21 @@ - /usr/bin/false - -home - "{{ github_runner_macos_actions_runner_root }}" - - -password - - '*' - no_log: true + - -roleAccount changed_when: true when: github_runner_macos_actions_runner_account.rc != 0 + - name: Read the runner account after creation + ansible.builtin.command: + argv: [dscl, ., -read, "/Users/{{ github_runner_macos_actions_runner_user }}", UniqueID] + register: github_runner_macos_actions_runner_verified_account + changed_when: false + when: not ansible_check_mode + - name: Require the runner account's expected identity ansible.builtin.assert: that: - - "github_runner_macos_actions_runner_account.rc != 0 or github_runner_macos_actions_runner_account.stdout is regex('UniqueID: ' ~ (github_runner_macos_actions_runner_uid | string) ~ '$')" + - "github_runner_macos_actions_runner_verified_account.stdout is regex('UniqueID: ' ~ (github_runner_macos_actions_runner_uid | string) ~ '$')" quiet: true when: not ansible_check_mode diff --git a/tests/unit/test_macos_runner_role_account.py b/tests/unit/test_macos_runner_role_account.py new file mode 100644 index 00000000..b2636632 --- /dev/null +++ b/tests/unit/test_macos_runner_role_account.py @@ -0,0 +1,29 @@ +"""Regression coverage for the native macOS runner's non-interactive account.""" + +from pathlib import Path +import unittest + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +ROLE = ROOT / "roles" / "github_runner_macos_actions_runner" + + +class RunnerRoleAccountTest(unittest.TestCase): + def test_role_account_has_macos_required_name_and_uid(self) -> None: + defaults = yaml.safe_load((ROLE / "defaults" / "main.yml").read_text()) + self.assertTrue(defaults["github_runner_macos_actions_runner_user"].startswith("_")) + self.assertIn(defaults["github_runner_macos_actions_runner_uid"], range(450, 500)) + + def test_creation_is_verified_instead_of_trusting_sysadminctl_exit(self) -> None: + tasks = yaml.safe_load((ROLE / "tasks" / "main.yml").read_text()) + block = next(task["block"] for task in tasks if task["name"] == "Configure the enabled macOS runner") + entries = {task["name"]: task for task in block} + argv = entries["Create the unprivileged runner account"]["ansible.builtin.command"]["argv"] + self.assertIn("-roleAccount", argv) + self.assertNotIn("-password", argv) + self.assertIn("github_runner_macos_actions_runner_verified_account.stdout", str(entries["Require the runner account's expected identity"]["ansible.builtin.assert"])) + + +if __name__ == "__main__": + unittest.main()