Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions bin/fm-azure-runner-routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def routing_lock(path):
os.close(handle)


def operator_environment_values(expected_subscription):
def operator_environment_values(expected_subscription=None):
"""Return exact values from one validated read of the operator environment.

The daemon step inherits only HOME and PATH. A routing selection therefore
Expand Down Expand Up @@ -365,7 +365,10 @@ def operator_environment_values(expected_subscription):
path, ", ".join(missing)
)
)
if values["FM_AZURE_SUBSCRIPTION_ID"] != expected_subscription:
if (
expected_subscription is not None
and values["FM_AZURE_SUBSCRIPTION_ID"] != expected_subscription
):
refuse(
"routing file subscription {} does not match the operator Azure environment subscription".format(
expected_subscription
Expand Down
56 changes: 36 additions & 20 deletions bin/fm-crosscheck-slack-service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,42 @@ write_plist() {
mkdir -p "$AGENT_DIR" "$LOG_DIR"
temporary=$(mktemp "$AGENT_DIR/.$LABEL.XXXXXX")
trap 'rm -f "$temporary"' EXIT
plutil -create xml1 "$temporary"
plutil -insert Label -string "$LABEL" "$temporary"
plutil -insert ProgramArguments -array "$temporary"
plutil -insert ProgramArguments.0 -string "$WRAPPER" "$temporary"
plutil -insert ProgramArguments.1 -string run "$temporary"
plutil -insert ProgramArguments.2 -string --config "$temporary"
plutil -insert ProgramArguments.3 -string "$CONFIG" "$temporary"
plutil -insert ProgramArguments.4 -string --keychain-only "$temporary"
plutil -insert EnvironmentVariables -dictionary "$temporary"
plutil -insert EnvironmentVariables.HOME -string "$HOME" "$temporary"
plutil -insert EnvironmentVariables.PATH -string "$PATH" "$temporary"
plutil -insert EnvironmentVariables.FM_CROSSCHECK_PYTHON -string "$interpreter" "$temporary"
plutil -insert EnvironmentVariables.FM_HOME -string "$FM_HOME" "$temporary"
plutil -insert EnvironmentVariables.FM_CROSSCHECK_SLACK_CONFIG \
-string "$CONFIG" "$temporary"
plutil -insert RunAtLoad -bool true "$temporary"
plutil -insert KeepAlive -bool true "$temporary"
plutil -insert ThrottleInterval -integer 10 "$temporary"
plutil -insert StandardOutPath -string "$STDOUT_LOG" "$temporary"
plutil -insert StandardErrorPath -string "$STDERR_LOG" "$temporary"
"$interpreter" - "$temporary" "$LABEL" "$WRAPPER" "$CONFIG" "$HOME" \
"$PATH" "$interpreter" "$FM_HOME" "$STDOUT_LOG" "$STDERR_LOG" <<'PY'
import plistlib
import sys

(
destination,
label,
wrapper,
config,
home,
path,
interpreter,
fm_home,
stdout_log,
stderr_log,
) = sys.argv[1:]
document = {
"Label": label,
"ProgramArguments": [wrapper, "run", "--config", config, "--keychain-only"],
"EnvironmentVariables": {
"HOME": home,
"PATH": path,
"FM_CROSSCHECK_PYTHON": interpreter,
"FM_HOME": fm_home,
"FM_CROSSCHECK_SLACK_CONFIG": config,
},
"RunAtLoad": True,
"KeepAlive": True,
"ThrottleInterval": 10,
"StandardOutPath": stdout_log,
"StandardErrorPath": stderr_log,
}
with open(destination, "wb") as handle:
plistlib.dump(document, handle, fmt=plistlib.FMT_XML, sort_keys=True)
PY
chmod 600 "$temporary"
validate_plist "$temporary"
mv "$temporary" "$PLIST"
Expand Down
40 changes: 39 additions & 1 deletion bin/fm-crosscheck-slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,41 @@ def crosscheck_core() -> Any:
return module


@functools.lru_cache(maxsize=1)
def operator_azure_environment() -> dict[str, str]:
"""Load the coordinator's proven fleet environment for launchd service use."""

module_path = BIN_DIR / "fm-azure-runner-routing.py"
spec = importlib.util.spec_from_file_location(
"fm_crosscheck_slack_azure_environment", module_path
)
require(
spec is not None and spec.loader is not None,
"central Azure environment owner is unavailable",
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
try:
values = module.operator_environment_values()
except SystemExit as exc:
raise SlackExposureError(
f"central Azure environment validation failed with exit {exc.code}"
) from exc
require(
isinstance(values, dict)
and values
and all(
isinstance(name, str)
and name.startswith("FM_AZURE_")
and isinstance(value, str)
and value
for name, value in values.items()
),
"central Azure environment owner returned malformed values",
)
return dict(values)


@functools.lru_cache(maxsize=128)
def crosscheck_model_family(model: str) -> str:
"""Use the core gate's exact family classifier, never a Slack-local guess."""
Expand Down Expand Up @@ -2706,6 +2741,7 @@ def main() -> int:
)
for name in (config.app_token_env, config.bot_token_env, config.github_token_env):
os.environ.pop(name, None)
os.environ.update(operator_azure_environment())
if args.command == "attest-launch":
path = Path(args.config) if args.config else default_config_path()
config = load_config(path)
Expand Down Expand Up @@ -2737,7 +2773,9 @@ def main() -> int:
config = load_config(path)
load_provenance_key(config.provenance_key_file)
configured_credentials(config)
print("preflight: config, provenance key, and central credentials are ready")
print(
"preflight: config, provenance key, central credentials, and Azure fleet are ready"
)
return 0
if args.command == "selftest":
path = Path(args.config) if args.config else default_config_path()
Expand Down
2 changes: 2 additions & 0 deletions docs/crosscheck-slack.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ The launch agent is `~/Library/LaunchAgents/com.firstmate.crosscheck-slack.plist
Logs are `$FM_HOME/logs/crosscheck-slack.log` and `$FM_HOME/logs/crosscheck-slack.error.log`.
The launch agent contains no credential values.
The launch agent persists the resolved absolute Python interpreter, executable PATH, HOME, and service configuration.
At preflight and every launch, the listener loads the existing owner-only `~/.fm-azure/fleet.env` through the Azure runner's bounded provenance-checked parser and passes only its allowlisted `FM_AZURE_*` values to Crosscheck children.
No Azure value is copied into the launch agent.
Install and start execute selftest and credential preflight with exactly the emitted environment and require Keychain access, ignoring inherited token variables.
Installation refuses before replacing an existing plist if validation fails.
The listener also requires Keychain credentials on every launch, including launchd restarts.
Expand Down
19 changes: 19 additions & 0 deletions tests/fm-crosscheck-slack.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,18 @@ fixture = root / "service-fixture"
bin_dir = fixture / "bin"
bin_dir.mkdir(parents=True)
home.mkdir()
azure_dir = home / ".fm-azure"
azure_dir.mkdir()
fleet = azure_dir / "fleet.env"
fleet.write_text("""FM_AZURE_TENANT_ID=11111111-1111-1111-1111-111111111111
FM_AZURE_SUBSCRIPTION_ID=22222222-2222-2222-2222-222222222222
FM_AZURE_NAMING_PREFIX=fixture
FM_AZURE_STORAGE_NAME=fixturestorage
FM_AZURE_OWNER_TAG=fixture-owner
FM_AZURE_DEPLOYMENT_GENERATION=fixture-generation
FM_AZURE_BLOB_PE_NIC_RESOURCE_GUID=33333333-3333-3333-3333-333333333333
""")
fleet.chmod(0o600)
config_path = fixture / "config.json"
config = json.loads(original_config.read_text())
config["keychain_services"] = dict(app_token="fixture-app", bot_token="fixture-bot", github_token="fixture-github")
Expand Down Expand Up @@ -1301,6 +1313,13 @@ assert 'fixture-keychain-secret' not in plist.read_text()
command = agent['ProgramArguments']
result = subprocess.run([command[0], 'preflight', *command[2:]], env=agent['EnvironmentVariables'], capture_output=True, text=True, timeout=30)
assert result.returncode == 0, result.stdout + result.stderr
assert 'Azure fleet are ready' in result.stdout
fleet_off = fleet.with_suffix('.env.off')
fleet.rename(fleet_off)
result = invoke('start')
assert result.returncode != 0, 'start accepted a missing central Azure fleet environment'
assert not launch_log.exists(), 'missing Azure fleet validation mutated service state'
fleet_off.rename(fleet)
result = invoke('start')
assert result.returncode == 0, result.stdout + result.stderr
assert 'bootstrap' in launch_log.read_text()
Expand Down
2 changes: 2 additions & 0 deletions tests/fm-spawn-dispatch-profile.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,8 @@ print(value["issuer"], value["task"]["task_id"], value["author"]["harness"], val
assert_contains "$shape" "$leased_worktree" \
"Pi spawn launch provenance did not bind the leased task worktree"
git -C "$leased_worktree" checkout -qb codex/crosscheck-provenance-fixture
git -C "$leased_worktree" config user.email fixture@example.com
git -C "$leased_worktree" config user.name Fixture
printf 'authored after launch\n' > "$leased_worktree/crosscheck-provenance-fixture.txt"
git -C "$leased_worktree" add crosscheck-provenance-fixture.txt
git -C "$leased_worktree" commit -qm 'test: author after launch'
Expand Down
Loading