Skip to content

feat(discovery): opt-in ssh_access exposes discovery scope as a dynamic ssh-proxy - #157

Merged
blue4209211 merged 4 commits into
mainfrom
feat/discovery-ssh-access
Sep 25, 2026
Merged

blue4209211 merged 4 commits into
mainfrom
feat/discovery-ssh-access

Conversation

@mayankpande88

@mayankpande88 mayankpande88 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Description

Discovery reaches hosts over SSH, but only to run signed inventory packs. Nothing else can run a command on a discovered host.

This PR adds an opt-in ssh_access flag on discovery datasources (off by default). When it is on, forager also registers a sibling ssh-proxy datasource in dynamic mode:

  • ID <id>:ssh, name <name>-ssh.
  • It reuses the discovery datasource's credentials, and its allowed_cidrs become the sibling's allowed_hosts.
  • It uses the same known_hosts_file for host-key checks.
  • Requests pick the target host per call through params.host, and it must be inside that scope.
  • The discovery datasource itself is unchanged.

Guards. The sibling doesn't start, and an error is logged, if any of these is missing:

  • signing_public_key
  • known_hosts_file
  • a non-empty allowed_cidrs

The flag turns credentials granted for inventory packs into a shell, so it gets a stricter bar than inventory.

Also in this PR

  • Cloud config sync now instantiates discovery-proxy. It used to be skipped as an unknown proxy type. The sibling is added or removed as the flag changes.
  • A config sync entry without a config block no longer panics.
  • Bare IPs in ssh-proxy allowed_hosts are treated as single-host networks, as discovery already does.
  • The greeting now advertises discovery-proxy.
  • The discovery README has a new "SSH access" section.
  • Includes the dependency bump from chore(deps): bump grpc 1.83.1 and x/crypto 0.56.0 to clear govulncheck #158, so vuln passes here whichever PR merges first.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Enhancement (non-breaking change which improves existing functionality)
  • Refactor (non-breaking change which improves code structure)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation
  • CI/CD

How Has This Been Tested?

  • Unit tests
  • Manual testing

The new unit tests cover:

  • each of the three guards;
  • the sibling's config and entry;
  • CIDRs arriving as []any from cloud push;
  • the config-sync lifecycle (sibling added, then removed when the flag is turned off);
  • no sibling when signing is disabled;
  • the nil-config panic;
  • bare-IP matching for IPv4 and IPv6.

go test -race ./..., govulncheck and golangci-lint all pass.

Checklist

  • CLA signed (the CLA bot will prompt on your first PR)
  • make validate passes (fmt + lint + test)
  • Docs updated if the wire shape, config surface, or proxy module behavior changed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces SSH access to discovered VMs by registering a sibling ssh-proxy datasource alongside the discovery-proxy datasource when the ssh_access flag is enabled. It includes a design specification, configuration parsing, validation logic, and comprehensive unit tests. The review feedback highlights a potential runtime panic in pkg/ws/handler.go if the datasource configuration is nil, and suggests improving IP/CIDR parsing robustness in pkg/proxy/discovery/ssh_access.go by converting bare IP addresses to single-host CIDRs.

Comment thread pkg/ws/handler.go
Comment thread pkg/proxy/discovery/ssh_access.go
@mayankpande88

Copy link
Copy Markdown
Contributor Author

Rebased onto current main; the branch had been cut from a stale local main. Also cherry-picked #158's dependency bump (grpc 1.83.1, x/crypto 0.56.0, go 1.26.6, golangci-lint v2.13.2) so vuln passes here without depending on merge order. If #158 merges first, those commits drop out on rebase.

@mayankpande88
mayankpande88 force-pushed the feat/discovery-ssh-access branch from dfbdfe9 to fff2d50 Compare September 25, 2026 08:21
@blue4209211

Copy link
Copy Markdown
Contributor

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces SSH access to discovered VMs by registering a dynamic-mode ssh-proxy sibling datasource alongside the main discovery-proxy datasource when ssh_access is enabled. It also refactors GCP Secret Manager credential loading to avoid deprecated APIs and updates bare IP parsing in the SSH proxy to treat them as single-host CIDRs. The review feedback correctly identifies a critical bug in the bare IP parsing logic where manually constructing a net.IPNet with a 16-byte IPv4 address and a 4-byte mask can cause IPNet.Contains to fail, and provides a clear code suggestion to resolve it by using ip.To4().

Comment thread pkg/proxy/ssh/proxy.go
GO-2026-6348 (grpc) and GO-2026-6354/6355 (x/crypto) fail the vuln job on
every PR. x/crypto 0.56.0 requires go 1.26, so the go directive moves to
1.26.6, matching the Dockerfile build image; 1.26.0 would pull in stdlib
vulns fixed in later patches.
v2.7.2 is built with go1.25 and refuses to load a go 1.26 module.
The newer staticcheck flags the deprecated DetectOptions.CredentialsFile
(now loaded via NewCredentialsFromJSON with the file's declared type) and
an SA6001 false positive in a test helper.
…ic ssh-proxy

Discovery reaches hosts over SSH, but only for signed inventory packs, so
nothing else could run a command on a discovered host.

With ssh_access: true a discovery datasource also registers a sibling
ssh-proxy (<id>:ssh) in dynamic mode, reusing its credentials, scope
(allowed_cidrs -> allowed_hosts) and known_hosts_file. The sibling is
refused unless signing, host key verification and an explicit scope are
configured, since it turns inventory credentials into a shell.

Also:
- instantiate discovery-proxy from cloud config sync (it was skipped as an
  unknown proxy type) and advertise it in the greeting capabilities;
- don't panic on a config sync entry without a config block;
- treat bare IPs in ssh-proxy allowed_hosts as single-host networks, as
  discovery already does.
@mayankpande88
mayankpande88 force-pushed the feat/discovery-ssh-access branch from fff2d50 to 7ddd3ae Compare September 25, 2026 08:31
@mayankpande88

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an ssh_access feature for the discovery-proxy datasource, allowing the registration of a sibling ssh-proxy to run ad-hoc commands on in-scope hosts. It also updates GCP Secret Manager credentials loading to avoid deprecated options. The review feedback highlights several potential nil pointer dereference panics, specifically when calling verifier.Enabled() if the verifier is nil, and when accessing the configuration map cfg in SSHAccessEnabled and SSHAccessConfig if it is nil. Additionally, it is recommended to consistently filter out empty strings in the stringSlice helper for both []string and []any inputs.

Comment thread cmd/app.go
Comment thread pkg/ws/handler.go
Comment thread pkg/proxy/discovery/ssh_access.go
Comment thread pkg/proxy/discovery/ssh_access.go
Comment thread pkg/proxy/discovery/ssh_access.go Outdated
@blue4209211
blue4209211 merged commit b00241e into main Sep 25, 2026
6 checks passed
@blue4209211
blue4209211 deleted the feat/discovery-ssh-access branch September 25, 2026 08:49
blue4209211 pushed a commit that referenced this pull request Sep 25, 2026
The go directive moved to 1.26 in #158/#157, but release.yml still pinned
golangci-lint v2.7.2, which is built with go1.25 and refuses to load the
module, so the Release workflow fails on main.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants