Skip to content

Tutorial: Setup Bastion

Learn how to integrate SSH-KLM with bastion hosts for secure jump-server access.

  • SSH-KLM instance running
  • Bastion host accessible
  • Admin access to both systems
┌──────────┐ ┌─────────┐ ┌─────────────┐
│ User │──────│ Bastion │──────│ Target Host │
└──────────┘ └─────────┘ └─────────────┘
│ │ │
└────────────┬────┴──────────────────┘
┌──────┴──────┐
│ SSH-KLM │
└─────────────┘
const { QcClient } = require('@qcecuring/ssh-sdk');
const client = new QcClient({
apiKey: process.env.SSHKLM_API_KEY
});
// Add bastion as managed host
const bastion = await client.ssh.addHost({
hostname: 'bastion.example.com',
port: 22,
labels: {
type: 'bastion',
environment: 'production'
}
});
console.log(`Bastion added: ${bastion.id}`);
Terminal window
# On the bastion host
curl -fsSL https://get.qcecuring.com/ssh-agent | sudo bash -s -- \
--server https://ssh-klm.example.com \
--token YOUR_REGISTRATION_TOKEN
# Enable bastion mode
sudo ssh-klm-agent configure --mode bastion

In SSH-KLM UI or via API:

const integration = await client.ssh.createBastionIntegration({
name: 'Production Bastion',
bastionHostId: bastion.id,
targetHosts: {
labels: { environment: 'production' }
},
settings: {
sessionRecording: true,
sessionTimeout: 3600, // 1 hour
allowedUsers: ['admin', 'deploy'],
requireApproval: false
}
});

Update user SSH config for automatic bastion routing:

~/.ssh/config
Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519
Host *.internal.example.com
ProxyJump bastion
User admin

For enhanced security, use SSH certificates:

// Configure CA for bastion
await client.ssh.configureBastionCA({
bastionId: bastion.id,
caSettings: {
enabled: true,
maxCertTTL: 3600, // 1 hour max
defaultTTL: 300, // 5 minutes default
allowedPrincipals: ['admin', 'deploy', 'readonly']
}
});

Users request short-lived certificates:

// Request access through bastion
const access = await client.ssh.requestBastionAccess({
bastionId: bastion.id,
targetHost: 'server01.internal.example.com',
username: 'deploy',
ttl: 300,
reason: 'Deploy v2.0.0'
});
// Save certificate
fs.writeFileSync('~/.ssh/bastion-cert', access.certificate);
fs.writeFileSync('~/.ssh/bastion-key', access.privateKey);
console.log(`Access granted until: ${access.expiresAt}`);
Terminal window
# SSH with certificate (auto-routed through bastion)
ssh -i ~/.ssh/bastion-key \
-o CertificateFile=~/.ssh/bastion-cert \
deploy@server01.internal.example.com

View recorded sessions:

const sessions = await client.ssh.listBastionSessions({
bastionId: bastion.id,
startDate: '2026-01-01',
endDate: '2026-01-07'
});
for (const session of sessions) {
console.log(`${session.user}${session.targetHost}`);
console.log(` Duration: ${session.duration}s`);
console.log(` Commands: ${session.commandCount}`);
}

Enable approval for sensitive hosts:

await client.ssh.updateBastionIntegration({
integrationId: integration.id,
settings: {
requireApproval: true,
approvers: ['security-team@example.com'],
approvalTimeout: 3600 // 1 hour to approve
}
});
// Real-time connection monitoring
const connections = await client.ssh.listActiveConnections({
bastionId: bastion.id
});
for (const conn of connections) {
console.log(`${conn.user} connected to ${conn.targetHost}`);
console.log(` Started: ${conn.startedAt}`);
console.log(` Source IP: ${conn.sourceIp}`);
}