Skip to content

Self-contained SSH key management: ssh_key_name config field with managed key directory #17

@jfding

Description

@jfding

Problem Statement

Currently, git-supervisor relies on the host machine's ~/.ssh directory for SSH authentication. The ssh_identity_file config field takes an absolute or ~-relative path to a private key that must already exist on the filesystem. This creates an external dependency: the CLI cannot function correctly unless the operator has pre-configured SSH keys in the right locations. This makes the tool harder to set up, less portable, and couples its configuration to the host's SSH setup.

Solution

Introduce a managed key directory (~/.config/git-supervisor/keys/) that git-supervisor owns. A new config field ssh_key_name on each host allows referencing a key by bare filename. At runtime, the CLI resolves the key by searching the managed directory first, then falling back to ~/.ssh/. This gives operators a self-contained way to organize deployment keys without depending on the system SSH config.

Users place key files manually into ~/.config/git-supervisor/keys/. No import/export/generation commands are needed.

User Stories

  1. As a DevOps engineer, I want to place a deployment key in ~/.config/git-supervisor/keys/deploy_key and reference it as ssh_key_name: deploy_key in my config, so that my SSH keys are organized separately from my personal SSH config.
  2. As a DevOps engineer, I want the CLI to search ~/.config/git-supervisor/keys/ first and then fall back to ~/.ssh/, so that I can use existing keys in ~/.ssh/ without copying them.
  3. As a DevOps engineer, I want the CLI to error clearly if a referenced key name is not found in either search directory, so that I can quickly diagnose misconfiguration.
  4. As a DevOps engineer, I want the CLI to warn or error if a key file has overly permissive permissions (not 0600 or 0400), so that I don't accidentally use insecure keys.
  5. As a DevOps engineer, I want ssh_key_name to take precedence over ssh_identity_file when both are set, so that the managed approach is preferred but backward compatibility is preserved.
  6. As a DevOps engineer, I want the existing ssh_identity_file field to continue working exactly as before, so that my current deployments are not broken.
  7. As a DevOps engineer, I want to use ssh_key_name with any host (remote or localhost), so that the key resolution is consistent regardless of target.
  8. As a DevOps engineer deploying via Docker, I want the Docker volume-mount approach (./keys:/root/.ssh) to continue working unchanged, so that containerized deployments are not affected.
  9. As a DevOps engineer, I want ssh_key_name to be a bare filename (not a path), so that configs are portable across machines with different home directories.
  10. As a DevOps engineer, I want a clear sample config showing the new ssh_key_name field alongside the existing ssh_identity_file, so that I understand both options.

Implementation Decisions

  • New config field: Add ssh_key_name: Option<String> to the Host struct in the config module. This is a bare filename, not a path.
  • Key resolution module: Create a new keys module responsible for resolving a key name to an absolute path. Search order: ~/.config/git-supervisor/keys/<name>~/.ssh/<name>. Return error if not found in either location.
  • Permission validation: The key resolver checks Unix file permissions on the resolved key file. If permissions are more permissive than 0600, emit an error. This mirrors OpenSSH's own behavior of rejecting overly-permissive keys.
  • Precedence: When both ssh_key_name and ssh_identity_file are set on a host, ssh_key_name wins. The resolved path from the key resolver is passed to ssh -i, exactly as ssh_identity_file paths are today.
  • SSH module changes: Before building the ssh command, the SSH module resolves the effective identity file: call the key resolver if ssh_key_name is set, otherwise use ssh_identity_file if present, otherwise no -i flag.
  • No new CLI subcommands: Users manage keys by placing files directly in ~/.config/git-supervisor/keys/. No key import, key list, key generate, or similar commands.
  • No Docker changes: The Docker Compose setup and Dockerfile remain unchanged.
  • Sample config update: Update deployments.sample.yaml to show ssh_key_name usage alongside the existing ssh_identity_file example.

Testing Decisions

A good test verifies external behavior (given this input, expect this output/error) without coupling to internal implementation details like struct field names or private function signatures.

Modules to test:

  1. Key resolution module (keys.rs) — the core new logic:

    • Resolves a key name found in the managed directory (~/.config/git-supervisor/keys/)
    • Falls back to ~/.ssh/ when not in managed directory
    • Errors when key is not found in either location
    • Errors when key file has overly permissive permissions
    • Uses temp directories in tests to avoid depending on the real filesystem
  2. Config module (config.rs) — extend existing tests:

    • Parses ssh_key_name field from YAML
    • Both ssh_key_name and ssh_identity_file can coexist in YAML

Prior art: existing unit tests in config.rs (e.g., parse_minimal_yaml, host_release_count_parsed) and ssh.rs (e.g., local_target_detection_supports_common_localhost_forms) follow the same pattern of inline YAML parsing and assertions.

Out of Scope

  • SSH key generation (ssh-keygen wrapper)
  • Key import/export CLI subcommands
  • Encryption or passphrase-protected key support
  • Docker deployment changes
  • SSH agent forwarding or agent-based authentication
  • Configurable search paths (the two directories are hardcoded)
  • Windows support (permission checks use Unix file modes)

Further Notes

  • The managed key directory ~/.config/git-supervisor/keys/ follows the XDG Base Directory convention ($XDG_CONFIG_HOME/git-supervisor/keys/). A future enhancement could respect $XDG_CONFIG_HOME if set, but for now ~/.config is hardcoded.
  • The permission check is Unix-only. If cross-platform support becomes a concern, the check can be conditionally compiled.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions