docs(prd): add ssh config provisioning plan
test / unit (pull_request) Successful in 49s
test / integration (pull_request) Successful in 57s

This commit is contained in:
2026-06-02 17:51:13 +00:00
parent 3885e2f5ad
commit 859092297f
+185
View File
@@ -0,0 +1,185 @@
# PRD 0046: SSH Config Provisioning
- **Status:** Draft
- **Author:** didericis-codex
- **Created:** 2026-06-02
- **Issue:** #150
## Summary
Add top-level bottle SSH client config support so operators can provide
approved `Host` stanzas to the agent and git environment without using
`ExtraHosts` as a DNS override. This lets SSH remotes that rely on host aliases
work inside a bottle while HTTP/API traffic continues to resolve through the
declared egress route.
## Problem
`git.remotes.*.ExtraHosts` is currently the available mechanism for steering
git-gate reachability when an upstream host needs an explicit address. That is
useful when the gate itself needs hosts-file resolution, but it is the wrong
tool for ordinary SSH client aliasing.
The Gitea remote case needs the bottle to carry operator-approved SSH client
config into the agent/git environment. Local SSH config can make
`git@gitea:didericis/bot-bottle.git` resolve to the same endpoint as
`ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git`, but encoding that
as `ExtraHosts` can affect Docker `extra_hosts`, sidecar hosts config, agent
hosts config, and egress DNS. That coupling can break HTTP/API access because
the egress path sees the public hostname resolving to an internal address.
## Goals / Success Criteria
- The manifest parser accepts a top-level `ssh.config` list.
- Each config entry supports at least `Host`, `Hostname`, `Port`, `User`, and
`IdentityFile`.
- `IdentityFile` values are host-side paths, and provisioning stages or mounts
the referenced key material through the existing key-handling path.
- Generated in-bottle SSH config rewrites `IdentityFile` to the staged
in-bottle key path.
- Private key contents are never printed, logged, committed, or inlined into
the manifest or generated config outside the intended staged key file.
- Git operations using SSH host aliases, such as
`git@gitea:didericis/bot-bottle.git`, work because SSH sees the provisioned
`Host gitea` stanza.
- `ssh.config` entries do not alter Docker `extra_hosts`, sidecar hosts config,
agent hosts config, git-gate `ExtraHosts`, or egress route DNS.
- Documentation distinguishes SSH client config, git `insteadOf` rewrites, and
`ExtraHosts`.
## Non-goals
- No gitconfig-only alias feature.
- No change to `git.remotes.*.ExtraHosts`; it remains the explicit hosts
override mechanism for git-gate reachability.
- No automatic import of the operator's full host `~/.ssh/config`.
- No SSH config support for arbitrary OpenSSH directives beyond the fields
listed in this PRD.
- No private key material in manifests, logs, PRDs, tests, or generated
non-key config files.
- No changes to HTTP/API egress auth or DNS routing semantics.
## Scope
In scope:
- Add manifest model and schema support for top-level `ssh.config`.
- Validate required SSH config fields and reject malformed entries with clear
manifest errors.
- Add a shared provisioning plan for staged SSH config and referenced identity
files.
- Apply the provisioning plan in both Docker and smolmachines agent/git
environments.
- Update focused unit tests for parsing, rendered SSH config, key path
rewriting, and hosts/DNS isolation.
- Update user documentation and examples.
Out of scope:
- Integration tests that require a live SSH server.
- Reworking git-gate URL rewriting or gitleaks scanning.
- Supporting `Include`, `Match`, `ProxyCommand`, `CertificateFile`, or other
advanced SSH config directives.
- Per-command SSH config injection for tools outside the bottle's provisioned
environment.
## Design
Add a top-level `ssh` manifest block:
```yaml
git:
remotes:
gitea.dideric.is:
Name: bot-bottle
Upstream: ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git
ssh:
config:
- Host: gitea
Hostname: 100.78.141.42
Port: 30009
User: git
IdentityFile: ~/.ssh/gitea-delos-2.pem
- Host: gitea.dideric.is
Hostname: 100.78.141.42
Port: 30009
User: git
IdentityFile: ~/.ssh/gitea-delos-2.pem
```
Represent each entry with a small manifest dataclass, for example:
```python
@dataclass(frozen=True)
class SshConfigEntry:
Host: str
Hostname: str
Port: int
User: str
IdentityFile: str
```
`BottleManifest` should expose the parsed entries on the bottle object, similar
to existing `git`, `env`, and `egress` accessors. Parser validation should
require non-empty strings for `Host`, `Hostname`, `User`, and `IdentityFile`,
and an integer `Port` in the valid TCP port range.
### Provisioning
Build an SSH provisioning plan during backend prepare. The plan should:
1. Expand each host-side `IdentityFile` path using the existing tilde and
key-file validation behavior.
2. Stage or mount each referenced key through the same private-key handling path
used for git remotes.
3. Assign each staged key a stable in-bottle path with private file
permissions.
4. Render an OpenSSH-compatible config file where each `IdentityFile` points to
the staged in-bottle key path.
5. Install the rendered config where agent and git commands will use it by
default.
The rendered config should contain only SSH directives and staged key paths. It
must not contain private key contents, host-side private key paths, or any
secret-derived material.
### Isolation from Hosts and Egress
`ssh.config` is SSH client configuration only. It must not be translated into:
- Docker `extra_hosts`.
- Sidecar hosts config.
- Agent hosts config.
- Git-gate `ExtraHosts`.
- Egress route DNS or auth config.
Git remotes continue to use `insteadOf` rewrites for git-gate routing. If an
operator needs a hosts-file override for the gate itself, they should continue
to use `git.remotes.*.ExtraHosts`. If they need SSH client aliases for the
agent/git environment, they should use `ssh.config`.
## Testing Strategy
- Unit-test manifest parsing for valid `ssh.config` entries.
- Unit-test parser errors for missing fields, empty string fields, non-integer
ports, and out-of-range ports.
- Unit-test SSH config rendering to prove `Host`, `Hostname`, `Port`, `User`,
and rewritten `IdentityFile` lines are emitted correctly.
- Unit-test duplicate `IdentityFile` handling so repeated keys are staged once
or otherwise handled deterministically.
- Unit-test Docker and smolmachines provisioning plans install the same
logical SSH config and staged key paths.
- Unit-test that `ssh.config` entries do not appear in Docker `extra_hosts`,
sidecar hosts config, agent hosts config, git-gate `ExtraHosts`, or egress
route config.
- Unit-test documentation examples through the existing manifest loader where
practical.
Run:
- `python3 -m unittest discover -s tests/unit`
## Open Questions
None.