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

  1. Check f.HasBastion() before passing useBastion=true to Dial, and fall back to useBastion=false if no bastion exists
  2. Verify the cluster has a bastion instance (kops get cluster -oyaml: topology.bastion / bastion instance group) and create it if missing (kops update cluster)
  3. 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

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


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/b36a1d88c11be1dd. Report an issue: GitHub.