docs(prd): revise ssh provisioning schema
test / unit (pull_request) Successful in 34s
test / integration (pull_request) Successful in 42s

This commit is contained in:
2026-06-02 18:01:40 +00:00
parent 859092297f
commit 28cbe40a22
+82 -37
View File
@@ -7,18 +7,18 @@
## Summary ## Summary
Add top-level bottle SSH client config support so operators can provide Add top-level bottle SSH support so operators can provide approved
approved `Host` stanzas to the agent and git environment without using `known_hosts` lines and `Host` stanzas to the agent and git environment.
`ExtraHosts` as a DNS override. This lets SSH remotes that rely on host aliases At the same time, simplify `git.remotes` to a logical name-to-upstream-URL map.
work inside a bottle while HTTP/API traffic continues to resolve through the This lets SSH remotes that rely on host aliases work inside a bottle while
declared egress route. HTTP/API traffic continues to resolve through the declared egress route.
## Problem ## Problem
`git.remotes.*.ExtraHosts` is currently the available mechanism for steering `git.remotes` currently mixes repository identity, SSH key material, known host
git-gate reachability when an upstream host needs an explicit address. That is material, and optional host overrides in one block. `ExtraHosts` is especially
useful when the gate itself needs hosts-file resolution, but it is the wrong awkward: it is a hosts-file override, but the Gitea remote case really needs SSH
tool for ordinary SSH client aliasing. client config and host key verification.
The Gitea remote case needs the bottle to carry operator-approved SSH client The Gitea remote case needs the bottle to carry operator-approved SSH client
config into the agent/git environment. Local SSH config can make config into the agent/git environment. Local SSH config can make
@@ -28,8 +28,17 @@ as `ExtraHosts` can affect Docker `extra_hosts`, sidecar hosts config, agent
hosts config, and egress DNS. That coupling can break HTTP/API access because 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. the egress path sees the public hostname resolving to an internal address.
Known host keys have the same ownership problem. They are SSH client trust
material, not repository metadata. They should be declared under `ssh`, rendered
to the default SSH known-hosts file, and kept independent from egress DNS and
HTTP/API behavior.
## Goals / Success Criteria ## Goals / Success Criteria
- The manifest parser accepts a simplified `git.remotes` mapping from logical
remote name to upstream URL.
- The manifest parser accepts a top-level `ssh.known_hosts` list of OpenSSH
`known_hosts` lines.
- The manifest parser accepts a top-level `ssh.config` list. - The manifest parser accepts a top-level `ssh.config` list.
- Each config entry supports at least `Host`, `Hostname`, `Port`, `User`, and - Each config entry supports at least `Host`, `Hostname`, `Port`, `User`, and
`IdentityFile`. `IdentityFile`.
@@ -39,19 +48,24 @@ the egress path sees the public hostname resolving to an internal address.
in-bottle key path. in-bottle key path.
- Private key contents are never printed, logged, committed, or inlined into - Private key contents are never printed, logged, committed, or inlined into
the manifest or generated config outside the intended staged key file. the manifest or generated config outside the intended staged key file.
- Known host lines are rendered to the default SSH known-hosts file for the
agent user, normally `~/.ssh/known_hosts`.
- Git operations using SSH host aliases, such as - Git operations using SSH host aliases, such as
`git@gitea:didericis/bot-bottle.git`, work because SSH sees the provisioned `git@gitea:didericis/bot-bottle.git`, work because SSH sees the provisioned
`Host gitea` stanza. `Host gitea` stanza.
- `ssh.config` entries do not alter Docker `extra_hosts`, sidecar hosts config, - `ssh.config` entries do not alter Docker `extra_hosts`, sidecar hosts config,
agent hosts config, git-gate `ExtraHosts`, or egress route DNS. agent hosts config, or egress route DNS.
- `ExtraHosts`, `IdentityFile`, and known-host fields are removed from the
`git.remotes` target model; after this PRD, `git.remotes` carries only remote
names and upstream URLs.
- Documentation distinguishes SSH client config, git `insteadOf` rewrites, and - Documentation distinguishes SSH client config, git `insteadOf` rewrites, and
`ExtraHosts`. egress DNS/HTTP policy.
## Non-goals ## Non-goals
- No gitconfig-only alias feature. - No gitconfig-only alias feature.
- No change to `git.remotes.*.ExtraHosts`; it remains the explicit hosts - No hosts-file override replacement. This PRD removes `ExtraHosts` from the
override mechanism for git-gate reachability. target git remote schema instead of adding a new hosts override elsewhere.
- No automatic import of the operator's full host `~/.ssh/config`. - No automatic import of the operator's full host `~/.ssh/config`.
- No SSH config support for arbitrary OpenSSH directives beyond the fields - No SSH config support for arbitrary OpenSSH directives beyond the fields
listed in this PRD. listed in this PRD.
@@ -63,21 +77,27 @@ the egress path sees the public hostname resolving to an internal address.
In scope: In scope:
- Change the target manifest model for `git.remotes` to
`name: upstream-url`.
- Remove `ExtraHosts`, `IdentityFile`, and embedded known-host fields from the
git remote target schema.
- Add manifest model and schema support for top-level `ssh.known_hosts`.
- Add manifest model and schema support for top-level `ssh.config`. - Add manifest model and schema support for top-level `ssh.config`.
- Validate known-host entries as non-empty strings.
- Validate required SSH config fields and reject malformed entries with clear - Validate required SSH config fields and reject malformed entries with clear
manifest errors. manifest errors.
- Add a shared provisioning plan for staged SSH config and referenced identity - Add a shared provisioning plan for staged SSH config and referenced identity
files. files, plus rendered known-hosts files.
- Apply the provisioning plan in both Docker and smolmachines agent/git - Apply the provisioning plan in both Docker and smolmachines agent/git
environments. environments.
- Update focused unit tests for parsing, rendered SSH config, key path - Update focused unit tests for parsing, rendered SSH config, key path
rewriting, and hosts/DNS isolation. rewriting, known-host rendering, and hosts/DNS isolation.
- Update user documentation and examples. - Update user documentation and examples.
Out of scope: Out of scope:
- Integration tests that require a live SSH server. - Integration tests that require a live SSH server.
- Reworking git-gate URL rewriting or gitleaks scanning. - Reworking git-gate gitleaks scanning.
- Supporting `Include`, `Match`, `ProxyCommand`, `CertificateFile`, or other - Supporting `Include`, `Match`, `ProxyCommand`, `CertificateFile`, or other
advanced SSH config directives. advanced SSH config directives.
- Per-command SSH config injection for tools outside the bottle's provisioned - Per-command SSH config injection for tools outside the bottle's provisioned
@@ -90,11 +110,11 @@ Add a top-level `ssh` manifest block:
```yaml ```yaml
git: git:
remotes: remotes:
gitea.dideric.is: bot-bottle: ssh://git@100.78.141.42:30009/didericis/bot-bottle.git
Name: bot-bottle
Upstream: ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git
ssh: ssh:
known_hosts:
- "[100.78.141.42]:30009 ssh-rsa ..."
config: config:
- Host: gitea - Host: gitea
Hostname: 100.78.141.42 Hostname: 100.78.141.42
@@ -108,9 +128,23 @@ ssh:
IdentityFile: ~/.ssh/gitea-delos-2.pem IdentityFile: ~/.ssh/gitea-delos-2.pem
``` ```
Represent remotes as the existing `GitEntry` equivalent, but with only:
```python
@dataclass(frozen=True)
class GitEntry:
Name: str
Upstream: str
```
Represent each entry with a small manifest dataclass, for example: Represent each entry with a small manifest dataclass, for example:
```python ```python
@dataclass(frozen=True)
class SshConfig:
known_hosts: tuple[str, ...]
config: tuple[SshConfigEntry, ...]
@dataclass(frozen=True) @dataclass(frozen=True)
class SshConfigEntry: class SshConfigEntry:
Host: str Host: str
@@ -120,10 +154,11 @@ class SshConfigEntry:
IdentityFile: str IdentityFile: str
``` ```
`BottleManifest` should expose the parsed entries on the bottle object, similar `BottleManifest` should expose parsed SSH data on the bottle object, similar to
to existing `git`, `env`, and `egress` accessors. Parser validation should existing `git`, `env`, and `egress` accessors. Parser validation should require
require non-empty strings for `Host`, `Hostname`, `User`, and `IdentityFile`, non-empty strings for each `known_hosts` entry and for `Host`, `Hostname`,
and an integer `Port` in the valid TCP port range. `User`, and `IdentityFile`; `Port` must be an integer in the valid TCP port
range.
### Provisioning ### Provisioning
@@ -135,13 +170,16 @@ Build an SSH provisioning plan during backend prepare. The plan should:
used for git remotes. used for git remotes.
3. Assign each staged key a stable in-bottle path with private file 3. Assign each staged key a stable in-bottle path with private file
permissions. permissions.
4. Render an OpenSSH-compatible config file where each `IdentityFile` points to 4. Render an OpenSSH-compatible known-hosts file from `ssh.known_hosts`.
5. Render an OpenSSH-compatible config file where each `IdentityFile` points to
the staged in-bottle key path. the staged in-bottle key path.
5. Install the rendered config where agent and git commands will use it by 6. Install the rendered files where agent and git commands will use them by
default. default: normally `~/.ssh/known_hosts` and `~/.ssh/config` for the `node`
user.
The rendered config should contain only SSH directives and staged key paths. It The rendered config should contain only SSH directives and staged key paths. The
must not contain private key contents, host-side private key paths, or any rendered known-hosts file should contain only the declared known-host lines.
Neither file may contain private key contents, host-side private key paths, or
secret-derived material. secret-derived material.
### Isolation from Hosts and Egress ### Isolation from Hosts and Egress
@@ -151,28 +189,35 @@ secret-derived material.
- Docker `extra_hosts`. - Docker `extra_hosts`.
- Sidecar hosts config. - Sidecar hosts config.
- Agent hosts config. - Agent hosts config.
- Git-gate `ExtraHosts`.
- Egress route DNS or auth config. - Egress route DNS or auth config.
Git remotes continue to use `insteadOf` rewrites for git-gate routing. If an `ssh.known_hosts` is SSH trust material only. It must not be translated into
operator needs a hosts-file override for the gate itself, they should continue hosts-file mappings, egress allowlists, or HTTP/API trust.
to use `git.remotes.*.ExtraHosts`. If they need SSH client aliases for the
agent/git environment, they should use `ssh.config`. Git remotes continue to use `insteadOf` rewrites for git-gate routing. The git
remote manifest block should only answer "which logical repo names are routed
to which upstream URLs." SSH config and known-host verification live under
`ssh`; egress DNS and HTTP/API behavior continue to live under `egress`.
## Testing Strategy ## Testing Strategy
- Unit-test manifest parsing for valid `ssh.config` entries. - Unit-test manifest parsing for the simplified `git.remotes` mapping.
- Unit-test manifest parsing for valid `ssh.known_hosts` and `ssh.config`
entries.
- Unit-test parser errors for malformed `git.remotes` values.
- Unit-test parser errors for empty known-host entries.
- Unit-test parser errors for missing fields, empty string fields, non-integer - Unit-test parser errors for missing fields, empty string fields, non-integer
ports, and out-of-range ports. ports, and out-of-range ports.
- Unit-test known-host rendering to prove declared lines are emitted exactly
once into the planned known-hosts file.
- Unit-test SSH config rendering to prove `Host`, `Hostname`, `Port`, `User`, - Unit-test SSH config rendering to prove `Host`, `Hostname`, `Port`, `User`,
and rewritten `IdentityFile` lines are emitted correctly. and rewritten `IdentityFile` lines are emitted correctly.
- Unit-test duplicate `IdentityFile` handling so repeated keys are staged once - Unit-test duplicate `IdentityFile` handling so repeated keys are staged once
or otherwise handled deterministically. or otherwise handled deterministically.
- Unit-test Docker and smolmachines provisioning plans install the same - Unit-test Docker and smolmachines provisioning plans install the same
logical SSH config and staged key paths. logical known-hosts file, SSH config, and staged key paths.
- Unit-test that `ssh.config` entries do not appear in Docker `extra_hosts`, - Unit-test that `ssh.config` entries do not appear in Docker `extra_hosts`,
sidecar hosts config, agent hosts config, git-gate `ExtraHosts`, or egress sidecar hosts config, agent hosts config, or egress route config.
route config.
- Unit-test documentation examples through the existing manifest loader where - Unit-test documentation examples through the existing manifest loader where
practical. practical.