conncheck

package
v0.25.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package conncheck validates that a configured server row is actually reachable and usable: that an SSH bastion accepts the stored key, and that a database target can be dialed (and authenticated against) — optionally through that bastion.

It exists so provisioning is not write-and-hope: creating a bastion with a typo'd host, a wrong username or a key the bastion does not accept used to look exactly like success until some user's first real query failed.

The result is deliberately staged: the stage is what tells an admin which field they got wrong. Secrets (private key, passphrase, password) are never echoed into the result or the logs.

Index

Constants

View Source
const DefaultTimeout = 15 * time.Second

DefaultTimeout bounds a whole check (bastion handshake + target dial + target auth). Kept below the shared dialer's own 30s per-dial timeout so a wedged host fails the HTTP request in bounded time rather than hanging on it.

Variables

This section is empty.

Functions

This section is empty.

Types

type Checker

type Checker struct {
	// contains filtered or unexported fields
}

Checker runs connectivity checks. It owns no long-lived state: every check builds a fresh SSH dialer so a pooled, already-open bastion client from the live proxy pool can never make a broken configuration look healthy.

func New

func New(resolver shared.ServerResolver, encryptionKey []byte) *Checker

New builds a Checker over the given resolver (normally *store.Store) and master encryption key.

func (*Checker) Check

func (c *Checker) Check(ctx context.Context, srv *store.Server) Result

Check validates srv: an SSH bastion handshake for `protocol: ssh` rows, or a target dial (through the bastion chain when via_uid is set) plus a protocol-level login for database rows.

It never returns an error: a failed check is a successful call with OK=false, because the staged failure is the answer the caller asked for.

func (*Checker) WithTimeout

func (c *Checker) WithTimeout(d time.Duration) *Checker

WithTimeout returns a copy of the checker bounded by d. Callers serving an HTTP request use it to stay inside their own write timeout.

type Code

type Code string

Code is a stable, machine-readable classification of the failure within a stage. The UI keys its guidance off this, not off the message text.

const (
	// CodeOK marks a successful check.
	CodeOK Code = "ok"
	// CodeDNSFailure means the hostname did not resolve.
	CodeDNSFailure Code = "dns_failure"
	// CodeTimeout means the dial or handshake exceeded the deadline.
	CodeTimeout Code = "timeout"
	// CodeUnreachable means the TCP connection was refused or the network is unreachable.
	CodeUnreachable Code = "unreachable"
	// CodeHostKeyMismatch means the bastion presented a host key differing from
	// the TOFU-pinned one.
	CodeHostKeyMismatch Code = "host_key_mismatch"
	// CodeAuthRejected means the bastion refused the offered credentials.
	CodeAuthRejected Code = "auth_rejected"
	// CodeBadPrivateKey means the stored private key could not be parsed (wrong
	// format, or a wrong/missing passphrase).
	CodeBadPrivateKey Code = "bad_private_key"
	// CodeNoAuthMethod means the bastion row carries neither key nor password.
	CodeNoAuthMethod Code = "no_auth_method"
	// CodeViaCycle means the via_uid chain loops.
	CodeViaCycle Code = "via_cycle"
	// CodeViaNotSSH means via_uid points at a row that is not an SSH bastion.
	CodeViaNotSSH Code = "via_not_ssh"
	// CodeHandshakeFailed is an SSH handshake failure that is not an auth
	// rejection (protocol mismatch, no common algorithm, ...).
	CodeHandshakeFailed Code = "handshake_failed"
	// CodeDBAuthFailed means the target accepted the connection but refused the
	// stored database credentials (or the database name).
	CodeDBAuthFailed Code = "db_auth_failed"
	// CodeDBHandshakeFailed means the target was reachable but the protocol
	// handshake did not complete (wrong port, TLS mismatch, not a database).
	CodeDBHandshakeFailed Code = "db_handshake_failed"
	// CodeMissingConfig means the row is missing a field the protocol needs
	// before anything can be dialed (an Oracle row with no service name).
	CodeMissingConfig Code = "missing_config"
	// CodeUnsupported means no protocol-level probe exists for this protocol;
	// reachability was verified but credentials were not.
	CodeUnsupported Code = "auth_not_verified"
	// CodeK8sForbidden means the ServiceAccount authenticated but RBAC denied
	// it — practically always a missing `pods/portforward` verb.
	CodeK8sForbidden Code = "k8s_forbidden"
	// CodeK8sTargetNotFound means no pod or service by that name exists in the
	// cluster row's namespace.
	CodeK8sTargetNotFound Code = "k8s_target_not_found"
	// CodeK8sTargetNotReady means the target exists but no ready pod backs it.
	CodeK8sTargetNotReady Code = "k8s_target_not_ready"
	// CodeK8sCAPinMismatch means the API server's certificate is not vouched
	// for by the CA dbbat pinned on first connect. Distinct from a plain
	// handshake failure because there *was* a working connect: this is the
	// cluster CA rotating, or somebody in the middle, and only an operator can
	// tell which.
	CodeK8sCAPinMismatch Code = "k8s_ca_pin_mismatch"
	// CodeInternal is an unclassified failure.
	CodeInternal Code = "internal_error"
	// CodeOracleServiceNameConflict is a *warning* code, not a failure: this
	// Oracle row's upstream service name is also claimed by rows spelling their
	// host differently, so a client connecting with the shared service name is
	// refused ORA-12514 even though this row itself checks out.
	CodeOracleServiceNameConflict Code = "oracle_service_name_conflict"
)

type Result

type Result struct {
	// OK reports whether the check succeeded end to end.
	OK bool `json:"ok"`
	// Stage is the last stage reached (on failure: the stage that failed).
	Stage Stage `json:"stage"`
	// Code classifies the outcome within the stage; CodeOK on success.
	Code Code `json:"code"`
	// Message is a human-readable explanation, safe to show to an admin.
	Message string `json:"message"`
	// HostKeyPinned is true when this check performed the TOFU pin (first
	// successful connect to a bastion that had no known_host_key yet).
	HostKeyPinned bool `json:"host_key_pinned,omitempty"`
	// KnownHostKey is the bastion's public host key after the check. Public
	// challenge material, safe to return.
	KnownHostKey string `json:"ssh_known_host_key,omitempty"`
	// CAPinned is the Kubernetes counterpart of HostKeyPinned: true when this
	// check performed the TOFU pin (first successful connect to a cluster row
	// that supplied no CA bundle and had learned none yet).
	CAPinned bool `json:"k8s_ca_pinned,omitempty"`
	// LearnedCACert is the cluster's TOFU-learned CA bundle after the check.
	// Public challenge material, like the host key; empty when the row supplied
	// its own bundle, which always wins.
	LearnedCACert string `json:"k8s_learned_ca_cert,omitempty"`
	// Warnings are problems that do not make the check fail but that an admin
	// needs to know about. They are reported whatever OK says: a row can be
	// perfectly reachable and still be unreachable *by service name*.
	Warnings []Warning `json:"warnings,omitempty"`
	// DurationMs is how long the whole check took.
	DurationMs int64 `json:"duration_ms"`
}

Result is the structured outcome of a connectivity check. It carries no secret material: only the stage reached, a machine-readable code, a human-readable message and (for SSH rows) the public host key.

type Stage

type Stage string

Stage identifies how far the check got before it stopped. On success the stage is the last stage actually reached, so the caller can tell "tunnel and database both verified" from "tunnel verified, database auth not verified".

const (
	// StageConfig covers everything that fails before any packet is sent:
	// missing credentials, an unusable private key, a via_uid cycle.
	StageConfig Stage = "config"
	// StageBastionDial is the TCP dial of the SSH bastion (DNS, routing, firewall).
	StageBastionDial Stage = "bastion_dial"
	// StageBastionAuth is the SSH handshake: host-key verification and auth.
	StageBastionAuth Stage = "bastion_auth"
	// StageTargetDial is the TCP dial of the database target — through the
	// bastion when via_uid is set, direct otherwise.
	StageTargetDial Stage = "target_dial"
	// StageTargetAuth is the protocol-level handshake and login against the
	// database target.
	StageTargetAuth Stage = "target_auth"
	// StageClusterAPI is reaching the Kubernetes API server (DNS, routing,
	// firewall, and the TLS trust established by the pasted CA bundle).
	StageClusterAPI Stage = "cluster_api"
	// StageClusterAuth is the API server accepting the ServiceAccount token.
	StageClusterAuth Stage = "cluster_auth"
	// StageClusterRBAC is the SelfSubjectAccessReview on `pods/portforward`:
	// authenticated, but may we actually open a tunnel in this namespace.
	StageClusterRBAC Stage = "cluster_rbac"
	// StageClusterTarget is resolving the addressed pod (or the ready pod
	// behind `svc/<name>`) and confirming it is Ready.
	StageClusterTarget Stage = "cluster_target"
)

type Warning added in v0.25.0

type Warning struct {
	Code    Code   `json:"code"`
	Message string `json:"message"`
}

Warning is a non-fatal configuration problem noticed while checking a row. The check itself can pass — the row is reachable and its credentials work — while something about how it sits next to its neighbors will bite a client later. Structured like a failure (a stable code plus text) so the UI keys off the code rather than the wording.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL