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