docs(prd): add ssh config provisioning plan
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user