kubernetes/kops · error
bastion is not set, but useBastion is true
Error message
bastion is not set, but useBastion is true
What it means
sshClientFactoryImplementation.Dial (pkg/dump/dumper.go:672) is asked to open an SSH connection routed through a bastion host, but the factory was constructed with an empty bastion field. The library refuses to silently fall back to a direct connection because the caller explicitly requested bastion routing (useBastion=true), which would change the security/network topology. It fails fast with this error instead.
Source
Thrown at pkg/dump/dumper.go:676
type sshClientFactoryImplementation struct {
bastion string
sshConfig *ssh.ClientConfig
keyRing agent.Agent
}
var _ sshClientFactory = &sshClientFactoryImplementation{}
// HasBastion implements sshClientFactory::HasBastion
func (f *sshClientFactoryImplementation) HasBastion() bool {
return f.bastion != ""
}
// Dial implements sshClientFactory::Dial
func (f *sshClientFactoryImplementation) Dial(ctx context.Context, host string, useBastion bool) (sshClient, error) {
addr := host
if useBastion {
if f.bastion == "" {
return nil, fmt.Errorf("bastion is not set, but useBastion is true")
}
addr = f.bastion
}
if addr == "" {
return nil, fmt.Errorf("host is empty")
}
addr = net.JoinHostPort(addr, "22")
d := net.Dialer{
Timeout: 5 * time.Second,
}
conn, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, fmt.Errorf("error dialing tcp %s: %w", addr, err)
}
// We have a TCP connection; we will force-close it to support context cancellation
View on GitHub (pinned to 4c8573c808)
Solutions
- Check f.HasBastion() before passing useBastion=true to Dial, and fall back to useBastion=false if no bastion exists
- Verify the cluster has a bastion instance (kops get cluster -oyaml: topology.bastion / bastion instance group) and create it if missing (kops update cluster)
- Ensure the sshClientFactory is built with the bastion public address populated (see where sshClientFactoryImplementation{} is constructed in pkg/dump)
Example fix
// before client, err := factory.Dial(ctx, nodeIP, isPrivateSubnet) // after useBastion := isPrivateSubnet && factory.HasBastion() client, err := factory.Dial(ctx, nodeIP, useBastion)
Defensive patterns
Strategy: validation
Validate before calling
if useBastion && !factory.HasBastion() {
return fmt.Errorf("cannot dial %s: bastion routing requested but no bastion is configured", host)
}
client, err := factory.Dial(ctx, host, useBastion) Type guard
func canUseBastion(f *dump.SSHClientFactory) bool { return f.HasBastion() } Try / catch
client, err := factory.Dial(ctx, host, useBastion)
if err != nil {
if strings.Contains(err.Error(), "bastion is not set") {
client, err = factory.Dial(ctx, host, false) // fallback to direct
}
if err != nil {
return err
}
} Prevention
- Always gate useBastion on factory.HasBastion()
- Keep cluster topology (bastion presence) in sync with the dumper configuration
- Add a unit test covering Dial with useBastion=true on a bastion-less factory
When it happens
Trigger: Calling Dial(ctx, host, true) on an sshClientFactoryImplementation whose bastion field is "". In practice this happens when a kOps cluster dump is run against a topology that has no bastion (or a bastion instance that was deleted) while the dumper still computes useBastion=true for nodes behind private subnets.
Common situations: Cluster built with topology without a bastion but instance groups marked as private; bastion deleted or never created while cluster spec still references it; code that sets useBastion based on subnet privacy rather than HasBastion(); stale cached cluster state after a topology change.
Related errors
- forwarding ssh auth to keyring: %w
- creating ssh session: %w
- bastion not healthy after update, stopping rolling-update: %
- exactly one 'admin' SSH public key can be specified when run
- SSH public key must be specified when running with Scaleway
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/b36a1d88c11be1dd.
Report an issue: GitHub.